diff options
Diffstat (limited to 'lcms2mt/testbed')
-rw-r--r-- | lcms2mt/testbed/Makefile.am | 34 | ||||
-rw-r--r-- | lcms2mt/testbed/Makefile.in | 647 | ||||
-rw-r--r-- | lcms2mt/testbed/bad.icc | 21 | ||||
-rw-r--r-- | lcms2mt/testbed/bad_mpe.icc | bin | 0 -> 300 bytes | |||
-rw-r--r-- | lcms2mt/testbed/crayons.icc | bin | 0 -> 8760 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/ibm-t61.icc | bin | 0 -> 25244 bytes | |||
-rw-r--r-- | lcms2mt/testbed/new.icc | bin | 0 -> 25244 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/test1.icc | bin | 0 -> 557536 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/test2.icc | bin | 0 -> 654496 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/test3.icc | bin | 0 -> 28484 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/test4.icc | bin | 0 -> 61020 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/test5.icc | bin | 0 -> 3240 bytes | |||
-rw-r--r-- | lcms2mt/testbed/testcms2.c | 8826 | ||||
-rwxr-xr-x | lcms2mt/testbed/testcms2.h | 84 | ||||
-rwxr-xr-x | lcms2mt/testbed/testplugin.c | 1426 | ||||
-rw-r--r-- | lcms2mt/testbed/testthread.cpp | 120 | ||||
-rwxr-xr-x | lcms2mt/testbed/toosmall.icc | bin | 0 -> 158 bytes | |||
-rwxr-xr-x | lcms2mt/testbed/zoo_icc.c | 309 |
18 files changed, 11467 insertions, 0 deletions
diff --git a/lcms2mt/testbed/Makefile.am b/lcms2mt/testbed/Makefile.am new file mode 100644 index 000000000..85e87119a --- /dev/null +++ b/lcms2mt/testbed/Makefile.am @@ -0,0 +1,34 @@ +# +# Makefile for building testcms +# + +# Don't require all the GNU mandated files +AUTOMAKE_OPTIONS = 1.7 foreign + +AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -I$(top_srcdir)/src + +check_PROGRAMS = testcms + +# CFLAGS = --pedantic -Wall -std=c99 -O2 + +# The testsuite binary is statically linked. This is necessary as it uses some +# of the internal funtions that are not necessarily exported by the shared +# library. +testcms_LDADD = $(top_builddir)/src/liblcms2.la +testcms_LDFLAGS = -static @LDFLAGS@ +testcms_SOURCES = testcms2.c testplugin.c zoo_icc.c testcms2.h + +EXTRA_DIST = test1.icc bad.icc toosmall.icc test2.icc \ + test3.icc test4.icc \ + test5.icc ibm-t61.icc + +check: + if [ $(top_srcdir) != $(top_builddir) ]; then \ + cp $(top_srcdir)/testbed/*.ic? $(top_builddir)/testbed; \ + fi + LD_LIBRARY_PATH=$(top_builddir)/src/.libs ./testcms + if [ $(top_srcdir) != $(top_builddir) ]; then \ + rm -f $(top_builddir)/testbed/*.ic?; \ + fi + + diff --git a/lcms2mt/testbed/Makefile.in b/lcms2mt/testbed/Makefile.in new file mode 100644 index 000000000..857709324 --- /dev/null +++ b/lcms2mt/testbed/Makefile.in @@ -0,0 +1,647 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 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@ + +# +# Makefile for building testcms +# +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@ +check_PROGRAMS = testcms$(EXEEXT) +subdir = testbed +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \ + $(top_srcdir)/m4/ax_append_compile_flags.m4 \ + $(top_srcdir)/m4/ax_append_flag.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.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_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am_testcms_OBJECTS = testcms2.$(OBJEXT) testplugin.$(OBJEXT) \ + zoo_icc.$(OBJEXT) +testcms_OBJECTS = $(am_testcms_OBJECTS) +testcms_DEPENDENCIES = $(top_builddir)/src/liblcms2.la +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 = +testcms_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(testcms_LDFLAGS) $(LDFLAGS) -o $@ +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@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +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 = $(testcms_SOURCES) +DIST_SOURCES = $(testcms_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JPEGICC_DEPLIBS = @JPEGICC_DEPLIBS@ +LCMS_LIB_DEPLIBS = @LCMS_LIB_DEPLIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBRARY_AGE = @LIBRARY_AGE@ +LIBRARY_CURRENT = @LIBRARY_CURRENT@ +LIBRARY_REVISION = @LIBRARY_REVISION@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBTOOL_DEPS = @LIBTOOL_DEPS@ +LIB_JPEG = @LIB_JPEG@ +LIB_MATH = @LIB_MATH@ +LIB_THREAD = @LIB_THREAD@ +LIB_TIFF = @LIB_TIFF@ +LIB_ZLIB = @LIB_ZLIB@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +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@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_CXX = @PTHREAD_CXX@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TIFFICC_DEPLIBS = @TIFFICC_DEPLIBS@ +VERSION = @VERSION@ +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_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +acx_pthread_config = @acx_pthread_config@ +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@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +inline = @inline@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +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@ + +# Don't require all the GNU mandated files +AUTOMAKE_OPTIONS = 1.7 foreign +AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -I$(top_srcdir)/src + +# CFLAGS = --pedantic -Wall -std=c99 -O2 + +# The testsuite binary is statically linked. This is necessary as it uses some +# of the internal funtions that are not necessarily exported by the shared +# library. +testcms_LDADD = $(top_builddir)/src/liblcms2.la +testcms_LDFLAGS = -static @LDFLAGS@ +testcms_SOURCES = testcms2.c testplugin.c zoo_icc.c testcms2.h +EXTRA_DIST = test1.icc bad.icc toosmall.icc test2.icc \ + test3.icc test4.icc \ + test5.icc ibm-t61.icc + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign testbed/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign testbed/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__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-checkPROGRAMS: + @list='$(check_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 + +testcms$(EXEEXT): $(testcms_OBJECTS) $(testcms_DEPENDENCIES) $(EXTRA_testcms_DEPENDENCIES) + @rm -f testcms$(EXEEXT) + $(AM_V_CCLD)$(testcms_LINK) $(testcms_OBJECTS) $(testcms_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testcms2.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testplugin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zoo_icc.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.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: $(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 + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-checkPROGRAMS clean-generic clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \ + clean-checkPROGRAMS clean-generic 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-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +check: + if [ $(top_srcdir) != $(top_builddir) ]; then \ + cp $(top_srcdir)/testbed/*.ic? $(top_builddir)/testbed; \ + fi + LD_LIBRARY_PATH=$(top_builddir)/src/.libs ./testcms + if [ $(top_srcdir) != $(top_builddir) ]; then \ + rm -f $(top_builddir)/testbed/*.ic?; \ + fi + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lcms2mt/testbed/bad.icc b/lcms2mt/testbed/bad.icc new file mode 100644 index 000000000..ddfa62291 --- /dev/null +++ b/lcms2mt/testbed/bad.icc @@ -0,0 +1,21 @@ +SHELL = /bin/sh + +CFLAGS = -g -O4 -fomit-frame-pointer -Wall -I../include + +testcms.o: testcms.c + +testcms: testcms.o ../src/liblcms.a + $(CC) $(CFLAGS) testcms.o ../src/liblcms.a -o $@ -lm + +all: testcms test + +test: testcms + ./testcms + +install: + # Nothing to install + +clean: + -rm testcms.o testcms testcms.exe + + diff --git a/lcms2mt/testbed/bad_mpe.icc b/lcms2mt/testbed/bad_mpe.icc Binary files differnew file mode 100644 index 000000000..5a86c6c87 --- /dev/null +++ b/lcms2mt/testbed/bad_mpe.icc diff --git a/lcms2mt/testbed/crayons.icc b/lcms2mt/testbed/crayons.icc Binary files differnew file mode 100644 index 000000000..d04ad0d2e --- /dev/null +++ b/lcms2mt/testbed/crayons.icc diff --git a/lcms2mt/testbed/ibm-t61.icc b/lcms2mt/testbed/ibm-t61.icc Binary files differnew file mode 100755 index 000000000..53b3505c6 --- /dev/null +++ b/lcms2mt/testbed/ibm-t61.icc diff --git a/lcms2mt/testbed/new.icc b/lcms2mt/testbed/new.icc Binary files differnew file mode 100644 index 000000000..53b3505c6 --- /dev/null +++ b/lcms2mt/testbed/new.icc diff --git a/lcms2mt/testbed/test1.icc b/lcms2mt/testbed/test1.icc Binary files differnew file mode 100755 index 000000000..d0245c813 --- /dev/null +++ b/lcms2mt/testbed/test1.icc diff --git a/lcms2mt/testbed/test2.icc b/lcms2mt/testbed/test2.icc Binary files differnew file mode 100755 index 000000000..73f1b5aa6 --- /dev/null +++ b/lcms2mt/testbed/test2.icc diff --git a/lcms2mt/testbed/test3.icc b/lcms2mt/testbed/test3.icc Binary files differnew file mode 100755 index 000000000..d0e79301d --- /dev/null +++ b/lcms2mt/testbed/test3.icc diff --git a/lcms2mt/testbed/test4.icc b/lcms2mt/testbed/test4.icc Binary files differnew file mode 100755 index 000000000..2270061be --- /dev/null +++ b/lcms2mt/testbed/test4.icc diff --git a/lcms2mt/testbed/test5.icc b/lcms2mt/testbed/test5.icc Binary files differnew file mode 100755 index 000000000..34583ab1c --- /dev/null +++ b/lcms2mt/testbed/test5.icc diff --git a/lcms2mt/testbed/testcms2.c b/lcms2mt/testbed/testcms2.c new file mode 100644 index 000000000..84728c8eb --- /dev/null +++ b/lcms2mt/testbed/testcms2.c @@ -0,0 +1,8826 @@ +//--------------------------------------------------------------------------------- +// +// Little Color Management System +// Copyright (c) 1998-2017 Marti Maria Saguer +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//--------------------------------------------------------------------------------- +// + + +#include "testcms2.h" + +// On Visual Studio, use debug CRT +#ifdef _MSC_VER +# include "crtdbg.h" +# include <io.h> +#endif + +// A single check. Returns 1 if success, 0 if failed +typedef cmsInt32Number (*TestFn)(void); + +// A parametric Tone curve test function +typedef cmsFloat32Number (* dblfnptr)(cmsFloat32Number x, const cmsFloat64Number Params[]); + +// Some globals to keep track of error +#define TEXT_ERROR_BUFFER_SIZE 4096 + +static char ReasonToFailBuffer[TEXT_ERROR_BUFFER_SIZE]; +static char SubTestBuffer[TEXT_ERROR_BUFFER_SIZE]; +static cmsInt32Number TotalTests = 0, TotalFail = 0; +static cmsBool TrappedError; +static cmsInt32Number SimultaneousErrors; + + +#define cmsmin(a, b) (((a) < (b)) ? (a) : (b)) + +// Die, a fatal unexpected error is detected! +void Die(const char* Reason, ...) +{ + va_list args; + va_start(args, Reason); + vsprintf(ReasonToFailBuffer, Reason, args); + va_end(args); + printf("\n%s\n", ReasonToFailBuffer); + fflush(stdout); + exit(1); +} + +// Memory management replacement ----------------------------------------------------------------------------- + + +// This is just a simple plug-in for malloc, free and realloc to keep track of memory allocated, +// maximum requested as a single block and maximum allocated at a given time. Results are printed at the end +static cmsUInt32Number SingleHit, MaxAllocated=0, TotalMemory=0; + +// I'm hidding the size before the block. This is a well-known technique and probably the blocks coming from +// malloc are built in a way similar to that, but I do on my own to be portable. +typedef struct { + cmsUInt32Number KeepSize; + cmsContext WhoAllocated; + cmsUInt32Number DontCheck; + + union { + cmsUInt64Number HiSparc; + + // '_cmsMemoryBlock' block is prepended by the + // allocator for any requested size. Thus, union holds + // "widest" type to guarantee proper '_cmsMemoryBlock' + // alignment for any requested size. + + } alignment; + + +} _cmsMemoryBlock; + +#define SIZE_OF_MEM_HEADER (sizeof(_cmsMemoryBlock)) + +// This is a fake thread descriptor used to check thread integrity. +// Basically it returns a different threadID each time it is called. +// Then the memory management replacement functions does check if each +// free() is being called with same ContextID used on malloc() +static +cmsContext DbgThread(void) +{ + static cmsUInt32Number n = 1; + + return (cmsContext) (void*)(n++ % 0xff0); +} + +// The allocate routine +static +void* DebugMalloc(cmsContext ContextID, cmsUInt32Number size) +{ + _cmsMemoryBlock* blk; + + if (size <= 0) { + Die("malloc requested with zero bytes"); + } + + TotalMemory += size; + + if (TotalMemory > MaxAllocated) + MaxAllocated = TotalMemory; + + if (size > SingleHit) + SingleHit = size; + + blk = (_cmsMemoryBlock*) malloc(size + SIZE_OF_MEM_HEADER); + if (blk == NULL) return NULL; + + blk ->KeepSize = size; + blk ->WhoAllocated = ContextID; + blk ->DontCheck = 0; + + return (void*) ((cmsUInt8Number*) blk + SIZE_OF_MEM_HEADER); +} + + +// The free routine +static +void DebugFree(cmsContext ContextID, void *Ptr) +{ + _cmsMemoryBlock* blk; + + if (Ptr == NULL) { + Die("NULL free (which is a no-op in C, but may be an clue of something going wrong)"); + } + + blk = (_cmsMemoryBlock*) (((cmsUInt8Number*) Ptr) - SIZE_OF_MEM_HEADER); + TotalMemory -= blk ->KeepSize; + + if (blk ->WhoAllocated != ContextID && !blk->DontCheck) { + Die("Trying to free memory allocated by a different thread"); + } + + free(blk); +} + + +// Reallocate, just a malloc, a copy and a free in this case. +static +void * DebugRealloc(cmsContext ContextID, void* Ptr, cmsUInt32Number NewSize) +{ + _cmsMemoryBlock* blk; + void* NewPtr; + cmsUInt32Number max_sz; + + NewPtr = DebugMalloc(ContextID, NewSize); + if (Ptr == NULL) return NewPtr; + + blk = (_cmsMemoryBlock*) (((cmsUInt8Number*) Ptr) - SIZE_OF_MEM_HEADER); + max_sz = blk -> KeepSize > NewSize ? NewSize : blk ->KeepSize; + memmove(NewPtr, Ptr, max_sz); + DebugFree(ContextID, Ptr); + + return NewPtr; +} + +// Let's know the totals +static +void DebugMemPrintTotals(void) +{ + printf("[Memory statistics]\n"); + printf("Allocated = %u MaxAlloc = %u Single block hit = %u\n", TotalMemory, MaxAllocated, SingleHit); +} + + +void DebugMemDontCheckThis(void *Ptr) +{ + _cmsMemoryBlock* blk = (_cmsMemoryBlock*) (((cmsUInt8Number*) Ptr) - SIZE_OF_MEM_HEADER); + + blk ->DontCheck = 1; +} + + +// Memory string +static +const char* MemStr(cmsUInt32Number size) +{ + static char Buffer[1024]; + + if (size > 1024*1024) { + sprintf(Buffer, "%g Mb", (cmsFloat64Number) size / (1024.0*1024.0)); + } + else + if (size > 1024) { + sprintf(Buffer, "%g Kb", (cmsFloat64Number) size / 1024.0); + } + else + sprintf(Buffer, "%g bytes", (cmsFloat64Number) size); + + return Buffer; +} + + +void TestMemoryLeaks(cmsBool ok) +{ + if (TotalMemory > 0) + printf("Ok, but %s are left!\n", MemStr(TotalMemory)); + else { + if (ok) printf("Ok.\n"); + } +} + +// Here we go with the plug-in declaration +static cmsPluginMemHandler DebugMemHandler = {{ cmsPluginMagicNumber, 2060, cmsPluginMemHandlerSig, NULL }, + DebugMalloc, DebugFree, DebugRealloc, NULL, NULL, NULL }; + +// Returns a pointer to the memhandler plugin +void* PluginMemHandler(void) +{ + return (void*) &DebugMemHandler; +} + +cmsContext WatchDogContext(void* usr) +{ + cmsContext ctx; + + ctx = cmsCreateContext(&DebugMemHandler, usr); + + if (ctx == NULL) + Die("Unable to create memory managed context"); + + DebugMemDontCheckThis(ctx); + return ctx; +} + + + +static +void FatalErrorQuit(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text) +{ + Die(Text); + + cmsUNUSED_PARAMETER(ContextID); + cmsUNUSED_PARAMETER(ErrorCode); +} + + +void ResetFatalError(void) +{ + cmsSetLogErrorHandler(FatalErrorQuit); +} + + +// Print a dot for gauging +void Dot(void) +{ + fprintf(stdout, "."); fflush(stdout); +} + +void Say(const char* str) +{ + fprintf(stdout, "%s", str); fflush(stdout); +} + + +// Keep track of the reason to fail + +void Fail(const char* frm, ...) +{ + va_list args; + va_start(args, frm); + vsprintf(ReasonToFailBuffer, frm, args); + va_end(args); +} + +// Keep track of subtest + +void SubTest(const char* frm, ...) +{ + va_list args; + + Dot(); + va_start(args, frm); + vsprintf(SubTestBuffer, frm, args); + va_end(args); +} + +// The check framework +static +void Check(const char* Title, TestFn Fn) +{ + printf("Checking %s ...", Title); + fflush(stdout); + + ReasonToFailBuffer[0] = 0; + SubTestBuffer[0] = 0; + TrappedError = FALSE; + SimultaneousErrors = 0; + TotalTests++; + + if (Fn() && !TrappedError) { + + // It is a good place to check memory + TestMemoryLeaks(TRUE); + + } + else { + printf("FAIL!\n"); + + if (SubTestBuffer[0]) + printf("%s: [%s]\n\t%s\n", Title, SubTestBuffer, ReasonToFailBuffer); + else + printf("%s:\n\t%s\n", Title, ReasonToFailBuffer); + + if (SimultaneousErrors > 1) + printf("\tMore than one (%d) errors were reported\n", SimultaneousErrors); + + TotalFail++; + } + fflush(stdout); +} + +// Dump a tone curve, for easy diagnostic +void DumpToneCurve(cmsToneCurve* gamma, const char* FileName) +{ + cmsHANDLE hIT8; + cmsUInt32Number i; + + hIT8 = cmsIT8Alloc(DbgThread()); + + cmsIT8SetPropertyDbl(DbgThread(), hIT8, "NUMBER_OF_FIELDS", 2); + cmsIT8SetPropertyDbl(DbgThread(), hIT8, "NUMBER_OF_SETS", gamma ->nEntries); + + cmsIT8SetDataFormat(DbgThread(), hIT8, 0, "SAMPLE_ID"); + cmsIT8SetDataFormat(DbgThread(), hIT8, 1, "VALUE"); + + for (i=0; i < gamma ->nEntries; i++) { + char Val[30]; + + sprintf(Val, "%u", i); + cmsIT8SetDataRowCol(DbgThread(), hIT8, i, 0, Val); + sprintf(Val, "0x%x", gamma ->Table16[i]); + cmsIT8SetDataRowCol(DbgThread(), hIT8, i, 1, Val); + } + + cmsIT8SaveToFile(DbgThread(), hIT8, FileName); + cmsIT8Free(DbgThread(), hIT8); +} + +// ------------------------------------------------------------------------------------------------- + + +// Used to perform several checks. +// The space used is a clone of a well-known commercial +// color space which I will name "Above RGB" +static +cmsHPROFILE Create_AboveRGB(void) +{ + cmsToneCurve* Curve[3]; + cmsHPROFILE hProfile; + cmsCIExyY D65; + cmsCIExyYTRIPLE Primaries = {{0.64, 0.33, 1 }, + {0.21, 0.71, 1 }, + {0.15, 0.06, 1 }}; + + Curve[0] = Curve[1] = Curve[2] = cmsBuildGamma(DbgThread(), 2.19921875); + + cmsWhitePointFromTemp(DbgThread(), &D65, 6504); + hProfile = cmsCreateRGBProfileTHR(DbgThread(), &D65, &Primaries, Curve); + cmsFreeToneCurve(DbgThread(), Curve[0]); + + return hProfile; +} + +// A gamma-2.2 gray space +static +cmsHPROFILE Create_Gray22(void) +{ + cmsHPROFILE hProfile; + cmsToneCurve* Curve = cmsBuildGamma(DbgThread(), 2.2); + if (Curve == NULL) return NULL; + + hProfile = cmsCreateGrayProfileTHR(DbgThread(), cmsD50_xyY(DbgThread()), Curve); + cmsFreeToneCurve(DbgThread(), Curve); + + return hProfile; +} + +// A gamma-3.0 gray space +static +cmsHPROFILE Create_Gray30(void) +{ + cmsHPROFILE hProfile; + cmsToneCurve* Curve = cmsBuildGamma(DbgThread(), 3.0); + if (Curve == NULL) return NULL; + + hProfile = cmsCreateGrayProfileTHR(DbgThread(), cmsD50_xyY(DbgThread()), Curve); + cmsFreeToneCurve(DbgThread(), Curve); + + return hProfile; +} + + +static +cmsHPROFILE Create_GrayLab(void) +{ + cmsHPROFILE hProfile; + cmsToneCurve* Curve = cmsBuildGamma(DbgThread(), 1.0); + if (Curve == NULL) return NULL; + + hProfile = cmsCreateGrayProfileTHR(DbgThread(), cmsD50_xyY(DbgThread()), Curve); + cmsFreeToneCurve(DbgThread(), Curve); + + cmsSetPCS(DbgThread(), hProfile, cmsSigLabData); + return hProfile; +} + +// A CMYK devicelink that adds gamma 3.0 to each channel +static +cmsHPROFILE Create_CMYK_DeviceLink(void) +{ + cmsHPROFILE hProfile; + cmsToneCurve* Tab[4]; + cmsToneCurve* Curve = cmsBuildGamma(DbgThread(), 3.0); + if (Curve == NULL) return NULL; + + Tab[0] = Curve; + Tab[1] = Curve; + Tab[2] = Curve; + Tab[3] = Curve; + + hProfile = cmsCreateLinearizationDeviceLinkTHR(DbgThread(), cmsSigCmykData, Tab); + if (hProfile == NULL) return NULL; + + cmsFreeToneCurve(DbgThread(), Curve); + + return hProfile; +} + + +// Create a fake CMYK profile, without any other requeriment that being coarse CMYK. +// DONT USE THIS PROFILE FOR ANYTHING, IT IS USELESS BUT FOR TESTING PURPOSES. +typedef struct { + + cmsHTRANSFORM hLab2sRGB; + cmsHTRANSFORM sRGB2Lab; + cmsHTRANSFORM hIlimit; + +} FakeCMYKParams; + +static +cmsFloat64Number Clip(cmsFloat64Number v) +{ + if (v < 0) return 0; + if (v > 1) return 1; + + return v; +} + +static +cmsInt32Number ForwardSampler(cmsContext ContextID, register const cmsUInt16Number In[], cmsUInt16Number Out[], void* Cargo) +{ + FakeCMYKParams* p = (FakeCMYKParams*) Cargo; + cmsFloat64Number rgb[3], cmyk[4]; + cmsFloat64Number c, m, y, k; + + cmsDoTransform(DbgThread(), p ->hLab2sRGB, In, rgb, 1); + + c = 1 - rgb[0]; + m = 1 - rgb[1]; + y = 1 - rgb[2]; + + k = (c < m ? cmsmin(c, y) : cmsmin(m, y)); + + // NONSENSE WARNING!: I'm doing this just because this is a test + // profile that may have ink limit up to 400%. There is no UCR here + // so the profile is basically useless for anything but testing. + + cmyk[0] = c; + cmyk[1] = m; + cmyk[2] = y; + cmyk[3] = k; + + cmsDoTransform(DbgThread(), p ->hIlimit, cmyk, Out, 1); + + return 1; +} + + +static +cmsInt32Number ReverseSampler(cmsContext ContextID, register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) +{ + FakeCMYKParams* p = (FakeCMYKParams*) Cargo; + cmsFloat64Number c, m, y, k, rgb[3]; + + c = In[0] / 65535.0; + m = In[1] / 65535.0; + y = In[2] / 65535.0; + k = In[3] / 65535.0; + + if (k == 0) { + + rgb[0] = Clip(1 - c); + rgb[1] = Clip(1 - m); + rgb[2] = Clip(1 - y); + } + else + if (k == 1) { + + rgb[0] = rgb[1] = rgb[2] = 0; + } + else { + + rgb[0] = Clip((1 - c) * (1 - k)); + rgb[1] = Clip((1 - m) * (1 - k)); + rgb[2] = Clip((1 - y) * (1 - k)); + } + + cmsDoTransform(DbgThread(), p ->sRGB2Lab, rgb, Out, 1); + return 1; +} + + + +static +cmsHPROFILE CreateFakeCMYK(cmsFloat64Number InkLimit, cmsBool lUseAboveRGB) +{ + cmsHPROFILE hICC; + cmsPipeline* AToB0, *BToA0; + cmsStage* CLUT; + cmsContext ContextID; + FakeCMYKParams p; + cmsHPROFILE hLab, hsRGB, hLimit; + cmsUInt32Number cmykfrm; + + + if (lUseAboveRGB) + hsRGB = Create_AboveRGB(); + else + hsRGB = cmsCreate_sRGBProfile(); + + hLab = cmsCreateLab4Profile(NULL); + hLimit = cmsCreateInkLimitingDeviceLink(cmsSigCmykData, InkLimit); + + cmykfrm = FLOAT_SH(1) | BYTES_SH(0)|CHANNELS_SH(4); + p.hLab2sRGB = cmsCreateTransform(hLab, TYPE_Lab_16, hsRGB, TYPE_RGB_DBL, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); + p.sRGB2Lab = cmsCreateTransform(hsRGB, TYPE_RGB_DBL, hLab, TYPE_Lab_16, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); + p.hIlimit = cmsCreateTransform(hLimit, cmykfrm, NULL, TYPE_CMYK_16, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); + + ContextID = DbgThread(); + cmsCloseProfile(ContextID, hLab); cmsCloseProfile(ContextID, hsRGB); cmsCloseProfile(ContextID, hLimit); + + hICC = cmsCreateProfilePlaceholder(ContextID); + if (!hICC) return NULL; + + cmsSetProfileVersion(ContextID, hICC, 4.3); + + cmsSetDeviceClass(ContextID, hICC, cmsSigOutputClass); + cmsSetColorSpace(ContextID, hICC, cmsSigCmykData); + cmsSetPCS(ContextID, hICC, cmsSigLabData); + + BToA0 = cmsPipelineAlloc(ContextID, 3, 4); + if (BToA0 == NULL) return 0; + CLUT = cmsStageAllocCLut16bit(ContextID, 17, 3, 4, NULL); + if (CLUT == NULL) return 0; + if (!cmsStageSampleCLut16bit(ContextID, CLUT, ForwardSampler, &p, 0)) return 0; + + cmsPipelineInsertStage(ContextID, BToA0, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3)); + cmsPipelineInsertStage(ContextID, BToA0, cmsAT_END, CLUT); + cmsPipelineInsertStage(ContextID, BToA0, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, 4)); + + if (!cmsWriteTag(ContextID, hICC, cmsSigBToA0Tag, (void*) BToA0)) return 0; + cmsPipelineFree(ContextID, BToA0); + + AToB0 = cmsPipelineAlloc(ContextID, 4, 3); + if (AToB0 == NULL) return 0; + CLUT = cmsStageAllocCLut16bit(ContextID, 17, 4, 3, NULL); + if (CLUT == NULL) return 0; + if (!cmsStageSampleCLut16bit(ContextID, CLUT, ReverseSampler, &p, 0)) return 0; + + cmsPipelineInsertStage(ContextID, AToB0, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 4)); + cmsPipelineInsertStage(ContextID, AToB0, cmsAT_END, CLUT); + cmsPipelineInsertStage(ContextID, AToB0, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, 3)); + + if (!cmsWriteTag(ContextID, hICC, cmsSigAToB0Tag, (void*) AToB0)) return 0; + cmsPipelineFree(ContextID, AToB0); + + cmsDeleteTransform(ContextID, p.hLab2sRGB); + cmsDeleteTransform(ContextID, p.sRGB2Lab); + cmsDeleteTransform(ContextID, p.hIlimit); + + cmsLinkTag(ContextID, hICC, cmsSigAToB1Tag, cmsSigAToB0Tag); + cmsLinkTag(ContextID, hICC, cmsSigAToB2Tag, cmsSigAToB0Tag); + cmsLinkTag(ContextID, hICC, cmsSigBToA1Tag, cmsSigBToA0Tag); + cmsLinkTag(ContextID, hICC, cmsSigBToA2Tag, cmsSigBToA0Tag); + + return hICC; +} + + +// Does create several profiles for latter use------------------------------------------------------------------------------------------------ + +static +cmsInt32Number OneVirtual(cmsHPROFILE h, const char* SubTestTxt, const char* FileName) +{ + SubTest(SubTestTxt); + if (h == NULL) return 0; + + if (!cmsSaveProfileToFile(DbgThread(), h, FileName)) return 0; + cmsCloseProfile(DbgThread(), h); + + h = cmsOpenProfileFromFile( FileName, "r"); + if (h == NULL) return 0; + + cmsCloseProfile(DbgThread(), h); + return 1; +} + + + +// This test checks the ability of lcms2 to save its built-ins as valid profiles. +// It does not check the functionality of such profiles +static +cmsInt32Number CreateTestProfiles(void) +{ + cmsHPROFILE h; + + h = cmsCreate_sRGBProfileTHR(DbgThread()); + if (!OneVirtual(h, "sRGB profile", "sRGBlcms2.icc")) return 0; + + // ---- + + h = Create_AboveRGB(); + if (!OneVirtual(h, "aRGB profile", "aRGBlcms2.icc")) return 0; + + // ---- + + h = Create_Gray22(); + if (!OneVirtual(h, "Gray profile", "graylcms2.icc")) return 0; + + // ---- + + h = Create_Gray30(); + if (!OneVirtual(h, "Gray 3.0 profile", "gray3lcms2.icc")) return 0; + + // ---- + + h = Create_GrayLab(); + if (!OneVirtual(h, "Gray Lab profile", "glablcms2.icc")) return 0; + + // ---- + + h = Create_CMYK_DeviceLink(); + if (!OneVirtual(h, "Linearization profile", "linlcms2.icc")) return 0; + + // ------- + h = cmsCreateInkLimitingDeviceLinkTHR(DbgThread(), cmsSigCmykData, 150); + if (h == NULL) return 0; + if (!OneVirtual(h, "Ink-limiting profile", "limitlcms2.icc")) return 0; + + // ------ + + h = cmsCreateLab2ProfileTHR(DbgThread(), NULL); + if (!OneVirtual(h, "Lab 2 identity profile", "labv2lcms2.icc")) return 0; + + // ---- + + h = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + if (!OneVirtual(h, "Lab 4 identity profile", "labv4lcms2.icc")) return 0; + + // ---- + + h = cmsCreateXYZProfileTHR(DbgThread()); + if (!OneVirtual(h, "XYZ identity profile", "xyzlcms2.icc")) return 0; + + // ---- + + h = cmsCreateNULLProfileTHR(DbgThread()); + if (!OneVirtual(h, "NULL profile", "nullcms2.icc")) return 0; + + // --- + + h = cmsCreateBCHSWabstractProfileTHR(DbgThread(), 17, 0, 0, 0, 0, 5000, 6000); + if (!OneVirtual(h, "BCHS profile", "bchslcms2.icc")) return 0; + + // --- + + h = CreateFakeCMYK(300, FALSE); + if (!OneVirtual(h, "Fake CMYK profile", "lcms2cmyk.icc")) return 0; + + // --- + + h = cmsCreateBCHSWabstractProfileTHR(DbgThread(), 17, 0, 1.2, 0, 3, 5000, 5000); + if (!OneVirtual(h, "Brightness", "brightness.icc")) return 0; + return 1; +} + +static +void RemoveTestProfiles(void) +{ + remove("sRGBlcms2.icc"); + remove("aRGBlcms2.icc"); + remove("graylcms2.icc"); + remove("gray3lcms2.icc"); + remove("linlcms2.icc"); + remove("limitlcms2.icc"); + remove("labv2lcms2.icc"); + remove("labv4lcms2.icc"); + remove("xyzlcms2.icc"); + remove("nullcms2.icc"); + remove("bchslcms2.icc"); + remove("lcms2cmyk.icc"); + remove("glablcms2.icc"); + remove("lcms2link.icc"); + remove("lcms2link2.icc"); + remove("brightness.icc"); +} + +// ------------------------------------------------------------------------------------------------- + +// Check the size of basic types. If this test fails, nothing is going to work anyway +static +cmsInt32Number CheckBaseTypes(void) +{ + // Ignore warnings about conditional expression +#ifdef _MSC_VER +#pragma warning(disable: 4127) +#endif + + if (sizeof(cmsUInt8Number) != 1) return 0; + if (sizeof(cmsInt8Number) != 1) return 0; + if (sizeof(cmsUInt16Number) != 2) return 0; + if (sizeof(cmsInt16Number) != 2) return 0; + if (sizeof(cmsUInt32Number) != 4) return 0; + if (sizeof(cmsInt32Number) != 4) return 0; + if (sizeof(cmsUInt64Number) != 8) return 0; + if (sizeof(cmsInt64Number) != 8) return 0; + if (sizeof(cmsFloat32Number) != 4) return 0; + if (sizeof(cmsFloat64Number) != 8) return 0; + if (sizeof(cmsSignature) != 4) return 0; + if (sizeof(cmsU8Fixed8Number) != 2) return 0; + if (sizeof(cmsS15Fixed16Number) != 4) return 0; + if (sizeof(cmsU16Fixed16Number) != 4) return 0; + + return 1; +} + +// ------------------------------------------------------------------------------------------------- + + +// Are we little or big endian? From Harbison&Steele. +static +cmsInt32Number CheckEndianess(void) +{ + cmsInt32Number BigEndian, IsOk; + union { + long l; + char c[sizeof (long)]; + } u; + + u.l = 1; + BigEndian = (u.c[sizeof (long) - 1] == 1); + +#ifdef CMS_USE_BIG_ENDIAN + IsOk = BigEndian; +#else + IsOk = !BigEndian; +#endif + + if (!IsOk) { + Die("\nOOOPPSS! You have CMS_USE_BIG_ENDIAN toggle misconfigured!\n\n" + "Please, edit lcms2mt.h and %s the CMS_USE_BIG_ENDIAN toggle.\n", BigEndian? "uncomment" : "comment"); + return 0; + } + + return 1; +} + +// Check quick floor +static +cmsInt32Number CheckQuickFloor(void) +{ + if ((_cmsQuickFloor(1.234) != 1) || + (_cmsQuickFloor(32767.234) != 32767) || + (_cmsQuickFloor(-1.234) != -2) || + (_cmsQuickFloor(-32767.1) != -32768)) { + + Die("\nOOOPPSS! _cmsQuickFloor() does not work as expected in your machine!\n\n" + "Please, edit lcms2mt.h and uncomment the CMS_DONT_USE_FAST_FLOOR toggle.\n"); + return 0; + + } + + return 1; +} + +// Quick floor restricted to word +static +cmsInt32Number CheckQuickFloorWord(void) +{ + cmsUInt32Number i; + + for (i=0; i < 65535; i++) { + + if (_cmsQuickFloorWord((cmsFloat64Number) i + 0.1234) != i) { + + Die("\nOOOPPSS! _cmsQuickFloorWord() does not work as expected in your machine!\n\n" + "Please, edit lcms2mt.h and uncomment the CMS_DONT_USE_FAST_FLOOR toggle.\n"); + return 0; + } + } + + return 1; +} + +// ------------------------------------------------------------------------------------------------- + +// Precision stuff. + +// On 15.16 fixed point, this is the maximum we can obtain. Remember ICC profiles have storage limits on this number +#define FIXED_PRECISION_15_16 (1.0 / 65535.0) + +// On 8.8 fixed point, that is the max we can obtain. +#define FIXED_PRECISION_8_8 (1.0 / 255.0) + +// On cmsFloat32Number type, this is the precision we expect +#define FLOAT_PRECISSION (0.00001) + +static cmsFloat64Number MaxErr; +static cmsFloat64Number AllowedErr = FIXED_PRECISION_15_16; + +cmsBool IsGoodVal(const char *title, cmsFloat64Number in, cmsFloat64Number out, cmsFloat64Number max) +{ + cmsFloat64Number Err = fabs(in - out); + + if (Err > MaxErr) MaxErr = Err; + + if ((Err > max )) { + + Fail("(%s): Must be %f, But is %f ", title, in, out); + return FALSE; + } + + return TRUE; +} + + +cmsBool IsGoodFixed15_16(const char *title, cmsFloat64Number in, cmsFloat64Number out) +{ + return IsGoodVal(title, in, out, FIXED_PRECISION_15_16); +} + + +cmsBool IsGoodFixed8_8(const char *title, cmsFloat64Number in, cmsFloat64Number out) +{ + return IsGoodVal(title, in, out, FIXED_PRECISION_8_8); +} + +cmsBool IsGoodWord(const char *title, cmsUInt16Number in, cmsUInt16Number out) +{ + if ((abs(in - out) > 0 )) { + + Fail("(%s): Must be %x, But is %x ", title, in, out); + return FALSE; + } + + return TRUE; +} + +cmsBool IsGoodWordPrec(const char *title, cmsUInt16Number in, cmsUInt16Number out, cmsUInt16Number maxErr) +{ + if ((abs(in - out) > maxErr )) { + + Fail("(%s): Must be %x, But is %x ", title, in, out); + return FALSE; + } + + return TRUE; +} + +// Fixed point ---------------------------------------------------------------------------------------------- + +static +cmsInt32Number TestSingleFixed15_16(cmsFloat64Number d) +{ + cmsS15Fixed16Number f = _cmsDoubleTo15Fixed16(DbgThread(), d); + cmsFloat64Number RoundTrip = _cms15Fixed16toDouble(DbgThread(), f); + cmsFloat64Number Error = fabs(d - RoundTrip); + + return ( Error <= FIXED_PRECISION_15_16); +} + +static +cmsInt32Number CheckFixedPoint15_16(void) +{ + if (!TestSingleFixed15_16(1.0)) return 0; + if (!TestSingleFixed15_16(2.0)) return 0; + if (!TestSingleFixed15_16(1.23456)) return 0; + if (!TestSingleFixed15_16(0.99999)) return 0; + if (!TestSingleFixed15_16(0.1234567890123456789099999)) return 0; + if (!TestSingleFixed15_16(-1.0)) return 0; + if (!TestSingleFixed15_16(-2.0)) return 0; + if (!TestSingleFixed15_16(-1.23456)) return 0; + if (!TestSingleFixed15_16(-1.1234567890123456789099999)) return 0; + if (!TestSingleFixed15_16(+32767.1234567890123456789099999)) return 0; + if (!TestSingleFixed15_16(-32767.1234567890123456789099999)) return 0; + return 1; +} + +static +cmsInt32Number TestSingleFixed8_8(cmsFloat64Number d) +{ + cmsS15Fixed16Number f = _cmsDoubleTo8Fixed8(DbgThread(), d); + cmsFloat64Number RoundTrip = _cms8Fixed8toDouble(DbgThread(), (cmsUInt16Number) f); + cmsFloat64Number Error = fabs(d - RoundTrip); + + return ( Error <= FIXED_PRECISION_8_8); +} + +static +cmsInt32Number CheckFixedPoint8_8(void) +{ + if (!TestSingleFixed8_8(1.0)) return 0; + if (!TestSingleFixed8_8(2.0)) return 0; + if (!TestSingleFixed8_8(1.23456)) return 0; + if (!TestSingleFixed8_8(0.99999)) return 0; + if (!TestSingleFixed8_8(0.1234567890123456789099999)) return 0; + if (!TestSingleFixed8_8(+255.1234567890123456789099999)) return 0; + + return 1; +} + +// D50 constant -------------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckD50Roundtrip(void) +{ + cmsFloat64Number cmsD50X_2 = 0.96420288; + cmsFloat64Number cmsD50Y_2 = 1.0; + cmsFloat64Number cmsD50Z_2 = 0.82490540; + + cmsS15Fixed16Number xe = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50X); + cmsS15Fixed16Number ye = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50Y); + cmsS15Fixed16Number ze = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50Z); + + cmsFloat64Number x = _cms15Fixed16toDouble(DbgThread(), xe); + cmsFloat64Number y = _cms15Fixed16toDouble(DbgThread(), ye); + cmsFloat64Number z = _cms15Fixed16toDouble(DbgThread(), ze); + + double dx = fabs(cmsD50X - x); + double dy = fabs(cmsD50Y - y); + double dz = fabs(cmsD50Z - z); + + double euc = sqrt(dx*dx + dy*dy + dz* dz); + + if (euc > 1E-5) { + + Fail("D50 roundtrip |err| > (%f) ", euc); + return 0; + } + + xe = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50X_2); + ye = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50Y_2); + ze = _cmsDoubleTo15Fixed16(DbgThread(), cmsD50Z_2); + + x = _cms15Fixed16toDouble(DbgThread(), xe); + y = _cms15Fixed16toDouble(DbgThread(), ye); + z = _cms15Fixed16toDouble(DbgThread(), ze); + + dx = fabs(cmsD50X_2 - x); + dy = fabs(cmsD50Y_2 - y); + dz = fabs(cmsD50Z_2 - z); + + euc = sqrt(dx*dx + dy*dy + dz* dz); + + if (euc > 1E-5) { + + Fail("D50 roundtrip |err| > (%f) ", euc); + return 0; + } + + + return 1; +} + +// Linear interpolation ----------------------------------------------------------------------------------------------- + +// Since prime factors of 65535 (FFFF) are, +// +// 0xFFFF = 3 * 5 * 17 * 257 +// +// I test tables of 2, 4, 6, and 18 points, that will be exact. + +static +void BuildTable(cmsInt32Number n, cmsUInt16Number Tab[], cmsBool Descending) +{ + cmsInt32Number i; + + for (i=0; i < n; i++) { + cmsFloat64Number v = (cmsFloat64Number) ((cmsFloat64Number) 65535.0 * i ) / (n-1); + + Tab[Descending ? (n - i - 1) : i ] = (cmsUInt16Number) floor(v + 0.5); + } +} + +// A single function that does check 1D interpolation +// nNodesToCheck = number on nodes to check +// Down = Create decreasing tables +// Reverse = Check reverse interpolation +// max_err = max allowed error + +static +cmsInt32Number Check1D(cmsInt32Number nNodesToCheck, cmsBool Down, cmsInt32Number max_err) +{ + cmsUInt32Number i; + cmsUInt16Number in, out; + cmsInterpParams* p; + cmsUInt16Number* Tab; + + Tab = (cmsUInt16Number*) malloc(sizeof(cmsUInt16Number)* nNodesToCheck); + if (Tab == NULL) return 0; + + p = _cmsComputeInterpParams(DbgThread(), nNodesToCheck, 1, 1, Tab, CMS_LERP_FLAGS_16BITS); + if (p == NULL) return 0; + + BuildTable(nNodesToCheck, Tab, Down); + + for (i=0; i <= 0xffff; i++) { + + in = (cmsUInt16Number) i; + out = 0; + + p ->Interpolation.Lerp16(DbgThread(), &in, &out, p); + + if (Down) out = 0xffff - out; + + if (abs(out - in) > max_err) { + + Fail("(%dp): Must be %x, But is %x : ", nNodesToCheck, in, out); + _cmsFreeInterpParams(DbgThread(), p); + free(Tab); + return 0; + } + } + + _cmsFreeInterpParams(DbgThread(), p); + free(Tab); + return 1; +} + + +static +cmsInt32Number Check1DLERP2(void) +{ + return Check1D(2, FALSE, 0); +} + + +static +cmsInt32Number Check1DLERP3(void) +{ + return Check1D(3, FALSE, 1); +} + + +static +cmsInt32Number Check1DLERP4(void) +{ + return Check1D(4, FALSE, 0); +} + +static +cmsInt32Number Check1DLERP6(void) +{ + return Check1D(6, FALSE, 0); +} + +static +cmsInt32Number Check1DLERP18(void) +{ + return Check1D(18, FALSE, 0); +} + + +static +cmsInt32Number Check1DLERP2Down(void) +{ + return Check1D(2, TRUE, 0); +} + + +static +cmsInt32Number Check1DLERP3Down(void) +{ + return Check1D(3, TRUE, 1); +} + +static +cmsInt32Number Check1DLERP6Down(void) +{ + return Check1D(6, TRUE, 0); +} + +static +cmsInt32Number Check1DLERP18Down(void) +{ + return Check1D(18, TRUE, 0); +} + +static +cmsInt32Number ExhaustiveCheck1DLERP(void) +{ + cmsUInt32Number j; + + printf("\n"); + for (j=10; j <= 4096; j++) { + + if ((j % 10) == 0) printf("%u \r", j); + + if (!Check1D(j, FALSE, 1)) return 0; + } + + printf("\rResult is "); + return 1; +} + +static +cmsInt32Number ExhaustiveCheck1DLERPDown(void) +{ + cmsUInt32Number j; + + printf("\n"); + for (j=10; j <= 4096; j++) { + + if ((j % 10) == 0) printf("%u \r", j); + + if (!Check1D(j, TRUE, 1)) return 0; + } + + + printf("\rResult is "); + return 1; +} + + + +// 3D interpolation ------------------------------------------------------------------------------------------------- + +static +cmsInt32Number Check3DinterpolationFloatTetrahedral(void) +{ + cmsInterpParams* p; + cmsInt32Number i; + cmsFloat32Number In[3], Out[3]; + cmsFloat32Number FloatTable[] = { //R G B + + 0, 0, 0, // B=0,G=0,R=0 + 0, 0, .25, // B=1,G=0,R=0 + + 0, .5, 0, // B=0,G=1,R=0 + 0, .5, .25, // B=1,G=1,R=0 + + 1, 0, 0, // B=0,G=0,R=1 + 1, 0, .25, // B=1,G=0,R=1 + + 1, .5, 0, // B=0,G=1,R=1 + 1, .5, .25 // B=1,G=1,R=1 + + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, FloatTable, CMS_LERP_FLAGS_FLOAT); + + + MaxErr = 0.0; + for (i=0; i < 0xffff; i++) { + + In[0] = In[1] = In[2] = (cmsFloat32Number) ( (cmsFloat32Number) i / 65535.0F); + + p ->Interpolation.LerpFloat(DbgThread(), In, Out, p); + + if (!IsGoodFixed15_16("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodFixed15_16("Channel 2", Out[1], (cmsFloat32Number) In[1] / 2.F)) goto Error; + if (!IsGoodFixed15_16("Channel 3", Out[2], (cmsFloat32Number) In[2] / 4.F)) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + +static +cmsInt32Number Check3DinterpolationFloatTrilinear(void) +{ + cmsInterpParams* p; + cmsInt32Number i; + cmsFloat32Number In[3], Out[3]; + cmsFloat32Number FloatTable[] = { //R G B + + 0, 0, 0, // B=0,G=0,R=0 + 0, 0, .25, // B=1,G=0,R=0 + + 0, .5, 0, // B=0,G=1,R=0 + 0, .5, .25, // B=1,G=1,R=0 + + 1, 0, 0, // B=0,G=0,R=1 + 1, 0, .25, // B=1,G=0,R=1 + + 1, .5, 0, // B=0,G=1,R=1 + 1, .5, .25 // B=1,G=1,R=1 + + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, FloatTable, CMS_LERP_FLAGS_FLOAT|CMS_LERP_FLAGS_TRILINEAR); + + MaxErr = 0.0; + for (i=0; i < 0xffff; i++) { + + In[0] = In[1] = In[2] = (cmsFloat32Number) ( (cmsFloat32Number) i / 65535.0F); + + p ->Interpolation.LerpFloat(DbgThread(), In, Out, p); + + if (!IsGoodFixed15_16("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodFixed15_16("Channel 2", Out[1], (cmsFloat32Number) In[1] / 2.F)) goto Error; + if (!IsGoodFixed15_16("Channel 3", Out[2], (cmsFloat32Number) In[2] / 4.F)) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; + +} + +static +cmsInt32Number Check3DinterpolationTetrahedral16(void) +{ + cmsInterpParams* p; + cmsInt32Number i; + cmsUInt16Number In[3], Out[3]; + cmsUInt16Number Table[] = { + + 0, 0, 0, + 0, 0, 0xffff, + + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, Table, CMS_LERP_FLAGS_16BITS); + + MaxErr = 0.0; + for (i=0; i < 0xffff; i++) { + + In[0] = In[1] = In[2] = (cmsUInt16Number) i; + + p ->Interpolation.Lerp16(DbgThread(), In, Out, p); + + if (!IsGoodWord("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodWord("Channel 2", Out[1], In[1])) goto Error; + if (!IsGoodWord("Channel 3", Out[2], In[2])) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + +static +cmsInt32Number Check3DinterpolationTrilinear16(void) +{ + cmsInterpParams* p; + cmsInt32Number i; + cmsUInt16Number In[3], Out[3]; + cmsUInt16Number Table[] = { + + 0, 0, 0, + 0, 0, 0xffff, + + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, Table, CMS_LERP_FLAGS_TRILINEAR); + + MaxErr = 0.0; + for (i=0; i < 0xffff; i++) { + + In[0] = In[1] = In[2] = (cmsUInt16Number) i; + + p ->Interpolation.Lerp16(DbgThread(), In, Out, p); + + if (!IsGoodWord("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodWord("Channel 2", Out[1], In[1])) goto Error; + if (!IsGoodWord("Channel 3", Out[2], In[2])) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + + +static +cmsInt32Number ExaustiveCheck3DinterpolationFloatTetrahedral(void) +{ + cmsInterpParams* p; + cmsInt32Number r, g, b; + cmsFloat32Number In[3], Out[3]; + cmsFloat32Number FloatTable[] = { //R G B + + 0, 0, 0, // B=0,G=0,R=0 + 0, 0, .25, // B=1,G=0,R=0 + + 0, .5, 0, // B=0,G=1,R=0 + 0, .5, .25, // B=1,G=1,R=0 + + 1, 0, 0, // B=0,G=0,R=1 + 1, 0, .25, // B=1,G=0,R=1 + + 1, .5, 0, // B=0,G=1,R=1 + 1, .5, .25 // B=1,G=1,R=1 + + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, FloatTable, CMS_LERP_FLAGS_FLOAT); + + MaxErr = 0.0; + for (r=0; r < 0xff; r++) + for (g=0; g < 0xff; g++) + for (b=0; b < 0xff; b++) + { + + In[0] = (cmsFloat32Number) r / 255.0F; + In[1] = (cmsFloat32Number) g / 255.0F; + In[2] = (cmsFloat32Number) b / 255.0F; + + + p ->Interpolation.LerpFloat(DbgThread(), In, Out, p); + + if (!IsGoodFixed15_16("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodFixed15_16("Channel 2", Out[1], (cmsFloat32Number) In[1] / 2.F)) goto Error; + if (!IsGoodFixed15_16("Channel 3", Out[2], (cmsFloat32Number) In[2] / 4.F)) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + +static +cmsInt32Number ExaustiveCheck3DinterpolationFloatTrilinear(void) +{ + cmsInterpParams* p; + cmsInt32Number r, g, b; + cmsFloat32Number In[3], Out[3]; + cmsFloat32Number FloatTable[] = { //R G B + + 0, 0, 0, // B=0,G=0,R=0 + 0, 0, .25, // B=1,G=0,R=0 + + 0, .5, 0, // B=0,G=1,R=0 + 0, .5, .25, // B=1,G=1,R=0 + + 1, 0, 0, // B=0,G=0,R=1 + 1, 0, .25, // B=1,G=0,R=1 + + 1, .5, 0, // B=0,G=1,R=1 + 1, .5, .25 // B=1,G=1,R=1 + + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, FloatTable, CMS_LERP_FLAGS_FLOAT|CMS_LERP_FLAGS_TRILINEAR); + + MaxErr = 0.0; + for (r=0; r < 0xff; r++) + for (g=0; g < 0xff; g++) + for (b=0; b < 0xff; b++) + { + + In[0] = (cmsFloat32Number) r / 255.0F; + In[1] = (cmsFloat32Number) g / 255.0F; + In[2] = (cmsFloat32Number) b / 255.0F; + + + p ->Interpolation.LerpFloat(DbgThread(), In, Out, p); + + if (!IsGoodFixed15_16("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodFixed15_16("Channel 2", Out[1], (cmsFloat32Number) In[1] / 2.F)) goto Error; + if (!IsGoodFixed15_16("Channel 3", Out[2], (cmsFloat32Number) In[2] / 4.F)) goto Error; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr); + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; + +} + +static +cmsInt32Number ExhaustiveCheck3DinterpolationTetrahedral16(void) +{ + cmsInterpParams* p; + cmsInt32Number r, g, b; + cmsUInt16Number In[3], Out[3]; + cmsUInt16Number Table[] = { + + 0, 0, 0, + 0, 0, 0xffff, + + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, Table, CMS_LERP_FLAGS_16BITS); + + for (r=0; r < 0xff; r++) + for (g=0; g < 0xff; g++) + for (b=0; b < 0xff; b++) + { + In[0] = (cmsUInt16Number) r ; + In[1] = (cmsUInt16Number) g ; + In[2] = (cmsUInt16Number) b ; + + + p ->Interpolation.Lerp16(DbgThread(), In, Out, p); + + if (!IsGoodWord("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodWord("Channel 2", Out[1], In[1])) goto Error; + if (!IsGoodWord("Channel 3", Out[2], In[2])) goto Error; + } + + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + +static +cmsInt32Number ExhaustiveCheck3DinterpolationTrilinear16(void) +{ + cmsInterpParams* p; + cmsInt32Number r, g, b; + cmsUInt16Number In[3], Out[3]; + cmsUInt16Number Table[] = { + + 0, 0, 0, + 0, 0, 0xffff, + + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + p = _cmsComputeInterpParams(DbgThread(), 2, 3, 3, Table, CMS_LERP_FLAGS_TRILINEAR); + + for (r=0; r < 0xff; r++) + for (g=0; g < 0xff; g++) + for (b=0; b < 0xff; b++) + { + In[0] = (cmsUInt16Number) r ; + In[1] = (cmsUInt16Number)g ; + In[2] = (cmsUInt16Number)b ; + + + p ->Interpolation.Lerp16(DbgThread(), In, Out, p); + + if (!IsGoodWord("Channel 1", Out[0], In[0])) goto Error; + if (!IsGoodWord("Channel 2", Out[1], In[1])) goto Error; + if (!IsGoodWord("Channel 3", Out[2], In[2])) goto Error; + } + + + _cmsFreeInterpParams(DbgThread(), p); + return 1; + +Error: + _cmsFreeInterpParams(DbgThread(), p); + return 0; +} + +// Check reverse interpolation on LUTS. This is right now exclusively used by K preservation algorithm +static +cmsInt32Number CheckReverseInterpolation3x3(void) +{ + cmsPipeline* Lut; + cmsStage* clut; + cmsFloat32Number Target[4], Result[4], Hint[4]; + cmsFloat32Number err, max; + cmsInt32Number i; + cmsUInt16Number Table[] = { + + 0, 0, 0, // 0 0 0 + 0, 0, 0xffff, // 0 0 1 + + 0, 0xffff, 0, // 0 1 0 + 0, 0xffff, 0xffff, // 0 1 1 + + 0xffff, 0, 0, // 1 0 0 + 0xffff, 0, 0xffff, // 1 0 1 + + 0xffff, 0xffff, 0, // 1 1 0 + 0xffff, 0xffff, 0xffff, // 1 1 1 + }; + + + + Lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + clut = cmsStageAllocCLut16bit(DbgThread(), 2, 3, 3, Table); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_BEGIN, clut); + + Target[0] = 0; Target[1] = 0; Target[2] = 0; + Hint[0] = 0; Hint[1] = 0; Hint[2] = 0; + cmsPipelineEvalReverseFloat(DbgThread(), Target, Result, NULL, Lut); + if (Result[0] != 0 || Result[1] != 0 || Result[2] != 0){ + + Fail("Reverse interpolation didn't find zero"); + goto Error; + } + + // Transverse identity + max = 0; + for (i=0; i <= 100; i++) { + + cmsFloat32Number in = i / 100.0F; + + Target[0] = in; Target[1] = 0; Target[2] = 0; + cmsPipelineEvalReverseFloat(DbgThread(), Target, Result, Hint, Lut); + + err = fabsf(in - Result[0]); + if (err > max) max = err; + + memcpy(Hint, Result, sizeof(Hint)); + } + + cmsPipelineFree(DbgThread(), Lut); + return (max <= FLOAT_PRECISSION); + +Error: + cmsPipelineFree(Lut); + return 0; +} + + +static +cmsInt32Number CheckReverseInterpolation4x3(void) +{ + cmsPipeline* Lut; + cmsStage* clut; + cmsFloat32Number Target[4], Result[4], Hint[4]; + cmsFloat32Number err, max; + cmsInt32Number i; + + // 4 -> 3, output gets 3 first channels copied + cmsUInt16Number Table[] = { + + 0, 0, 0, // 0 0 0 0 = ( 0, 0, 0) + 0, 0, 0, // 0 0 0 1 = ( 0, 0, 0) + + 0, 0, 0xffff, // 0 0 1 0 = ( 0, 0, 1) + 0, 0, 0xffff, // 0 0 1 1 = ( 0, 0, 1) + + 0, 0xffff, 0, // 0 1 0 0 = ( 0, 1, 0) + 0, 0xffff, 0, // 0 1 0 1 = ( 0, 1, 0) + + 0, 0xffff, 0xffff, // 0 1 1 0 = ( 0, 1, 1) + 0, 0xffff, 0xffff, // 0 1 1 1 = ( 0, 1, 1) + + 0xffff, 0, 0, // 1 0 0 0 = ( 1, 0, 0) + 0xffff, 0, 0, // 1 0 0 1 = ( 1, 0, 0) + + 0xffff, 0, 0xffff, // 1 0 1 0 = ( 1, 0, 1) + 0xffff, 0, 0xffff, // 1 0 1 1 = ( 1, 0, 1) + + 0xffff, 0xffff, 0, // 1 1 0 0 = ( 1, 1, 0) + 0xffff, 0xffff, 0, // 1 1 0 1 = ( 1, 1, 0) + + 0xffff, 0xffff, 0xffff, // 1 1 1 0 = ( 1, 1, 1) + 0xffff, 0xffff, 0xffff, // 1 1 1 1 = ( 1, 1, 1) + }; + + + Lut = cmsPipelineAlloc(DbgThread(), 4, 3); + + clut = cmsStageAllocCLut16bit(DbgThread(), 2, 4, 3, Table); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_BEGIN, clut); + + // Check if the LUT is behaving as expected + SubTest("4->3 feasibility"); + for (i=0; i <= 100; i++) { + + Target[0] = i / 100.0F; + Target[1] = Target[0]; + Target[2] = 0; + Target[3] = 12; + + cmsPipelineEvalFloat(DbgThread(), Target, Result, Lut); + + if (!IsGoodFixed15_16("0", Target[0], Result[0])) goto Error; + if (!IsGoodFixed15_16("1", Target[1], Result[1])) goto Error; + if (!IsGoodFixed15_16("2", Target[2], Result[2])) goto Error; + } + + SubTest("4->3 zero"); + Target[0] = 0; + Target[1] = 0; + Target[2] = 0; + + // This one holds the fixed K + Target[3] = 0; + + // This is our hint (which is a big lie in this case) + Hint[0] = 0.1F; Hint[1] = 0.1F; Hint[2] = 0.1F; + + cmsPipelineEvalReverseFloat(DbgThread(), Target, Result, Hint, Lut); + + if (Result[0] != 0 || Result[1] != 0 || Result[2] != 0 || Result[3] != 0){ + + Fail("Reverse interpolation didn't find zero"); + goto Error; + } + + SubTest("4->3 find CMY"); + max = 0; + for (i=0; i <= 100; i++) { + + cmsFloat32Number in = i / 100.0F; + + Target[0] = in; Target[1] = 0; Target[2] = 0; + cmsPipelineEvalReverseFloat(DbgThread(), Target, Result, Hint, Lut); + + err = fabsf(in - Result[0]); + if (err > max) max = err; + + memcpy(Hint, Result, sizeof(Hint)); + } + + cmsPipelineFree(DbgThread(), Lut); + return (max <= FLOAT_PRECISSION); + +Error: + cmsPipelineFree(Lut); + return 0; +} + + + +// Check all interpolation. + +static +cmsUInt16Number Fn8D1(cmsUInt16Number a1, cmsUInt16Number a2, cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6, cmsUInt16Number a7, cmsUInt16Number a8, + cmsUInt32Number m) +{ + return (cmsUInt16Number) ((a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8) / m); +} + + +static +cmsUInt16Number Fn8D2(cmsUInt16Number a1, cmsUInt16Number a2, cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6, cmsUInt16Number a7, cmsUInt16Number a8, + cmsUInt32Number m) +{ + return (cmsUInt16Number) ((a1 + 3* a2 + 3* a3 + a4 + a5 + a6 + a7 + a8 ) / (m + 4)); +} + + +static +cmsUInt16Number Fn8D3(cmsUInt16Number a1, cmsUInt16Number a2, cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6, cmsUInt16Number a7, cmsUInt16Number a8, + cmsUInt32Number m) +{ + return (cmsUInt16Number) ((3*a1 + 2*a2 + 3*a3 + a4 + a5 + a6 + a7 + a8) / (m + 5)); +} + + + + +static +cmsInt32Number Sampler3D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], 0, 0, 0, 0, 0, 3); + Out[1] = Fn8D2(In[0], In[1], In[2], 0, 0, 0, 0, 0, 3); + Out[2] = Fn8D3(In[0], In[1], In[2], 0, 0, 0, 0, 0, 3); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); + +} + +static +cmsInt32Number Sampler4D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], In[3], 0, 0, 0, 0, 4); + Out[1] = Fn8D2(In[0], In[1], In[2], In[3], 0, 0, 0, 0, 4); + Out[2] = Fn8D3(In[0], In[1], In[2], In[3], 0, 0, 0, 0, 4); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); +} + +static +cmsInt32Number Sampler5D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], In[3], In[4], 0, 0, 0, 5); + Out[1] = Fn8D2(In[0], In[1], In[2], In[3], In[4], 0, 0, 0, 5); + Out[2] = Fn8D3(In[0], In[1], In[2], In[3], In[4], 0, 0, 0, 5); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); +} + +static +cmsInt32Number Sampler6D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], In[3], In[4], In[5], 0, 0, 6); + Out[1] = Fn8D2(In[0], In[1], In[2], In[3], In[4], In[5], 0, 0, 6); + Out[2] = Fn8D3(In[0], In[1], In[2], In[3], In[4], In[5], 0, 0, 6); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); +} + +static +cmsInt32Number Sampler7D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], In[3], In[4], In[5], In[6], 0, 7); + Out[1] = Fn8D2(In[0], In[1], In[2], In[3], In[4], In[5], In[6], 0, 7); + Out[2] = Fn8D3(In[0], In[1], In[2], In[3], In[4], In[5], In[6], 0, 7); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); +} + +static +cmsInt32Number Sampler8D(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register void * Cargo) +{ + + Out[0] = Fn8D1(In[0], In[1], In[2], In[3], In[4], In[5], In[6], In[7], 8); + Out[1] = Fn8D2(In[0], In[1], In[2], In[3], In[4], In[5], In[6], In[7], 8); + Out[2] = Fn8D3(In[0], In[1], In[2], In[3], In[4], In[5], In[6], In[7], 8); + + return 1; + + cmsUNUSED_PARAMETER(Cargo); +} + +static +cmsBool CheckOne3D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, cmsUInt16Number a3) +{ + cmsUInt16Number In[3], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler3D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + +static +cmsBool CheckOne4D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, cmsUInt16Number a3, cmsUInt16Number a4) +{ + cmsUInt16Number In[4], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; In[3] = a4; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler4D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + +static +cmsBool CheckOne5D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, + cmsUInt16Number a3, cmsUInt16Number a4, cmsUInt16Number a5) +{ + cmsUInt16Number In[5], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; In[3] = a4; In[4] = a5; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler5D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + +static +cmsBool CheckOne6D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, + cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6) +{ + cmsUInt16Number In[6], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; In[3] = a4; In[4] = a5; In[5] = a6; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler6D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + + +static +cmsBool CheckOne7D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, + cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6, + cmsUInt16Number a7) +{ + cmsUInt16Number In[7], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; In[3] = a4; In[4] = a5; In[5] = a6; In[6] = a7; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler7D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + + +static +cmsBool CheckOne8D(cmsPipeline* lut, cmsUInt16Number a1, cmsUInt16Number a2, + cmsUInt16Number a3, cmsUInt16Number a4, + cmsUInt16Number a5, cmsUInt16Number a6, + cmsUInt16Number a7, cmsUInt16Number a8) +{ + cmsUInt16Number In[8], Out1[3], Out2[3]; + + In[0] = a1; In[1] = a2; In[2] = a3; In[3] = a4; In[4] = a5; In[5] = a6; In[6] = a7; In[7] = a8; + + // This is the interpolated value + cmsPipelineEval16(DbgThread(), In, Out1, lut); + + // This is the real value + Sampler8D(DbgThread(), In, Out2, NULL); + + // Let's see the difference + + if (!IsGoodWordPrec("Channel 1", Out1[0], Out2[0], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 2", Out1[1], Out2[1], 2)) return FALSE; + if (!IsGoodWordPrec("Channel 3", Out1[2], Out2[2], 2)) return FALSE; + + return TRUE; +} + + +static +cmsInt32Number Check3Dinterp(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + + lut = cmsPipelineAlloc(DbgThread(), 3, 3); + mpe = cmsStageAllocCLut16bit(DbgThread(), 9, 3, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler3D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne3D(lut, 0, 0, 0)) return 0; + if (!CheckOne3D(lut, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne3D(lut, 0x8080, 0x8080, 0x8080)) return 0; + if (!CheckOne3D(lut, 0x0000, 0xFE00, 0x80FF)) return 0; + if (!CheckOne3D(lut, 0x1111, 0x2222, 0x3333)) return 0; + if (!CheckOne3D(lut, 0x0000, 0x0012, 0x0013)) return 0; + if (!CheckOne3D(lut, 0x3141, 0x1415, 0x1592)) return 0; + if (!CheckOne3D(lut, 0xFF00, 0xFF01, 0xFF12)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + +static +cmsInt32Number Check3DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 7, 8, 9 }; + + lut = cmsPipelineAlloc(DbgThread(), 3, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 3, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler3D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne3D(lut, 0, 0, 0)) return 0; + if (!CheckOne3D(lut, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne3D(lut, 0x8080, 0x8080, 0x8080)) return 0; + if (!CheckOne3D(lut, 0x0000, 0xFE00, 0x80FF)) return 0; + if (!CheckOne3D(lut, 0x1111, 0x2222, 0x3333)) return 0; + if (!CheckOne3D(lut, 0x0000, 0x0012, 0x0013)) return 0; + if (!CheckOne3D(lut, 0x3141, 0x1415, 0x1592)) return 0; + if (!CheckOne3D(lut, 0xFF00, 0xFF01, 0xFF12)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + + +static +cmsInt32Number Check4Dinterp(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + + lut = cmsPipelineAlloc(DbgThread(), 4, 3); + mpe = cmsStageAllocCLut16bit(DbgThread(), 9, 4, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler4D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne4D(lut, 0, 0, 0, 0)) return 0; + if (!CheckOne4D(lut, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne4D(lut, 0x8080, 0x8080, 0x8080, 0x8080)) return 0; + if (!CheckOne4D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888)) return 0; + if (!CheckOne4D(lut, 0x1111, 0x2222, 0x3333, 0x4444)) return 0; + if (!CheckOne4D(lut, 0x0000, 0x0012, 0x0013, 0x0014)) return 0; + if (!CheckOne4D(lut, 0x3141, 0x1415, 0x1592, 0x9261)) return 0; + if (!CheckOne4D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + + + +static +cmsInt32Number Check4DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 9, 8, 7, 6 }; + + lut = cmsPipelineAlloc(DbgThread(), 4, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 4, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler4D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne4D(lut, 0, 0, 0, 0)) return 0; + if (!CheckOne4D(lut, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne4D(lut, 0x8080, 0x8080, 0x8080, 0x8080)) return 0; + if (!CheckOne4D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888)) return 0; + if (!CheckOne4D(lut, 0x1111, 0x2222, 0x3333, 0x4444)) return 0; + if (!CheckOne4D(lut, 0x0000, 0x0012, 0x0013, 0x0014)) return 0; + if (!CheckOne4D(lut, 0x3141, 0x1415, 0x1592, 0x9261)) return 0; + if (!CheckOne4D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + + +static +cmsInt32Number Check5DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 3, 2, 2, 2, 2 }; + + lut = cmsPipelineAlloc(DbgThread(), 5, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 5, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler5D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne5D(lut, 0, 0, 0, 0, 0)) return 0; + if (!CheckOne5D(lut, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne5D(lut, 0x8080, 0x8080, 0x8080, 0x8080, 0x1234)) return 0; + if (!CheckOne5D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888, 0x8078)) return 0; + if (!CheckOne5D(lut, 0x1111, 0x2222, 0x3333, 0x4444, 0x1455)) return 0; + if (!CheckOne5D(lut, 0x0000, 0x0012, 0x0013, 0x0014, 0x2333)) return 0; + if (!CheckOne5D(lut, 0x3141, 0x1415, 0x1592, 0x9261, 0x4567)) return 0; + if (!CheckOne5D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13, 0xF344)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + +static +cmsInt32Number Check6DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 4, 3, 3, 2, 2, 2 }; + + lut = cmsPipelineAlloc(DbgThread(), 6, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 6, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler6D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne6D(lut, 0, 0, 0, 0, 0, 0)) return 0; + if (!CheckOne6D(lut, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne6D(lut, 0x8080, 0x8080, 0x8080, 0x8080, 0x1234, 0x1122)) return 0; + if (!CheckOne6D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888, 0x8078, 0x2233)) return 0; + if (!CheckOne6D(lut, 0x1111, 0x2222, 0x3333, 0x4444, 0x1455, 0x3344)) return 0; + if (!CheckOne6D(lut, 0x0000, 0x0012, 0x0013, 0x0014, 0x2333, 0x4455)) return 0; + if (!CheckOne6D(lut, 0x3141, 0x1415, 0x1592, 0x9261, 0x4567, 0x5566)) return 0; + if (!CheckOne6D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13, 0xF344, 0x6677)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + +static +cmsInt32Number Check7DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 4, 3, 3, 2, 2, 2, 2 }; + + lut = cmsPipelineAlloc(DbgThread(), 7, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 7, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler7D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne7D(lut, 0, 0, 0, 0, 0, 0, 0)) return 0; + if (!CheckOne7D(lut, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne7D(lut, 0x8080, 0x8080, 0x8080, 0x8080, 0x1234, 0x1122, 0x0056)) return 0; + if (!CheckOne7D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888, 0x8078, 0x2233, 0x0088)) return 0; + if (!CheckOne7D(lut, 0x1111, 0x2222, 0x3333, 0x4444, 0x1455, 0x3344, 0x1987)) return 0; + if (!CheckOne7D(lut, 0x0000, 0x0012, 0x0013, 0x0014, 0x2333, 0x4455, 0x9988)) return 0; + if (!CheckOne7D(lut, 0x3141, 0x1415, 0x1592, 0x9261, 0x4567, 0x5566, 0xfe56)) return 0; + if (!CheckOne7D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13, 0xF344, 0x6677, 0xbabe)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + + +static +cmsInt32Number Check8DinterpGranular(void) +{ + cmsPipeline* lut; + cmsStage* mpe; + cmsUInt32Number Dimensions[] = { 4, 3, 3, 2, 2, 2, 2, 2 }; + + lut = cmsPipelineAlloc(DbgThread(), 8, 3); + mpe = cmsStageAllocCLut16bitGranular(DbgThread(), Dimensions, 8, 3, NULL); + cmsStageSampleCLut16bit(DbgThread(), mpe, Sampler8D, NULL, 0); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_BEGIN, mpe); + + // Check accuracy + + if (!CheckOne8D(lut, 0, 0, 0, 0, 0, 0, 0, 0)) return 0; + if (!CheckOne8D(lut, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)) return 0; + + if (!CheckOne8D(lut, 0x8080, 0x8080, 0x8080, 0x8080, 0x1234, 0x1122, 0x0056, 0x0011)) return 0; + if (!CheckOne8D(lut, 0x0000, 0xFE00, 0x80FF, 0x8888, 0x8078, 0x2233, 0x0088, 0x2020)) return 0; + if (!CheckOne8D(lut, 0x1111, 0x2222, 0x3333, 0x4444, 0x1455, 0x3344, 0x1987, 0x4532)) return 0; + if (!CheckOne8D(lut, 0x0000, 0x0012, 0x0013, 0x0014, 0x2333, 0x4455, 0x9988, 0x1200)) return 0; + if (!CheckOne8D(lut, 0x3141, 0x1415, 0x1592, 0x9261, 0x4567, 0x5566, 0xfe56, 0x6666)) return 0; + if (!CheckOne8D(lut, 0xFF00, 0xFF01, 0xFF12, 0xFF13, 0xF344, 0x6677, 0xbabe, 0xface)) return 0; + + cmsPipelineFree(DbgThread(), lut); + + return 1; +} + +// Colorimetric conversions ------------------------------------------------------------------------------------------------- + +// Lab to LCh and back should be performed at 1E-12 accuracy at least +static +cmsInt32Number CheckLab2LCh(void) +{ + cmsInt32Number l, a, b; + cmsFloat64Number dist, Max = 0; + cmsCIELab Lab, Lab2; + cmsCIELCh LCh; + + for (l=0; l <= 100; l += 10) { + + for (a=-128; a <= +128; a += 8) { + + for (b=-128; b <= 128; b += 8) { + + Lab.L = l; + Lab.a = a; + Lab.b = b; + + cmsLab2LCh(DbgThread(), &LCh, &Lab); + cmsLCh2Lab(DbgThread(), &Lab2, &LCh); + + dist = cmsDeltaE(DbgThread(), &Lab, &Lab2); + if (dist > Max) Max = dist; + } + } + } + + return Max < 1E-12; +} + +// Lab to LCh and back should be performed at 1E-12 accuracy at least +static +cmsInt32Number CheckLab2XYZ(void) +{ + cmsInt32Number l, a, b; + cmsFloat64Number dist, Max = 0; + cmsCIELab Lab, Lab2; + cmsCIEXYZ XYZ; + + for (l=0; l <= 100; l += 10) { + + for (a=-128; a <= +128; a += 8) { + + for (b=-128; b <= 128; b += 8) { + + Lab.L = l; + Lab.a = a; + Lab.b = b; + + cmsLab2XYZ(DbgThread(), NULL, &XYZ, &Lab); + cmsXYZ2Lab(DbgThread(), NULL, &Lab2, &XYZ); + + dist = cmsDeltaE(DbgThread(), &Lab, &Lab2); + if (dist > Max) Max = dist; + + } + } + } + + return Max < 1E-12; +} + +// Lab to xyY and back should be performed at 1E-12 accuracy at least +static +cmsInt32Number CheckLab2xyY(void) +{ + cmsInt32Number l, a, b; + cmsFloat64Number dist, Max = 0; + cmsCIELab Lab, Lab2; + cmsCIEXYZ XYZ; + cmsCIExyY xyY; + + for (l=0; l <= 100; l += 10) { + + for (a=-128; a <= +128; a += 8) { + + for (b=-128; b <= 128; b += 8) { + + Lab.L = l; + Lab.a = a; + Lab.b = b; + + cmsLab2XYZ(DbgThread(), NULL, &XYZ, &Lab); + cmsXYZ2xyY(DbgThread(), &xyY, &XYZ); + cmsxyY2XYZ(DbgThread(), &XYZ, &xyY); + cmsXYZ2Lab(DbgThread(), NULL, &Lab2, &XYZ); + + dist = cmsDeltaE(DbgThread(), &Lab, &Lab2); + if (dist > Max) Max = dist; + + } + } + } + + return Max < 1E-12; +} + + +static +cmsInt32Number CheckLabV2encoding(void) +{ + cmsInt32Number n2, i, j; + cmsUInt16Number Inw[3], aw[3]; + cmsCIELab Lab; + + n2=0; + + for (j=0; j < 65535; j++) { + + Inw[0] = Inw[1] = Inw[2] = (cmsUInt16Number) j; + + cmsLabEncoded2FloatV2(DbgThread(), &Lab, Inw); + cmsFloat2LabEncodedV2(DbgThread(), aw, &Lab); + + for (i=0; i < 3; i++) { + + if (aw[i] != j) { + n2++; + } + } + + } + + return (n2 == 0); +} + +static +cmsInt32Number CheckLabV4encoding(void) +{ + cmsInt32Number n2, i, j; + cmsUInt16Number Inw[3], aw[3]; + cmsCIELab Lab; + + n2=0; + + for (j=0; j < 65535; j++) { + + Inw[0] = Inw[1] = Inw[2] = (cmsUInt16Number) j; + + cmsLabEncoded2Float(DbgThread(), &Lab, Inw); + cmsFloat2LabEncoded(DbgThread(), aw, &Lab); + + for (i=0; i < 3; i++) { + + if (aw[i] != j) { + n2++; + } + } + + } + + return (n2 == 0); +} + + +// BlackBody ----------------------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckTemp2CHRM(void) +{ + cmsInt32Number j; + cmsFloat64Number d, v, Max = 0; + cmsCIExyY White; + + for (j=4000; j < 25000; j++) { + + cmsWhitePointFromTemp(DbgThread(), &White, j); + if (!cmsTempFromWhitePoint(DbgThread(), &v, &White)) return 0; + + d = fabs(v - j); + if (d > Max) Max = d; + } + + // 100 degree is the actual resolution + return (Max < 100); +} + + + +// Tone curves ----------------------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckGammaEstimation(cmsToneCurve* c, cmsFloat64Number g) +{ + cmsFloat64Number est = cmsEstimateGamma(DbgThread(), c, 0.001); + + SubTest("Gamma estimation"); + if (fabs(est - g) > 0.001) return 0; + return 1; +} + +static +cmsInt32Number CheckGammaCreation16(void) +{ + cmsToneCurve* LinGamma = cmsBuildGamma(DbgThread(), 1.0); + cmsInt32Number i; + cmsUInt16Number in, out; + + for (i=0; i < 0xffff; i++) { + + in = (cmsUInt16Number) i; + out = cmsEvalToneCurve16(DbgThread(), LinGamma, in); + if (in != out) { + Fail("(lin gamma): Must be %x, But is %x : ", in, out); + cmsFreeToneCurve(DbgThread(), LinGamma); + return 0; + } + } + + if (!CheckGammaEstimation(LinGamma, 1.0)) return 0; + + cmsFreeToneCurve(DbgThread(), LinGamma); + return 1; + +} + +static +cmsInt32Number CheckGammaCreationFlt(void) +{ + cmsToneCurve* LinGamma = cmsBuildGamma(DbgThread(), 1.0); + cmsInt32Number i; + cmsFloat32Number in, out; + + for (i=0; i < 0xffff; i++) { + + in = (cmsFloat32Number) (i / 65535.0); + out = cmsEvalToneCurveFloat(DbgThread(), LinGamma, in); + if (fabs(in - out) > (1/65535.0)) { + Fail("(lin gamma): Must be %f, But is %f : ", in, out); + cmsFreeToneCurve(DbgThread(), LinGamma); + return 0; + } + } + + if (!CheckGammaEstimation(LinGamma, 1.0)) return 0; + cmsFreeToneCurve(DbgThread(), LinGamma); + return 1; +} + +// Curve curves using a single power function +// Error is given in 0..ffff counts +static +cmsInt32Number CheckGammaFloat(cmsFloat64Number g) +{ + cmsToneCurve* Curve = cmsBuildGamma(DbgThread(), g); + cmsInt32Number i; + cmsFloat32Number in, out; + cmsFloat64Number val, Err; + + MaxErr = 0.0; + for (i=0; i < 0xffff; i++) { + + in = (cmsFloat32Number) (i / 65535.0); + out = cmsEvalToneCurveFloat(DbgThread(), Curve, in); + val = pow((cmsFloat64Number) in, g); + + Err = fabs( val - out); + if (Err > MaxErr) MaxErr = Err; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr * 65535.0); + + if (!CheckGammaEstimation(Curve, g)) return 0; + + cmsFreeToneCurve(DbgThread(), Curve); + return 1; +} + +static cmsInt32Number CheckGamma18(void) +{ + return CheckGammaFloat(1.8); +} + +static cmsInt32Number CheckGamma22(void) +{ + return CheckGammaFloat(2.2); +} + +static cmsInt32Number CheckGamma30(void) +{ + return CheckGammaFloat(3.0); +} + + +// Check table-based gamma functions +static +cmsInt32Number CheckGammaFloatTable(cmsFloat64Number g) +{ + cmsFloat32Number Values[1025]; + cmsToneCurve* Curve; + cmsInt32Number i; + cmsFloat32Number in, out; + cmsFloat64Number val, Err; + + for (i=0; i <= 1024; i++) { + + in = (cmsFloat32Number) (i / 1024.0); + Values[i] = powf(in, (float) g); + } + + Curve = cmsBuildTabulatedToneCurveFloat(DbgThread(), 1025, Values); + + MaxErr = 0.0; + for (i=0; i <= 0xffff; i++) { + + in = (cmsFloat32Number) (i / 65535.0); + out = cmsEvalToneCurveFloat(DbgThread(), Curve, in); + val = pow(in, g); + + Err = fabs(val - out); + if (Err > MaxErr) MaxErr = Err; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr * 65535.0); + + if (!CheckGammaEstimation(Curve, g)) return 0; + + cmsFreeToneCurve(DbgThread(), Curve); + return 1; +} + + +static cmsInt32Number CheckGamma18Table(void) +{ + return CheckGammaFloatTable(1.8); +} + +static cmsInt32Number CheckGamma22Table(void) +{ + return CheckGammaFloatTable(2.2); +} + +static cmsInt32Number CheckGamma30Table(void) +{ + return CheckGammaFloatTable(3.0); +} + +// Create a curve from a table (which is a pure gamma function) and check it against the pow function. +static +cmsInt32Number CheckGammaWordTable(cmsFloat64Number g) +{ + cmsUInt16Number Values[1025]; + cmsToneCurve* Curve; + cmsInt32Number i; + cmsFloat32Number in, out; + cmsFloat64Number val, Err; + + for (i=0; i <= 1024; i++) { + + in = (cmsFloat32Number) (i / 1024.0); + Values[i] = (cmsUInt16Number) floor(pow(in, g) * 65535.0 + 0.5); + } + + Curve = cmsBuildTabulatedToneCurve16(DbgThread(), 1025, Values); + + MaxErr = 0.0; + for (i=0; i <= 0xffff; i++) { + + in = (cmsFloat32Number) (i / 65535.0); + out = cmsEvalToneCurveFloat(DbgThread(), Curve, in); + val = pow(in, g); + + Err = fabs(val - out); + if (Err > MaxErr) MaxErr = Err; + } + + if (MaxErr > 0) printf("|Err|<%lf ", MaxErr * 65535.0); + + if (!CheckGammaEstimation(Curve, g)) return 0; + + cmsFreeToneCurve(DbgThread(), Curve); + return 1; +} + +static cmsInt32Number CheckGamma18TableWord(void) +{ + return CheckGammaWordTable(1.8); +} + +static cmsInt32Number CheckGamma22TableWord(void) +{ + return CheckGammaWordTable(2.2); +} + +static cmsInt32Number CheckGamma30TableWord(void) +{ + return CheckGammaWordTable(3.0); +} + + +// Curve joining test. Joining two high-gamma of 3.0 curves should +// give something like linear +static +cmsInt32Number CheckJointCurves(void) +{ + cmsToneCurve *Forward, *Reverse, *Result; + cmsBool rc; + + Forward = cmsBuildGamma(DbgThread(), 3.0); + Reverse = cmsBuildGamma(DbgThread(), 3.0); + + Result = cmsJoinToneCurve(DbgThread(), Forward, Reverse, 256); + + cmsFreeToneCurve(DbgThread(), Forward); cmsFreeToneCurve(DbgThread(), Reverse); + + rc = cmsIsToneCurveLinear(DbgThread(), Result); + cmsFreeToneCurve(DbgThread(), Result); + + if (!rc) + Fail("Joining same curve twice does not result in a linear ramp"); + + return rc; +} + + +// Create a gamma curve by cheating the table +static +cmsToneCurve* GammaTableLinear(cmsInt32Number nEntries, cmsBool Dir) +{ + cmsInt32Number i; + cmsToneCurve* g = cmsBuildTabulatedToneCurve16(DbgThread(), nEntries, NULL); + + for (i=0; i < nEntries; i++) { + + cmsInt32Number v = _cmsQuantizeVal(i, nEntries); + + if (Dir) + g->Table16[i] = (cmsUInt16Number) v; + else + g->Table16[i] = (cmsUInt16Number) (0xFFFF - v); + } + + return g; +} + + +static +cmsInt32Number CheckJointCurvesDescending(void) +{ + cmsToneCurve *Forward, *Reverse, *Result; + cmsInt32Number i, rc; + + Forward = cmsBuildGamma(DbgThread(), 2.2); + + // Fake the curve to be table-based + + for (i=0; i < 4096; i++) + Forward ->Table16[i] = 0xffff - Forward->Table16[i]; + Forward ->Segments[0].Type = 0; + + Reverse = cmsReverseToneCurve(DbgThread(), Forward); + + Result = cmsJoinToneCurve(DbgThread(), Reverse, Reverse, 256); + + cmsFreeToneCurve(DbgThread(), Forward); + cmsFreeToneCurve(DbgThread(), Reverse); + + rc = cmsIsToneCurveLinear(DbgThread(), Result); + cmsFreeToneCurve(DbgThread(), Result); + + return rc; +} + + +static +cmsInt32Number CheckFToneCurvePoint(cmsToneCurve* c, cmsUInt16Number Point, cmsInt32Number Value) +{ + cmsInt32Number Result; + + Result = cmsEvalToneCurve16(DbgThread(), c, Point); + + return (abs(Value - Result) < 2); +} + +static +cmsInt32Number CheckReverseDegenerated(void) +{ + cmsToneCurve* p, *g; + cmsUInt16Number Tab[16]; + + Tab[0] = 0; + Tab[1] = 0; + Tab[2] = 0; + Tab[3] = 0; + Tab[4] = 0; + Tab[5] = 0x5555; + Tab[6] = 0x6666; + Tab[7] = 0x7777; + Tab[8] = 0x8888; + Tab[9] = 0x9999; + Tab[10]= 0xffff; + Tab[11]= 0xffff; + Tab[12]= 0xffff; + Tab[13]= 0xffff; + Tab[14]= 0xffff; + Tab[15]= 0xffff; + + p = cmsBuildTabulatedToneCurve16(DbgThread(), 16, Tab); + g = cmsReverseToneCurve(DbgThread(), p); + + // Now let's check some points + if (!CheckFToneCurvePoint(g, 0x5555, 0x5555)) return 0; + if (!CheckFToneCurvePoint(g, 0x7777, 0x7777)) return 0; + + // First point for zero + if (!CheckFToneCurvePoint(g, 0x0000, 0x4444)) return 0; + + // Last point + if (!CheckFToneCurvePoint(g, 0xFFFF, 0xFFFF)) return 0; + + cmsFreeToneCurve(DbgThread(), p); + cmsFreeToneCurve(DbgThread(), g); + + return 1; +} + + +// Build a parametric sRGB-like curve +static +cmsToneCurve* Build_sRGBGamma(void) +{ + cmsFloat64Number Parameters[5]; + + Parameters[0] = 2.4; + Parameters[1] = 1. / 1.055; + Parameters[2] = 0.055 / 1.055; + Parameters[3] = 1. / 12.92; + Parameters[4] = 0.04045; // d + + return cmsBuildParametricToneCurve(DbgThread(), 4, Parameters); +} + + + +// Join two gamma tables in floating point format. Result should be a straight line +static +cmsToneCurve* CombineGammaFloat(cmsToneCurve* g1, cmsToneCurve* g2) +{ + cmsUInt16Number Tab[256]; + cmsFloat32Number f; + cmsInt32Number i; + + for (i=0; i < 256; i++) { + + f = (cmsFloat32Number) i / 255.0F; + f = cmsEvalToneCurveFloat(DbgThread(), g2, cmsEvalToneCurveFloat(DbgThread(), g1, f)); + + Tab[i] = (cmsUInt16Number) floor(f * 65535.0 + 0.5); + } + + return cmsBuildTabulatedToneCurve16(DbgThread(), 256, Tab); +} + +// Same of anterior, but using quantized tables +static +cmsToneCurve* CombineGamma16(cmsToneCurve* g1, cmsToneCurve* g2) +{ + cmsUInt16Number Tab[256]; + + cmsInt32Number i; + + for (i=0; i < 256; i++) { + + cmsUInt16Number wValIn; + + wValIn = _cmsQuantizeVal(i, 256); + Tab[i] = cmsEvalToneCurve16(DbgThread(), g2, cmsEvalToneCurve16(DbgThread(), g1, wValIn)); + } + + return cmsBuildTabulatedToneCurve16(DbgThread(), 256, Tab); +} + +static +cmsInt32Number CheckJointFloatCurves_sRGB(void) +{ + cmsToneCurve *Forward, *Reverse, *Result; + cmsBool rc; + + Forward = Build_sRGBGamma(); + Reverse = cmsReverseToneCurve(DbgThread(), Forward); + Result = CombineGammaFloat(Forward, Reverse); + cmsFreeToneCurve(DbgThread(), Forward); cmsFreeToneCurve(DbgThread(), Reverse); + + rc = cmsIsToneCurveLinear(DbgThread(), Result); + cmsFreeToneCurve(DbgThread(), Result); + + return rc; +} + +static +cmsInt32Number CheckJoint16Curves_sRGB(void) +{ + cmsToneCurve *Forward, *Reverse, *Result; + cmsBool rc; + + Forward = Build_sRGBGamma(); + Reverse = cmsReverseToneCurve(DbgThread(), Forward); + Result = CombineGamma16(Forward, Reverse); + cmsFreeToneCurve(DbgThread(), Forward); cmsFreeToneCurve(DbgThread(), Reverse); + + rc = cmsIsToneCurveLinear(DbgThread(), Result); + cmsFreeToneCurve(DbgThread(), Result); + + return rc; +} + +// sigmoidal curve f(x) = (1-x^g) ^(1/g) + +static +cmsInt32Number CheckJointCurvesSShaped(void) +{ + cmsFloat64Number p = 3.2; + cmsToneCurve *Forward, *Reverse, *Result; + cmsInt32Number rc; + + Forward = cmsBuildParametricToneCurve(DbgThread(), 108, &p); + Reverse = cmsReverseToneCurve(DbgThread(), Forward); + Result = cmsJoinToneCurve(DbgThread(), Forward, Forward, 4096); + + cmsFreeToneCurve(DbgThread(), Forward); + cmsFreeToneCurve(DbgThread(), Reverse); + + rc = cmsIsToneCurveLinear(DbgThread(), Result); + cmsFreeToneCurve(DbgThread(), Result); + return rc; +} + + +// -------------------------------------------------------------------------------------------------------- + +// Implementation of some tone curve functions +static +cmsFloat32Number Gamma(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + return (cmsFloat32Number) pow(x, Params[0]); +} + +static +cmsFloat32Number CIE122(cmsFloat32Number x, const cmsFloat64Number Params[]) + +{ + cmsFloat64Number e, Val; + + if (x >= -Params[2] / Params[1]) { + + e = Params[1]*x + Params[2]; + + if (e > 0) + Val = pow(e, Params[0]); + else + Val = 0; + } + else + Val = 0; + + return (cmsFloat32Number) Val; +} + +static +cmsFloat32Number IEC61966_3(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number e, Val; + + if (x >= -Params[2] / Params[1]) { + + e = Params[1]*x + Params[2]; + + if (e > 0) + Val = pow(e, Params[0]) + Params[3]; + else + Val = 0; + } + else + Val = Params[3]; + + return (cmsFloat32Number) Val; +} + +static +cmsFloat32Number IEC61966_21(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number e, Val; + + if (x >= Params[4]) { + + e = Params[1]*x + Params[2]; + + if (e > 0) + Val = pow(e, Params[0]); + else + Val = 0; + } + else + Val = x * Params[3]; + + return (cmsFloat32Number) Val; +} + +static +cmsFloat32Number param_5(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number e, Val; + // Y = (aX + b)^Gamma + e | X >= d + // Y = cX + f | else + if (x >= Params[4]) { + + e = Params[1]*x + Params[2]; + if (e > 0) + Val = pow(e, Params[0]) + Params[5]; + else + Val = 0; + } + else + Val = x*Params[3] + Params[6]; + + return (cmsFloat32Number) Val; +} + +static +cmsFloat32Number param_6(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number e, Val; + + e = Params[1]*x + Params[2]; + if (e > 0) + Val = pow(e, Params[0]) + Params[3]; + else + Val = 0; + + return (cmsFloat32Number) Val; +} + +static +cmsFloat32Number param_7(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number Val; + + + Val = Params[1]*log10(Params[2] * pow(x, Params[0]) + Params[3]) + Params[4]; + + return (cmsFloat32Number) Val; +} + + +static +cmsFloat32Number param_8(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number Val; + + Val = (Params[0] * pow(Params[1], Params[2] * x + Params[3]) + Params[4]); + + return (cmsFloat32Number) Val; +} + + +static +cmsFloat32Number sigmoidal(cmsFloat32Number x, const cmsFloat64Number Params[]) +{ + cmsFloat64Number Val; + + Val = pow(1.0 - pow(1 - x, 1/Params[0]), 1/Params[0]); + + return (cmsFloat32Number) Val; +} + + +static +cmsBool CheckSingleParametric(const char* Name, dblfnptr fn, cmsInt32Number Type, const cmsFloat64Number Params[]) +{ + cmsInt32Number i; + cmsToneCurve* tc; + cmsToneCurve* tc_1; + char InverseText[256]; + + tc = cmsBuildParametricToneCurve(DbgThread(), Type, Params); + tc_1 = cmsBuildParametricToneCurve(DbgThread(), -Type, Params); + + for (i=0; i <= 1000; i++) { + + cmsFloat32Number x = (cmsFloat32Number) i / 1000; + cmsFloat32Number y_fn, y_param, x_param, y_param2; + + y_fn = fn(x, Params); + y_param = cmsEvalToneCurveFloat(DbgThread(), tc, x); + x_param = cmsEvalToneCurveFloat(DbgThread(), tc_1, y_param); + + y_param2 = fn(x_param, Params); + + if (!IsGoodVal(Name, y_fn, y_param, FIXED_PRECISION_15_16)) + goto Error; + + sprintf(InverseText, "Inverse %s", Name); + if (!IsGoodVal(InverseText, y_fn, y_param2, FIXED_PRECISION_15_16)) + goto Error; + } + + cmsFreeToneCurve(DbgThread(), tc); + cmsFreeToneCurve(DbgThread(), tc_1); + return TRUE; + +Error: + cmsFreeToneCurve(DbgThread(), tc); + cmsFreeToneCurve(DbgThread(), tc_1); + return FALSE; +} + +// Check against some known values +static +cmsInt32Number CheckParametricToneCurves(void) +{ + cmsFloat64Number Params[10]; + + // 1) X = Y ^ Gamma + + Params[0] = 2.2; + + if (!CheckSingleParametric("Gamma", Gamma, 1, Params)) return 0; + + // 2) CIE 122-1966 + // Y = (aX + b)^Gamma | X >= -b/a + // Y = 0 | else + + Params[0] = 2.2; + Params[1] = 1.5; + Params[2] = -0.5; + + if (!CheckSingleParametric("CIE122-1966", CIE122, 2, Params)) return 0; + + // 3) IEC 61966-3 + // Y = (aX + b)^Gamma | X <= -b/a + // Y = c | else + + Params[0] = 2.2; + Params[1] = 1.5; + Params[2] = -0.5; + Params[3] = 0.3; + + + if (!CheckSingleParametric("IEC 61966-3", IEC61966_3, 3, Params)) return 0; + + // 4) IEC 61966-2.1 (sRGB) + // Y = (aX + b)^Gamma | X >= d + // Y = cX | X < d + + Params[0] = 2.4; + Params[1] = 1. / 1.055; + Params[2] = 0.055 / 1.055; + Params[3] = 1. / 12.92; + Params[4] = 0.04045; + + if (!CheckSingleParametric("IEC 61966-2.1", IEC61966_21, 4, Params)) return 0; + + + // 5) Y = (aX + b)^Gamma + e | X >= d + // Y = cX + f | else + + Params[0] = 2.2; + Params[1] = 0.7; + Params[2] = 0.2; + Params[3] = 0.3; + Params[4] = 0.1; + Params[5] = 0.5; + Params[6] = 0.2; + + if (!CheckSingleParametric("param_5", param_5, 5, Params)) return 0; + + // 6) Y = (aX + b) ^ Gamma + c + + Params[0] = 2.2; + Params[1] = 0.7; + Params[2] = 0.2; + Params[3] = 0.3; + + if (!CheckSingleParametric("param_6", param_6, 6, Params)) return 0; + + // 7) Y = a * log (b * X^Gamma + c) + d + + Params[0] = 2.2; + Params[1] = 0.9; + Params[2] = 0.9; + Params[3] = 0.02; + Params[4] = 0.1; + + if (!CheckSingleParametric("param_7", param_7, 7, Params)) return 0; + + // 8) Y = a * b ^ (c*X+d) + e + + Params[0] = 0.9; + Params[1] = 0.9; + Params[2] = 1.02; + Params[3] = 0.1; + Params[4] = 0.2; + + if (!CheckSingleParametric("param_8", param_8, 8, Params)) return 0; + + // 108: S-Shaped: (1 - (1-x)^1/g)^1/g + + Params[0] = 1.9; + if (!CheckSingleParametric("sigmoidal", sigmoidal, 108, Params)) return 0; + + // All OK + + return 1; +} + +// LUT checks ------------------------------------------------------------------------------ + +static +cmsInt32Number CheckLUTcreation(void) +{ + cmsPipeline* lut; + cmsPipeline* lut2; + cmsInt32Number n1, n2; + + lut = cmsPipelineAlloc(DbgThread(), 1, 1); + n1 = cmsPipelineStageCount(DbgThread(), lut); + lut2 = cmsPipelineDup(DbgThread(), lut); + n2 = cmsPipelineStageCount(DbgThread(), lut2); + + cmsPipelineFree(DbgThread(), lut); + cmsPipelineFree(DbgThread(), lut2); + + return (n1 == 0) && (n2 == 0); +} + +// Create a MPE for a identity matrix +static +void AddIdentityMatrix(cmsPipeline* lut) +{ + const cmsFloat64Number Identity[] = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + 0, 0, 0 }; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, cmsStageAllocMatrix(DbgThread(), 3, 3, Identity, NULL)); +} + +// Create a MPE for identity cmsFloat32Number CLUT +static +void AddIdentityCLUTfloat(cmsPipeline* lut) +{ + const cmsFloat32Number Table[] = { + + 0, 0, 0, + 0, 0, 1.0, + + 0, 1.0, 0, + 0, 1.0, 1.0, + + 1.0, 0, 0, + 1.0, 0, 1.0, + + 1.0, 1.0, 0, + 1.0, 1.0, 1.0 + }; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, cmsStageAllocCLutFloat(DbgThread(), 2, 3, 3, Table)); +} + +// Create a MPE for identity cmsFloat32Number CLUT +static +void AddIdentityCLUT16(cmsPipeline* lut) +{ + const cmsUInt16Number Table[] = { + + 0, 0, 0, + 0, 0, 0xffff, + + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, cmsStageAllocCLut16bit(DbgThread(), 2, 3, 3, Table)); +} + + +// Create a 3 fn identity curves + +static +void Add3GammaCurves(cmsPipeline* lut, cmsFloat64Number Curve) +{ + cmsToneCurve* id = cmsBuildGamma(DbgThread(), Curve); + cmsToneCurve* id3[3]; + + id3[0] = id; + id3[1] = id; + id3[2] = id; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, cmsStageAllocToneCurves(DbgThread(), 3, id3)); + + cmsFreeToneCurve(DbgThread(), id); +} + + +static +cmsInt32Number CheckFloatLUT(cmsPipeline* lut) +{ + cmsInt32Number n1, i, j; + cmsFloat32Number Inf[3], Outf[3]; + + n1=0; + + for (j=0; j < 65535; j++) { + + cmsInt32Number af[3]; + + Inf[0] = Inf[1] = Inf[2] = (cmsFloat32Number) j / 65535.0F; + cmsPipelineEvalFloat(DbgThread(), Inf, Outf, lut); + + af[0] = (cmsInt32Number) floor(Outf[0]*65535.0 + 0.5); + af[1] = (cmsInt32Number) floor(Outf[1]*65535.0 + 0.5); + af[2] = (cmsInt32Number) floor(Outf[2]*65535.0 + 0.5); + + for (i=0; i < 3; i++) { + + if (af[i] != j) { + n1++; + } + } + + } + + return (n1 == 0); +} + + +static +cmsInt32Number Check16LUT(cmsPipeline* lut) +{ + cmsInt32Number n2, i, j; + cmsUInt16Number Inw[3], Outw[3]; + + n2=0; + + for (j=0; j < 65535; j++) { + + cmsInt32Number aw[3]; + + Inw[0] = Inw[1] = Inw[2] = (cmsUInt16Number) j; + cmsPipelineEval16(DbgThread(), Inw, Outw, lut); + aw[0] = Outw[0]; + aw[1] = Outw[1]; + aw[2] = Outw[2]; + + for (i=0; i < 3; i++) { + + if (aw[i] != j) { + n2++; + } + } + + } + + return (n2 == 0); +} + + +// Check any LUT that is linear +static +cmsInt32Number CheckStagesLUT(cmsPipeline* lut, cmsInt32Number ExpectedStages) +{ + + cmsInt32Number nInpChans, nOutpChans, nStages; + + nInpChans = cmsPipelineInputChannels(DbgThread(), lut); + nOutpChans = cmsPipelineOutputChannels(DbgThread(), lut); + nStages = cmsPipelineStageCount(DbgThread(), lut); + + return (nInpChans == 3) && (nOutpChans == 3) && (nStages == ExpectedStages); +} + + +static +cmsInt32Number CheckFullLUT(cmsPipeline* lut, cmsInt32Number ExpectedStages) +{ + cmsInt32Number rc = CheckStagesLUT(lut, ExpectedStages) && Check16LUT(lut) && CheckFloatLUT(lut); + + cmsPipelineFree(DbgThread(), lut); + return rc; +} + + +static +cmsInt32Number Check1StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + return CheckFullLUT(lut, 1); +} + + + +static +cmsInt32Number Check2StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUTfloat(lut); + + return CheckFullLUT(lut, 2); +} + +static +cmsInt32Number Check2Stage16LUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUT16(lut); + + return CheckFullLUT(lut, 2); +} + + + +static +cmsInt32Number Check3StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUTfloat(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 3); +} + +static +cmsInt32Number Check3Stage16LUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUT16(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 3); +} + + + +static +cmsInt32Number Check4StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUTfloat(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + + return CheckFullLUT(lut, 4); +} + +static +cmsInt32Number Check4Stage16LUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUT16(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + + return CheckFullLUT(lut, 4); +} + +static +cmsInt32Number Check5StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUTfloat(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 5); +} + + +static +cmsInt32Number Check5Stage16LUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + AddIdentityCLUT16(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 5); +} + +static +cmsInt32Number Check6StageLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityCLUTfloat(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 6); +} + +static +cmsInt32Number Check6Stage16LUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityCLUT16(lut); + Add3GammaCurves(lut, 1.0); + AddIdentityMatrix(lut); + Add3GammaCurves(lut, 1.0); + + return CheckFullLUT(lut, 6); +} + + +static +cmsInt32Number CheckLab2LabLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + cmsInt32Number rc; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocLab2XYZ(DbgThread())); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocXYZ2Lab(DbgThread())); + + rc = CheckFloatLUT(lut) && CheckStagesLUT(lut, 2); + + cmsPipelineFree(DbgThread(), lut); + + return rc; +} + + +static +cmsInt32Number CheckXYZ2XYZLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + cmsInt32Number rc; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocXYZ2Lab(DbgThread())); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocLab2XYZ(DbgThread())); + + rc = CheckFloatLUT(lut) && CheckStagesLUT(lut, 2); + + cmsPipelineFree(DbgThread(), lut); + + return rc; +} + + + +static +cmsInt32Number CheckLab2LabMatLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + cmsInt32Number rc; + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocLab2XYZ(DbgThread())); + AddIdentityMatrix(lut); + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocXYZ2Lab(DbgThread())); + + rc = CheckFloatLUT(lut) && CheckStagesLUT(lut, 3); + + cmsPipelineFree(DbgThread(), lut); + + return rc; +} + +static +cmsInt32Number CheckNamedColorLUT(void) +{ + cmsPipeline* lut = cmsPipelineAlloc(DbgThread(), 3, 3); + cmsNAMEDCOLORLIST* nc; + cmsInt32Number i,j, rc = 1, n2; + cmsUInt16Number PCS[3]; + cmsUInt16Number Colorant[cmsMAXCHANNELS]; + char Name[255]; + cmsUInt16Number Inw[3], Outw[3]; + + + + nc = cmsAllocNamedColorList(DbgThread(), 256, 3, "pre", "post"); + if (nc == NULL) return 0; + + for (i=0; i < 256; i++) { + + PCS[0] = PCS[1] = PCS[2] = (cmsUInt16Number) i; + Colorant[0] = Colorant[1] = Colorant[2] = Colorant[3] = (cmsUInt16Number) i; + + sprintf(Name, "#%d", i); + if (!cmsAppendNamedColor(DbgThread(), nc, Name, PCS, Colorant)) { rc = 0; break; } + } + + cmsPipelineInsertStage(DbgThread(), lut, cmsAT_END, _cmsStageAllocNamedColor(DbgThread(), nc, FALSE)); + + cmsFreeNamedColorList(DbgThread(), nc); + if (rc == 0) return 0; + + n2=0; + + for (j=0; j < 256; j++) { + + Inw[0] = (cmsUInt16Number) j; + + cmsPipelineEval16(DbgThread(), Inw, Outw, lut); + for (i=0; i < 3; i++) { + + if (Outw[i] != j) { + n2++; + } + } + + } + + cmsPipelineFree(DbgThread(), lut); + return (n2 == 0); +} + + + +// -------------------------------------------------------------------------------------------- + +// A lightweight test of multilocalized unicode structures. + +static +cmsInt32Number CheckMLU(void) +{ + cmsMLU* mlu, *mlu2, *mlu3; + char Buffer[256], Buffer2[256]; + cmsInt32Number rc = 1; + cmsInt32Number i; + cmsHPROFILE h= NULL; + + // Allocate a MLU structure, no preferred size + mlu = cmsMLUalloc(DbgThread(), 0); + + // Add some localizations + cmsMLUsetWide(DbgThread(), mlu, "en", "US", L"Hello, world"); + cmsMLUsetWide(DbgThread(), mlu, "es", "ES", L"Hola, mundo"); + cmsMLUsetWide(DbgThread(), mlu, "fr", "FR", L"Bonjour, le monde"); + cmsMLUsetWide(DbgThread(), mlu, "ca", "CA", L"Hola, mon"); + + + // Check the returned string for each language + + cmsMLUgetASCII(DbgThread(), mlu, "en", "US", Buffer, 256); + if (strcmp(Buffer, "Hello, world") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "es", "ES", Buffer, 256); + if (strcmp(Buffer, "Hola, mundo") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "fr", "FR", Buffer, 256); + if (strcmp(Buffer, "Bonjour, le monde") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "ca", "CA", Buffer, 256); + if (strcmp(Buffer, "Hola, mon") != 0) rc = 0; + + if (rc == 0) + Fail("Unexpected string '%s'", Buffer); + + // So far, so good. + cmsMLUfree(DbgThread(), mlu); + + // Now for performance, allocate an empty struct + mlu = cmsMLUalloc(DbgThread(), 0); + + // Fill it with several thousands of different lenguages + for (i=0; i < 4096; i++) { + + char Lang[3]; + + Lang[0] = (char) (i % 255); + Lang[1] = (char) (i / 255); + Lang[2] = 0; + + sprintf(Buffer, "String #%i", i); + cmsMLUsetASCII(DbgThread(), mlu, Lang, Lang, Buffer); + } + + // Duplicate it + mlu2 = cmsMLUdup(DbgThread(), mlu); + + // Get rid of original + cmsMLUfree(DbgThread(), mlu); + + // Check all is still in place + for (i=0; i < 4096; i++) { + + char Lang[3]; + + Lang[0] = (char)(i % 255); + Lang[1] = (char)(i / 255); + Lang[2] = 0; + + cmsMLUgetASCII(DbgThread(), mlu2, Lang, Lang, Buffer2, 256); + sprintf(Buffer, "String #%i", i); + + if (strcmp(Buffer, Buffer2) != 0) { rc = 0; break; } + } + + if (rc == 0) + Fail("Unexpected string '%s'", Buffer2); + + // Check profile IO + + h = cmsOpenProfileFromFileTHR(DbgThread(), "mlucheck.icc", "w"); + + cmsSetProfileVersion(DbgThread(), h, 4.3); + + cmsWriteTag(DbgThread(), h, cmsSigProfileDescriptionTag, mlu2); + cmsCloseProfile(DbgThread(), h); + cmsMLUfree(DbgThread(), mlu2); + + + h = cmsOpenProfileFromFileTHR(DbgThread(), "mlucheck.icc", "r"); + + mlu3 = cmsReadTag(DbgThread(), h, cmsSigProfileDescriptionTag); + if (mlu3 == NULL) { Fail("Profile didn't get the MLU\n"); rc = 0; goto Error; } + + // Check all is still in place + for (i=0; i < 4096; i++) { + + char Lang[3]; + + Lang[0] = (char) (i % 255); + Lang[1] = (char) (i / 255); + Lang[2] = 0; + + cmsMLUgetASCII(DbgThread(), mlu3, Lang, Lang, Buffer2, 256); + sprintf(Buffer, "String #%i", i); + + if (strcmp(Buffer, Buffer2) != 0) { rc = 0; break; } + } + + if (rc == 0) Fail("Unexpected string '%s'", Buffer2); + +Error: + + if (h != NULL) cmsCloseProfile(DbgThread(), h); + remove("mlucheck.icc"); + + return rc; +} + + +// A lightweight test of named color structures. +static +cmsInt32Number CheckNamedColorList(void) +{ + cmsNAMEDCOLORLIST* nc = NULL, *nc2; + cmsInt32Number i, j, rc=1; + char Name[cmsMAX_PATH]; + cmsUInt16Number PCS[3]; + cmsUInt16Number Colorant[cmsMAXCHANNELS]; + char CheckName[cmsMAX_PATH]; + cmsUInt16Number CheckPCS[3]; + cmsUInt16Number CheckColorant[cmsMAXCHANNELS]; + cmsHPROFILE h; + + nc = cmsAllocNamedColorList(DbgThread(), 0, 4, "prefix", "suffix"); + if (nc == NULL) return 0; + + for (i=0; i < 4096; i++) { + + + PCS[0] = PCS[1] = PCS[2] = (cmsUInt16Number) i; + Colorant[0] = Colorant[1] = Colorant[2] = Colorant[3] = (cmsUInt16Number) (4096 - i); + + sprintf(Name, "#%d", i); + if (!cmsAppendNamedColor(DbgThread(), nc, Name, PCS, Colorant)) { rc = 0; break; } + } + + for (i=0; i < 4096; i++) { + + CheckPCS[0] = CheckPCS[1] = CheckPCS[2] = (cmsUInt16Number) i; + CheckColorant[0] = CheckColorant[1] = CheckColorant[2] = CheckColorant[3] = (cmsUInt16Number) (4096 - i); + + sprintf(CheckName, "#%d", i); + if (!cmsNamedColorInfo(DbgThread(), nc, i, Name, NULL, NULL, PCS, Colorant)) { rc = 0; goto Error; } + + + for (j=0; j < 3; j++) { + if (CheckPCS[j] != PCS[j]) { rc = 0; Fail("Invalid PCS"); goto Error; } + } + + for (j=0; j < 4; j++) { + if (CheckColorant[j] != Colorant[j]) { rc = 0; Fail("Invalid Colorant"); goto Error; }; + } + + if (strcmp(Name, CheckName) != 0) {rc = 0; Fail("Invalid Name"); goto Error; }; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "namedcol.icc", "w"); + if (h == NULL) return 0; + if (!cmsWriteTag(DbgThread(), h, cmsSigNamedColor2Tag, nc)) return 0; + cmsCloseProfile(DbgThread(), h); + cmsFreeNamedColorList(DbgThread(), nc); + nc = NULL; + + h = cmsOpenProfileFromFileTHR(DbgThread(), "namedcol.icc", "r"); + nc2 = cmsReadTag(DbgThread(), h, cmsSigNamedColor2Tag); + + if (cmsNamedColorCount(DbgThread(), nc2) != 4096) { rc = 0; Fail("Invalid count"); goto Error; } + + i = cmsNamedColorIndex(DbgThread(), nc2, "#123"); + if (i != 123) { rc = 0; Fail("Invalid index"); goto Error; } + + + for (i=0; i < 4096; i++) { + + CheckPCS[0] = CheckPCS[1] = CheckPCS[2] = (cmsUInt16Number) i; + CheckColorant[0] = CheckColorant[1] = CheckColorant[2] = CheckColorant[3] = (cmsUInt16Number) (4096 - i); + + sprintf(CheckName, "#%d", i); + if (!cmsNamedColorInfo(DbgThread(), nc2, i, Name, NULL, NULL, PCS, Colorant)) { rc = 0; goto Error; } + + + for (j=0; j < 3; j++) { + if (CheckPCS[j] != PCS[j]) { rc = 0; Fail("Invalid PCS"); goto Error; } + } + + for (j=0; j < 4; j++) { + if (CheckColorant[j] != Colorant[j]) { rc = 0; Fail("Invalid Colorant"); goto Error; }; + } + + if (strcmp(Name, CheckName) != 0) {rc = 0; Fail("Invalid Name"); goto Error; }; + } + + cmsCloseProfile(DbgThread(), h); + remove("namedcol.icc"); + +Error: + if (nc != NULL) cmsFreeNamedColorList(DbgThread(), nc); + return rc; +} + + + +// ---------------------------------------------------------------------------------------------------------- + +// Formatters + +static cmsBool FormatterFailed; + +static +void CheckSingleFormatter16(cmsContext id, cmsUInt32Number Type, const char* Text) +{ + cmsUInt16Number Values[cmsMAXCHANNELS]; + cmsUInt8Number Buffer[1024]; + cmsFormatter f, b; + cmsInt32Number i, j, nChannels, bytes; + _cmsTRANSFORM info; + + // Already failed? + if (FormatterFailed) return; + + memset(&info, 0, sizeof(info)); + info.OutputFormat = info.InputFormat = Type; + + // Go forth and back + f = _cmsGetFormatter(id, Type, cmsFormatterInput, CMS_PACK_FLAGS_16BITS); + b = _cmsGetFormatter(id, Type, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS); + + if (f.Fmt16 == NULL || b.Fmt16 == NULL) { + Fail("no formatter for %s", Text); + FormatterFailed = TRUE; + + // Useful for debug + f = _cmsGetFormatter(id, Type, cmsFormatterInput, CMS_PACK_FLAGS_16BITS); + b = _cmsGetFormatter(id, Type, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS); + return; + } + + nChannels = T_CHANNELS(Type); + bytes = T_BYTES(Type); + + for (j=0; j < 5; j++) { + + for (i=0; i < nChannels; i++) { + Values[i] = (cmsUInt16Number) (i+j); + // For 8-bit + if (bytes == 1) + Values[i] <<= 8; + } + + b.Fmt16(DbgThread(), &info, Values, Buffer, 1); + memset(Values, 0, sizeof(Values)); + f.Fmt16(DbgThread(), &info, Values, Buffer, 1); + + for (i=0; i < nChannels; i++) { + if (bytes == 1) + Values[i] >>= 8; + + if (Values[i] != i+j) { + + Fail("%s failed", Text); + FormatterFailed = TRUE; + + // Useful for debug + for (i=0; i < nChannels; i++) { + Values[i] = (cmsUInt16Number) (i+j); + // For 8-bit + if (bytes == 1) + Values[i] <<= 8; + } + + b.Fmt16(DbgThread(), &info, Values, Buffer, 1); + f.Fmt16(DbgThread(), &info, Values, Buffer, 1); + return; + } + } + } +} + +#define C(a) CheckSingleFormatter16(0, a, #a) + + +// Check all formatters +static +cmsInt32Number CheckFormatters16(void) +{ + FormatterFailed = FALSE; + + C( TYPE_GRAY_8 ); + C( TYPE_GRAY_8_REV ); + C( TYPE_GRAY_16 ); + C( TYPE_GRAY_16_REV ); + C( TYPE_GRAY_16_SE ); + C( TYPE_GRAYA_8 ); + C( TYPE_GRAYA_16 ); + C( TYPE_GRAYA_16_SE ); + C( TYPE_GRAYA_8_PLANAR ); + C( TYPE_GRAYA_16_PLANAR ); + C( TYPE_RGB_8 ); + C( TYPE_RGB_8_PLANAR ); + C( TYPE_BGR_8 ); + C( TYPE_BGR_8_PLANAR ); + C( TYPE_RGB_16 ); + C( TYPE_RGB_16_PLANAR ); + C( TYPE_RGB_16_SE ); + C( TYPE_BGR_16 ); + C( TYPE_BGR_16_PLANAR ); + C( TYPE_BGR_16_SE ); + C( TYPE_RGBA_8 ); + C( TYPE_RGBA_8_PLANAR ); + C( TYPE_RGBA_16 ); + C( TYPE_RGBA_16_PLANAR ); + C( TYPE_RGBA_16_SE ); + C( TYPE_ARGB_8 ); + C( TYPE_ARGB_8_PLANAR ); + C( TYPE_ARGB_16 ); + C( TYPE_ABGR_8 ); + C( TYPE_ABGR_8_PLANAR ); + C( TYPE_ABGR_16 ); + C( TYPE_ABGR_16_PLANAR ); + C( TYPE_ABGR_16_SE ); + C( TYPE_BGRA_8 ); + C( TYPE_BGRA_8_PLANAR ); + C( TYPE_BGRA_16 ); + C( TYPE_BGRA_16_SE ); + C( TYPE_CMY_8 ); + C( TYPE_CMY_8_PLANAR ); + C( TYPE_CMY_16 ); + C( TYPE_CMY_16_PLANAR ); + C( TYPE_CMY_16_SE ); + C( TYPE_CMYK_8 ); + C( TYPE_CMYKA_8 ); + C( TYPE_CMYK_8_REV ); + C( TYPE_YUVK_8 ); + C( TYPE_CMYK_8_PLANAR ); + C( TYPE_CMYK_16 ); + C( TYPE_CMYK_16_REV ); + C( TYPE_YUVK_16 ); + C( TYPE_CMYK_16_PLANAR ); + C( TYPE_CMYK_16_SE ); + C( TYPE_KYMC_8 ); + C( TYPE_KYMC_16 ); + C( TYPE_KYMC_16_SE ); + C( TYPE_KCMY_8 ); + C( TYPE_KCMY_8_REV ); + C( TYPE_KCMY_16 ); + C( TYPE_KCMY_16_REV ); + C( TYPE_KCMY_16_SE ); + C( TYPE_CMYK5_8 ); + C( TYPE_CMYK5_16 ); + C( TYPE_CMYK5_16_SE ); + C( TYPE_KYMC5_8 ); + C( TYPE_KYMC5_16 ); + C( TYPE_KYMC5_16_SE ); + C( TYPE_CMYK6_8 ); + C( TYPE_CMYK6_8_PLANAR ); + C( TYPE_CMYK6_16 ); + C( TYPE_CMYK6_16_PLANAR ); + C( TYPE_CMYK6_16_SE ); + C( TYPE_CMYK7_8 ); + C( TYPE_CMYK7_16 ); + C( TYPE_CMYK7_16_SE ); + C( TYPE_KYMC7_8 ); + C( TYPE_KYMC7_16 ); + C( TYPE_KYMC7_16_SE ); + C( TYPE_CMYK8_8 ); + C( TYPE_CMYK8_16 ); + C( TYPE_CMYK8_16_SE ); + C( TYPE_KYMC8_8 ); + C( TYPE_KYMC8_16 ); + C( TYPE_KYMC8_16_SE ); + C( TYPE_CMYK9_8 ); + C( TYPE_CMYK9_16 ); + C( TYPE_CMYK9_16_SE ); + C( TYPE_KYMC9_8 ); + C( TYPE_KYMC9_16 ); + C( TYPE_KYMC9_16_SE ); + C( TYPE_CMYK10_8 ); + C( TYPE_CMYK10_16 ); + C( TYPE_CMYK10_16_SE ); + C( TYPE_KYMC10_8 ); + C( TYPE_KYMC10_16 ); + C( TYPE_KYMC10_16_SE ); + C( TYPE_CMYK11_8 ); + C( TYPE_CMYK11_16 ); + C( TYPE_CMYK11_16_SE ); + C( TYPE_KYMC11_8 ); + C( TYPE_KYMC11_16 ); + C( TYPE_KYMC11_16_SE ); + C( TYPE_CMYK12_8 ); + C( TYPE_CMYK12_16 ); + C( TYPE_CMYK12_16_SE ); + C( TYPE_KYMC12_8 ); + C( TYPE_KYMC12_16 ); + C( TYPE_KYMC12_16_SE ); + C( TYPE_XYZ_16 ); + C( TYPE_Lab_8 ); + C( TYPE_ALab_8 ); + C( TYPE_Lab_16 ); + C( TYPE_Yxy_16 ); + C( TYPE_YCbCr_8 ); + C( TYPE_YCbCr_8_PLANAR ); + C( TYPE_YCbCr_16 ); + C( TYPE_YCbCr_16_PLANAR ); + C( TYPE_YCbCr_16_SE ); + C( TYPE_YUV_8 ); + C( TYPE_YUV_8_PLANAR ); + C( TYPE_YUV_16 ); + C( TYPE_YUV_16_PLANAR ); + C( TYPE_YUV_16_SE ); + C( TYPE_HLS_8 ); + C( TYPE_HLS_8_PLANAR ); + C( TYPE_HLS_16 ); + C( TYPE_HLS_16_PLANAR ); + C( TYPE_HLS_16_SE ); + C( TYPE_HSV_8 ); + C( TYPE_HSV_8_PLANAR ); + C( TYPE_HSV_16 ); + C( TYPE_HSV_16_PLANAR ); + C( TYPE_HSV_16_SE ); + + C( TYPE_XYZ_FLT ); + C( TYPE_Lab_FLT ); + C( TYPE_GRAY_FLT ); + C( TYPE_RGB_FLT ); + C( TYPE_BGR_FLT ); + C( TYPE_CMYK_FLT ); + C( TYPE_LabA_FLT ); + C( TYPE_RGBA_FLT ); + C( TYPE_ARGB_FLT ); + C( TYPE_BGRA_FLT ); + C( TYPE_ABGR_FLT ); + + + C( TYPE_XYZ_DBL ); + C( TYPE_Lab_DBL ); + C( TYPE_GRAY_DBL ); + C( TYPE_RGB_DBL ); + C( TYPE_BGR_DBL ); + C( TYPE_CMYK_DBL ); + + C( TYPE_LabV2_8 ); + C( TYPE_ALabV2_8 ); + C( TYPE_LabV2_16 ); + +#ifndef CMS_NO_HALF_SUPPORT + + C( TYPE_GRAY_HALF_FLT ); + C( TYPE_RGB_HALF_FLT ); + C( TYPE_CMYK_HALF_FLT ); + C( TYPE_RGBA_HALF_FLT ); + + C( TYPE_RGBA_HALF_FLT ); + C( TYPE_ARGB_HALF_FLT ); + C( TYPE_BGR_HALF_FLT ); + C( TYPE_BGRA_HALF_FLT ); + C( TYPE_ABGR_HALF_FLT ); + +#endif + + return FormatterFailed == 0 ? 1 : 0; +} +#undef C + +static +void CheckSingleFormatterFloat(cmsUInt32Number Type, const char* Text) +{ + cmsFloat32Number Values[cmsMAXCHANNELS]; + cmsUInt8Number Buffer[1024]; + cmsFormatter f, b; + cmsInt32Number i, j, nChannels; + _cmsTRANSFORM info; + + // Already failed? + if (FormatterFailed) return; + + memset(&info, 0, sizeof(info)); + info.OutputFormat = info.InputFormat = Type; + + // Go forth and back + f = _cmsGetFormatter(0, Type, cmsFormatterInput, CMS_PACK_FLAGS_FLOAT); + b = _cmsGetFormatter(0, Type, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT); + + if (f.FmtFloat == NULL || b.FmtFloat == NULL) { + Fail("no formatter for %s", Text); + FormatterFailed = TRUE; + + // Useful for debug + f = _cmsGetFormatter(0, Type, cmsFormatterInput, CMS_PACK_FLAGS_FLOAT); + b = _cmsGetFormatter(0, Type, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT); + return; + } + + nChannels = T_CHANNELS(Type); + + for (j=0; j < 5; j++) { + + for (i=0; i < nChannels; i++) { + Values[i] = (cmsFloat32Number) (i+j); + } + + b.FmtFloat(DbgThread(), &info, Values, Buffer, 1); + memset(Values, 0, sizeof(Values)); + f.FmtFloat(DbgThread(), &info, Values, Buffer, 1); + + for (i=0; i < nChannels; i++) { + + cmsFloat64Number delta = fabs(Values[i] - ( i+j)); + + if (delta > 0.000000001) { + + Fail("%s failed", Text); + FormatterFailed = TRUE; + + // Useful for debug + for (i=0; i < nChannels; i++) { + Values[i] = (cmsFloat32Number) (i+j); + } + + b.FmtFloat(DbgThread(), &info, Values, Buffer, 1); + f.FmtFloat(DbgThread(), &info, Values, Buffer, 1); + return; + } + } + } +} + +#define C(a) CheckSingleFormatterFloat(a, #a) + +static +cmsInt32Number CheckFormattersFloat(void) +{ + FormatterFailed = FALSE; + + C( TYPE_XYZ_FLT ); + C( TYPE_Lab_FLT ); + C( TYPE_GRAY_FLT ); + C( TYPE_RGB_FLT ); + C( TYPE_BGR_FLT ); + C( TYPE_CMYK_FLT ); + + C( TYPE_LabA_FLT ); + C( TYPE_RGBA_FLT ); + + C( TYPE_ARGB_FLT ); + C( TYPE_BGRA_FLT ); + C( TYPE_ABGR_FLT ); + + C( TYPE_XYZ_DBL ); + C( TYPE_Lab_DBL ); + C( TYPE_GRAY_DBL ); + C( TYPE_RGB_DBL ); + C( TYPE_BGR_DBL ); + C( TYPE_CMYK_DBL ); + C( TYPE_XYZ_FLT ); + +#ifndef CMS_NO_HALF_SUPPORT + C( TYPE_GRAY_HALF_FLT ); + C( TYPE_RGB_HALF_FLT ); + C( TYPE_CMYK_HALF_FLT ); + C( TYPE_RGBA_HALF_FLT ); + + C( TYPE_RGBA_HALF_FLT ); + C( TYPE_ARGB_HALF_FLT ); + C( TYPE_BGR_HALF_FLT ); + C( TYPE_BGRA_HALF_FLT ); + C( TYPE_ABGR_HALF_FLT ); +#endif + + + + + return FormatterFailed == 0 ? 1 : 0; +} +#undef C + +#ifndef CMS_NO_HALF_SUPPORT + +// Check half float +#define my_isfinite(x) ((x) != (x)) +static +cmsInt32Number CheckFormattersHalf(void) +{ + int i, j; + + + for (i=0; i < 0xffff; i++) { + + cmsFloat32Number f = _cmsHalf2Float((cmsUInt16Number) i); + + if (!my_isfinite(f)) { + + j = _cmsFloat2Half(f); + + if (i != j) { + Fail("%d != %d in Half float support!\n", i, j); + return 0; + } + } + } + + return 1; +} + +#endif + +static +cmsInt32Number CheckOneRGB(cmsHTRANSFORM xform, cmsUInt16Number R, cmsUInt16Number G, cmsUInt16Number B, cmsUInt16Number Ro, cmsUInt16Number Go, cmsUInt16Number Bo) +{ + cmsUInt16Number RGB[3]; + cmsUInt16Number Out[3]; + + RGB[0] = R; + RGB[1] = G; + RGB[2] = B; + + cmsDoTransform(DbgThread(), xform, RGB, Out, 1); + + return IsGoodWord("R", Ro , Out[0]) && + IsGoodWord("G", Go , Out[1]) && + IsGoodWord("B", Bo , Out[2]); +} + +// Check known values going from sRGB to XYZ +static +cmsInt32Number CheckOneRGB_double(cmsHTRANSFORM xform, cmsFloat64Number R, cmsFloat64Number G, cmsFloat64Number B, cmsFloat64Number Ro, cmsFloat64Number Go, cmsFloat64Number Bo) +{ + cmsFloat64Number RGB[3]; + cmsFloat64Number Out[3]; + + RGB[0] = R; + RGB[1] = G; + RGB[2] = B; + + cmsDoTransform(DbgThread(), xform, RGB, Out, 1); + + return IsGoodVal("R", Ro , Out[0], 0.01) && + IsGoodVal("G", Go , Out[1], 0.01) && + IsGoodVal("B", Bo , Out[2], 0.01); +} + + +static +cmsInt32Number CheckChangeBufferFormat(void) +{ + cmsHPROFILE hsRGB = cmsCreate_sRGBProfile(); + cmsHTRANSFORM xform; + + + xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hsRGB, TYPE_RGB_16, INTENT_PERCEPTUAL, 0); + cmsCloseProfile(DbgThread(), hsRGB); + if (xform == NULL) return 0; + + + if (!CheckOneRGB(xform, 0, 0, 0, 0, 0, 0)) return 0; + if (!CheckOneRGB(xform, 120, 0, 0, 120, 0, 0)) return 0; + if (!CheckOneRGB(xform, 0, 222, 255, 0, 222, 255)) return 0; + + + if (!cmsChangeBuffersFormat(DbgThread(), xform, TYPE_BGR_16, TYPE_RGB_16)) return 0; + + if (!CheckOneRGB(xform, 0, 0, 123, 123, 0, 0)) return 0; + if (!CheckOneRGB(xform, 154, 234, 0, 0, 234, 154)) return 0; + + if (!cmsChangeBuffersFormat(DbgThread(), xform, TYPE_RGB_DBL, TYPE_RGB_DBL)) return 0; + + if (!CheckOneRGB_double(xform, 0.20, 0, 0, 0.20, 0, 0)) return 0; + if (!CheckOneRGB_double(xform, 0, 0.9, 1, 0, 0.9, 1)) return 0; + + cmsDeleteTransform(DbgThread(),xform); + +return 1; +} + + +// Write tag testbed ---------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckXYZ(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsCIEXYZ XYZ, *Pt; + + + switch (Pass) { + + case 1: + + XYZ.X = 1.0; XYZ.Y = 1.1; XYZ.Z = 1.2; + return cmsWriteTag(DbgThread(), hProfile, tag, &XYZ); + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + return IsGoodFixed15_16("X", 1.0, Pt ->X) && + IsGoodFixed15_16("Y", 1.1, Pt->Y) && + IsGoodFixed15_16("Z", 1.2, Pt -> Z); + + default: + return 0; + } +} + + +static +cmsInt32Number CheckGamma(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsToneCurve *g, *Pt; + cmsInt32Number rc; + + switch (Pass) { + + case 1: + + g = cmsBuildGamma(DbgThread(), 1.0); + rc = cmsWriteTag(DbgThread(), hProfile, tag, g); + cmsFreeToneCurve(DbgThread(), g); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + return cmsIsToneCurveLinear(DbgThread(), Pt); + + default: + return 0; + } +} + +static +cmsInt32Number CheckTextSingle(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsMLU *m, *Pt; + cmsInt32Number rc; + char Buffer[256]; + + + switch (Pass) { + + case 1: + m = cmsMLUalloc(DbgThread(), 0); + cmsMLUsetASCII(DbgThread(), m, cmsNoLanguage, cmsNoCountry, "Test test"); + rc = cmsWriteTag(DbgThread(), hProfile, tag, m); + cmsMLUfree(DbgThread(), m); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + cmsMLUgetASCII(DbgThread(), Pt, cmsNoLanguage, cmsNoCountry, Buffer, 256); + if (strcmp(Buffer, "Test test") != 0) return FALSE; + return TRUE; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckText(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsMLU *m, *Pt; + cmsInt32Number rc; + char Buffer[256]; + + + switch (Pass) { + + case 1: + m = cmsMLUalloc(DbgThread(), 0); + cmsMLUsetASCII(DbgThread(), m, cmsNoLanguage, cmsNoCountry, "Test test"); + cmsMLUsetASCII(DbgThread(), m, "en", "US", "1 1 1 1"); + cmsMLUsetASCII(DbgThread(), m, "es", "ES", "2 2 2 2"); + cmsMLUsetASCII(DbgThread(), m, "ct", "ES", "3 3 3 3"); + cmsMLUsetASCII(DbgThread(), m, "en", "GB", "444444444"); + rc = cmsWriteTag(DbgThread(), hProfile, tag, m); + cmsMLUfree(DbgThread(), m); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + cmsMLUgetASCII(DbgThread(), Pt, cmsNoLanguage, cmsNoCountry, Buffer, 256); + if (strcmp(Buffer, "Test test") != 0) return FALSE; + cmsMLUgetASCII(DbgThread(), Pt, "en", "US", Buffer, 256); + if (strcmp(Buffer, "1 1 1 1") != 0) return FALSE; + cmsMLUgetASCII(DbgThread(), Pt, "es", "ES", Buffer, 256); + if (strcmp(Buffer, "2 2 2 2") != 0) return FALSE; + cmsMLUgetASCII(DbgThread(), Pt, "ct", "ES", Buffer, 256); + if (strcmp(Buffer, "3 3 3 3") != 0) return FALSE; + cmsMLUgetASCII(DbgThread(), Pt, "en", "GB", Buffer, 256); + if (strcmp(Buffer, "444444444") != 0) return FALSE; + return TRUE; + + default: + return 0; + } +} + +static +cmsInt32Number CheckData(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsICCData *Pt; + cmsICCData d = { 1, 0, { '?' }}; + cmsInt32Number rc; + + + switch (Pass) { + + case 1: + rc = cmsWriteTag(DbgThread(), hProfile, tag, &d); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + return (Pt ->data[0] == '?') && (Pt ->flag == 0) && (Pt ->len == 1); + + default: + return 0; + } +} + + +static +cmsInt32Number CheckSignature(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsTagSignature *Pt, Holder; + + switch (Pass) { + + case 1: + Holder = cmsSigPerceptualReferenceMediumGamut; + return cmsWriteTag(DbgThread(), hProfile, tag, &Holder); + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + return *Pt == cmsSigPerceptualReferenceMediumGamut; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckDateTime(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + struct tm *Pt, Holder; + + switch (Pass) { + + case 1: + + Holder.tm_hour = 1; + Holder.tm_min = 2; + Holder.tm_sec = 3; + Holder.tm_mday = 4; + Holder.tm_mon = 5; + Holder.tm_year = 2009 - 1900; + return cmsWriteTag(DbgThread(), hProfile, tag, &Holder); + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + return (Pt ->tm_hour == 1 && + Pt ->tm_min == 2 && + Pt ->tm_sec == 3 && + Pt ->tm_mday == 4 && + Pt ->tm_mon == 5 && + Pt ->tm_year == 2009 - 1900); + + default: + return 0; + } + +} + + +static +cmsInt32Number CheckNamedColor(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag, cmsInt32Number max_check, cmsBool colorant_check) +{ + cmsNAMEDCOLORLIST* nc; + cmsInt32Number i, j, rc; + char Name[255]; + cmsUInt16Number PCS[3]; + cmsUInt16Number Colorant[cmsMAXCHANNELS]; + char CheckName[255]; + cmsUInt16Number CheckPCS[3]; + cmsUInt16Number CheckColorant[cmsMAXCHANNELS]; + + switch (Pass) { + + case 1: + + nc = cmsAllocNamedColorList(DbgThread(), 0, 4, "prefix", "suffix"); + if (nc == NULL) return 0; + + for (i=0; i < max_check; i++) { + + PCS[0] = PCS[1] = PCS[2] = (cmsUInt16Number) i; + Colorant[0] = Colorant[1] = Colorant[2] = Colorant[3] = (cmsUInt16Number) (max_check - i); + + sprintf(Name, "#%d", i); + if (!cmsAppendNamedColor(DbgThread(), nc, Name, PCS, Colorant)) { Fail("Couldn't append named color"); return 0; } + } + + rc = cmsWriteTag(DbgThread(), hProfile, tag, nc); + cmsFreeNamedColorList(DbgThread(), nc); + return rc; + + case 2: + + nc = cmsReadTag(DbgThread(), hProfile, tag); + if (nc == NULL) return 0; + + for (i=0; i < max_check; i++) { + + CheckPCS[0] = CheckPCS[1] = CheckPCS[2] = (cmsUInt16Number) i; + CheckColorant[0] = CheckColorant[1] = CheckColorant[2] = CheckColorant[3] = (cmsUInt16Number) (max_check - i); + + sprintf(CheckName, "#%d", i); + if (!cmsNamedColorInfo(DbgThread(), nc, i, Name, NULL, NULL, PCS, Colorant)) { Fail("Invalid string"); return 0; } + + + for (j=0; j < 3; j++) { + if (CheckPCS[j] != PCS[j]) { Fail("Invalid PCS"); return 0; } + } + + // This is only used on named color list + if (colorant_check) { + + for (j=0; j < 4; j++) { + if (CheckColorant[j] != Colorant[j]) { Fail("Invalid Colorant"); return 0; }; + } + } + + if (strcmp(Name, CheckName) != 0) { Fail("Invalid Name"); return 0; }; + } + return 1; + + + default: return 0; + } +} + + +static +cmsInt32Number CheckLUT(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsPipeline* Lut, *Pt; + cmsInt32Number rc; + + + switch (Pass) { + + case 1: + + Lut = cmsPipelineAlloc(DbgThread(), 3, 3); + if (Lut == NULL) return 0; + + // Create an identity LUT + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(DbgThread(), 3)); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_END, _cmsStageAllocIdentityCLut(DbgThread(), 3)); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_END, _cmsStageAllocIdentityCurves(DbgThread(), 3)); + + rc = cmsWriteTag(DbgThread(), hProfile, tag, Lut); + cmsPipelineFree(DbgThread(), Lut); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + // Transform values, check for identity + return Check16LUT(Pt); + + default: + return 0; + } +} + +static +cmsInt32Number CheckCHAD(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsFloat64Number *Pt; + cmsFloat64Number CHAD[] = { 0, .1, .2, .3, .4, .5, .6, .7, .8 }; + cmsInt32Number i; + + switch (Pass) { + + case 1: + return cmsWriteTag(DbgThread(), hProfile, tag, CHAD); + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + for (i=0; i < 9; i++) { + if (!IsGoodFixed15_16("CHAD", Pt[i], CHAD[i])) return 0; + } + + return 1; + + default: + return 0; + } +} + +static +cmsInt32Number CheckChromaticity(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsCIExyYTRIPLE *Pt, c = { {0, .1, 1 }, { .3, .4, 1 }, { .6, .7, 1 }}; + + switch (Pass) { + + case 1: + return cmsWriteTag(DbgThread(), hProfile, tag, &c); + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + if (!IsGoodFixed15_16("xyY", Pt ->Red.x, c.Red.x)) return 0; + if (!IsGoodFixed15_16("xyY", Pt ->Red.y, c.Red.y)) return 0; + if (!IsGoodFixed15_16("xyY", Pt ->Green.x, c.Green.x)) return 0; + if (!IsGoodFixed15_16("xyY", Pt ->Green.y, c.Green.y)) return 0; + if (!IsGoodFixed15_16("xyY", Pt ->Blue.x, c.Blue.x)) return 0; + if (!IsGoodFixed15_16("xyY", Pt ->Blue.y, c.Blue.y)) return 0; + return 1; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckColorantOrder(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsUInt8Number *Pt, c[cmsMAXCHANNELS]; + cmsInt32Number i; + + switch (Pass) { + + case 1: + for (i=0; i < cmsMAXCHANNELS; i++) c[i] = (cmsUInt8Number) (cmsMAXCHANNELS - i - 1); + return cmsWriteTag(DbgThread(), hProfile, tag, c); + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + for (i=0; i < cmsMAXCHANNELS; i++) { + if (Pt[i] != ( cmsMAXCHANNELS - i - 1 )) return 0; + } + return 1; + + default: + return 0; + } +} + +static +cmsInt32Number CheckMeasurement(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsICCMeasurementConditions *Pt, m; + + switch (Pass) { + + case 1: + m.Backing.X = 0.1; + m.Backing.Y = 0.2; + m.Backing.Z = 0.3; + m.Flare = 1.0; + m.Geometry = 1; + m.IlluminantType = cmsILLUMINANT_TYPE_D50; + m.Observer = 1; + return cmsWriteTag(DbgThread(), hProfile, tag, &m); + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + if (!IsGoodFixed15_16("Backing", Pt ->Backing.X, 0.1)) return 0; + if (!IsGoodFixed15_16("Backing", Pt ->Backing.Y, 0.2)) return 0; + if (!IsGoodFixed15_16("Backing", Pt ->Backing.Z, 0.3)) return 0; + if (!IsGoodFixed15_16("Flare", Pt ->Flare, 1.0)) return 0; + + if (Pt ->Geometry != 1) return 0; + if (Pt ->IlluminantType != cmsILLUMINANT_TYPE_D50) return 0; + if (Pt ->Observer != 1) return 0; + return 1; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckUcrBg(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsUcrBg *Pt, m; + cmsInt32Number rc; + char Buffer[256]; + + switch (Pass) { + + case 1: + m.Ucr = cmsBuildGamma(DbgThread(), 2.4); + m.Bg = cmsBuildGamma(DbgThread(), -2.2); + m.Desc = cmsMLUalloc(DbgThread(), 1); + cmsMLUsetASCII(DbgThread(), m.Desc, cmsNoLanguage, cmsNoCountry, "test UCR/BG"); + rc = cmsWriteTag(DbgThread(), hProfile, tag, &m); + cmsMLUfree(DbgThread(), m.Desc); + cmsFreeToneCurve(DbgThread(), m.Bg); + cmsFreeToneCurve(DbgThread(), m.Ucr); + return rc; + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + cmsMLUgetASCII(DbgThread(), Pt ->Desc, cmsNoLanguage, cmsNoCountry, Buffer, 256); + if (strcmp(Buffer, "test UCR/BG") != 0) return 0; + return 1; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckCRDinfo(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsMLU *mlu; + char Buffer[256]; + cmsInt32Number rc; + + switch (Pass) { + + case 1: + mlu = cmsMLUalloc(DbgThread(), 5); + + cmsMLUsetWide(DbgThread(), mlu, "PS", "nm", L"test postscript"); + cmsMLUsetWide(DbgThread(), mlu, "PS", "#0", L"perceptual"); + cmsMLUsetWide(DbgThread(), mlu, "PS", "#1", L"relative_colorimetric"); + cmsMLUsetWide(DbgThread(), mlu, "PS", "#2", L"saturation"); + cmsMLUsetWide(DbgThread(), mlu, "PS", "#3", L"absolute_colorimetric"); + rc = cmsWriteTag(DbgThread(), hProfile, tag, mlu); + cmsMLUfree(DbgThread(), mlu); + return rc; + + + case 2: + mlu = (cmsMLU*) cmsReadTag(DbgThread(), hProfile, tag); + if (mlu == NULL) return 0; + + + + cmsMLUgetASCII(DbgThread(), mlu, "PS", "nm", Buffer, 256); + if (strcmp(Buffer, "test postscript") != 0) return 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "PS", "#0", Buffer, 256); + if (strcmp(Buffer, "perceptual") != 0) return 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "PS", "#1", Buffer, 256); + if (strcmp(Buffer, "relative_colorimetric") != 0) return 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "PS", "#2", Buffer, 256); + if (strcmp(Buffer, "saturation") != 0) return 0; + + + cmsMLUgetASCII(DbgThread(), mlu, "PS", "#3", Buffer, 256); + if (strcmp(Buffer, "absolute_colorimetric") != 0) return 0; + return 1; + + default: + return 0; + } +} + + +static +cmsToneCurve *CreateSegmentedCurve(void) +{ + cmsCurveSegment Seg[3]; + cmsFloat32Number Sampled[2] = { 0, 1}; + + Seg[0].Type = 6; + Seg[0].Params[0] = 1; + Seg[0].Params[1] = 0; + Seg[0].Params[2] = 0; + Seg[0].Params[3] = 0; + Seg[0].x0 = -1E22F; + Seg[0].x1 = 0; + + Seg[1].Type = 0; + Seg[1].nGridPoints = 2; + Seg[1].SampledPoints = Sampled; + Seg[1].x0 = 0; + Seg[1].x1 = 1; + + Seg[2].Type = 6; + Seg[2].Params[0] = 1; + Seg[2].Params[1] = 0; + Seg[2].Params[2] = 0; + Seg[2].Params[3] = 0; + Seg[2].x0 = 1; + Seg[2].x1 = 1E22F; + + return cmsBuildSegmentedToneCurve(DbgThread(), 3, Seg); +} + + +static +cmsInt32Number CheckMPE(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsPipeline* Lut, *Pt; + cmsToneCurve* G[3]; + cmsInt32Number rc; + + switch (Pass) { + + case 1: + + Lut = cmsPipelineAlloc(DbgThread(), 3, 3); + + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4(DbgThread())); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_END, _cmsStageAllocLabV4ToV2(DbgThread())); + AddIdentityCLUTfloat(Lut); + + G[0] = G[1] = G[2] = CreateSegmentedCurve(); + cmsPipelineInsertStage(DbgThread(), Lut, cmsAT_END, cmsStageAllocToneCurves(DbgThread(), 3, G)); + cmsFreeToneCurve(DbgThread(), G[0]); + + rc = cmsWriteTag(DbgThread(), hProfile, tag, Lut); + cmsPipelineFree(DbgThread(), Lut); + return rc; + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + return CheckFloatLUT(Pt); + + default: + return 0; + } +} + + +static +cmsInt32Number CheckScreening(cmsInt32Number Pass, cmsHPROFILE hProfile, cmsTagSignature tag) +{ + cmsScreening *Pt, sc; + cmsInt32Number rc; + + switch (Pass) { + + case 1: + + sc.Flag = 0; + sc.nChannels = 1; + sc.Channels[0].Frequency = 2.0; + sc.Channels[0].ScreenAngle = 3.0; + sc.Channels[0].SpotShape = cmsSPOT_ELLIPSE; + + rc = cmsWriteTag(DbgThread(), hProfile, tag, &sc); + return rc; + + + case 2: + Pt = cmsReadTag(DbgThread(), hProfile, tag); + if (Pt == NULL) return 0; + + if (Pt ->nChannels != 1) return 0; + if (Pt ->Flag != 0) return 0; + if (!IsGoodFixed15_16("Freq", Pt ->Channels[0].Frequency, 2.0)) return 0; + if (!IsGoodFixed15_16("Angle", Pt ->Channels[0].ScreenAngle, 3.0)) return 0; + if (Pt ->Channels[0].SpotShape != cmsSPOT_ELLIPSE) return 0; + return 1; + + default: + return 0; + } +} + + +static +cmsBool CheckOneStr(cmsMLU* mlu, cmsInt32Number n) +{ + char Buffer[256], Buffer2[256]; + + + cmsMLUgetASCII(DbgThread(), mlu, "en", "US", Buffer, 255); + sprintf(Buffer2, "Hello, world %d", n); + if (strcmp(Buffer, Buffer2) != 0) return FALSE; + + + cmsMLUgetASCII(DbgThread(), mlu, "es", "ES", Buffer, 255); + sprintf(Buffer2, "Hola, mundo %d", n); + if (strcmp(Buffer, Buffer2) != 0) return FALSE; + + return TRUE; +} + + +static +void SetOneStr(cmsMLU** mlu, wchar_t* s1, wchar_t* s2) +{ + *mlu = cmsMLUalloc(DbgThread(), 0); + cmsMLUsetWide(DbgThread(), *mlu, "en", "US", s1); + cmsMLUsetWide(DbgThread(), *mlu, "es", "ES", s2); +} + + +static +cmsInt32Number CheckProfileSequenceTag(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsSEQ* s; + cmsInt32Number i; + + switch (Pass) { + + case 1: + + s = cmsAllocProfileSequenceDescription(DbgThread(), 3); + if (s == NULL) return 0; + + SetOneStr(&s -> seq[0].Manufacturer, L"Hello, world 0", L"Hola, mundo 0"); + SetOneStr(&s -> seq[0].Model, L"Hello, world 0", L"Hola, mundo 0"); + SetOneStr(&s -> seq[1].Manufacturer, L"Hello, world 1", L"Hola, mundo 1"); + SetOneStr(&s -> seq[1].Model, L"Hello, world 1", L"Hola, mundo 1"); + SetOneStr(&s -> seq[2].Manufacturer, L"Hello, world 2", L"Hola, mundo 2"); + SetOneStr(&s -> seq[2].Model, L"Hello, world 2", L"Hola, mundo 2"); + + +#ifdef CMS_DONT_USE_INT64 + s ->seq[0].attributes[0] = cmsTransparency|cmsMatte; + s ->seq[0].attributes[1] = 0; +#else + s ->seq[0].attributes = cmsTransparency|cmsMatte; +#endif + +#ifdef CMS_DONT_USE_INT64 + s ->seq[1].attributes[0] = cmsReflective|cmsMatte; + s ->seq[1].attributes[1] = 0; +#else + s ->seq[1].attributes = cmsReflective|cmsMatte; +#endif + +#ifdef CMS_DONT_USE_INT64 + s ->seq[2].attributes[0] = cmsTransparency|cmsGlossy; + s ->seq[2].attributes[1] = 0; +#else + s ->seq[2].attributes = cmsTransparency|cmsGlossy; +#endif + + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigProfileSequenceDescTag, s)) return 0; + cmsFreeProfileSequenceDescription(DbgThread(), s); + return 1; + + case 2: + + s = cmsReadTag(DbgThread(), hProfile, cmsSigProfileSequenceDescTag); + if (s == NULL) return 0; + + if (s ->n != 3) return 0; + +#ifdef CMS_DONT_USE_INT64 + if (s ->seq[0].attributes[0] != (cmsTransparency|cmsMatte)) return 0; + if (s ->seq[0].attributes[1] != 0) return 0; +#else + if (s ->seq[0].attributes != (cmsTransparency|cmsMatte)) return 0; +#endif + +#ifdef CMS_DONT_USE_INT64 + if (s ->seq[1].attributes[0] != (cmsReflective|cmsMatte)) return 0; + if (s ->seq[1].attributes[1] != 0) return 0; +#else + if (s ->seq[1].attributes != (cmsReflective|cmsMatte)) return 0; +#endif + +#ifdef CMS_DONT_USE_INT64 + if (s ->seq[2].attributes[0] != (cmsTransparency|cmsGlossy)) return 0; + if (s ->seq[2].attributes[1] != 0) return 0; +#else + if (s ->seq[2].attributes != (cmsTransparency|cmsGlossy)) return 0; +#endif + + // Check MLU + for (i=0; i < 3; i++) { + + if (!CheckOneStr(s -> seq[i].Manufacturer, i)) return 0; + if (!CheckOneStr(s -> seq[i].Model, i)) return 0; + } + return 1; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckProfileSequenceIDTag(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsSEQ* s; + cmsInt32Number i; + + switch (Pass) { + + case 1: + + s = cmsAllocProfileSequenceDescription(DbgThread(), 3); + if (s == NULL) return 0; + + memcpy(s ->seq[0].ProfileID.ID8, "0123456789ABCDEF", 16); + memcpy(s ->seq[1].ProfileID.ID8, "1111111111111111", 16); + memcpy(s ->seq[2].ProfileID.ID8, "2222222222222222", 16); + + + SetOneStr(&s -> seq[0].Description, L"Hello, world 0", L"Hola, mundo 0"); + SetOneStr(&s -> seq[1].Description, L"Hello, world 1", L"Hola, mundo 1"); + SetOneStr(&s -> seq[2].Description, L"Hello, world 2", L"Hola, mundo 2"); + + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigProfileSequenceIdTag, s)) return 0; + cmsFreeProfileSequenceDescription(DbgThread(), s); + return 1; + + case 2: + + s = cmsReadTag(DbgThread(), hProfile, cmsSigProfileSequenceIdTag); + if (s == NULL) return 0; + + if (s ->n != 3) return 0; + + if (memcmp(s ->seq[0].ProfileID.ID8, "0123456789ABCDEF", 16) != 0) return 0; + if (memcmp(s ->seq[1].ProfileID.ID8, "1111111111111111", 16) != 0) return 0; + if (memcmp(s ->seq[2].ProfileID.ID8, "2222222222222222", 16) != 0) return 0; + + for (i=0; i < 3; i++) { + + if (!CheckOneStr(s -> seq[i].Description, i)) return 0; + } + + return 1; + + default: + return 0; + } +} + + +static +cmsInt32Number CheckICCViewingConditions(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsICCViewingConditions* v; + cmsICCViewingConditions s; + + switch (Pass) { + + case 1: + s.IlluminantType = 1; + s.IlluminantXYZ.X = 0.1; + s.IlluminantXYZ.Y = 0.2; + s.IlluminantXYZ.Z = 0.3; + s.SurroundXYZ.X = 0.4; + s.SurroundXYZ.Y = 0.5; + s.SurroundXYZ.Z = 0.6; + + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigViewingConditionsTag, &s)) return 0; + return 1; + + case 2: + v = cmsReadTag(DbgThread(), hProfile, cmsSigViewingConditionsTag); + if (v == NULL) return 0; + + if (v ->IlluminantType != 1) return 0; + if (!IsGoodVal("IlluminantXYZ.X", v ->IlluminantXYZ.X, 0.1, 0.001)) return 0; + if (!IsGoodVal("IlluminantXYZ.Y", v ->IlluminantXYZ.Y, 0.2, 0.001)) return 0; + if (!IsGoodVal("IlluminantXYZ.Z", v ->IlluminantXYZ.Z, 0.3, 0.001)) return 0; + + if (!IsGoodVal("SurroundXYZ.X", v ->SurroundXYZ.X, 0.4, 0.001)) return 0; + if (!IsGoodVal("SurroundXYZ.Y", v ->SurroundXYZ.Y, 0.5, 0.001)) return 0; + if (!IsGoodVal("SurroundXYZ.Z", v ->SurroundXYZ.Z, 0.6, 0.001)) return 0; + + return 1; + + default: + return 0; + } + +} + + +static +cmsInt32Number CheckVCGT(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsToneCurve* Curves[3]; + cmsToneCurve** PtrCurve; + + switch (Pass) { + + case 1: + Curves[0] = cmsBuildGamma(DbgThread(), 1.1); + Curves[1] = cmsBuildGamma(DbgThread(), 2.2); + Curves[2] = cmsBuildGamma(DbgThread(), 3.4); + + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigVcgtTag, Curves)) return 0; + + cmsFreeToneCurveTriple(DbgThread(), Curves); + return 1; + + + case 2: + + PtrCurve = cmsReadTag(DbgThread(), hProfile, cmsSigVcgtTag); + if (PtrCurve == NULL) return 0; + if (!IsGoodVal("VCGT R", cmsEstimateGamma(DbgThread(), PtrCurve[0], 0.01), 1.1, 0.001)) return 0; + if (!IsGoodVal("VCGT G", cmsEstimateGamma(DbgThread(), PtrCurve[1], 0.01), 2.2, 0.001)) return 0; + if (!IsGoodVal("VCGT B", cmsEstimateGamma(DbgThread(), PtrCurve[2], 0.01), 3.4, 0.001)) return 0; + return 1; + + default:; + } + + return 0; +} + + +// Only one of the two following may be used, as they share the same tag +static +cmsInt32Number CheckDictionary16(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsHANDLE hDict; + const cmsDICTentry* e; + switch (Pass) { + + case 1: + hDict = cmsDictAlloc(DbgThread()); + cmsDictAddEntry(DbgThread(), hDict, L"Name0", NULL, NULL, NULL); + cmsDictAddEntry(DbgThread(), hDict, L"Name1", L"", NULL, NULL); + cmsDictAddEntry(DbgThread(), hDict, L"Name", L"String", NULL, NULL); + cmsDictAddEntry(DbgThread(), hDict, L"Name2", L"12", NULL, NULL); + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigMetaTag, hDict)) return 0; + cmsDictFree(DbgThread(), hDict); + return 1; + + + case 2: + + hDict = cmsReadTag(DbgThread(), hProfile, cmsSigMetaTag); + if (hDict == NULL) return 0; + e = cmsDictGetEntryList(DbgThread(), hDict); + if (memcmp(e ->Name, L"Name2", sizeof(wchar_t) * 5) != 0) return 0; + if (memcmp(e ->Value, L"12", sizeof(wchar_t) * 2) != 0) return 0; + e = cmsDictNextEntry(DbgThread(), e); + if (memcmp(e ->Name, L"Name", sizeof(wchar_t) * 4) != 0) return 0; + if (memcmp(e ->Value, L"String", sizeof(wchar_t) * 5) != 0) return 0; + e = cmsDictNextEntry(DbgThread(), e); + if (memcmp(e ->Name, L"Name1", sizeof(wchar_t) *5) != 0) return 0; + if (e ->Value == NULL) return 0; + if (*e->Value != 0) return 0; + e = cmsDictNextEntry(DbgThread(), e); + if (memcmp(e ->Name, L"Name0", sizeof(wchar_t) * 5) != 0) return 0; + if (e ->Value != NULL) return 0; + return 1; + + + default:; + } + + return 0; +} + + + +static +cmsInt32Number CheckDictionary24(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + cmsHANDLE hDict; + const cmsDICTentry* e; + cmsMLU* DisplayName; + char Buffer[256]; + cmsInt32Number rc = 1; + + switch (Pass) { + + case 1: + hDict = cmsDictAlloc(DbgThread()); + + DisplayName = cmsMLUalloc(DbgThread(), 0); + + cmsMLUsetWide(DbgThread(), DisplayName, "en", "US", L"Hello, world"); + cmsMLUsetWide(DbgThread(), DisplayName, "es", "ES", L"Hola, mundo"); + cmsMLUsetWide(DbgThread(), DisplayName, "fr", "FR", L"Bonjour, le monde"); + cmsMLUsetWide(DbgThread(), DisplayName, "ca", "CA", L"Hola, mon"); + + cmsDictAddEntry(DbgThread(), hDict, L"Name", L"String", DisplayName, NULL); + cmsMLUfree(DbgThread(), DisplayName); + + cmsDictAddEntry(DbgThread(), hDict, L"Name2", L"12", NULL, NULL); + if (!cmsWriteTag(DbgThread(), hProfile, cmsSigMetaTag, hDict)) return 0; + cmsDictFree(DbgThread(), hDict); + + return 1; + + + case 2: + + hDict = cmsReadTag(DbgThread(), hProfile, cmsSigMetaTag); + if (hDict == NULL) return 0; + + e = cmsDictGetEntryList(DbgThread(), hDict); + if (memcmp(e ->Name, L"Name2", sizeof(wchar_t) * 5) != 0) return 0; + if (memcmp(e ->Value, L"12", sizeof(wchar_t) * 2) != 0) return 0; + e = cmsDictNextEntry(DbgThread(), e); + if (memcmp(e ->Name, L"Name", sizeof(wchar_t) * 4) != 0) return 0; + if (memcmp(e ->Value, L"String", sizeof(wchar_t) * 5) != 0) return 0; + + cmsMLUgetASCII(DbgThread(), e->DisplayName, "en", "US", Buffer, 256); + if (strcmp(Buffer, "Hello, world") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), e->DisplayName, "es", "ES", Buffer, 256); + if (strcmp(Buffer, "Hola, mundo") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), e->DisplayName, "fr", "FR", Buffer, 256); + if (strcmp(Buffer, "Bonjour, le monde") != 0) rc = 0; + + + cmsMLUgetASCII(DbgThread(), e->DisplayName, "ca", "CA", Buffer, 256); + if (strcmp(Buffer, "Hola, mon") != 0) rc = 0; + + if (rc == 0) + Fail("Unexpected string '%s'", Buffer); + return 1; + + default:; + } + + return 0; +} + +static +cmsInt32Number CheckRAWtags(cmsInt32Number Pass, cmsHPROFILE hProfile) +{ + char Buffer[7]; + + switch (Pass) { + + case 1: + return cmsWriteRawTag(DbgThread(), hProfile, 0x31323334, "data123", 7); + + case 2: + if (!cmsReadRawTag(DbgThread(), hProfile, 0x31323334, Buffer, 7)) return 0; + + if (strncmp(Buffer, "data123", 7) != 0) return 0; + return 1; + + default: + return 0; + } +} + + +// This is a very big test that checks every single tag +static +cmsInt32Number CheckProfileCreation(void) +{ + cmsHPROFILE h; + cmsInt32Number Pass; + + h = cmsCreateProfilePlaceholder(DbgThread()); + if (h == NULL) return 0; + + cmsSetProfileVersion(DbgThread(), h, 4.3); + if (cmsGetTagCount(DbgThread(), h) != 0) { Fail("Empty profile with nonzero number of tags"); goto Error; } + if (cmsIsTag(DbgThread(), h, cmsSigAToB0Tag)) { Fail("Found a tag in an empty profile"); goto Error; } + + cmsSetColorSpace(DbgThread(), h, cmsSigRgbData); + if (cmsGetColorSpace(DbgThread(), h) != cmsSigRgbData) { Fail("Unable to set colorspace"); goto Error; } + + cmsSetPCS(DbgThread(), h, cmsSigLabData); + if (cmsGetPCS(DbgThread(), h) != cmsSigLabData) { Fail("Unable to set colorspace"); goto Error; } + + cmsSetDeviceClass(DbgThread(), h, cmsSigDisplayClass); + if (cmsGetDeviceClass(DbgThread(), h) != cmsSigDisplayClass) { Fail("Unable to set deviceclass"); goto Error; } + + cmsSetHeaderRenderingIntent(DbgThread(), h, INTENT_SATURATION); + if (cmsGetHeaderRenderingIntent(DbgThread(), h) != INTENT_SATURATION) { Fail("Unable to set rendering intent"); goto Error; } + + for (Pass = 1; Pass <= 2; Pass++) { + + SubTest("Tags holding XYZ"); + + if (!CheckXYZ(Pass, h, cmsSigBlueColorantTag)) goto Error; + if (!CheckXYZ(Pass, h, cmsSigGreenColorantTag)) goto Error; + if (!CheckXYZ(Pass, h, cmsSigRedColorantTag)) goto Error; + if (!CheckXYZ(Pass, h, cmsSigMediaBlackPointTag)) goto Error; + if (!CheckXYZ(Pass, h, cmsSigMediaWhitePointTag)) goto Error; + if (!CheckXYZ(Pass, h, cmsSigLuminanceTag)) goto Error; + + SubTest("Tags holding curves"); + + if (!CheckGamma(Pass, h, cmsSigBlueTRCTag)) goto Error; + if (!CheckGamma(Pass, h, cmsSigGrayTRCTag)) goto Error; + if (!CheckGamma(Pass, h, cmsSigGreenTRCTag)) goto Error; + if (!CheckGamma(Pass, h, cmsSigRedTRCTag)) goto Error; + + SubTest("Tags holding text"); + + if (!CheckTextSingle(Pass, h, cmsSigCharTargetTag)) goto Error; + if (!CheckTextSingle(Pass, h, cmsSigScreeningDescTag)) goto Error; + + if (!CheckText(Pass, h, cmsSigCopyrightTag)) goto Error; + if (!CheckText(Pass, h, cmsSigProfileDescriptionTag)) goto Error; + if (!CheckText(Pass, h, cmsSigDeviceMfgDescTag)) goto Error; + if (!CheckText(Pass, h, cmsSigDeviceModelDescTag)) goto Error; + if (!CheckText(Pass, h, cmsSigViewingCondDescTag)) goto Error; + + + + SubTest("Tags holding cmsICCData"); + + if (!CheckData(Pass, h, cmsSigPs2CRD0Tag)) goto Error; + if (!CheckData(Pass, h, cmsSigPs2CRD1Tag)) goto Error; + if (!CheckData(Pass, h, cmsSigPs2CRD2Tag)) goto Error; + if (!CheckData(Pass, h, cmsSigPs2CRD3Tag)) goto Error; + if (!CheckData(Pass, h, cmsSigPs2CSATag)) goto Error; + if (!CheckData(Pass, h, cmsSigPs2RenderingIntentTag)) goto Error; + + SubTest("Tags holding signatures"); + + if (!CheckSignature(Pass, h, cmsSigColorimetricIntentImageStateTag)) goto Error; + if (!CheckSignature(Pass, h, cmsSigPerceptualRenderingIntentGamutTag)) goto Error; + if (!CheckSignature(Pass, h, cmsSigSaturationRenderingIntentGamutTag)) goto Error; + if (!CheckSignature(Pass, h, cmsSigTechnologyTag)) goto Error; + + SubTest("Tags holding date_time"); + + if (!CheckDateTime(Pass, h, cmsSigCalibrationDateTimeTag)) goto Error; + if (!CheckDateTime(Pass, h, cmsSigDateTimeTag)) goto Error; + + SubTest("Tags holding named color lists"); + + if (!CheckNamedColor(Pass, h, cmsSigColorantTableTag, 15, FALSE)) goto Error; + if (!CheckNamedColor(Pass, h, cmsSigColorantTableOutTag, 15, FALSE)) goto Error; + if (!CheckNamedColor(Pass, h, cmsSigNamedColor2Tag, 4096, TRUE)) goto Error; + + SubTest("Tags holding LUTs"); + + if (!CheckLUT(Pass, h, cmsSigAToB0Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigAToB1Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigAToB2Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigBToA0Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigBToA1Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigBToA2Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigPreview0Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigPreview1Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigPreview2Tag)) goto Error; + if (!CheckLUT(Pass, h, cmsSigGamutTag)) goto Error; + + SubTest("Tags holding CHAD"); + if (!CheckCHAD(Pass, h, cmsSigChromaticAdaptationTag)) goto Error; + + SubTest("Tags holding Chromaticity"); + if (!CheckChromaticity(Pass, h, cmsSigChromaticityTag)) goto Error; + + SubTest("Tags holding colorant order"); + if (!CheckColorantOrder(Pass, h, cmsSigColorantOrderTag)) goto Error; + + SubTest("Tags holding measurement"); + if (!CheckMeasurement(Pass, h, cmsSigMeasurementTag)) goto Error; + + SubTest("Tags holding CRD info"); + if (!CheckCRDinfo(Pass, h, cmsSigCrdInfoTag)) goto Error; + + SubTest("Tags holding UCR/BG"); + if (!CheckUcrBg(Pass, h, cmsSigUcrBgTag)) goto Error; + + SubTest("Tags holding MPE"); + if (!CheckMPE(Pass, h, cmsSigDToB0Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigDToB1Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigDToB2Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigDToB3Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigBToD0Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigBToD1Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigBToD2Tag)) goto Error; + if (!CheckMPE(Pass, h, cmsSigBToD3Tag)) goto Error; + + SubTest("Tags using screening"); + if (!CheckScreening(Pass, h, cmsSigScreeningTag)) goto Error; + + SubTest("Tags holding profile sequence description"); + if (!CheckProfileSequenceTag(Pass, h)) goto Error; + if (!CheckProfileSequenceIDTag(Pass, h)) goto Error; + + SubTest("Tags holding ICC viewing conditions"); + if (!CheckICCViewingConditions(Pass, h)) goto Error; + + SubTest("VCGT tags"); + if (!CheckVCGT(Pass, h)) goto Error; + + SubTest("RAW tags"); + if (!CheckRAWtags(Pass, h)) goto Error; + + SubTest("Dictionary meta tags"); + // if (!CheckDictionary16(Pass, h)) goto Error; + if (!CheckDictionary24(Pass, h)) goto Error; + + if (Pass == 1) { + cmsSaveProfileToFile(DbgThread(), h, "alltags.icc"); + cmsCloseProfile(DbgThread(), h); + h = cmsOpenProfileFromFileTHR(DbgThread(), "alltags.icc", "r"); + } + + } + + /* + Not implemented (by design): + + cmsSigDataTag = 0x64617461, // 'data' -- Unused + cmsSigDeviceSettingsTag = 0x64657673, // 'devs' -- Unused + cmsSigNamedColorTag = 0x6E636f6C, // 'ncol' -- Don't use this one, deprecated by ICC + cmsSigOutputResponseTag = 0x72657370, // 'resp' -- Possible patent on this + */ + + cmsCloseProfile(DbgThread(), h); + remove("alltags.icc"); + return 1; + +Error: + cmsCloseProfile(h); + remove("alltags.icc"); + return 0; +} + + +// Thanks to Christopher James Halse Rogers for the bugfixing and providing this test +static +cmsInt32Number CheckVersionHeaderWriting(void) +{ + cmsHPROFILE h; + int index; + float test_versions[] = { + 2.3f, + 4.08f, + 4.09f, + 4.3f + }; + + for (index = 0; index < sizeof(test_versions)/sizeof(test_versions[0]); index++) { + + h = cmsCreateProfilePlaceholder(DbgThread()); + if (h == NULL) return 0; + + cmsSetProfileVersion(DbgThread(), h, test_versions[index]); + + cmsSaveProfileToFile(DbgThread(), h, "versions.icc"); + cmsCloseProfile(DbgThread(), h); + + h = cmsOpenProfileFromFileTHR(DbgThread(), "versions.icc", "r"); + + // Only the first 3 digits are significant + if (fabs(cmsGetProfileVersion(DbgThread(), h) - test_versions[index]) > 0.005) { + Fail("Version failed to round-trip: wrote %.2f, read %.2f", + test_versions[index], cmsGetProfileVersion(DbgThread(), h)); + return 0; + } + + cmsCloseProfile(DbgThread(), h); + remove("versions.icc"); + } + return 1; +} + + +// Test on Richard Hughes "crayons.icc" +static +cmsInt32Number CheckMultilocalizedProfile(void) +{ + cmsHPROFILE hProfile; + cmsMLU *Pt; + char Buffer[256]; + + hProfile = cmsOpenProfileFromFile( "crayons.icc", "r"); + + Pt = cmsReadTag(DbgThread(), hProfile, cmsSigProfileDescriptionTag); + cmsMLUgetASCII(DbgThread(), Pt, "en", "GB", Buffer, 256); + if (strcmp(Buffer, "Crayon Colours") != 0) return FALSE; + cmsMLUgetASCII(DbgThread(), Pt, "en", "US", Buffer, 256); + if (strcmp(Buffer, "Crayon Colors") != 0) return FALSE; + + cmsCloseProfile(DbgThread(), hProfile); + + return TRUE; +} + + +// Error reporting ------------------------------------------------------------------------------------------------------- + + +static +void ErrorReportingFunction(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text) +{ + TrappedError = TRUE; + SimultaneousErrors++; + strncpy(ReasonToFailBuffer, Text, TEXT_ERROR_BUFFER_SIZE-1); + + cmsUNUSED_PARAMETER(ContextID); + cmsUNUSED_PARAMETER(ErrorCode); +} + + +static +cmsInt32Number CheckBadProfiles(void) +{ + cmsHPROFILE h; + + h = cmsOpenProfileFromFileTHR(DbgThread(), "IDoNotExist.icc", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "IAmIllFormed*.icc", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + // No profile name given + h = cmsOpenProfileFromFileTHR(DbgThread(), "", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "..", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "IHaveBadAccessMode.icc", "@"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "bad.icc", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromFileTHR(DbgThread(), "toosmall.icc", "r"); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromMemTHR(DbgThread(), NULL, 3); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + h = cmsOpenProfileFromMemTHR(DbgThread(), "123", 3); + if (h != NULL) { + cmsCloseProfile(DbgThread(), h); + return 0; + } + + if (SimultaneousErrors != 9) return 0; + + return 1; +} + + +static +cmsInt32Number CheckErrReportingOnBadProfiles(void) +{ + cmsInt32Number rc; + + cmsSetLogErrorHandler(ErrorReportingFunction); + rc = CheckBadProfiles(); + cmsSetLogErrorHandler(FatalErrorQuit); + + // Reset the error state + TrappedError = FALSE; + return rc; +} + + +static +cmsInt32Number CheckBadTransforms(void) +{ + cmsHPROFILE h1 = cmsCreate_sRGBProfile(); + cmsHTRANSFORM x1; + + x1 = cmsCreateTransform(NULL, 0, NULL, 0, 0, 0); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + + + + x1 = cmsCreateTransform(h1, TYPE_RGB_8, h1, TYPE_RGB_8, 12345, 0); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + + x1 = cmsCreateTransform(h1, TYPE_CMYK_8, h1, TYPE_RGB_8, 0, 0); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + + x1 = cmsCreateTransform(h1, TYPE_RGB_8, h1, TYPE_CMYK_8, 1, 0); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + + // sRGB does its output as XYZ! + x1 = cmsCreateTransform(h1, TYPE_RGB_8, NULL, TYPE_Lab_8, 1, 0); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + + cmsCloseProfile(DbgThread(), h1); + + + { + + cmsHPROFILE hp1 = cmsOpenProfileFromFile( "test1.icc", "r"); + cmsHPROFILE hp2 = cmsCreate_sRGBProfile(); + + x1 = cmsCreateTransform(hp1, TYPE_BGR_8, hp2, TYPE_BGR_8, INTENT_PERCEPTUAL, 0); + + cmsCloseProfile(DbgThread(), hp1); cmsCloseProfile(DbgThread(), hp2); + if (x1 != NULL) { + cmsDeleteTransform(DbgThread(), x1); + return 0; + } + } + + return 1; + +} + +static +cmsInt32Number CheckErrReportingOnBadTransforms(void) +{ + cmsInt32Number rc; + + cmsSetLogErrorHandler(ErrorReportingFunction); + rc = CheckBadTransforms(); + cmsSetLogErrorHandler(FatalErrorQuit); + + // Reset the error state + TrappedError = FALSE; + return rc; +} + + + + +// --------------------------------------------------------------------------------------------------------- + +// Check a linear xform +static +cmsInt32Number Check8linearXFORM(cmsHTRANSFORM xform, cmsInt32Number nChan) +{ + cmsInt32Number n2, i, j; + cmsUInt8Number Inw[cmsMAXCHANNELS], Outw[cmsMAXCHANNELS]; + + n2=0; + + for (j=0; j < 0xFF; j++) { + + memset(Inw, j, sizeof(Inw)); + cmsDoTransform(DbgThread(), xform, Inw, Outw, 1); + + for (i=0; i < nChan; i++) { + + cmsInt32Number dif = abs(Outw[i] - j); + if (dif > n2) n2 = dif; + + } + } + + // We allow 2 contone of difference on 8 bits + if (n2 > 2) { + + Fail("Differences too big (%x)", n2); + return 0; + } + + return 1; +} + +static +cmsInt32Number Compare8bitXFORM(cmsHTRANSFORM xform1, cmsHTRANSFORM xform2, cmsInt32Number nChan) +{ + cmsInt32Number n2, i, j; + cmsUInt8Number Inw[cmsMAXCHANNELS], Outw1[cmsMAXCHANNELS], Outw2[cmsMAXCHANNELS];; + + n2=0; + + for (j=0; j < 0xFF; j++) { + + memset(Inw, j, sizeof(Inw)); + cmsDoTransform(DbgThread(), xform1, Inw, Outw1, 1); + cmsDoTransform(DbgThread(), xform2, Inw, Outw2, 1); + + for (i=0; i < nChan; i++) { + + cmsInt32Number dif = abs(Outw2[i] - Outw1[i]); + if (dif > n2) n2 = dif; + + } + } + + // We allow 2 contone of difference on 8 bits + if (n2 > 2) { + + Fail("Differences too big (%x)", n2); + return 0; + } + + + return 1; +} + + +// Check a linear xform +static +cmsInt32Number Check16linearXFORM(cmsHTRANSFORM xform, cmsInt32Number nChan) +{ + cmsInt32Number n2, i, j; + cmsUInt16Number Inw[cmsMAXCHANNELS], Outw[cmsMAXCHANNELS]; + + n2=0; + for (j=0; j < 0xFFFF; j++) { + + for (i=0; i < nChan; i++) Inw[i] = (cmsUInt16Number) j; + + cmsDoTransform(DbgThread(), xform, Inw, Outw, 1); + + for (i=0; i < nChan; i++) { + + cmsInt32Number dif = abs(Outw[i] - j); + if (dif > n2) n2 = dif; + + } + + + // We allow 2 contone of difference on 16 bits + if (n2 > 0x200) { + + Fail("Differences too big (%x)", n2); + return 0; + } + } + + return 1; +} + +static +cmsInt32Number Compare16bitXFORM(cmsHTRANSFORM xform1, cmsHTRANSFORM xform2, cmsInt32Number nChan) +{ + cmsInt32Number n2, i, j; + cmsUInt16Number Inw[cmsMAXCHANNELS], Outw1[cmsMAXCHANNELS], Outw2[cmsMAXCHANNELS];; + + n2=0; + + for (j=0; j < 0xFFFF; j++) { + + for (i=0; i < nChan; i++) Inw[i] = (cmsUInt16Number) j; + + cmsDoTransform(DbgThread(), xform1, Inw, Outw1, 1); + cmsDoTransform(DbgThread(), xform2, Inw, Outw2, 1); + + for (i=0; i < nChan; i++) { + + cmsInt32Number dif = abs(Outw2[i] - Outw1[i]); + if (dif > n2) n2 = dif; + + } + } + + // We allow 2 contone of difference on 16 bits + if (n2 > 0x200) { + + Fail("Differences too big (%x)", n2); + return 0; + } + + + return 1; +} + + +// Check a linear xform +static +cmsInt32Number CheckFloatlinearXFORM(cmsHTRANSFORM xform, cmsInt32Number nChan) +{ + cmsInt32Number i, j; + cmsFloat32Number In[cmsMAXCHANNELS], Out[cmsMAXCHANNELS]; + + for (j=0; j < 0xFFFF; j++) { + + for (i=0; i < nChan; i++) In[i] = (cmsFloat32Number) (j / 65535.0);; + + cmsDoTransform(DbgThread(), xform, In, Out, 1); + + for (i=0; i < nChan; i++) { + + // We allow no difference in floating point + if (!IsGoodFixed15_16("linear xform cmsFloat32Number", Out[i], (cmsFloat32Number) (j / 65535.0))) + return 0; + } + } + + return 1; +} + + +// Check a linear xform +static +cmsInt32Number CompareFloatXFORM(cmsHTRANSFORM xform1, cmsHTRANSFORM xform2, cmsInt32Number nChan) +{ + cmsInt32Number i, j; + cmsFloat32Number In[cmsMAXCHANNELS], Out1[cmsMAXCHANNELS], Out2[cmsMAXCHANNELS]; + + for (j=0; j < 0xFFFF; j++) { + + for (i=0; i < nChan; i++) In[i] = (cmsFloat32Number) (j / 65535.0);; + + cmsDoTransform(DbgThread(), xform1, In, Out1, 1); + cmsDoTransform(DbgThread(), xform2, In, Out2, 1); + + for (i=0; i < nChan; i++) { + + // We allow no difference in floating point + if (!IsGoodFixed15_16("linear xform cmsFloat32Number", Out1[i], Out2[i])) + return 0; + } + + } + + return 1; +} + + +// Curves only transforms ---------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckCurvesOnlyTransforms(void) +{ + + cmsHTRANSFORM xform1, xform2; + cmsHPROFILE h1, h2, h3; + cmsToneCurve* c1, *c2, *c3; + cmsInt32Number rc = 1; + + + c1 = cmsBuildGamma(DbgThread(), 2.2); + c2 = cmsBuildGamma(DbgThread(), 1/2.2); + c3 = cmsBuildGamma(DbgThread(), 4.84); + + h1 = cmsCreateLinearizationDeviceLinkTHR(DbgThread(), cmsSigGrayData, &c1); + h2 = cmsCreateLinearizationDeviceLinkTHR(DbgThread(), cmsSigGrayData, &c2); + h3 = cmsCreateLinearizationDeviceLinkTHR(DbgThread(), cmsSigGrayData, &c3); + + SubTest("Gray float optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_FLT, h2, TYPE_GRAY_FLT, INTENT_PERCEPTUAL, 0); + rc &= CheckFloatlinearXFORM(xform1, 1); + cmsDeleteTransform(DbgThread(), xform1); + if (rc == 0) goto Error; + + SubTest("Gray 8 optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_8, h2, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + rc &= Check8linearXFORM(xform1, 1); + cmsDeleteTransform(DbgThread(), xform1); + if (rc == 0) goto Error; + + SubTest("Gray 16 optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_16, h2, TYPE_GRAY_16, INTENT_PERCEPTUAL, 0); + rc &= Check16linearXFORM(xform1, 1); + cmsDeleteTransform(DbgThread(), xform1); + if (rc == 0) goto Error; + + SubTest("Gray float non-optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_FLT, h1, TYPE_GRAY_FLT, INTENT_PERCEPTUAL, 0); + xform2 = cmsCreateTransform(h3, TYPE_GRAY_FLT, NULL, TYPE_GRAY_FLT, INTENT_PERCEPTUAL, 0); + + rc &= CompareFloatXFORM(xform1, xform2, 1); + cmsDeleteTransform(DbgThread(), xform1); + cmsDeleteTransform(DbgThread(), xform2); + if (rc == 0) goto Error; + + SubTest("Gray 8 non-optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_8, h1, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + xform2 = cmsCreateTransform(h3, TYPE_GRAY_8, NULL, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + + rc &= Compare8bitXFORM(xform1, xform2, 1); + cmsDeleteTransform(DbgThread(), xform1); + cmsDeleteTransform(DbgThread(), xform2); + if (rc == 0) goto Error; + + + SubTest("Gray 16 non-optimizeable transform"); + xform1 = cmsCreateTransform(h1, TYPE_GRAY_16, h1, TYPE_GRAY_16, INTENT_PERCEPTUAL, 0); + xform2 = cmsCreateTransform(h3, TYPE_GRAY_16, NULL, TYPE_GRAY_16, INTENT_PERCEPTUAL, 0); + + rc &= Compare16bitXFORM(xform1, xform2, 1); + cmsDeleteTransform(DbgThread(), xform1); + cmsDeleteTransform(DbgThread(), xform2); + if (rc == 0) goto Error; + +Error: + + cmsCloseProfile(DbgThread(), h1); cmsCloseProfile(DbgThread(), h2); cmsCloseProfile(DbgThread(), h3); + cmsFreeToneCurve(DbgThread(), c1); cmsFreeToneCurve(DbgThread(), c2); cmsFreeToneCurve(DbgThread(), c3); + + return rc; +} + + + +// Lab to Lab trivial transforms ---------------------------------------------------------------------------------------- + +static cmsFloat64Number MaxDE; + +static +cmsInt32Number CheckOneLab(cmsHTRANSFORM xform, cmsFloat64Number L, cmsFloat64Number a, cmsFloat64Number b) +{ + cmsCIELab In, Out; + cmsFloat64Number dE; + + In.L = L; In.a = a; In.b = b; + cmsDoTransform(DbgThread(), xform, &In, &Out, 1); + + dE = cmsDeltaE(DbgThread(), &In, &Out); + + if (dE > MaxDE) MaxDE = dE; + + if (MaxDE > 0.003) { + Fail("dE=%f Lab1=(%f, %f, %f)\n\tLab2=(%f %f %f)", MaxDE, In.L, In.a, In.b, Out.L, Out.a, Out.b); + cmsDoTransform(DbgThread(), xform, &In, &Out, 1); + return 0; + } + + return 1; +} + +// Check several Lab, slicing at non-exact values. Precision should be 16 bits. 50x50x50 checks aprox. +static +cmsInt32Number CheckSeveralLab(cmsHTRANSFORM xform) +{ + cmsInt32Number L, a, b; + + MaxDE = 0; + for (L=0; L < 65536; L += 1311) { + + for (a = 0; a < 65536; a += 1232) { + + for (b = 0; b < 65536; b += 1111) { + + if (!CheckOneLab(xform, (L * 100.0) / 65535.0, + (a / 257.0) - 128, (b / 257.0) - 128)) + return 0; + } + + } + + } + return 1; +} + + +static +cmsInt32Number OneTrivialLab(cmsHPROFILE hLab1, cmsHPROFILE hLab2, const char* txt) +{ + cmsHTRANSFORM xform; + cmsInt32Number rc; + + SubTest(txt); + xform = cmsCreateTransformTHR(DbgThread(), hLab1, TYPE_Lab_DBL, hLab2, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hLab1); cmsCloseProfile(DbgThread(), hLab2); + + rc = CheckSeveralLab(xform); + cmsDeleteTransform(DbgThread(), xform); + return rc; +} + + +static +cmsInt32Number CheckFloatLabTransforms(void) +{ + return OneTrivialLab(cmsCreateLab4ProfileTHR(DbgThread(), NULL), cmsCreateLab4ProfileTHR(DbgThread(), NULL), "Lab4/Lab4") && + OneTrivialLab(cmsCreateLab2ProfileTHR(DbgThread(), NULL), cmsCreateLab2ProfileTHR(DbgThread(), NULL), "Lab2/Lab2") && + OneTrivialLab(cmsCreateLab4ProfileTHR(DbgThread(), NULL), cmsCreateLab2ProfileTHR(DbgThread(), NULL), "Lab4/Lab2") && + OneTrivialLab(cmsCreateLab2ProfileTHR(DbgThread(), NULL), cmsCreateLab4ProfileTHR(DbgThread(), NULL), "Lab2/Lab4"); +} + + +static +cmsInt32Number CheckEncodedLabTransforms(void) +{ + cmsHTRANSFORM xform; + cmsUInt16Number In[3]; + cmsCIELab Lab; + cmsCIELab White = { 100, 0, 0 }; + cmsHPROFILE hLab1 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + cmsHPROFILE hLab2 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + + xform = cmsCreateTransformTHR(DbgThread(), hLab1, TYPE_Lab_16, hLab2, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hLab1); cmsCloseProfile(DbgThread(), hLab2); + + In[0] = 0xFFFF; + In[1] = 0x8080; + In[2] = 0x8080; + + cmsDoTransform(DbgThread(), xform, In, &Lab, 1); + + if (cmsDeltaE(DbgThread(), &Lab, &White) > 0.0001) return 0; + cmsDeleteTransform(DbgThread(), xform); + + hLab1 = cmsCreateLab2ProfileTHR(DbgThread(), NULL); + hLab2 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hLab1, TYPE_LabV2_16, hLab2, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hLab1); cmsCloseProfile(DbgThread(), hLab2); + + + In[0] = 0xFF00; + In[1] = 0x8000; + In[2] = 0x8000; + + cmsDoTransform(DbgThread(), xform, In, &Lab, 1); + + if (cmsDeltaE(DbgThread(), &Lab, &White) > 0.0001) return 0; + + cmsDeleteTransform(DbgThread(), xform); + + hLab2 = cmsCreateLab2ProfileTHR(DbgThread(), NULL); + hLab1 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hLab1, TYPE_Lab_DBL, hLab2, TYPE_LabV2_16, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hLab1); cmsCloseProfile(DbgThread(), hLab2); + + Lab.L = 100; + Lab.a = 0; + Lab.b = 0; + + cmsDoTransform(DbgThread(), xform, &Lab, In, 1); + if (In[0] != 0xFF00 || + In[1] != 0x8000 || + In[2] != 0x8000) return 0; + + cmsDeleteTransform(DbgThread(), xform); + + hLab1 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + hLab2 = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hLab1, TYPE_Lab_DBL, hLab2, TYPE_Lab_16, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hLab1); cmsCloseProfile(DbgThread(), hLab2); + + Lab.L = 100; + Lab.a = 0; + Lab.b = 0; + + cmsDoTransform(DbgThread(), xform, &Lab, In, 1); + + if (In[0] != 0xFFFF || + In[1] != 0x8080 || + In[2] != 0x8080) return 0; + + cmsDeleteTransform(DbgThread(), xform); + + return 1; +} + +static +cmsInt32Number CheckStoredIdentities(void) +{ + cmsHPROFILE hLab, hLink, h4, h2; + cmsHTRANSFORM xform; + cmsInt32Number rc = 1; + + hLab = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + xform = cmsCreateTransformTHR(DbgThread(), hLab, TYPE_Lab_8, hLab, TYPE_Lab_8, 0, 0); + + hLink = cmsTransform2DeviceLink(NULL, xform, 3.4, 0); + cmsSaveProfileToFile(DbgThread(), hLink, "abstractv2.icc"); + cmsCloseProfile(DbgThread(), hLink); + + hLink = cmsTransform2DeviceLink(NULL, xform, 4.3, 0); + cmsSaveProfileToFile(DbgThread(), hLink, "abstractv4.icc"); + cmsCloseProfile(DbgThread(), hLink); + + cmsDeleteTransform(DbgThread(), xform); + cmsCloseProfile(DbgThread(), hLab); + + h4 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv4.icc", "r"); + + xform = cmsCreateTransformTHR(DbgThread(), h4, TYPE_Lab_DBL, h4, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + + SubTest("V4"); + rc &= CheckSeveralLab(xform); + + cmsDeleteTransform(DbgThread(), xform); + cmsCloseProfile(DbgThread(), h4); + if (!rc) goto Error; + + + SubTest("V2"); + h2 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv2.icc", "r"); + + xform = cmsCreateTransformTHR(DbgThread(), h2, TYPE_Lab_DBL, h2, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + rc &= CheckSeveralLab(xform); + cmsDeleteTransform(DbgThread(), xform); + cmsCloseProfile(DbgThread(), h2); + if (!rc) goto Error; + + + SubTest("V2 -> V4"); + h2 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv2.icc", "r"); + h4 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv4.icc", "r"); + + xform = cmsCreateTransformTHR(DbgThread(), h4, TYPE_Lab_DBL, h2, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + rc &= CheckSeveralLab(xform); + cmsDeleteTransform(DbgThread(), xform); + cmsCloseProfile(DbgThread(), h2); + cmsCloseProfile(DbgThread(), h4); + + SubTest("V4 -> V2"); + h2 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv2.icc", "r"); + h4 = cmsOpenProfileFromFileTHR(DbgThread(), "abstractv4.icc", "r"); + + xform = cmsCreateTransformTHR(DbgThread(), h2, TYPE_Lab_DBL, h4, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + rc &= CheckSeveralLab(xform); + cmsDeleteTransform(DbgThread(), xform); + cmsCloseProfile(DbgThread(), h2); + cmsCloseProfile(DbgThread(), h4); + +Error: + remove("abstractv2.icc"); + remove("abstractv4.icc"); + return rc; + +} + + + +// Check a simple xform from a matrix profile to itself. Test floating point accuracy. +static +cmsInt32Number CheckMatrixShaperXFORMFloat(void) +{ + cmsHPROFILE hAbove, hSRGB; + cmsHTRANSFORM xform; + cmsInt32Number rc1, rc2; + + hAbove = Create_AboveRGB(); + xform = cmsCreateTransformTHR(DbgThread(), hAbove, TYPE_RGB_FLT, hAbove, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hAbove); + rc1 = CheckFloatlinearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + hSRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + xform = cmsCreateTransformTHR(DbgThread(), hSRGB, TYPE_RGB_FLT, hSRGB, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hSRGB); + rc2 = CheckFloatlinearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + + return rc1 && rc2; +} + +// Check a simple xform from a matrix profile to itself. Test 16 bits accuracy. +static +cmsInt32Number CheckMatrixShaperXFORM16(void) +{ + cmsHPROFILE hAbove, hSRGB; + cmsHTRANSFORM xform; + cmsInt32Number rc1, rc2; + + hAbove = Create_AboveRGB(); + xform = cmsCreateTransformTHR(DbgThread(), hAbove, TYPE_RGB_16, hAbove, TYPE_RGB_16, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hAbove); + + rc1 = Check16linearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + hSRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + xform = cmsCreateTransformTHR(DbgThread(), hSRGB, TYPE_RGB_16, hSRGB, TYPE_RGB_16, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hSRGB); + rc2 = Check16linearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + return rc1 && rc2; + +} + + +// Check a simple xform from a matrix profile to itself. Test 8 bits accuracy. +static +cmsInt32Number CheckMatrixShaperXFORM8(void) +{ + cmsHPROFILE hAbove, hSRGB; + cmsHTRANSFORM xform; + cmsInt32Number rc1, rc2; + + hAbove = Create_AboveRGB(); + xform = cmsCreateTransformTHR(DbgThread(), hAbove, TYPE_RGB_8, hAbove, TYPE_RGB_8, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hAbove); + rc1 = Check8linearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + hSRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + xform = cmsCreateTransformTHR(DbgThread(), hSRGB, TYPE_RGB_8, hSRGB, TYPE_RGB_8, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hSRGB); + rc2 = Check8linearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + + + return rc1 && rc2; +} + + +// TODO: Check LUT based to LUT based transforms for CMYK + + + + + + +// ----------------------------------------------------------------------------------------------------------------- + + +// Check known values going from sRGB to XYZ +static +cmsInt32Number CheckOneRGB_f(cmsHTRANSFORM xform, cmsInt32Number R, cmsInt32Number G, cmsInt32Number B, cmsFloat64Number X, cmsFloat64Number Y, cmsFloat64Number Z, cmsFloat64Number err) +{ + cmsFloat32Number RGB[3]; + cmsFloat64Number Out[3]; + + RGB[0] = (cmsFloat32Number) (R / 255.0); + RGB[1] = (cmsFloat32Number) (G / 255.0); + RGB[2] = (cmsFloat32Number) (B / 255.0); + + cmsDoTransform(DbgThread(), xform, RGB, Out, 1); + + return IsGoodVal("X", X , Out[0], err) && + IsGoodVal("Y", Y , Out[1], err) && + IsGoodVal("Z", Z , Out[2], err); +} + +static +cmsInt32Number Chack_sRGB_Float(void) +{ + cmsHPROFILE hsRGB, hXYZ, hLab; + cmsHTRANSFORM xform1, xform2; + cmsInt32Number rc; + + + hsRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + hXYZ = cmsCreateXYZProfileTHR(DbgThread()); + hLab = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform1 = cmsCreateTransformTHR(DbgThread(), hsRGB, TYPE_RGB_FLT, hXYZ, TYPE_XYZ_DBL, + INTENT_RELATIVE_COLORIMETRIC, 0); + + xform2 = cmsCreateTransformTHR(DbgThread(), hsRGB, TYPE_RGB_FLT, hLab, TYPE_Lab_DBL, + INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hsRGB); + cmsCloseProfile(DbgThread(), hXYZ); + cmsCloseProfile(DbgThread(), hLab); + + MaxErr = 0; + + // Xform 1 goes from 8 bits to XYZ, + rc = CheckOneRGB_f(xform1, 1, 1, 1, 0.0002927, 0.0003035, 0.000250, 0.0001); + rc &= CheckOneRGB_f(xform1, 127, 127, 127, 0.2046329, 0.212230, 0.175069, 0.0001); + rc &= CheckOneRGB_f(xform1, 12, 13, 15, 0.0038364, 0.0039928, 0.003853, 0.0001); + rc &= CheckOneRGB_f(xform1, 128, 0, 0, 0.0941240, 0.0480256, 0.003005, 0.0001); + rc &= CheckOneRGB_f(xform1, 190, 25, 210, 0.3204592, 0.1605926, 0.468213, 0.0001); + + // Xform 2 goes from 8 bits to Lab, we allow 0.01 error max + rc &= CheckOneRGB_f(xform2, 1, 1, 1, 0.2741748, 0, 0, 0.01); + rc &= CheckOneRGB_f(xform2, 127, 127, 127, 53.192776, 0, 0, 0.01); + rc &= CheckOneRGB_f(xform2, 190, 25, 210, 47.052136, 74.565610, -56.883274, 0.01); + rc &= CheckOneRGB_f(xform2, 128, 0, 0, 26.164701, 48.478171, 39.4384713, 0.01); + + cmsDeleteTransform(DbgThread(), xform1); + cmsDeleteTransform(DbgThread(), xform2); + return rc; +} + + +// --------------------------------------------------- + +static +cmsBool GetProfileRGBPrimaries(cmsHPROFILE hProfile, + cmsCIEXYZTRIPLE *result, + cmsUInt32Number intent) +{ + cmsHPROFILE hXYZ; + cmsHTRANSFORM hTransform; + cmsFloat64Number rgb[3][3] = {{1., 0., 0.}, + {0., 1., 0.}, + {0., 0., 1.}}; + + hXYZ = cmsCreateXYZProfile(); + if (hXYZ == NULL) return FALSE; + + hTransform = cmsCreateTransform(hProfile, TYPE_RGB_DBL, hXYZ, TYPE_XYZ_DBL, + intent, cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); + cmsCloseProfile(DbgThread(), hXYZ); + if (hTransform == NULL) return FALSE; + + cmsDoTransform(DbgThread(), hTransform, rgb, result, 3); + cmsDeleteTransform(DbgThread(), hTransform); + return TRUE; +} + + +static +int CheckRGBPrimaries(void) +{ + cmsHPROFILE hsRGB; + cmsCIEXYZTRIPLE tripXYZ; + cmsCIExyYTRIPLE tripxyY; + cmsBool result; + + cmsSetAdaptationState(0); + hsRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + if (!hsRGB) return 0; + + result = GetProfileRGBPrimaries(hsRGB, &tripXYZ, + INTENT_ABSOLUTE_COLORIMETRIC); + + cmsCloseProfile(DbgThread(), hsRGB); + if (!result) return 0; + + cmsXYZ2xyY(DbgThread(), &tripxyY.Red, &tripXYZ.Red); + cmsXYZ2xyY(DbgThread(), &tripxyY.Green, &tripXYZ.Green); + cmsXYZ2xyY(DbgThread(), &tripxyY.Blue, &tripXYZ.Blue); + + /* valus were taken from + http://en.wikipedia.org/wiki/RGB_color_spaces#Specifications */ + + if (!IsGoodFixed15_16("xRed", tripxyY.Red.x, 0.64) || + !IsGoodFixed15_16("yRed", tripxyY.Red.y, 0.33) || + !IsGoodFixed15_16("xGreen", tripxyY.Green.x, 0.30) || + !IsGoodFixed15_16("yGreen", tripxyY.Green.y, 0.60) || + !IsGoodFixed15_16("xBlue", tripxyY.Blue.x, 0.15) || + !IsGoodFixed15_16("yBlue", tripxyY.Blue.y, 0.06)) { + Fail("One or more primaries are wrong."); + return FALSE; + } + + return TRUE; +} + + +// ----------------------------------------------------------------------------------------------------------------- + +// This function will check CMYK -> CMYK transforms. It uses FOGRA29 and SWOP ICC profiles + +static +cmsInt32Number CheckCMYK(cmsInt32Number Intent, const char *Profile1, const char* Profile2) +{ + cmsHPROFILE hSWOP = cmsOpenProfileFromFileTHR(DbgThread(), Profile1, "r"); + cmsHPROFILE hFOGRA = cmsOpenProfileFromFileTHR(DbgThread(), Profile2, "r"); + cmsHTRANSFORM xform, swop_lab, fogra_lab; + cmsFloat32Number CMYK1[4], CMYK2[4]; + cmsCIELab Lab1, Lab2; + cmsHPROFILE hLab; + cmsFloat64Number DeltaL, Max; + cmsInt32Number i; + + hLab = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hFOGRA, TYPE_CMYK_FLT, Intent, 0); + + swop_lab = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, Intent, 0); + fogra_lab = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, Intent, 0); + + Max = 0; + for (i=0; i <= 100; i++) { + + CMYK1[0] = 10; + CMYK1[1] = 20; + CMYK1[2] = 30; + CMYK1[3] = (cmsFloat32Number) i; + + cmsDoTransform(DbgThread(), swop_lab, CMYK1, &Lab1, 1); + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + cmsDoTransform(DbgThread(), fogra_lab, CMYK2, &Lab2, 1); + + DeltaL = fabs(Lab1.L - Lab2.L); + + if (DeltaL > Max) Max = DeltaL; + } + + + cmsDeleteTransform(DbgThread(), xform); + + if (Max > 3.0) return 0; + + xform = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hSWOP, TYPE_CMYK_FLT, Intent, 0); + + Max = 0; + + for (i=0; i <= 100; i++) { + CMYK1[0] = 10; + CMYK1[1] = 20; + CMYK1[2] = 30; + CMYK1[3] = (cmsFloat32Number) i; + + cmsDoTransform(DbgThread(), fogra_lab, CMYK1, &Lab1, 1); + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + cmsDoTransform(DbgThread(), swop_lab, CMYK2, &Lab2, 1); + + DeltaL = fabs(Lab1.L - Lab2.L); + + if (DeltaL > Max) Max = DeltaL; + } + + + cmsCloseProfile(DbgThread(), hSWOP); + cmsCloseProfile(DbgThread(), hFOGRA); + cmsCloseProfile(DbgThread(), hLab); + + cmsDeleteTransform(DbgThread(), xform); + cmsDeleteTransform(DbgThread(), swop_lab); + cmsDeleteTransform(DbgThread(), fogra_lab); + + return Max < 3.0; +} + +static +cmsInt32Number CheckCMYKRoundtrip(void) +{ + return CheckCMYK(INTENT_RELATIVE_COLORIMETRIC, "test1.icc", "test1.icc"); +} + + +static +cmsInt32Number CheckCMYKPerceptual(void) +{ + return CheckCMYK(INTENT_PERCEPTUAL, "test1.icc", "test2.icc"); +} + + + +static +cmsInt32Number CheckCMYKRelCol(void) +{ + return CheckCMYK(INTENT_RELATIVE_COLORIMETRIC, "test1.icc", "test2.icc"); +} + + + +static +cmsInt32Number CheckKOnlyBlackPreserving(void) +{ + cmsHPROFILE hSWOP = cmsOpenProfileFromFileTHR(DbgThread(), "test1.icc", "r"); + cmsHPROFILE hFOGRA = cmsOpenProfileFromFileTHR(DbgThread(), "test2.icc", "r"); + cmsHTRANSFORM xform, swop_lab, fogra_lab; + cmsFloat32Number CMYK1[4], CMYK2[4]; + cmsCIELab Lab1, Lab2; + cmsHPROFILE hLab; + cmsFloat64Number DeltaL, Max; + cmsInt32Number i; + + hLab = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hFOGRA, TYPE_CMYK_FLT, INTENT_PRESERVE_K_ONLY_PERCEPTUAL, 0); + + swop_lab = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, INTENT_PERCEPTUAL, 0); + fogra_lab = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, INTENT_PERCEPTUAL, 0); + + Max = 0; + + for (i=0; i <= 100; i++) { + CMYK1[0] = 0; + CMYK1[1] = 0; + CMYK1[2] = 0; + CMYK1[3] = (cmsFloat32Number) i; + + // SWOP CMYK to Lab1 + cmsDoTransform(DbgThread(), swop_lab, CMYK1, &Lab1, 1); + + // SWOP To FOGRA using black preservation + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + + // Obtained FOGRA CMYK to Lab2 + cmsDoTransform(DbgThread(), fogra_lab, CMYK2, &Lab2, 1); + + // We care only on L* + DeltaL = fabs(Lab1.L - Lab2.L); + + if (DeltaL > Max) Max = DeltaL; + } + + + cmsDeleteTransform(DbgThread(), xform); + + // dL should be below 3.0 + if (Max > 3.0) return 0; + + + // Same, but FOGRA to SWOP + xform = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hSWOP, TYPE_CMYK_FLT, INTENT_PRESERVE_K_ONLY_PERCEPTUAL, 0); + + Max = 0; + + for (i=0; i <= 100; i++) { + CMYK1[0] = 0; + CMYK1[1] = 0; + CMYK1[2] = 0; + CMYK1[3] = (cmsFloat32Number) i; + + cmsDoTransform(DbgThread(), fogra_lab, CMYK1, &Lab1, 1); + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + cmsDoTransform(DbgThread(), swop_lab, CMYK2, &Lab2, 1); + + DeltaL = fabs(Lab1.L - Lab2.L); + + if (DeltaL > Max) Max = DeltaL; + } + + + cmsCloseProfile(DbgThread(), hSWOP); + cmsCloseProfile(DbgThread(), hFOGRA); + cmsCloseProfile(DbgThread(), hLab); + + cmsDeleteTransform(DbgThread(), xform); + cmsDeleteTransform(DbgThread(), swop_lab); + cmsDeleteTransform(DbgThread(), fogra_lab); + + return Max < 3.0; +} + +static +cmsInt32Number CheckKPlaneBlackPreserving(void) +{ + cmsHPROFILE hSWOP = cmsOpenProfileFromFileTHR(DbgThread(), "test1.icc", "r"); + cmsHPROFILE hFOGRA = cmsOpenProfileFromFileTHR(DbgThread(), "test2.icc", "r"); + cmsHTRANSFORM xform, swop_lab, fogra_lab; + cmsFloat32Number CMYK1[4], CMYK2[4]; + cmsCIELab Lab1, Lab2; + cmsHPROFILE hLab; + cmsFloat64Number DeltaE, Max; + cmsInt32Number i; + + hLab = cmsCreateLab4ProfileTHR(DbgThread(), NULL); + + xform = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hFOGRA, TYPE_CMYK_FLT, INTENT_PERCEPTUAL, 0); + + swop_lab = cmsCreateTransformTHR(DbgThread(), hSWOP, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, INTENT_PERCEPTUAL, 0); + fogra_lab = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hLab, TYPE_Lab_DBL, INTENT_PERCEPTUAL, 0); + + Max = 0; + + for (i=0; i <= 100; i++) { + CMYK1[0] = 0; + CMYK1[1] = 0; + CMYK1[2] = 0; + CMYK1[3] = (cmsFloat32Number) i; + + cmsDoTransform(DbgThread(), swop_lab, CMYK1, &Lab1, 1); + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + cmsDoTransform(DbgThread(), fogra_lab, CMYK2, &Lab2, 1); + + DeltaE = cmsDeltaE(DbgThread(), &Lab1, &Lab2); + + if (DeltaE > Max) Max = DeltaE; + } + + + cmsDeleteTransform(DbgThread(), xform); + + xform = cmsCreateTransformTHR(DbgThread(), hFOGRA, TYPE_CMYK_FLT, hSWOP, TYPE_CMYK_FLT, INTENT_PRESERVE_K_PLANE_PERCEPTUAL, 0); + + for (i=0; i <= 100; i++) { + CMYK1[0] = 30; + CMYK1[1] = 20; + CMYK1[2] = 10; + CMYK1[3] = (cmsFloat32Number) i; + + cmsDoTransform(DbgThread(), fogra_lab, CMYK1, &Lab1, 1); + cmsDoTransform(DbgThread(), xform, CMYK1, CMYK2, 1); + cmsDoTransform(DbgThread(), swop_lab, CMYK2, &Lab2, 1); + + DeltaE = cmsDeltaE(DbgThread(), &Lab1, &Lab2); + + if (DeltaE > Max) Max = DeltaE; + } + + cmsDeleteTransform(DbgThread(), xform); + + + + cmsCloseProfile(DbgThread(), hSWOP); + cmsCloseProfile(DbgThread(), hFOGRA); + cmsCloseProfile(DbgThread(), hLab); + + + cmsDeleteTransform(DbgThread(), swop_lab); + cmsDeleteTransform(DbgThread(), fogra_lab); + + return Max < 30.0; +} + + +// ------------------------------------------------------------------------------------------------------ + + +static +cmsInt32Number CheckProofingXFORMFloat(void) +{ + cmsHPROFILE hAbove; + cmsHTRANSFORM xform; + cmsInt32Number rc; + + hAbove = Create_AboveRGB(); + xform = cmsCreateProofingTransformTHR(DbgThread(), hAbove, TYPE_RGB_FLT, hAbove, TYPE_RGB_FLT, hAbove, + INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_SOFTPROOFING); + cmsCloseProfile(DbgThread(), hAbove); + rc = CheckFloatlinearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + return rc; +} + +static +cmsInt32Number CheckProofingXFORM16(void) +{ + cmsHPROFILE hAbove; + cmsHTRANSFORM xform; + cmsInt32Number rc; + + hAbove = Create_AboveRGB(); + xform = cmsCreateProofingTransformTHR(DbgThread(), hAbove, TYPE_RGB_16, hAbove, TYPE_RGB_16, hAbove, + INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_SOFTPROOFING|cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hAbove); + rc = Check16linearXFORM(xform, 3); + cmsDeleteTransform(DbgThread(), xform); + return rc; +} + + +static +cmsInt32Number CheckGamutCheck(void) +{ + cmsHPROFILE hSRGB, hAbove; + cmsHTRANSFORM xform; + cmsInt32Number rc; + cmsUInt16Number Alarm[16] = { 0xDEAD, 0xBABE, 0xFACE }; + + // Set alarm codes to fancy values so we could check the out of gamut condition + cmsSetAlarmCodes(Alarm); + + // Create the profiles + hSRGB = cmsCreate_sRGBProfileTHR(DbgThread()); + hAbove = Create_AboveRGB(); + + if (hSRGB == NULL || hAbove == NULL) return 0; // Failed + + SubTest("Gamut check on floating point"); + + // Create a gamut checker in the same space. No value should be out of gamut + xform = cmsCreateProofingTransformTHR(DbgThread(), hAbove, TYPE_RGB_FLT, hAbove, TYPE_RGB_FLT, hAbove, + INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_GAMUTCHECK); + + + if (!CheckFloatlinearXFORM(xform, 3)) { + cmsCloseProfile(DbgThread(), hSRGB); + cmsCloseProfile(DbgThread(), hAbove); + cmsDeleteTransform(DbgThread(), xform); + Fail("Gamut check on same profile failed"); + return 0; + } + + cmsDeleteTransform(DbgThread(), xform); + + SubTest("Gamut check on 16 bits"); + + xform = cmsCreateProofingTransformTHR(DbgThread(), hAbove, TYPE_RGB_16, hAbove, TYPE_RGB_16, hSRGB, + INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_GAMUTCHECK); + + cmsCloseProfile(DbgThread(), hSRGB); + cmsCloseProfile(DbgThread(), hAbove); + + rc = Check16linearXFORM(xform, 3); + + cmsDeleteTransform(DbgThread(), xform); + + return rc; +} + + + +// ------------------------------------------------------------------------------------------------------------------- + +static +cmsInt32Number CheckBlackPoint(void) +{ + cmsHPROFILE hProfile; + cmsCIEXYZ Black; + cmsCIELab Lab; + + hProfile = cmsOpenProfileFromFileTHR(DbgThread(), "test5.icc", "r"); + cmsDetectDestinationBlackPoint(DbgThread(), &Black, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hProfile); + + + hProfile = cmsOpenProfileFromFileTHR(DbgThread(), "test1.icc", "r"); + cmsDetectDestinationBlackPoint(DbgThread(), &Black, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsXYZ2Lab(DbgThread(), NULL, &Lab, &Black); + cmsCloseProfile(DbgThread(), hProfile); + + hProfile = cmsOpenProfileFromFileTHR(DbgThread(), "lcms2cmyk.icc", "r"); + cmsDetectDestinationBlackPoint(DbgThread(), &Black, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsXYZ2Lab(DbgThread(), NULL, &Lab, &Black); + cmsCloseProfile(DbgThread(), hProfile); + + hProfile = cmsOpenProfileFromFileTHR(DbgThread(), "test2.icc", "r"); + cmsDetectDestinationBlackPoint(DbgThread(), &Black, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsXYZ2Lab(DbgThread(), NULL, &Lab, &Black); + cmsCloseProfile(DbgThread(), hProfile); + + hProfile = cmsOpenProfileFromFileTHR(DbgThread(), "test1.icc", "r"); + cmsDetectDestinationBlackPoint(DbgThread(), &Black, hProfile, INTENT_PERCEPTUAL, 0); + cmsXYZ2Lab(DbgThread(), NULL, &Lab, &Black); + cmsCloseProfile(DbgThread(), hProfile); + + return 1; +} + + +static +cmsInt32Number CheckOneTAC(cmsFloat64Number InkLimit) +{ + cmsHPROFILE h; + cmsFloat64Number d; + + h =CreateFakeCMYK(InkLimit, TRUE); + cmsSaveProfileToFile(DbgThread(), h, "lcmstac.icc"); + cmsCloseProfile(DbgThread(), h); + + h = cmsOpenProfileFromFile( "lcmstac.icc", "r"); + d = cmsDetectTAC(DbgThread(), h); + cmsCloseProfile(DbgThread(), h); + + remove("lcmstac.icc"); + + if (fabs(d - InkLimit) > 5) return 0; + + return 1; +} + + +static +cmsInt32Number CheckTAC(void) +{ + if (!CheckOneTAC(180)) return 0; + if (!CheckOneTAC(220)) return 0; + if (!CheckOneTAC(286)) return 0; + if (!CheckOneTAC(310)) return 0; + if (!CheckOneTAC(330)) return 0; + + return 1; +} + +// ------------------------------------------------------------------------------------------------------- + + +#define NPOINTS_IT8 10 // (17*17*17*17) + +static +cmsInt32Number CheckCGATS(void) +{ + cmsHANDLE it8; + cmsInt32Number i; + + SubTest("IT8 creation"); + it8 = cmsIT8Alloc(DbgThread()); + if (it8 == NULL) return 0; + + cmsIT8SetSheetType(DbgThread(), it8, "LCMS/TESTING"); + cmsIT8SetPropertyStr(DbgThread(), it8, "ORIGINATOR", "1 2 3 4"); + cmsIT8SetPropertyUncooked(DbgThread(), it8, "DESCRIPTOR", "1234"); + cmsIT8SetPropertyStr(DbgThread(), it8, "MANUFACTURER", "3"); + cmsIT8SetPropertyDbl(DbgThread(), it8, "CREATED", 4); + cmsIT8SetPropertyDbl(DbgThread(), it8, "SERIAL", 5); + cmsIT8SetPropertyHex(DbgThread(), it8, "MATERIAL", 0x123); + + cmsIT8SetPropertyDbl(DbgThread(), it8, "NUMBER_OF_SETS", NPOINTS_IT8); + cmsIT8SetPropertyDbl(DbgThread(), it8, "NUMBER_OF_FIELDS", 4); + + cmsIT8SetDataFormat(DbgThread(), it8, 0, "SAMPLE_ID"); + cmsIT8SetDataFormat(DbgThread(), it8, 1, "RGB_R"); + cmsIT8SetDataFormat(DbgThread(), it8, 2, "RGB_G"); + cmsIT8SetDataFormat(DbgThread(), it8, 3, "RGB_B"); + + SubTest("Table creation"); + for (i=0; i < NPOINTS_IT8; i++) { + + char Patch[20]; + + sprintf(Patch, "P%d", i); + + cmsIT8SetDataRowCol(DbgThread(), it8, i, 0, Patch); + cmsIT8SetDataRowColDbl(DbgThread(), it8, i, 1, i); + cmsIT8SetDataRowColDbl(DbgThread(), it8, i, 2, i); + cmsIT8SetDataRowColDbl(DbgThread(), it8, i, 3, i); + } + + SubTest("Save to file"); + cmsIT8SaveToFile(DbgThread(), it8, "TEST.IT8"); + cmsIT8Free(DbgThread(), it8); + + SubTest("Load from file"); + it8 = cmsIT8LoadFromFile(DbgThread(), "TEST.IT8"); + if (it8 == NULL) return 0; + + SubTest("Save again file"); + cmsIT8SaveToFile(DbgThread(), it8, "TEST.IT8"); + cmsIT8Free(DbgThread(), it8); + + + SubTest("Load from file (II)"); + it8 = cmsIT8LoadFromFile(DbgThread(), "TEST.IT8"); + if (it8 == NULL) return 0; + + + SubTest("Change prop value"); + if (cmsIT8GetPropertyDbl(DbgThread(), it8, "DESCRIPTOR") != 1234) { + + return 0; + } + + + cmsIT8SetPropertyDbl(DbgThread(), it8, "DESCRIPTOR", 5678); + if (cmsIT8GetPropertyDbl(DbgThread(), it8, "DESCRIPTOR") != 5678) { + + return 0; + } + + SubTest("Positive numbers"); + if (cmsIT8GetDataDbl(DbgThread(), it8, "P3", "RGB_G") != 3) { + + return 0; + } + + + SubTest("Positive exponent numbers"); + cmsIT8SetPropertyDbl(DbgThread(), it8, "DBL_PROP", 123E+12); + if ((cmsIT8GetPropertyDbl(DbgThread(), it8, "DBL_PROP") - 123E+12) > 1 ) { + + return 0; + } + + SubTest("Negative exponent numbers"); + cmsIT8SetPropertyDbl(DbgThread(), it8, "DBL_PROP_NEG", 123E-45); + if ((cmsIT8GetPropertyDbl(DbgThread(), it8, "DBL_PROP_NEG") - 123E-45) > 1E-45 ) { + + return 0; + } + + + SubTest("Negative numbers"); + cmsIT8SetPropertyDbl(DbgThread(), it8, "DBL_NEG_VAL", -123); + if ((cmsIT8GetPropertyDbl(DbgThread(), it8, "DBL_NEG_VAL")) != -123 ) { + + return 0; + } + + cmsIT8Free(DbgThread(), it8); + + remove("TEST.IT8"); + return 1; + +} + + +static +cmsInt32Number CheckCGATS2(void) +{ + cmsHANDLE handle; + const cmsUInt8Number junk[] = { 0x0, 0xd, 0xd, 0xa, 0x20, 0xd, 0x20, 0x20, 0x20, 0x3a, 0x31, 0x3d, 0x3d, 0x3d, 0x3d }; + + handle = cmsIT8LoadFromMem(0, (const void*)junk, sizeof(junk)); + if (handle) + cmsIT8Free(DbgThread(), handle); + + return 1; +} + + +static +cmsInt32Number CheckCGATS_Overflow(void) +{ + cmsHANDLE handle; + const cmsUInt8Number junk[] = { "@\nA 1.e2147483648\n" }; + + handle = cmsIT8LoadFromMem(0, (const void*)junk, sizeof(junk)); + if (handle) + cmsIT8Free(DbgThread(), handle); + + return 1; +} + +// Create CSA/CRD + +static +void GenerateCSA(const char* cInProf, const char* FileName) +{ + cmsHPROFILE hProfile; + cmsUInt32Number n; + char* Buffer; + cmsContext BuffThread = DbgThread(); + FILE* o; + + + if (cInProf == NULL) + hProfile = cmsCreateLab4Profile(NULL); + else + hProfile = cmsOpenProfileFromFile( cInProf, "r"); + + n = cmsGetPostScriptCSA(DbgThread(), hProfile, 0, 0, NULL, 0); + if (n == 0) return; + + Buffer = (char*) _cmsMalloc(BuffThread, n + 1); + cmsGetPostScriptCSA(DbgThread(), hProfile, 0, 0, Buffer, n); + Buffer[n] = 0; + + if (FileName != NULL) { + o = fopen(FileName, "wb"); + fwrite(Buffer, n, 1, o); + fclose(o); + } + + _cmsFree(BuffThread, Buffer); + cmsCloseProfile(DbgThread(), hProfile); + if (FileName != NULL) + remove(FileName); +} + + +static +void GenerateCRD(const char* cOutProf, const char* FileName) +{ + cmsHPROFILE hProfile; + cmsUInt32Number n; + char* Buffer; + cmsUInt32Number dwFlags = 0; + cmsContext BuffThread = DbgThread(); + + + if (cOutProf == NULL) + hProfile = cmsCreateLab4Profile(NULL); + else + hProfile = cmsOpenProfileFromFile( cOutProf, "r"); + + n = cmsGetPostScriptCRD(DbgThread(), hProfile, 0, dwFlags, NULL, 0); + if (n == 0) return; + + Buffer = (char*) _cmsMalloc(BuffThread, n + 1); + cmsGetPostScriptCRD(DbgThread(), hProfile, 0, dwFlags, Buffer, n); + Buffer[n] = 0; + + if (FileName != NULL) { + FILE* o = fopen(FileName, "wb"); + fwrite(Buffer, n, 1, o); + fclose(o); + } + + _cmsFree(BuffThread, Buffer); + cmsCloseProfile(DbgThread(), hProfile); + if (FileName != NULL) + remove(FileName); +} + +static +cmsInt32Number CheckPostScript(void) +{ + GenerateCSA("test5.icc", "sRGB_CSA.ps"); + GenerateCSA("aRGBlcms2.icc", "aRGB_CSA.ps"); + GenerateCSA("test4.icc", "sRGBV4_CSA.ps"); + GenerateCSA("test1.icc", "SWOP_CSA.ps"); + GenerateCSA(NULL, "Lab_CSA.ps"); + GenerateCSA("graylcms2.icc", "gray_CSA.ps"); + + GenerateCRD("test5.icc", "sRGB_CRD.ps"); + GenerateCRD("aRGBlcms2.icc", "aRGB_CRD.ps"); + GenerateCRD(NULL, "Lab_CRD.ps"); + GenerateCRD("test1.icc", "SWOP_CRD.ps"); + GenerateCRD("test4.icc", "sRGBV4_CRD.ps"); + GenerateCRD("graylcms2.icc", "gray_CRD.ps"); + + return 1; +} + + +static +cmsInt32Number CheckGray(cmsHTRANSFORM xform, cmsUInt8Number g, double L) +{ + cmsCIELab Lab; + + cmsDoTransform(DbgThread(), xform, &g, &Lab, 1); + + if (!IsGoodVal("a axis on gray", 0, Lab.a, 0.001)) return 0; + if (!IsGoodVal("b axis on gray", 0, Lab.b, 0.001)) return 0; + + return IsGoodVal("Gray value", L, Lab.L, 0.01); +} + +static +cmsInt32Number CheckInputGray(void) +{ + cmsHPROFILE hGray = Create_Gray22(); + cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); + cmsHTRANSFORM xform; + + if (hGray == NULL || hLab == NULL) return 0; + + xform = cmsCreateTransform(hGray, TYPE_GRAY_8, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hGray); cmsCloseProfile(DbgThread(), hLab); + + if (!CheckGray(xform, 0, 0)) return 0; + if (!CheckGray(xform, 125, 52.768)) return 0; + if (!CheckGray(xform, 200, 81.069)) return 0; + if (!CheckGray(xform, 255, 100.0)) return 0; + + cmsDeleteTransform(DbgThread(), xform); + return 1; +} + +static +cmsInt32Number CheckLabInputGray(void) +{ + cmsHPROFILE hGray = Create_GrayLab(); + cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); + cmsHTRANSFORM xform; + + if (hGray == NULL || hLab == NULL) return 0; + + xform = cmsCreateTransform(hGray, TYPE_GRAY_8, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hGray); cmsCloseProfile(DbgThread(), hLab); + + if (!CheckGray(xform, 0, 0)) return 0; + if (!CheckGray(xform, 125, 49.019)) return 0; + if (!CheckGray(xform, 200, 78.431)) return 0; + if (!CheckGray(xform, 255, 100.0)) return 0; + + cmsDeleteTransform(DbgThread(), xform); + return 1; +} + + +static +cmsInt32Number CheckOutGray(cmsHTRANSFORM xform, double L, cmsUInt8Number g) +{ + cmsCIELab Lab; + cmsUInt8Number g_out; + + Lab.L = L; + Lab.a = 0; + Lab.b = 0; + + cmsDoTransform(DbgThread(), xform, &Lab, &g_out, 1); + + return IsGoodVal("Gray value", g, (double) g_out, 0.01); +} + +static +cmsInt32Number CheckOutputGray(void) +{ + cmsHPROFILE hGray = Create_Gray22(); + cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); + cmsHTRANSFORM xform; + + if (hGray == NULL || hLab == NULL) return 0; + + xform = cmsCreateTransform( hLab, TYPE_Lab_DBL, hGray, TYPE_GRAY_8, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hGray); cmsCloseProfile(DbgThread(), hLab); + + if (!CheckOutGray(xform, 0, 0)) return 0; + if (!CheckOutGray(xform, 100, 255)) return 0; + + if (!CheckOutGray(xform, 20, 52)) return 0; + if (!CheckOutGray(xform, 50, 118)) return 0; + + + cmsDeleteTransform(DbgThread(), xform); + return 1; +} + + +static +cmsInt32Number CheckLabOutputGray(void) +{ + cmsHPROFILE hGray = Create_GrayLab(); + cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); + cmsHTRANSFORM xform; + cmsInt32Number i; + + if (hGray == NULL || hLab == NULL) return 0; + + xform = cmsCreateTransform( hLab, TYPE_Lab_DBL, hGray, TYPE_GRAY_8, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), hGray); cmsCloseProfile(DbgThread(), hLab); + + if (!CheckOutGray(xform, 0, 0)) return 0; + if (!CheckOutGray(xform, 100, 255)) return 0; + + for (i=0; i < 100; i++) { + + cmsUInt8Number g; + + g = (cmsUInt8Number) floor(i * 255.0 / 100.0 + 0.5); + + if (!CheckOutGray(xform, i, g)) return 0; + } + + + cmsDeleteTransform(DbgThread(), xform); + return 1; +} + + +static +cmsInt32Number CheckV4gamma(void) +{ + cmsHPROFILE h; + cmsUInt16Number Lin[] = {0, 0xffff}; + cmsToneCurve*g = cmsBuildTabulatedToneCurve16(DbgThread(), 2, Lin); + + h = cmsOpenProfileFromFileTHR(DbgThread(), "v4gamma.icc", "w"); + if (h == NULL) return 0; + + + cmsSetProfileVersion(DbgThread(), h, 4.3); + + if (!cmsWriteTag(DbgThread(), h, cmsSigGrayTRCTag, g)) return 0; + cmsCloseProfile(DbgThread(), h); + + cmsFreeToneCurve(DbgThread(), g); + remove("v4gamma.icc"); + return 1; +} + +// cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname); + +// Gamut descriptor routines +static +cmsInt32Number CheckGBD(void) +{ + cmsCIELab Lab; + cmsHANDLE h; + cmsInt32Number L, a, b; + cmsUInt32Number r1, g1, b1; + cmsHPROFILE hLab, hsRGB; + cmsHTRANSFORM xform; + + h = cmsGBDAlloc(DbgThread()); + if (h == NULL) return 0; + + // Fill all Lab gamut as valid + SubTest("Filling RAW gamut"); + + for (L=0; L <= 100; L += 10) + for (a = -128; a <= 128; a += 5) + for (b = -128; b <= 128; b += 5) { + + Lab.L = L; + Lab.a = a; + Lab.b = b; + if (!cmsGDBAddPoint(DbgThread(), h, &Lab)) return 0; + } + + // Complete boundaries + SubTest("computing Lab gamut"); + if (!cmsGDBCompute(DbgThread(), h, 0)) return 0; + + + // All points should be inside gamut + SubTest("checking Lab gamut"); + for (L=10; L <= 90; L += 25) + for (a = -120; a <= 120; a += 25) + for (b = -120; b <= 120; b += 25) { + + Lab.L = L; + Lab.a = a; + Lab.b = b; + if (!cmsGDBCheckPoint(DbgThread(), h, &Lab)) { + return 0; + } + } + cmsGBDFree(DbgThread(), h); + + + // Now for sRGB + SubTest("checking sRGB gamut"); + h = cmsGBDAlloc(DbgThread()); + hsRGB = cmsCreate_sRGBProfile(); + hLab = cmsCreateLab4Profile(NULL); + + xform = cmsCreateTransform(hsRGB, TYPE_RGB_8, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hsRGB); cmsCloseProfile(DbgThread(), hLab); + + + for (r1=0; r1 < 256; r1 += 5) { + for (g1=0; g1 < 256; g1 += 5) + for (b1=0; b1 < 256; b1 += 5) { + + + cmsUInt8Number rgb[3]; + + rgb[0] = (cmsUInt8Number) r1; + rgb[1] = (cmsUInt8Number) g1; + rgb[2] = (cmsUInt8Number) b1; + + cmsDoTransform(DbgThread(), xform, rgb, &Lab, 1); + + // if (fabs(Lab.b) < 20 && Lab.a > 0) continue; + + if (!cmsGDBAddPoint(DbgThread(), h, &Lab)) { + cmsGBDFree(DbgThread(), h); + return 0; + } + + + } + } + + + if (!cmsGDBCompute(DbgThread(), h, 0)) return 0; + // cmsGBDdumpVRML(h, "c:\\colormaps\\lab.wrl"); + + for (r1=10; r1 < 200; r1 += 10) { + for (g1=10; g1 < 200; g1 += 10) + for (b1=10; b1 < 200; b1 += 10) { + + + cmsUInt8Number rgb[3]; + + rgb[0] = (cmsUInt8Number) r1; + rgb[1] = (cmsUInt8Number) g1; + rgb[2] = (cmsUInt8Number) b1; + + cmsDoTransform(DbgThread(), xform, rgb, &Lab, 1); + if (!cmsGDBCheckPoint(DbgThread(), h, &Lab)) { + + cmsDeleteTransform(DbgThread(), xform); + cmsGBDFree(DbgThread(), h); + return 0; + } + } + } + + + cmsDeleteTransform(DbgThread(), xform); + cmsGBDFree(DbgThread(), h); + + SubTest("checking LCh chroma ring"); + h = cmsGBDAlloc(DbgThread()); + + + for (r1=0; r1 < 360; r1++) { + + cmsCIELCh LCh; + + LCh.L = 70; + LCh.C = 60; + LCh.h = r1; + + cmsLCh2Lab(DbgThread(), &Lab, &LCh); + if (!cmsGDBAddPoint(DbgThread(), h, &Lab)) { + cmsGBDFree(DbgThread(), h); + return 0; + } + } + + + if (!cmsGDBCompute(DbgThread(), h, 0)) return 0; + + cmsGBDFree(DbgThread(), h); + + return 1; +} + + +static +int CheckMD5(void) +{ + _cmsICCPROFILE* h; + cmsHPROFILE pProfile = cmsOpenProfileFromFile( "sRGBlcms2.icc", "r"); + cmsProfileID ProfileID1, ProfileID2, ProfileID3, ProfileID4; + + h =(_cmsICCPROFILE*) pProfile; + if (cmsMD5computeID(DbgThread(), pProfile)) cmsGetHeaderProfileID(DbgThread(), pProfile, ProfileID1.ID8); + if (cmsMD5computeID(DbgThread(), pProfile)) cmsGetHeaderProfileID(DbgThread(), pProfile,ProfileID2.ID8); + + cmsCloseProfile(DbgThread(), pProfile); + + + pProfile = cmsOpenProfileFromFile( "sRGBlcms2.icc", "r"); + + h =(_cmsICCPROFILE*) pProfile; + if (cmsMD5computeID(DbgThread(), pProfile)) cmsGetHeaderProfileID(DbgThread(), pProfile, ProfileID3.ID8); + if (cmsMD5computeID(DbgThread(), pProfile)) cmsGetHeaderProfileID(DbgThread(), pProfile,ProfileID4.ID8); + + cmsCloseProfile(DbgThread(), pProfile); + + return ((memcmp(ProfileID1.ID8, ProfileID3.ID8, sizeof(ProfileID1)) == 0) && + (memcmp(ProfileID2.ID8, ProfileID4.ID8, sizeof(ProfileID2)) == 0)); +} + + + +static +int CheckLinking(void) +{ + cmsHPROFILE h; + cmsPipeline * pipeline; + cmsStage *stageBegin, *stageEnd; + + // Create a CLUT based profile + h = cmsCreateInkLimitingDeviceLinkTHR(DbgThread(), cmsSigCmykData, 150); + + // link a second tag + cmsLinkTag(DbgThread(), h, cmsSigAToB1Tag, cmsSigAToB0Tag); + + // Save the linked devicelink + if (!cmsSaveProfileToFile(DbgThread(), h, "lcms2link.icc")) return 0; + cmsCloseProfile(DbgThread(), h); + + // Now open the profile and read the pipeline + h = cmsOpenProfileFromFile( "lcms2link.icc", "r"); + if (h == NULL) return 0; + + pipeline = (cmsPipeline*) cmsReadTag(DbgThread(), h, cmsSigAToB1Tag); + if (pipeline == NULL) + { + return 0; + } + + pipeline = cmsPipelineDup(DbgThread(), pipeline); + + // extract stage from pipe line + cmsPipelineUnlinkStage(DbgThread(), pipeline, cmsAT_BEGIN, &stageBegin); + cmsPipelineUnlinkStage(DbgThread(), pipeline, cmsAT_END, &stageEnd); + cmsPipelineInsertStage(DbgThread(), pipeline, cmsAT_END, stageEnd); + cmsPipelineInsertStage(DbgThread(), pipeline, cmsAT_BEGIN, stageBegin); + + if (cmsTagLinkedTo(DbgThread(), h, cmsSigAToB1Tag) != cmsSigAToB0Tag) return 0; + + cmsWriteTag(DbgThread(), h, cmsSigAToB0Tag, pipeline); + cmsPipelineFree(DbgThread(), pipeline); + + if (!cmsSaveProfileToFile(DbgThread(), h, "lcms2link2.icc")) return 0; + cmsCloseProfile(DbgThread(), h); + + + return 1; + +} + +// TestMPE +// +// Created by Paul Miller on 30/08/2016. +// +static +cmsHPROFILE IdentityMatrixProfile( cmsColorSpaceSignature dataSpace) +{ + cmsContext ctx = 0; + cmsVEC3 zero = {{0,0,0}}; + cmsMAT3 identity; + cmsPipeline* forward; + cmsPipeline* reverse; + cmsHPROFILE identityProfile = cmsCreateProfilePlaceholder( ctx); + + + cmsSetProfileVersion(DbgThread(), identityProfile, 4.3); + + cmsSetDeviceClass(DbgThread(), identityProfile, cmsSigColorSpaceClass); + cmsSetColorSpace(DbgThread(), identityProfile, dataSpace); + cmsSetPCS(DbgThread(), identityProfile, cmsSigXYZData); + + cmsSetHeaderRenderingIntent(DbgThread(), identityProfile, INTENT_RELATIVE_COLORIMETRIC); + + cmsWriteTag(DbgThread(), identityProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ(DbgThread())); + + + + _cmsMAT3identity(DbgThread(), &identity); + + // build forward transform.... (RGB to PCS) + forward = cmsPipelineAlloc( 0, 3, 3); + cmsPipelineInsertStage(DbgThread(), forward, cmsAT_END, cmsStageAllocMatrix( ctx, 3, 3, (cmsFloat64Number*)&identity, (cmsFloat64Number*)&zero)); + cmsWriteTag(DbgThread(), identityProfile, cmsSigDToB1Tag, forward); + + cmsPipelineFree(DbgThread(), forward); + + reverse = cmsPipelineAlloc( 0, 3, 3); + cmsPipelineInsertStage(DbgThread(), reverse, cmsAT_END, cmsStageAllocMatrix( ctx, 3, 3, (cmsFloat64Number*)&identity, (cmsFloat64Number*)&zero)); + cmsWriteTag(DbgThread(), identityProfile, cmsSigBToD1Tag, reverse); + + cmsPipelineFree(DbgThread(), reverse); + + return identityProfile; +} + +static +cmsInt32Number CheckFloatXYZ(void) +{ + cmsHPROFILE input; + cmsHPROFILE xyzProfile = cmsCreateXYZProfile(); + cmsHTRANSFORM xform; + cmsFloat32Number in[3]; + cmsFloat32Number out[3]; + + in[0] = 1.0; + in[1] = 1.0; + in[2] = 1.0; + + // RGB to XYZ + input = IdentityMatrixProfile( cmsSigRgbData); + + xform = cmsCreateTransform( input, TYPE_RGB_FLT, xyzProfile, TYPE_XYZ_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), input); + + cmsDoTransform(DbgThread(), xform, in, out, 1); + cmsDeleteTransform(DbgThread(), xform); + + if (!IsGoodVal("Float RGB->XYZ", in[0], out[0], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->XYZ", in[1], out[1], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->XYZ", in[2], out[2], FLOAT_PRECISSION)) + return 0; + + + // XYZ to XYZ + input = IdentityMatrixProfile( cmsSigXYZData); + + xform = cmsCreateTransform( input, TYPE_XYZ_FLT, xyzProfile, TYPE_XYZ_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), input); + + cmsDoTransform(DbgThread(), xform, in, out, 1); + + + cmsDeleteTransform(DbgThread(), xform); + + if (!IsGoodVal("Float XYZ->XYZ", in[0], out[0], FLOAT_PRECISSION) || + !IsGoodVal("Float XYZ->XYZ", in[1], out[1], FLOAT_PRECISSION) || + !IsGoodVal("Float XYZ->XYZ", in[2], out[2], FLOAT_PRECISSION)) + return 0; + + + // XYZ to RGB + input = IdentityMatrixProfile( cmsSigRgbData); + + xform = cmsCreateTransform( xyzProfile, TYPE_XYZ_FLT, input, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), input); + + cmsDoTransform(DbgThread(), xform, in, out, 1); + + cmsDeleteTransform(DbgThread(), xform); + + if (!IsGoodVal("Float XYZ->RGB", in[0], out[0], FLOAT_PRECISSION) || + !IsGoodVal("Float XYZ->RGB", in[1], out[1], FLOAT_PRECISSION) || + !IsGoodVal("Float XYZ->RGB", in[2], out[2], FLOAT_PRECISSION)) + return 0; + + + // Now the optimizer should remove a stage + + // XYZ to RGB + input = IdentityMatrixProfile( cmsSigRgbData); + + xform = cmsCreateTransform( input, TYPE_RGB_FLT, input, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(DbgThread(), input); + + cmsDoTransform(DbgThread(), xform, in, out, 1); + + cmsDeleteTransform(DbgThread(), xform); + + if (!IsGoodVal("Float RGB->RGB", in[0], out[0], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->RGB", in[1], out[1], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->RGB", in[2], out[2], FLOAT_PRECISSION)) + return 0; + + cmsCloseProfile(DbgThread(), xyzProfile); + + + return 1; +} + + +/* +Bug reported + + 1) + sRGB built-in V4.3 -> Lab identity built-in V4.3 + Flags: "cmsFLAGS_NOCACHE", "cmsFLAGS_NOOPTIMIZE" + Input format: TYPE_RGBA_FLT + Output format: TYPE_LabA_FLT + + 2) and back + Lab identity built-in V4.3 -> sRGB built-in V4.3 + Flags: "cmsFLAGS_NOCACHE", "cmsFLAGS_NOOPTIMIZE" + Input format: TYPE_LabA_FLT + Output format: TYPE_RGBA_FLT + +*/ +static +cmsInt32Number ChecksRGB2LabFLT(void) +{ + cmsHPROFILE hSRGB = cmsCreate_sRGBProfile(); + cmsHPROFILE hLab = cmsCreateLab4Profile(NULL); + + cmsHTRANSFORM xform1 = cmsCreateTransform(hSRGB, TYPE_RGBA_FLT, hLab, TYPE_LabA_FLT, 0, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); + cmsHTRANSFORM xform2 = cmsCreateTransform(hLab, TYPE_LabA_FLT, hSRGB, TYPE_RGBA_FLT, 0, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); + + cmsFloat32Number RGBA1[4], RGBA2[4], LabA[4]; + int i; + + + for (i = 0; i <= 100; i++) + { + RGBA1[0] = i / 100.0F; + RGBA1[1] = i / 100.0F; + RGBA1[2] = i / 100.0F; + RGBA1[3] = 0; + + cmsDoTransform(DbgThread(), xform1, RGBA1, LabA, 1); + cmsDoTransform(DbgThread(), xform2, LabA, RGBA2, 1); + + if (!IsGoodVal("Float RGB->RGB", RGBA1[0], RGBA2[0], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->RGB", RGBA1[1], RGBA2[1], FLOAT_PRECISSION) || + !IsGoodVal("Float RGB->RGB", RGBA1[2], RGBA2[2], FLOAT_PRECISSION)) + return 0; + } + + + cmsDeleteTransform(DbgThread(), xform1); + cmsDeleteTransform(DbgThread(), xform2); + cmsCloseProfile(DbgThread(), hSRGB); + cmsCloseProfile(DbgThread(), hLab); + + return 1; +} + +/* + * parametric curve for Rec709 + */ +static +double Rec709(double L) +{ + if (L <0.018) return 4.5*L; + else + { + double a = 1.099* pow(L, 0.45); + + a = a - 0.099; + return a; + } +} + + +static +cmsInt32Number CheckParametricRec709(void) +{ + cmsFloat64Number params[7]; + cmsToneCurve* t; + int i; + + params[0] = 0.45; /* y */ + params[1] = pow(1.099, 1.0 / 0.45); /* a */ + params[2] = 0.0; /* b */ + params[3] = 4.5; /* c */ + params[4] = 0.018; /* d */ + params[5] = -0.099; /* e */ + params[6] = 0.0; /* f */ + + t = cmsBuildParametricToneCurve (NULL, 5, params); + + + for (i=0; i < 256; i++) + { + cmsFloat32Number n = (cmsFloat32Number) i / 255.0F; + cmsUInt16Number f1 = (cmsUInt16Number) floor(255.0 * cmsEvalToneCurveFloat(DbgThread(), t, n) + 0.5); + cmsUInt16Number f2 = (cmsUInt16Number) floor(255.0*Rec709((double) i / 255.0) + 0.5); + + if (f1 != f2) + { + cmsFreeToneCurve(DbgThread(), t); + return 0; + } + } + + cmsFreeToneCurve(DbgThread(), t); + return 1; +} + + +#define kNumPoints 10 + +typedef cmsFloat32Number(*Function)(cmsFloat32Number x); + +static cmsFloat32Number StraightLine( cmsFloat32Number x) +{ + return (cmsFloat32Number) (0.1 + 0.9 * x); +} + +static cmsInt32Number TestCurve( const char* label, cmsToneCurve* curve, Function fn) +{ + cmsInt32Number ok = 1; + int i; + for (i = 0; i < kNumPoints*3; i++) { + + cmsFloat32Number x = (cmsFloat32Number)i / (kNumPoints*3 - 1); + cmsFloat32Number expectedY = fn(x); + cmsFloat32Number out = cmsEvalToneCurveFloat(DbgThread(), curve, x); + + if (!IsGoodVal(label, expectedY, out, FLOAT_PRECISSION)) { + ok = 0; + } + } + return ok; +} + +static +cmsInt32Number CheckFloatSamples(void) +{ + cmsFloat32Number y[kNumPoints]; + int i; + cmsToneCurve *curve; + cmsInt32Number ok; + + for (i = 0; i < kNumPoints; i++) { + cmsFloat32Number x = (cmsFloat32Number)i / (kNumPoints-1); + + y[i] = StraightLine(x); + } + + curve = cmsBuildTabulatedToneCurveFloat(NULL, kNumPoints, y); + ok = TestCurve( "Float Samples", curve, StraightLine); + cmsFreeToneCurve(DbgThread(), curve); + + return ok; +} + +static +cmsInt32Number CheckFloatSegments(void) +{ + cmsInt32Number ok = 1; + int i; + cmsToneCurve *curve; + + cmsFloat32Number y[ kNumPoints]; + + // build a segmented curve with a sampled section... + cmsCurveSegment Seg[3]; + + // Initialize segmented curve part up to 0.1 + Seg[0].x0 = -1e22f; // -infinity + Seg[0].x1 = 0.1f; + Seg[0].Type = 6; // Y = (a * X + b) ^ Gamma + c + Seg[0].Params[0] = 1.0f; // gamma + Seg[0].Params[1] = 0.9f; // a + Seg[0].Params[2] = 0.0f; // b + Seg[0].Params[3] = 0.1f; // c + Seg[0].Params[4] = 0.0f; + + // From zero to 1 + Seg[1].x0 = 0.1f; + Seg[1].x1 = 0.9f; + Seg[1].Type = 0; + + Seg[1].nGridPoints = kNumPoints; + Seg[1].SampledPoints = y; + + for (i = 0; i < kNumPoints; i++) { + cmsFloat32Number x = (cmsFloat32Number) (0.1 + ((cmsFloat32Number)i / (kNumPoints-1)) * (0.9 - 0.1)); + y[i] = StraightLine(x); + } + + // from 1 to +infinity + Seg[2].x0 = 0.9f; + Seg[2].x1 = 1e22f; // +infinity + Seg[2].Type = 6; + + Seg[2].Params[0] = 1.0f; + Seg[2].Params[1] = 0.9f; + Seg[2].Params[2] = 0.0f; + Seg[2].Params[3] = 0.1f; + Seg[2].Params[4] = 0.0f; + + curve = cmsBuildSegmentedToneCurve(0, 3, Seg); + + ok = TestCurve( "Float Segmented Curve", curve, StraightLine); + + cmsFreeToneCurve(DbgThread(), curve); + + return ok; +} + + +static +cmsInt32Number CheckReadRAW(void) +{ + cmsInt32Number tag_size, tag_size1; + char buffer[4]; + cmsHPROFILE hProfile; + + + SubTest("RAW read on on-disk"); + hProfile = cmsOpenProfileFromFile( "test1.icc", "r"); + + if (hProfile == NULL) + return 0; + + tag_size = cmsReadRawTag(DbgThread(), hProfile, cmsSigGamutTag, buffer, 4); + tag_size1 = cmsReadRawTag(DbgThread(), hProfile, cmsSigGamutTag, NULL, 0); + + cmsCloseProfile(DbgThread(), hProfile); + + if (tag_size != 4) + return 0; + + if (tag_size1 != 37009) + return 0; + + SubTest("RAW read on in-memory created profiles"); + hProfile = cmsCreate_sRGBProfile(); + tag_size = cmsReadRawTag(DbgThread(), hProfile, cmsSigGreenColorantTag, buffer, 4); + tag_size1 = cmsReadRawTag(DbgThread(), hProfile, cmsSigGreenColorantTag, NULL, 0); + + cmsCloseProfile(DbgThread(), hProfile); + + if (tag_size != 4) + return 0; + if (tag_size1 != 20) + return 0; + + return 1; +} + + +static +cmsInt32Number CheckMeta(void) +{ + char *data; + cmsHANDLE dict; + cmsHPROFILE p; + cmsUInt32Number clen; + FILE *fp; + int rc; + + /* open file */ + p = cmsOpenProfileFromFile( "ibm-t61.icc", "r"); + if (p == NULL) return 0; + + /* read dictionary, but don't do anything with the value */ + //COMMENT OUT THE NEXT TWO LINES AND IT WORKS FINE!!! + dict = cmsReadTag(DbgThread(), p, cmsSigMetaTag); + if (dict == NULL) return 0; + + /* serialize profile to memory */ + rc = cmsSaveProfileToMem(DbgThread(), p, NULL, &clen); + if (!rc) return 0; + + data = (char*) malloc(clen); + rc = cmsSaveProfileToMem(DbgThread(), p, data, &clen); + if (!rc) return 0; + + /* write the memory blob to a file */ + //NOTE: The crash does not happen if cmsSaveProfileToFile() is used */ + fp = fopen("new.icc", "wb"); + fwrite(data, 1, clen, fp); + fclose(fp); + free(data); + + cmsCloseProfile(DbgThread(), p); + + /* open newly created file and read metadata */ + p = cmsOpenProfileFromFile( "new.icc", "r"); + //ERROR: Bad dictionary Name/Value + //ERROR: Corrupted tag 'meta' + //test: test.c:59: main: Assertion `dict' failed. + dict = cmsReadTag(DbgThread(), p, cmsSigMetaTag); + if (dict == NULL) return 0; + + cmsCloseProfile(DbgThread(), p); + return 1; +} + + +// Bug on applying null transforms on floating point buffers +static +cmsInt32Number CheckFloatNULLxform(void) +{ + int i; + cmsFloat32Number in[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + cmsFloat32Number out[10]; + + cmsHTRANSFORM xform = cmsCreateTransform(NULL, TYPE_GRAY_FLT, NULL, TYPE_GRAY_FLT, INTENT_PERCEPTUAL, cmsFLAGS_NULLTRANSFORM); + + if (xform == NULL) { + Fail("Unable to create float null transform"); + return 0; + } + + cmsDoTransform(DbgThread(), xform, in, out, 10); + + cmsDeleteTransform(DbgThread(), xform); + for (i=0; i < 10; i++) { + + if (!IsGoodVal("float nullxform", in[i], out[i], 0.001)) { + + return 0; + } + } + + return 1; +} + +static +cmsInt32Number CheckRemoveTag(void) +{ + cmsHPROFILE p; + cmsMLU *mlu; + int ret; + + p = cmsCreate_sRGBProfileTHR(NULL); + + /* set value */ + mlu = cmsMLUalloc (NULL, 1); + ret = cmsMLUsetASCII(DbgThread(), mlu, "en", "US", "bar"); + if (!ret) return 0; + + ret = cmsWriteTag(DbgThread(), p, cmsSigDeviceMfgDescTag, mlu); + if (!ret) return 0; + + cmsMLUfree(DbgThread(), mlu); + + /* remove the tag */ + ret = cmsWriteTag(DbgThread(), p, cmsSigDeviceMfgDescTag, NULL); + if (!ret) return 0; + + /* THIS EXPLODES */ + cmsCloseProfile(DbgThread(), p); + return 1; +} + + +static +cmsInt32Number CheckMatrixSimplify(void) +{ + + cmsHPROFILE pIn; + cmsHPROFILE pOut; + cmsHTRANSFORM t; + unsigned char buf[3] = { 127, 32, 64 }; + + + pIn = cmsCreate_sRGBProfile(); + pOut = cmsOpenProfileFromFile( "ibm-t61.icc", "r"); + if (pIn == NULL || pOut == NULL) + return 0; + + t = cmsCreateTransform(pIn, TYPE_RGB_8, pOut, TYPE_RGB_8, INTENT_PERCEPTUAL, 0); + cmsDoTransformStride(DbgThread(), t, buf, buf, 1, 1); + cmsDeleteTransform(DbgThread(), t); + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + + + return buf[0] == 144 && buf[1] == 0 && buf[2] == 69; +} + + + +static +cmsInt32Number CheckTransformLineStride(void) +{ + + cmsHPROFILE pIn; + cmsHPROFILE pOut; + cmsHTRANSFORM t; + + // Our buffer is formed by 4 RGB8 lines, each line is 2 pixels wide plus a padding of one byte + + cmsUInt8Number buf1[]= { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, }; + + // Our buffer2 is formed by 4 RGBA lines, each line is 2 pixels wide plus a padding of one byte + + cmsUInt8Number buf2[] = { 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff, 1, 0, + 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff, 1, 0, + 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff, 1, 0, + 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff, 1, 0}; + + // Our buffer3 is formed by 4 RGBA16 lines, each line is 2 pixels wide plus a padding of two bytes + + cmsUInt16Number buf3[] = { 0xffff, 0xffff, 0xffff, 0x0101, 0xffff, 0xffff, 0xffff, 0x0101, 0, + 0xffff, 0xffff, 0xffff, 0x0101, 0xffff, 0xffff, 0xffff, 0x0101, 0, + 0xffff, 0xffff, 0xffff, 0x0101, 0xffff, 0xffff, 0xffff, 0x0101, 0, + 0xffff, 0xffff, 0xffff, 0x0101, 0xffff, 0xffff, 0xffff, 0x0101, 0 }; + + cmsUInt8Number out[1024]; + + + memset(out, 0, sizeof(out)); + pIn = cmsCreate_sRGBProfile(); + pOut = cmsOpenProfileFromFile( "ibm-t61.icc", "r"); + if (pIn == NULL || pOut == NULL) + return 0; + + t = cmsCreateTransform(pIn, TYPE_RGB_8, pOut, TYPE_RGB_8, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA); + + cmsDoTransformLineStride(DbgThread(), t, buf1, out, 2, 4, 7, 7, 0, 0); + cmsDeleteTransform(DbgThread(), t); + + if (memcmp(out, buf1, sizeof(buf1)) != 0) { + Fail("Failed transform line stride on RGB8"); + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + return 0; + } + + memset(out, 0, sizeof(out)); + + t = cmsCreateTransform(pIn, TYPE_RGBA_8, pOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA); + + cmsDoTransformLineStride(DbgThread(), t, buf2, out, 2, 4, 9, 9, 0, 0); + + cmsDeleteTransform(DbgThread(), t); + + + if (memcmp(out, buf2, sizeof(buf2)) != 0) { + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + Fail("Failed transform line stride on RGBA8"); + return 0; + } + + memset(out, 0, sizeof(out)); + + t = cmsCreateTransform(pIn, TYPE_RGBA_16, pOut, TYPE_RGBA_16, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA); + + cmsDoTransformLineStride(DbgThread(), t, buf3, out, 2, 4, 18, 18, 0, 0); + + cmsDeleteTransform(DbgThread(), t); + + if (memcmp(out, buf3, sizeof(buf3)) != 0) { + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + Fail("Failed transform line stride on RGBA16"); + return 0; + } + + + memset(out, 0, sizeof(out)); + + + // From 8 to 16 + t = cmsCreateTransform(pIn, TYPE_RGBA_8, pOut, TYPE_RGBA_16, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA); + + cmsDoTransformLineStride(DbgThread(), t, buf2, out, 2, 4, 9, 18, 0, 0); + + cmsDeleteTransform(DbgThread(), t); + + if (memcmp(out, buf3, sizeof(buf3)) != 0) { + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + Fail("Failed transform line stride on RGBA16"); + return 0; + } + + + + cmsCloseProfile(DbgThread(), pIn); + cmsCloseProfile(DbgThread(), pOut); + + return 1; +} + + +static +int CheckPlanar8opt(void) +{ + cmsHPROFILE aboveRGB = Create_AboveRGB(); + cmsHPROFILE sRGB = cmsCreate_sRGBProfile(); + + cmsHTRANSFORM transform = cmsCreateTransform(sRGB, TYPE_RGB_8_PLANAR, + aboveRGB, TYPE_RGB_8_PLANAR, + INTENT_PERCEPTUAL, 0); + + cmsDeleteTransform(DbgThread(), transform); + cmsCloseProfile(DbgThread(), aboveRGB); + cmsCloseProfile(DbgThread(), sRGB); + + return 1; +} + +/** +* Bug reported & fixed. Thanks to Kornel Lesinski for spotting this. +*/ +static +int CheckSE(void) +{ + cmsHPROFILE input_profile = Create_AboveRGB(); + cmsHPROFILE output_profile = cmsCreate_sRGBProfile(); + + cmsHTRANSFORM tr = cmsCreateTransform(input_profile, TYPE_RGBA_8, output_profile, TYPE_RGBA_16_SE, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_COPY_ALPHA); + + cmsUInt8Number rgba[4] = { 40, 41, 41, 0xfa }; + cmsUInt16Number out[4]; + + cmsDoTransform(DbgThread(), tr, rgba, out, 1); + cmsCloseProfile(DbgThread(), input_profile); + cmsCloseProfile(DbgThread(), output_profile); + cmsDeleteTransform(DbgThread(), tr); + + if (out[0] != 0xf622 || out[1] != 0x7f24 || out[2] != 0x7f24) + return 0; + + return 1; +} + +/** +* Bug reported. +*/ +static +int CheckForgedMPE(void) +{ + cmsUInt32Number i; + cmsHPROFILE srcProfile; + cmsHPROFILE dstProfile; + cmsColorSpaceSignature srcCS; + cmsUInt32Number nSrcComponents; + cmsUInt32Number srcFormat; + cmsUInt32Number intent = 0; + cmsUInt32Number flags = 0; + cmsHTRANSFORM hTransform; + cmsUInt8Number output[4]; + + srcProfile = cmsOpenProfileFromFile( "bad_mpe.icc", "r"); + if (!srcProfile) + return 0; + + dstProfile = cmsCreate_sRGBProfile(); + if (!dstProfile) { + cmsCloseProfile(DbgThread(), srcProfile); + return 0; + } + + srcCS = cmsGetColorSpace(DbgThread(), srcProfile); + nSrcComponents = cmsChannelsOf(DbgThread(), srcCS); + + if (srcCS == cmsSigLabData) { + srcFormat = + COLORSPACE_SH(PT_Lab) | CHANNELS_SH(nSrcComponents) | BYTES_SH(0); + } + else { + srcFormat = + COLORSPACE_SH(PT_ANY) | CHANNELS_SH(nSrcComponents) | BYTES_SH(1); + } + + cmsSetLogErrorHandler(ErrorReportingFunction); + + hTransform = cmsCreateTransform(srcProfile, srcFormat, dstProfile, + TYPE_BGR_8, intent, flags); + cmsCloseProfile(DbgThread(), srcProfile); + cmsCloseProfile(DbgThread(), dstProfile); + + cmsSetLogErrorHandler(FatalErrorQuit); + + // Should report error + if (!TrappedError) return 0; + + TrappedError = FALSE; + + // Transform should NOT be created + if (!hTransform) return 1; + + // Never should reach here + if (T_BYTES(srcFormat) == 0) { // 0 means double + double input[128]; + for (i = 0; i < nSrcComponents; i++) + input[i] = 0.5f; + cmsDoTransform(DbgThread(), hTransform, input, output, 1); + } + else { + cmsUInt8Number input[128]; + for (i = 0; i < nSrcComponents; i++) + input[i] = 128; + cmsDoTransform(DbgThread(), hTransform, input, output, 1); + } + cmsDeleteTransform(DbgThread(), hTransform); + + return 0; +} + +/** +* What the self test is trying to do is creating a proofing transform +* with gamut check, so we can getting the coverage of one profile of +* another, i.e. to approximate the gamut intersection. e.g. +* Thanks to Richard Hughes for providing the test +*/ +static +int CheckProofingIntersection(void) +{ + cmsHPROFILE profile_null, hnd1, hnd2; + cmsHTRANSFORM transform; + + hnd1 = cmsCreate_sRGBProfile(); + hnd2 = Create_AboveRGB(); + + profile_null = cmsCreateNULLProfileTHR(DbgThread()); + transform = cmsCreateProofingTransformTHR(DbgThread(), + hnd1, + TYPE_RGB_FLT, + profile_null, + TYPE_GRAY_FLT, + hnd2, + INTENT_ABSOLUTE_COLORIMETRIC, + INTENT_ABSOLUTE_COLORIMETRIC, + cmsFLAGS_GAMUTCHECK | + cmsFLAGS_SOFTPROOFING); + + cmsCloseProfile(DbgThread(), hnd1); + cmsCloseProfile(DbgThread(), hnd2); + cmsCloseProfile(DbgThread(), profile_null); + + // Failed? + if (transform == NULL) return 0; + + cmsDeleteTransform(DbgThread(), transform); + return 1; +} + +// -------------------------------------------------------------------------------------------------- +// P E R F O R M A N C E C H E C K S +// -------------------------------------------------------------------------------------------------- + + +typedef struct {cmsUInt8Number r, g, b, a;} Scanline_rgb1; +typedef struct {cmsUInt16Number r, g, b, a;} Scanline_rgb2; +typedef struct {cmsUInt8Number r, g, b;} Scanline_rgb8; +typedef struct {cmsUInt16Number r, g, b;} Scanline_rgb0; + + +static +void TitlePerformance(const char* Txt) +{ + printf("%-45s: ", Txt); fflush(stdout); +} + +static +void PrintPerformance(cmsUInt32Number Bytes, cmsUInt32Number SizeOfPixel, cmsFloat64Number diff) +{ + cmsFloat64Number seconds = (cmsFloat64Number) diff / CLOCKS_PER_SEC; + cmsFloat64Number mpix_sec = Bytes / (1024.0*1024.0*seconds*SizeOfPixel); + + printf("%g MPixel/sec.\n", mpix_sec); + fflush(stdout); +} + + + + + +static +void SpeedTest16bits(const char * Title, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent) +{ + + cmsInt32Number r, g, b, j; + clock_t atime; + cmsFloat64Number diff; + cmsHTRANSFORM hlcmsxform; + Scanline_rgb0 *In; + cmsUInt32Number Mb; + + if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) + Die("Unable to open profiles"); + + hlcmsxform = cmsCreateTransformTHR(DbgThread(), hlcmsProfileIn, TYPE_RGB_16, + hlcmsProfileOut, TYPE_RGB_16, Intent, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hlcmsProfileIn); + cmsCloseProfile(DbgThread(), hlcmsProfileOut); + + Mb = 256*256*256*sizeof(Scanline_rgb0); + In = (Scanline_rgb0*) malloc(Mb); + + j = 0; + for (r=0; r < 256; r++) + for (g=0; g < 256; g++) + for (b=0; b < 256; b++) { + + In[j].r = (cmsUInt16Number) ((r << 8) | r); + In[j].g = (cmsUInt16Number) ((g << 8) | g); + In[j].b = (cmsUInt16Number) ((b << 8) | b); + + j++; + } + + + TitlePerformance(Title); + + atime = clock(); + + cmsDoTransform(DbgThread(), hlcmsxform, In, In, 256*256*256); + + diff = clock() - atime; + free(In); + + PrintPerformance(Mb, sizeof(Scanline_rgb0), diff); + cmsDeleteTransform(DbgThread(), hlcmsxform); + +} + + +static +void SpeedTest16bitsCMYK(const char * Title, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) +{ + cmsInt32Number r, g, b, j; + clock_t atime; + cmsFloat64Number diff; + cmsHTRANSFORM hlcmsxform; + Scanline_rgb2 *In; + cmsUInt32Number Mb; + + if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) + Die("Unable to open profiles"); + + hlcmsxform = cmsCreateTransformTHR(DbgThread(), hlcmsProfileIn, TYPE_CMYK_16, + hlcmsProfileOut, TYPE_CMYK_16, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hlcmsProfileIn); + cmsCloseProfile(DbgThread(), hlcmsProfileOut); + + Mb = 256*256*256*sizeof(Scanline_rgb2); + + In = (Scanline_rgb2*) malloc(Mb); + + j = 0; + for (r=0; r < 256; r++) + for (g=0; g < 256; g++) + for (b=0; b < 256; b++) { + + In[j].r = (cmsUInt16Number) ((r << 8) | r); + In[j].g = (cmsUInt16Number) ((g << 8) | g); + In[j].b = (cmsUInt16Number) ((b << 8) | b); + In[j].a = 0; + + j++; + } + + + TitlePerformance(Title); + + atime = clock(); + + cmsDoTransform(DbgThread(), hlcmsxform, In, In, 256*256*256); + + diff = clock() - atime; + + free(In); + + PrintPerformance(Mb, sizeof(Scanline_rgb2), diff); + + cmsDeleteTransform(DbgThread(), hlcmsxform); + +} + + +static +void SpeedTest8bits(const char * Title, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent) +{ + cmsInt32Number r, g, b, j; + clock_t atime; + cmsFloat64Number diff; + cmsHTRANSFORM hlcmsxform; + Scanline_rgb8 *In; + cmsUInt32Number Mb; + + if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) + Die("Unable to open profiles"); + + hlcmsxform = cmsCreateTransformTHR(DbgThread(), hlcmsProfileIn, TYPE_RGB_8, + hlcmsProfileOut, TYPE_RGB_8, Intent, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hlcmsProfileIn); + cmsCloseProfile(DbgThread(), hlcmsProfileOut); + + Mb = 256*256*256*sizeof(Scanline_rgb8); + + In = (Scanline_rgb8*) malloc(Mb); + + j = 0; + for (r=0; r < 256; r++) + for (g=0; g < 256; g++) + for (b=0; b < 256; b++) { + + In[j].r = (cmsUInt8Number) r; + In[j].g = (cmsUInt8Number) g; + In[j].b = (cmsUInt8Number) b; + + j++; + } + + TitlePerformance(Title); + + atime = clock(); + + cmsDoTransform(DbgThread(), hlcmsxform, In, In, 256*256*256); + + diff = clock() - atime; + + free(In); + + PrintPerformance(Mb, sizeof(Scanline_rgb8), diff); + + cmsDeleteTransform(DbgThread(), hlcmsxform); + +} + + +static +void SpeedTest8bitsCMYK(const char * Title, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut) +{ + cmsInt32Number r, g, b, j; + clock_t atime; + cmsFloat64Number diff; + cmsHTRANSFORM hlcmsxform; + Scanline_rgb2 *In; + cmsUInt32Number Mb; + + if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) + Die("Unable to open profiles"); + + hlcmsxform = cmsCreateTransformTHR(DbgThread(), hlcmsProfileIn, TYPE_CMYK_8, + hlcmsProfileOut, TYPE_CMYK_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hlcmsProfileIn); + cmsCloseProfile(DbgThread(), hlcmsProfileOut); + + Mb = 256*256*256*sizeof(Scanline_rgb2); + + In = (Scanline_rgb2*) malloc(Mb); + + j = 0; + for (r=0; r < 256; r++) + for (g=0; g < 256; g++) + for (b=0; b < 256; b++) { + + In[j].r = (cmsUInt8Number) r; + In[j].g = (cmsUInt8Number) g; + In[j].b = (cmsUInt8Number) b; + In[j].a = (cmsUInt8Number) 0; + + j++; + } + + TitlePerformance(Title); + + atime = clock(); + + cmsDoTransform(DbgThread(), hlcmsxform, In, In, 256*256*256); + + diff = clock() - atime; + + free(In); + + PrintPerformance(Mb, sizeof(Scanline_rgb2), diff); + + + cmsDeleteTransform(DbgThread(), hlcmsxform); + +} + + +static +void SpeedTest8bitsGray(const char * Title, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent) +{ + cmsInt32Number r, g, b, j; + clock_t atime; + cmsFloat64Number diff; + cmsHTRANSFORM hlcmsxform; + cmsUInt8Number *In; + cmsUInt32Number Mb; + + + if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL) + Die("Unable to open profiles"); + + hlcmsxform = cmsCreateTransformTHR(DbgThread(), hlcmsProfileIn, + TYPE_GRAY_8, hlcmsProfileOut, TYPE_GRAY_8, Intent, cmsFLAGS_NOCACHE); + cmsCloseProfile(DbgThread(), hlcmsProfileIn); + cmsCloseProfile(DbgThread(), hlcmsProfileOut); + Mb = 256*256*256; + + In = (cmsUInt8Number*) malloc(Mb); + + j = 0; + for (r=0; r < 256; r++) + for (g=0; g < 256; g++) + for (b=0; b < 256; b++) { + + In[j] = (cmsUInt8Number) r; + + j++; + } + + TitlePerformance(Title); + + atime = clock(); + + cmsDoTransform(DbgThread(), hlcmsxform, In, In, 256*256*256); + + diff = clock() - atime; + free(In); + + PrintPerformance(Mb, sizeof(cmsUInt8Number), diff); + cmsDeleteTransform(DbgThread(), hlcmsxform); +} + + +static +cmsHPROFILE CreateCurves(void) +{ + cmsToneCurve* Gamma = cmsBuildGamma(DbgThread(), 1.1); + cmsToneCurve* Transfer[3]; + cmsHPROFILE h; + + Transfer[0] = Transfer[1] = Transfer[2] = Gamma; + h = cmsCreateLinearizationDeviceLink(cmsSigRgbData, Transfer); + + cmsFreeToneCurve(DbgThread(), Gamma); + + return h; +} + + +static +void SpeedTest(void) +{ + printf("\n\nP E R F O R M A N C E T E S T S\n"); + printf( "=================================\n\n"); + fflush(stdout); + + SpeedTest16bits("16 bits on CLUT profiles", + cmsOpenProfileFromFile( "test5.icc", "r"), + cmsOpenProfileFromFile( "test3.icc", "r"), INTENT_PERCEPTUAL); + + SpeedTest8bits("8 bits on CLUT profiles", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("test3.icc", "r"), + INTENT_PERCEPTUAL); + + SpeedTest8bits("8 bits on Matrix-Shaper profiles", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + INTENT_PERCEPTUAL); + + SpeedTest8bits("8 bits on SAME Matrix-Shaper profiles", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("test5.icc", "r"), + INTENT_PERCEPTUAL); + + SpeedTest8bits("8 bits on Matrix-Shaper profiles (AbsCol)", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + INTENT_ABSOLUTE_COLORIMETRIC); + + SpeedTest16bits("16 bits on Matrix-Shaper profiles", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + INTENT_PERCEPTUAL); + + SpeedTest16bits("16 bits on SAME Matrix-Shaper profiles", + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + INTENT_PERCEPTUAL); + + SpeedTest16bits("16 bits on Matrix-Shaper profiles (AbsCol)", + cmsOpenProfileFromFile("test5.icc", "r"), + cmsOpenProfileFromFile("aRGBlcms2.icc", "r"), + INTENT_ABSOLUTE_COLORIMETRIC); + + SpeedTest8bits("8 bits on curves", + CreateCurves(), + CreateCurves(), + INTENT_PERCEPTUAL); + + SpeedTest16bits("16 bits on curves", + CreateCurves(), + CreateCurves(), + INTENT_PERCEPTUAL); + + SpeedTest8bitsCMYK("8 bits on CMYK profiles", + cmsOpenProfileFromFile("test1.icc", "r"), + cmsOpenProfileFromFile("test2.icc", "r")); + + SpeedTest16bitsCMYK("16 bits on CMYK profiles", + cmsOpenProfileFromFile("test1.icc", "r"), + cmsOpenProfileFromFile("test2.icc", "r")); + + SpeedTest8bitsGray("8 bits on gray-to gray", + cmsOpenProfileFromFile("gray3lcms2.icc", "r"), + cmsOpenProfileFromFile("graylcms2.icc", "r"), INTENT_RELATIVE_COLORIMETRIC); + + SpeedTest8bitsGray("8 bits on gray-to-lab gray", + cmsOpenProfileFromFile("graylcms2.icc", "r"), + cmsOpenProfileFromFile("glablcms2.icc", "r"), INTENT_RELATIVE_COLORIMETRIC); + + SpeedTest8bitsGray("8 bits on SAME gray-to-gray", + cmsOpenProfileFromFile("graylcms2.icc", "r"), + cmsOpenProfileFromFile("graylcms2.icc", "r"), INTENT_PERCEPTUAL); +} + + +// ----------------------------------------------------------------------------------------------------- + + +// Print the supported intents +static +void PrintSupportedIntents(void) +{ + cmsUInt32Number n, i; + cmsUInt32Number Codes[200]; + char* Descriptions[200]; + + n = cmsGetSupportedIntents(200, Codes, Descriptions); + + printf("Supported intents:\n"); + for (i=0; i < n; i++) { + printf("\t%u - %s\n", Codes[i], Descriptions[i]); + } + printf("\n"); +} + + + +// --------------------------------------------------------------------------------------- + +#ifdef LCMS_FAST_EXTENSIONS + void* cmsFast8Bitextensions(void); +#endif + +int main(int argc, char* argv[]) +{ + cmsInt32Number Exhaustive = 0; + cmsInt32Number DoSpeedTests = 1; + cmsInt32Number DoCheckTests = 1; + cmsInt32Number DoPluginTests = 1; + cmsInt32Number DoZooTests = 0; + +#ifdef _MSC_VER + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); +#endif + + + // First of all, check for the right header + if (cmsGetEncodedCMMversion() != LCMS_VERSION) { + Die("Oops, you are mixing header and shared lib!\nHeader version reports to be '%d' and shared lib '%d'\n", LCMS_VERSION, cmsGetEncodedCMMversion()); + } + + printf("LittleCMS %2.2f test bed %s %s\n\n", LCMS_VERSION / 1000.0, __DATE__, __TIME__); + + if ((argc == 2) && strcmp(argv[1], "--exhaustive") == 0) { + + Exhaustive = 1; + printf("Running exhaustive tests (will take a while...)\n\n"); + } + +#ifdef LCMS_FAST_EXTENSIONS + printf("Installing fast 8 bit extension ..."); + cmsPlugin(cmsFast8Bitextensions()); + printf("done.\n"); +#endif + + + printf("Installing debug memory plug-in ... "); + cmsPluginTHR(DbgThread(), &DebugMemHandler); + printf("done.\n"); + + printf("Installing error logger ... "); + cmsSetLogErrorHandler(FatalErrorQuit); + printf("done.\n"); + + PrintSupportedIntents(); + + Check("Base types", CheckBaseTypes); + Check("endianess", CheckEndianess); + Check("quick floor", CheckQuickFloor); + Check("quick floor word", CheckQuickFloorWord); + Check("Fixed point 15.16 representation", CheckFixedPoint15_16); + Check("Fixed point 8.8 representation", CheckFixedPoint8_8); + Check("D50 roundtrip", CheckD50Roundtrip); + + // Create utility profiles + if (DoCheckTests || DoSpeedTests) + Check("Creation of test profiles", CreateTestProfiles); + + if (DoCheckTests) { + + // Forward 1D interpolation + Check("1D interpolation in 2pt tables", Check1DLERP2); + Check("1D interpolation in 3pt tables", Check1DLERP3); + Check("1D interpolation in 4pt tables", Check1DLERP4); + Check("1D interpolation in 6pt tables", Check1DLERP6); + Check("1D interpolation in 18pt tables", Check1DLERP18); + Check("1D interpolation in descending 2pt tables", Check1DLERP2Down); + Check("1D interpolation in descending 3pt tables", Check1DLERP3Down); + Check("1D interpolation in descending 6pt tables", Check1DLERP6Down); + Check("1D interpolation in descending 18pt tables", Check1DLERP18Down); + + if (Exhaustive) { + + Check("1D interpolation in n tables", ExhaustiveCheck1DLERP); + Check("1D interpolation in descending tables", ExhaustiveCheck1DLERPDown); + } + + // Forward 3D interpolation + Check("3D interpolation Tetrahedral (float) ", Check3DinterpolationFloatTetrahedral); + Check("3D interpolation Trilinear (float) ", Check3DinterpolationFloatTrilinear); + Check("3D interpolation Tetrahedral (16) ", Check3DinterpolationTetrahedral16); + Check("3D interpolation Trilinear (16) ", Check3DinterpolationTrilinear16); + + if (Exhaustive) { + + Check("Exhaustive 3D interpolation Tetrahedral (float) ", ExaustiveCheck3DinterpolationFloatTetrahedral); + Check("Exhaustive 3D interpolation Trilinear (float) ", ExaustiveCheck3DinterpolationFloatTrilinear); + Check("Exhaustive 3D interpolation Tetrahedral (16) ", ExhaustiveCheck3DinterpolationTetrahedral16); + Check("Exhaustive 3D interpolation Trilinear (16) ", ExhaustiveCheck3DinterpolationTrilinear16); + } + + Check("Reverse interpolation 3 -> 3", CheckReverseInterpolation3x3); + Check("Reverse interpolation 4 -> 3", CheckReverseInterpolation4x3); + + + // High dimensionality interpolation + + Check("3D interpolation", Check3Dinterp); + Check("3D interpolation with granularity", Check3DinterpGranular); + Check("4D interpolation", Check4Dinterp); + Check("4D interpolation with granularity", Check4DinterpGranular); + Check("5D interpolation with granularity", Check5DinterpGranular); + Check("6D interpolation with granularity", Check6DinterpGranular); + Check("7D interpolation with granularity", Check7DinterpGranular); + Check("8D interpolation with granularity", Check8DinterpGranular); + + // Encoding of colorspaces + Check("Lab to LCh and back (float only) ", CheckLab2LCh); + Check("Lab to XYZ and back (float only) ", CheckLab2XYZ); + Check("Lab to xyY and back (float only) ", CheckLab2xyY); + Check("Lab V2 encoding", CheckLabV2encoding); + Check("Lab V4 encoding", CheckLabV4encoding); + + // BlackBody + Check("Blackbody radiator", CheckTemp2CHRM); + + // Tone curves + Check("Linear gamma curves (16 bits)", CheckGammaCreation16); + Check("Linear gamma curves (float)", CheckGammaCreationFlt); + + Check("Curve 1.8 (float)", CheckGamma18); + Check("Curve 2.2 (float)", CheckGamma22); + Check("Curve 3.0 (float)", CheckGamma30); + + Check("Curve 1.8 (table)", CheckGamma18Table); + Check("Curve 2.2 (table)", CheckGamma22Table); + Check("Curve 3.0 (table)", CheckGamma30Table); + + Check("Curve 1.8 (word table)", CheckGamma18TableWord); + Check("Curve 2.2 (word table)", CheckGamma22TableWord); + Check("Curve 3.0 (word table)", CheckGamma30TableWord); + + Check("Parametric curves", CheckParametricToneCurves); + + Check("Join curves", CheckJointCurves); + Check("Join curves descending", CheckJointCurvesDescending); + Check("Join curves degenerated", CheckReverseDegenerated); + Check("Join curves sRGB (Float)", CheckJointFloatCurves_sRGB); + Check("Join curves sRGB (16 bits)", CheckJoint16Curves_sRGB); + Check("Join curves sigmoidal", CheckJointCurvesSShaped); + + // LUT basics + Check("LUT creation & dup", CheckLUTcreation); + Check("1 Stage LUT ", Check1StageLUT); + Check("2 Stage LUT ", Check2StageLUT); + Check("2 Stage LUT (16 bits)", Check2Stage16LUT); + Check("3 Stage LUT ", Check3StageLUT); + Check("3 Stage LUT (16 bits)", Check3Stage16LUT); + Check("4 Stage LUT ", Check4StageLUT); + Check("4 Stage LUT (16 bits)", Check4Stage16LUT); + Check("5 Stage LUT ", Check5StageLUT); + Check("5 Stage LUT (16 bits) ", Check5Stage16LUT); + Check("6 Stage LUT ", Check6StageLUT); + Check("6 Stage LUT (16 bits) ", Check6Stage16LUT); + + // LUT operation + Check("Lab to Lab LUT (float only) ", CheckLab2LabLUT); + Check("XYZ to XYZ LUT (float only) ", CheckXYZ2XYZLUT); + Check("Lab to Lab MAT LUT (float only) ", CheckLab2LabMatLUT); + Check("Named Color LUT", CheckNamedColorLUT); + Check("Usual formatters", CheckFormatters16); + Check("Floating point formatters", CheckFormattersFloat); + +#ifndef CMS_NO_HALF_SUPPORT + Check("HALF formatters", CheckFormattersHalf); +#endif + // ChangeBuffersFormat + Check("ChangeBuffersFormat", CheckChangeBufferFormat); + + // MLU + Check("Multilocalized Unicode", CheckMLU); + + // Named color + Check("Named color lists", CheckNamedColorList); + + // Profile I/O (this one is huge!) + Check("Profile creation", CheckProfileCreation); + Check("Header version", CheckVersionHeaderWriting); + Check("Multilocalized profile", CheckMultilocalizedProfile); + + // Error reporting + Check("Error reporting on bad profiles", CheckErrReportingOnBadProfiles); + Check("Error reporting on bad transforms", CheckErrReportingOnBadTransforms); + + // Transforms + Check("Curves only transforms", CheckCurvesOnlyTransforms); + Check("Float Lab->Lab transforms", CheckFloatLabTransforms); + Check("Encoded Lab->Lab transforms", CheckEncodedLabTransforms); + Check("Stored identities", CheckStoredIdentities); + + Check("Matrix-shaper transform (float)", CheckMatrixShaperXFORMFloat); + Check("Matrix-shaper transform (16 bits)", CheckMatrixShaperXFORM16); + Check("Matrix-shaper transform (8 bits)", CheckMatrixShaperXFORM8); + + Check("Primaries of sRGB", CheckRGBPrimaries); + + // Known values + Check("Known values across matrix-shaper", Chack_sRGB_Float); + Check("Gray input profile", CheckInputGray); + Check("Gray Lab input profile", CheckLabInputGray); + Check("Gray output profile", CheckOutputGray); + Check("Gray Lab output profile", CheckLabOutputGray); + + Check("Matrix-shaper proofing transform (float)", CheckProofingXFORMFloat); + Check("Matrix-shaper proofing transform (16 bits)", CheckProofingXFORM16); + + Check("Gamut check", CheckGamutCheck); + + Check("CMYK roundtrip on perceptual transform", CheckCMYKRoundtrip); + + Check("CMYK perceptual transform", CheckCMYKPerceptual); + // Check("CMYK rel.col. transform", CheckCMYKRelCol); + + Check("Black ink only preservation", CheckKOnlyBlackPreserving); + Check("Black plane preservation", CheckKPlaneBlackPreserving); + + + Check("Deciding curve types", CheckV4gamma); + + Check("Black point detection", CheckBlackPoint); + Check("TAC detection", CheckTAC); + + Check("CGATS parser", CheckCGATS); + Check("CGATS parser on junk", CheckCGATS2); + Check("CGATS parser on overflow", CheckCGATS_Overflow); + Check("PostScript generator", CheckPostScript); + Check("Segment maxima GBD", CheckGBD); + Check("MD5 digest", CheckMD5); + Check("Linking", CheckLinking); + Check("floating point tags on XYZ", CheckFloatXYZ); + Check("RGB->Lab->RGB with alpha on FLT", ChecksRGB2LabFLT); + Check("Parametric curve on Rec709", CheckParametricRec709); + Check("Floating Point sampled curve with non-zero start", CheckFloatSamples); + Check("Floating Point segmented curve with short sampled segement", CheckFloatSegments); + Check("Read RAW portions", CheckReadRAW); + Check("Check MetaTag", CheckMeta); + Check("Null transform on floats", CheckFloatNULLxform); + Check("Set free a tag", CheckRemoveTag); + Check("Matrix simplification", CheckMatrixSimplify); + Check("Planar 8 optimization", CheckPlanar8opt); + Check("Swap endian feature", CheckSE); + Check("Transform line stride RGB", CheckTransformLineStride); + Check("Forged MPE profile", CheckForgedMPE); + Check("Proofing intersection", CheckProofingIntersection); + } + + if (DoPluginTests) + { + + Check("Context memory handling", CheckAllocContext); + Check("Simple context functionality", CheckSimpleContext); + Check("Alarm codes context", CheckAlarmColorsContext); + Check("Adaptation state context", CheckAdaptationStateContext); + Check("1D interpolation plugin", CheckInterp1DPlugin); + Check("3D interpolation plugin", CheckInterp3DPlugin); + Check("Parametric curve plugin", CheckParametricCurvePlugin); + Check("Formatters plugin", CheckFormattersPlugin); + Check("Tag type plugin", CheckTagTypePlugin); + Check("MPE type plugin", CheckMPEPlugin); + Check("Optimization plugin", CheckOptimizationPlugin); + Check("Rendering intent plugin", CheckIntentPlugin); + Check("Full transform plugin", CheckTransformPlugin); + Check("Mutex plugin", CheckMutexPlugin); + + } + + + if (DoSpeedTests) + SpeedTest(); + + +#ifdef CMS_IS_WINDOWS_ + if (DoZooTests) + CheckProfileZOO(NULL); +#endif + + DebugMemPrintTotals(); + + cmsUnregisterPlugins(DbgThread()); + + // Cleanup + if (DoCheckTests || DoSpeedTests) + RemoveTestProfiles(); + + return TotalFail; +} diff --git a/lcms2mt/testbed/testcms2.h b/lcms2mt/testbed/testcms2.h new file mode 100755 index 000000000..043e64b1c --- /dev/null +++ b/lcms2mt/testbed/testcms2.h @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------------------- +// +// Little Color Management System +// Copyright (c) 1998-2014 Marti Maria Saguer +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//--------------------------------------------------------------------------------- +// + +#ifndef TESTCMS2_H +#define TESTCMS2_H + +#ifdef _MSC_VER +# define _CRT_SECURE_NO_WARNINGS 1 +# include "crtdbg.h" +# include <io.h> +#endif + +#include "lcms2_internal.h" + +#define cmsmin(a, b) (((a) < (b)) ? (a) : (b)) + +// Used to mark special pointers +void DebugMemDontCheckThis(void *Ptr); + + +cmsBool IsGoodVal(const char *title, cmsFloat64Number in, cmsFloat64Number out, cmsFloat64Number max); +cmsBool IsGoodFixed15_16(const char *title, cmsFloat64Number in, cmsFloat64Number out); +cmsBool IsGoodFixed8_8(const char *title, cmsFloat64Number in, cmsFloat64Number out); +cmsBool IsGoodWord(const char *title, cmsUInt16Number in, cmsUInt16Number out); +cmsBool IsGoodWordPrec(const char *title, cmsUInt16Number in, cmsUInt16Number out, cmsUInt16Number maxErr); + +void* PluginMemHandler(void); +cmsContext WatchDogContext(void* usr); + +void ResetFatalError(void); +void Die(const char* Reason, ...); +void Dot(void); +void Fail(const char* frm, ...); +void SubTest(const char* frm, ...); +void TestMemoryLeaks(cmsBool ok); +void Say(const char* str); + +// Plug-in tests +cmsInt32Number CheckSimpleContext(void); +cmsInt32Number CheckAllocContext(void); +cmsInt32Number CheckAlarmColorsContext(void); +cmsInt32Number CheckAdaptationStateContext(void); +cmsInt32Number CheckInterp1DPlugin(void); +cmsInt32Number CheckInterp3DPlugin(void); +cmsInt32Number CheckParametricCurvePlugin(void); +cmsInt32Number CheckFormattersPlugin(void); +cmsInt32Number CheckTagTypePlugin(void); +cmsInt32Number CheckMPEPlugin(void); +cmsInt32Number CheckOptimizationPlugin(void); +cmsInt32Number CheckIntentPlugin(void); +cmsInt32Number CheckTransformPlugin(void); +cmsInt32Number CheckMutexPlugin(void); + + +cmsInt32Number CheckOptimizationPluginLeak(void); + +// Zoo +void CheckProfileZOO(cmsContext ContextID); + +#endif + diff --git a/lcms2mt/testbed/testplugin.c b/lcms2mt/testbed/testplugin.c new file mode 100755 index 000000000..f871ddb6f --- /dev/null +++ b/lcms2mt/testbed/testplugin.c @@ -0,0 +1,1426 @@ +//--------------------------------------------------------------------------------- +// +// Little Color Management System +// Copyright (c) 1998-2017 Marti Maria Saguer +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//--------------------------------------------------------------------------------- +// + +#include "testcms2.h" + +// -------------------------------------------------------------------------------------------------- +// Auxiliar, duplicate a context and mark the block as non-debug because in this case the allocator +// and deallocator have different context owners +// -------------------------------------------------------------------------------------------------- + +static +cmsContext DupContext(cmsContext src, void* Data) +{ + cmsContext cpy = cmsDupContext(src, Data); + + DebugMemDontCheckThis(cpy); + + return cpy; +} + +// -------------------------------------------------------------------------------------------------- +// Simple context functions +// -------------------------------------------------------------------------------------------------- + +// Allocation order +cmsInt32Number CheckAllocContext(void) +{ + cmsContext c1, c2, c3, c4; + + c1 = cmsCreateContext(NULL, NULL); // This creates a context by using the normal malloc + DebugMemDontCheckThis(c1); + cmsDeleteContext(c1); + + c2 = cmsCreateContext(PluginMemHandler(), NULL); // This creates a context by using the debug malloc + DebugMemDontCheckThis(c2); + cmsDeleteContext(c2); + + c1 = cmsCreateContext(NULL, NULL); + DebugMemDontCheckThis(c1); + + c2 = cmsCreateContext(PluginMemHandler(), NULL); + DebugMemDontCheckThis(c2); + + cmsPluginTHR(c1, PluginMemHandler()); // Now the context have custom allocators + + c3 = DupContext(c1, NULL); + c4 = DupContext(c2, NULL); + + cmsDeleteContext(c1); // Should be deleted by using nomal malloc + cmsDeleteContext(c2); // Should be deleted by using debug malloc + cmsDeleteContext(c3); // Should be deleted by using nomal malloc + cmsDeleteContext(c4); // Should be deleted by using debug malloc + + return 1; +} + +// Test the very basic context capabilities +cmsInt32Number CheckSimpleContext(void) +{ + int a = 1; + int b = 32; + cmsInt32Number rc = 0; + + cmsContext c1, c2, c3; + + // This function creates a context with a special + // memory manager that check allocation + c1 = WatchDogContext(&a); + cmsDeleteContext(c1); + + c1 = WatchDogContext(&a); + + // Let's check duplication + c2 = DupContext(c1, NULL); + c3 = DupContext(c2, NULL); + + // User data should have been propagated + rc = (*(int*) cmsGetContextUserData(c3)) == 1 ; + + // Free resources + cmsDeleteContext(c1); + cmsDeleteContext(c2); + cmsDeleteContext(c3); + + if (!rc) { + Fail("Creation of user data failed"); + return 0; + } + + // Back to create 3 levels of inherance + c1 = cmsCreateContext(NULL, &a); + DebugMemDontCheckThis(c1); + + c2 = DupContext(c1, NULL); + c3 = DupContext(c2, &b); + + rc = (*(int*) cmsGetContextUserData(c3)) == 32 ; + + cmsDeleteContext(c1); + cmsDeleteContext(c2); + cmsDeleteContext(c3); + + if (!rc) { + Fail("Modification of user data failed"); + return 0; + } + + // All seems ok + return rc; +} + + + + +// -------------------------------------------------------------------------------------------------- +//Alarm color functions +// -------------------------------------------------------------------------------------------------- + +// This function tests the alarm codes across contexts +cmsInt32Number CheckAlarmColorsContext(void) +{ + cmsInt32Number rc = 0; + const cmsUInt16Number codes[] = {0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888, 0x9999, 0xaaaa, 0xbbbb, 0xcccc, 0xdddd, 0xeeee, 0xffff}; + cmsUInt16Number out[16]; + cmsContext c1, c2, c3; + int i; + + c1 = WatchDogContext(NULL); + + cmsSetAlarmCodesTHR(c1, codes); + c2 = DupContext(c1, NULL); + c3 = DupContext(c2, NULL); + + cmsGetAlarmCodesTHR(c3, out); + + rc = 1; + for (i=0; i < 16; i++) { + if (out[i] != codes[i]) { + Fail("Bad alarm code %x != %x", out[i], codes[i]); + rc = 0; + break; + } + } + + cmsDeleteContext(c1); + cmsDeleteContext(c2); + cmsDeleteContext(c3); + + return rc; +} + + +// -------------------------------------------------------------------------------------------------- +//Adaptation state functions +// -------------------------------------------------------------------------------------------------- + +// Similar to the previous, but for adaptation state +cmsInt32Number CheckAdaptationStateContext(void) +{ + cmsInt32Number rc = 0; + cmsContext c1, c2, c3; + cmsFloat64Number old1, old2; + + old1 = cmsSetAdaptationStateTHR(NULL, -1); + + c1 = WatchDogContext(NULL); + + cmsSetAdaptationStateTHR(c1, 0.7); + + c2 = DupContext(c1, NULL); + c3 = DupContext(c2, NULL); + + rc = IsGoodVal("Adaptation state", cmsSetAdaptationStateTHR(c3, -1), 0.7, 0.001); + + cmsDeleteContext(c1); + cmsDeleteContext(c2); + cmsDeleteContext(c3); + + old2 = cmsSetAdaptationStateTHR(NULL, -1); + + if (old1 != old2) { + Fail("Adaptation state has changed"); + return 0; + } + + return rc; +} + +// -------------------------------------------------------------------------------------------------- +// Interpolation plugin check: A fake 1D and 3D interpolation will be used to test the functionality. +// -------------------------------------------------------------------------------------------------- + +// This fake interpolation takes always the closest lower node in the interpolation table for 1D +static +void Fake1Dfloat(cmsContext ContextID, const cmsFloat32Number Value[], + cmsFloat32Number Output[], + const cmsInterpParams* p) +{ + cmsFloat32Number val2; + int cell; + const cmsFloat32Number* LutTable = (const cmsFloat32Number*) p ->Table; + + // Clip upper values + if (Value[0] >= 1.0) { + Output[0] = LutTable[p -> Domain[0]]; + return; + } + + val2 = p -> Domain[0] * Value[0]; + cell = (int) floor(val2); + Output[0] = LutTable[cell] ; +} + +// This fake interpolation just uses scrambled negated indexes for output +static +void Fake3D16(cmsContext ContextID, register const cmsUInt16Number Input[], + register cmsUInt16Number Output[], + register const struct _cms_interp_struc* p) +{ + Output[0] = 0xFFFF - Input[2]; + Output[1] = 0xFFFF - Input[1]; + Output[2] = 0xFFFF - Input[0]; +} + +// The factory chooses interpolation routines on depending on certain conditions. +cmsInterpFunction my_Interpolators_Factory(cmsContext ContextID, cmsUInt32Number nInputChannels, + cmsUInt32Number nOutputChannels, + cmsUInt32Number dwFlags) +{ + cmsInterpFunction Interpolation; + cmsBool IsFloat = (dwFlags & CMS_LERP_FLAGS_FLOAT); + + // Initialize the return to zero as a non-supported mark + memset(&Interpolation, 0, sizeof(Interpolation)); + + // For 1D to 1D and floating point + if (nInputChannels == 1 && nOutputChannels == 1 && IsFloat) { + + Interpolation.LerpFloat = Fake1Dfloat; + } + else + if (nInputChannels == 3 && nOutputChannels == 3 && !IsFloat) { + + // For 3D to 3D and 16 bits + Interpolation.Lerp16 = Fake3D16; + } + + // Here is the interpolation + return Interpolation; +} + +// Interpolation plug-in +static +cmsPluginInterpolation InterpPluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginInterpolationSig, NULL }, + my_Interpolators_Factory +}; + + +// This is the check code for 1D interpolation plug-in +cmsInt32Number CheckInterp1DPlugin(void) +{ + cmsToneCurve* Sampled1D = NULL; + cmsContext ctx = NULL; + cmsContext cpy = NULL; + const cmsFloat32Number tab[] = { 0.0f, 0.10f, 0.20f, 0.30f, 0.40f, 0.50f, 0.60f, 0.70f, 0.80f, 0.90f, 1.00f }; // A straight line + + // 1st level context + ctx = WatchDogContext(NULL); + if (ctx == NULL) { + Fail("Cannot create context"); + goto Error; + } + + cmsPluginTHR(ctx, &InterpPluginSample); + + cpy = DupContext(ctx, NULL); + if (cpy == NULL) { + Fail("Cannot create context (2)"); + goto Error; + } + + Sampled1D = cmsBuildTabulatedToneCurveFloat(cpy, 11, tab); + if (Sampled1D == NULL) { + Fail("Cannot create tone curve (1)"); + goto Error; + } + + // Do some interpolations with the plugin + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.10f), 0.10, 0.01)) goto Error; + if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.13f), 0.10, 0.01)) goto Error; + if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.55f), 0.50, 0.01)) goto Error; + if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.9999f), 0.90, 0.01)) goto Error; + + cmsFreeToneCurve(cpy, Sampled1D); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + // Now in global context + Sampled1D = cmsBuildTabulatedToneCurveFloat(NULL, 11, tab); + if (Sampled1D == NULL) { + Fail("Cannot create tone curve (2)"); + goto Error; + } + + // Now without the plug-in + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.10f), 0.10, 0.001)) goto Error; + if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.13f), 0.13, 0.001)) goto Error; + if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.55f), 0.55, 0.001)) goto Error; + if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.9999f), 0.9999, 0.001)) goto Error; + + cmsFreeToneCurve(NULL, Sampled1D); + return 1; + +Error: + if (ctx != NULL) cmsDeleteContext(ctx); + if (cpy != NULL) cmsDeleteContext(ctx); + if (Sampled1D != NULL) cmsFreeToneCurve(NULL, Sampled1D); + return 0; + +} + +// Checks the 3D interpolation +cmsInt32Number CheckInterp3DPlugin(void) +{ + + cmsPipeline* p; + cmsStage* clut; + cmsContext ctx; + cmsUInt16Number In[3], Out[3]; + cmsUInt16Number identity[] = { + + 0, 0, 0, + 0, 0, 0xffff, + 0, 0xffff, 0, + 0, 0xffff, 0xffff, + 0xffff, 0, 0, + 0xffff, 0, 0xffff, + 0xffff, 0xffff, 0, + 0xffff, 0xffff, 0xffff + }; + + + ctx = WatchDogContext(NULL); + if (ctx == NULL) { + Fail("Cannot create context"); + return 0; + } + + cmsPluginTHR(ctx, &InterpPluginSample); + + p = cmsPipelineAlloc(ctx, 3, 3); + clut = cmsStageAllocCLut16bit(ctx, 2, 3, 3, identity); + cmsPipelineInsertStage(ctx, p, cmsAT_BEGIN, clut); + + // Do some interpolations with the plugin + + In[0] = 0; In[1] = 0; In[2] = 0; + cmsPipelineEval16(ctx, In, Out, p); + + if (!IsGoodWord("0", Out[0], 0xFFFF - 0)) goto Error; + if (!IsGoodWord("1", Out[1], 0xFFFF - 0)) goto Error; + if (!IsGoodWord("2", Out[2], 0xFFFF - 0)) goto Error; + + In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC; + cmsPipelineEval16(ctx, In, Out, p); + + if (!IsGoodWord("0", 0xFFFF - 0x9ABC, Out[0])) goto Error; + if (!IsGoodWord("1", 0xFFFF - 0x5678, Out[1])) goto Error; + if (!IsGoodWord("2", 0xFFFF - 0x1234, Out[2])) goto Error; + + cmsPipelineFree(ctx, p); + cmsDeleteContext(ctx); + + // Now without the plug-in + + p = cmsPipelineAlloc(NULL, 3, 3); + clut = cmsStageAllocCLut16bit(NULL, 2, 3, 3, identity); + cmsPipelineInsertStage(NULL, p, cmsAT_BEGIN, clut); + + In[0] = 0; In[1] = 0; In[2] = 0; + cmsPipelineEval16(NULL, In, Out, p); + + if (!IsGoodWord("0", 0, Out[0])) goto Error; + if (!IsGoodWord("1", 0, Out[1])) goto Error; + if (!IsGoodWord("2", 0, Out[2])) goto Error; + + In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC; + cmsPipelineEval16(NULL, In, Out, p); + + if (!IsGoodWord("0", 0x1234, Out[0])) goto Error; + if (!IsGoodWord("1", 0x5678, Out[1])) goto Error; + if (!IsGoodWord("2", 0x9ABC, Out[2])) goto Error; + + cmsPipelineFree(NULL, p); + return 1; + +Error: + cmsPipelineFree(NULL, p); + return 0; +} + +// -------------------------------------------------------------------------------------------------- +// Parametric curve plugin check: sin(x)/cos(x) function will be used to test the functionality. +// -------------------------------------------------------------------------------------------------- + +#define TYPE_SIN 1000 +#define TYPE_COS 1010 +#define TYPE_TAN 1020 +#define TYPE_709 709 + +static cmsFloat64Number my_fns(cmsContext ContextID, cmsInt32Number Type, + const cmsFloat64Number Params[], + cmsFloat64Number R) +{ + cmsFloat64Number Val; + switch (Type) { + + case TYPE_SIN: + Val = Params[0]* sin(R * M_PI); + break; + + case -TYPE_SIN: + Val = asin(R) / (M_PI * Params[0]); + break; + + case TYPE_COS: + Val = Params[0]* cos(R * M_PI); + break; + + case -TYPE_COS: + Val = acos(R) / (M_PI * Params[0]); + break; + + default: return -1.0; + + } + + return Val; +} + +static +cmsFloat64Number my_fns2(cmsContext ContextID, cmsInt32Number Type, + const cmsFloat64Number Params[], + cmsFloat64Number R) +{ + cmsFloat64Number Val; + switch (Type) { + + case TYPE_TAN: + Val = Params[0]* tan(R * M_PI); + break; + + case -TYPE_TAN: + Val = atan(R) / (M_PI * Params[0]); + break; + + default: return -1.0; + } + + return Val; +} + + +static double Rec709Math(cmsContext ContextID, int Type, const double Params[], double R) +{ + double Fun = 0; + + switch (Type) + { + case 709: + + if (R <= (Params[3]*Params[4])) Fun = R / Params[3]; + else Fun = pow(((R - Params[2])/Params[1]), Params[0]); + break; + + case -709: + + if (R <= Params[4]) Fun = R * Params[3]; + else Fun = Params[1] * pow(R, (1/Params[0])) + Params[2]; + break; + } + return Fun; +} + + +// Add nonstandard TRC curves -> Rec709 + +cmsPluginParametricCurves Rec709Plugin = { + + { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL }, + + 1, {TYPE_709}, {5}, Rec709Math + +}; + + +static +cmsPluginParametricCurves CurvePluginSample = { + { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL }, + + 2, // nFunctions + { TYPE_SIN, TYPE_COS }, // Function Types + { 1, 1 }, // ParameterCount + my_fns // Evaluator +}; + +static +cmsPluginParametricCurves CurvePluginSample2 = { + { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL }, + + 1, // nFunctions + { TYPE_TAN}, // Function Types + { 1 }, // ParameterCount + my_fns2 // Evaluator +}; + +// -------------------------------------------------------------------------------------------------- +// In this test, the DupContext function will be checked as well +// -------------------------------------------------------------------------------------------------- +cmsInt32Number CheckParametricCurvePlugin(void) +{ + cmsContext ctx = NULL; + cmsContext cpy = NULL; + cmsToneCurve* sinus; + cmsToneCurve* cosinus; + cmsToneCurve* tangent; + cmsToneCurve* reverse_sinus; + cmsToneCurve* reverse_cosinus; + cmsFloat64Number scale = 1.0; + + ctx = WatchDogContext(NULL); + + cmsPluginTHR(ctx, &CurvePluginSample); + + cpy = DupContext(ctx, NULL); + + cmsPluginTHR(cpy, &CurvePluginSample2); + + sinus = cmsBuildParametricToneCurve(cpy, TYPE_SIN, &scale); + cosinus = cmsBuildParametricToneCurve(cpy, TYPE_COS, &scale); + tangent = cmsBuildParametricToneCurve(cpy, TYPE_TAN, &scale); + reverse_sinus = cmsReverseToneCurve(cpy, sinus); + reverse_cosinus = cmsReverseToneCurve(cpy, cosinus); + + + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, sinus, 0.10f), sin(0.10 * M_PI) , 0.001)) goto Error; + if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, sinus, 0.60f), sin(0.60* M_PI), 0.001)) goto Error; + if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, sinus, 0.90f), sin(0.90* M_PI), 0.001)) goto Error; + + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, cosinus, 0.10f), cos(0.10* M_PI), 0.001)) goto Error; + if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, cosinus, 0.60f), cos(0.60* M_PI), 0.001)) goto Error; + if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, cosinus, 0.90f), cos(0.90* M_PI), 0.001)) goto Error; + + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, tangent, 0.10f), tan(0.10* M_PI), 0.001)) goto Error; + if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, tangent, 0.60f), tan(0.60* M_PI), 0.001)) goto Error; + if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, tangent, 0.90f), tan(0.90* M_PI), 0.001)) goto Error; + + + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.10f), asin(0.10)/M_PI, 0.001)) goto Error; + if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.60f), asin(0.60)/M_PI, 0.001)) goto Error; + if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.90f), asin(0.90)/M_PI, 0.001)) goto Error; + + if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.10f), acos(0.10)/M_PI, 0.001)) goto Error; + if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.60f), acos(0.60)/M_PI, 0.001)) goto Error; + if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.90f), acos(0.90)/M_PI, 0.001)) goto Error; + + cmsFreeToneCurve(cpy, sinus); + cmsFreeToneCurve(cpy, cosinus); + cmsFreeToneCurve(cpy, tangent); + cmsFreeToneCurve(cpy, reverse_sinus); + cmsFreeToneCurve(cpy, reverse_cosinus); + + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + return 1; + +Error: + + cmsFreeToneCurve(cpy, sinus); + cmsFreeToneCurve(cpy, reverse_sinus); + cmsFreeToneCurve(cpy, cosinus); + cmsFreeToneCurve(cpy, reverse_cosinus); + + if (ctx != NULL) cmsDeleteContext(ctx); + if (cpy != NULL) cmsDeleteContext(cpy); + return 0; +} + +// -------------------------------------------------------------------------------------------------- +// formatters plugin check: 5-6-5 RGB format +// -------------------------------------------------------------------------------------------------- + +// We define this special type as 0 bytes not float, and set the upper bit + +#define TYPE_RGB_565 (COLORSPACE_SH(PT_RGB)|CHANNELS_SH(3)|BYTES_SH(0) | (1 << 23)) + +cmsUInt8Number* my_Unroll565(cmsContext ContextID, register struct _cmstransform_struct* nfo, + register cmsUInt16Number wIn[], + register cmsUInt8Number* accum, + register cmsUInt32Number Stride) +{ + cmsUInt16Number pixel = *(cmsUInt16Number*) accum; // Take whole pixel + + double r = floor(((double) (pixel & 31) * 65535.0) / 31.0 + 0.5); + double g = floor((((pixel >> 5) & 63) * 65535.0) / 63.0 + 0.5); + double b = floor((((pixel >> 11) & 31) * 65535.0) / 31.0 + 0.5); + + wIn[2] = (cmsUInt16Number) r; + wIn[1] = (cmsUInt16Number) g; + wIn[0] = (cmsUInt16Number) b; + + return accum + 2; +} + +cmsUInt8Number* my_Pack565(cmsContext ContextID, register _cmsTRANSFORM* info, + register cmsUInt16Number wOut[], + register cmsUInt8Number* output, + register cmsUInt32Number Stride) +{ + + register cmsUInt16Number pixel; + int r, g, b; + + r = (int) floor(( wOut[2] * 31) / 65535.0 + 0.5); + g = (int) floor(( wOut[1] * 63) / 65535.0 + 0.5); + b = (int) floor(( wOut[0] * 31) / 65535.0 + 0.5); + + + pixel = (r & 31) | (( g & 63) << 5) | ((b & 31) << 11); + + + *(cmsUInt16Number*) output = pixel; + return output + 2; +} + + +cmsFormatter my_FormatterFactory(cmsContext ContextID, cmsUInt32Number Type, + cmsFormatterDirection Dir, + cmsUInt32Number dwFlags) +{ + cmsFormatter Result = { NULL }; + + if ((Type == TYPE_RGB_565) && + !(dwFlags & CMS_PACK_FLAGS_FLOAT) && + (Dir == cmsFormatterInput)) { + Result.Fmt16 = my_Unroll565; + } + return Result; +} + + +cmsFormatter my_FormatterFactory2(cmsContext ContextID, cmsUInt32Number Type, + cmsFormatterDirection Dir, + cmsUInt32Number dwFlags) +{ + cmsFormatter Result = { NULL }; + + if ((Type == TYPE_RGB_565) && + !(dwFlags & CMS_PACK_FLAGS_FLOAT) && + (Dir == cmsFormatterOutput)) { + Result.Fmt16 = my_Pack565; + } + return Result; +} + +static +cmsPluginFormatters FormattersPluginSample = { {cmsPluginMagicNumber, + 2060, + cmsPluginFormattersSig, + NULL}, + my_FormatterFactory }; + + + +static +cmsPluginFormatters FormattersPluginSample2 = { {cmsPluginMagicNumber, + 2060, + cmsPluginFormattersSig, + NULL}, + my_FormatterFactory2 }; + + +cmsInt32Number CheckFormattersPlugin(void) +{ + cmsContext ctx = WatchDogContext(NULL); + cmsContext cpy; + cmsHTRANSFORM xform; + cmsUInt16Number stream[]= { 0xffffU, 0x1234U, 0x0000U, 0x33ddU }; + cmsUInt16Number result[4]; + int i; + + cmsPluginTHR(ctx, &FormattersPluginSample); + + cpy = DupContext(ctx, NULL); + + cmsPluginTHR(cpy, &FormattersPluginSample2); + + xform = cmsCreateTransformTHR(cpy, NULL, TYPE_RGB_565, NULL, TYPE_RGB_565, INTENT_PERCEPTUAL, cmsFLAGS_NULLTRANSFORM); + + cmsDoTransform(cpy, xform, stream, result, 4); + + cmsDeleteTransform(cpy, xform); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + for (i=0; i < 4; i++) + if (stream[i] != result[i]) return 0; + + return 1; +} + +// -------------------------------------------------------------------------------------------------- +// TagTypePlugin plugin check +// -------------------------------------------------------------------------------------------------- + +#define SigIntType ((cmsTagTypeSignature) 0x74747448) // 'tttH' +#define SigInt ((cmsTagSignature) 0x74747448) // 'tttH' + +static +void *Type_int_Read(cmsContext ContextID, struct _cms_typehandler_struct* self, + cmsIOHANDLER* io, + cmsUInt32Number* nItems, + cmsUInt32Number SizeOfTag) +{ + cmsUInt32Number* Ptr = (cmsUInt32Number*) _cmsMalloc(ContextID, sizeof(cmsUInt32Number)); + if (Ptr == NULL) return NULL; + if (!_cmsReadUInt32Number(ContextID, io, Ptr)) return NULL; + *nItems = 1; + return Ptr; +} + +static +cmsBool Type_int_Write(cmsContext ContextID, struct _cms_typehandler_struct* self, + cmsIOHANDLER* io, + void* Ptr, cmsUInt32Number nItems) +{ + return _cmsWriteUInt32Number(ContextID, io, *(cmsUInt32Number*) Ptr); +} + +static +void* Type_int_Dup(cmsContext ContextID, struct _cms_typehandler_struct* self, + const void *Ptr, cmsUInt32Number n) +{ + return _cmsDupMem(ContextID, Ptr, n * sizeof(cmsUInt32Number)); +} + +void Type_int_Free(cmsContext ContextID, struct _cms_typehandler_struct* self, + void* Ptr) +{ + _cmsFree(ContextID, Ptr); +} + + +static cmsPluginTag HiddenTagPluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginTagSig, NULL}, + SigInt, { 1, 1, { SigIntType }, NULL } +}; + +static cmsPluginTagType TagTypePluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginTagTypeSig, (cmsPluginBase*) &HiddenTagPluginSample}, + { SigIntType, Type_int_Read, Type_int_Write, Type_int_Dup, Type_int_Free, 0 } +}; + + +cmsInt32Number CheckTagTypePlugin(void) +{ + cmsContext ctx = NULL; + cmsContext cpy = NULL; + cmsHPROFILE h = NULL; + cmsUInt32Number myTag = 1234; + cmsUInt32Number rc = 0; + char* data = NULL; + cmsUInt32Number *ptr = NULL; + cmsUInt32Number clen = 0; + + ctx = WatchDogContext(NULL); + cmsPluginTHR(ctx, &TagTypePluginSample); + + cpy = DupContext(ctx, NULL); + + h = cmsCreateProfilePlaceholder(cpy); + if (h == NULL) { + Fail("Create placeholder failed"); + goto Error; + } + + + if (!cmsWriteTag(cpy, h, SigInt, &myTag)) { + Fail("Plug-in failed"); + goto Error; + } + + rc = cmsSaveProfileToMem(cpy, h, NULL, &clen); + if (!rc) { + Fail("Fetch mem size failed"); + goto Error; + } + + + data = (char*) malloc(clen); + if (data == NULL) { + Fail("malloc failed ?!?"); + goto Error; + } + + + rc = cmsSaveProfileToMem(cpy, h, data, &clen); + if (!rc) { + Fail("Save to mem failed"); + goto Error; + } + + cmsCloseProfile(cpy, h); + + cmsSetLogErrorHandler(NULL); + h = cmsOpenProfileFromMem(data, clen); + if (h == NULL) { + Fail("Open profile failed"); + goto Error; + } + + ptr = (cmsUInt32Number*) cmsReadTag(cpy, h, SigInt); + if (ptr != NULL) { + + Fail("read tag/context switching failed"); + goto Error; + } + + cmsCloseProfile(cpy, h); + ResetFatalError(); + + h = cmsOpenProfileFromMemTHR(cpy, data, clen); + if (h == NULL) { + Fail("Open profile from mem failed"); + goto Error; + } + + // Get rid of data + free(data); data = NULL; + + ptr = (cmsUInt32Number*) cmsReadTag(cpy, h, SigInt); + if (ptr == NULL) { + Fail("Read tag/conext switching failed (2)"); + return 0; + } + + rc = (*ptr == 1234); + + cmsCloseProfile(cpy, h); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + return rc; + +Error: + + if (h != NULL) cmsCloseProfile(cpy, h); + if (ctx != NULL) cmsDeleteContext(ctx); + if (cpy != NULL) cmsDeleteContext(cpy); + if (data) free(data); + + return 0; +} + +// -------------------------------------------------------------------------------------------------- +// MPE plugin check: +// -------------------------------------------------------------------------------------------------- +#define SigNegateType ((cmsStageSignature)0x6E202020) + +static +void EvaluateNegate(cmsContext ContextID, const cmsFloat32Number In[], + cmsFloat32Number Out[], + const cmsStage *mpe) +{ + Out[0] = 1.0f - In[0]; + Out[1] = 1.0f - In[1]; + Out[2] = 1.0f - In[2]; +} + +static +cmsStage* StageAllocNegate(cmsContext ContextID) +{ + return _cmsStageAllocPlaceholder(ContextID, + SigNegateType, 3, 3, EvaluateNegate, + NULL, NULL, NULL); +} + +static +void *Type_negate_Read(cmsContext ContextID, struct _cms_typehandler_struct* self, + cmsIOHANDLER* io, + cmsUInt32Number* nItems, + cmsUInt32Number SizeOfTag) +{ + cmsUInt16Number Chans; + if (!_cmsReadUInt16Number(ContextID, io, &Chans)) return NULL; + if (Chans != 3) return NULL; + + *nItems = 1; + return StageAllocNegate(ContextID); +} + +static +cmsBool Type_negate_Write(cmsContext ContextID, struct _cms_typehandler_struct* self, + cmsIOHANDLER* io, + void* Ptr, cmsUInt32Number nItems) +{ + + if (!_cmsWriteUInt16Number(ContextID, io, 3)) return FALSE; + return TRUE; +} + +static +cmsPluginMultiProcessElement MPEPluginSample = { + + {cmsPluginMagicNumber, 2060, cmsPluginMultiProcessElementSig, NULL}, + + { (cmsTagTypeSignature) SigNegateType, Type_negate_Read, Type_negate_Write, NULL, NULL, 0 } +}; + + +cmsInt32Number CheckMPEPlugin(void) +{ + cmsContext ctx = NULL; + cmsContext cpy = NULL; + cmsHPROFILE h = NULL; + cmsUInt32Number myTag = 1234; + cmsUInt32Number rc = 0; + char* data = NULL; + cmsUInt32Number clen = 0; + cmsFloat32Number In[3], Out[3]; + cmsPipeline* pipe; + + ctx = WatchDogContext(NULL); + cmsPluginTHR(ctx, &MPEPluginSample); + + cpy = DupContext(ctx, NULL); + + h = cmsCreateProfilePlaceholder(cpy); + if (h == NULL) { + Fail("Create placeholder failed"); + goto Error; + } + + pipe = cmsPipelineAlloc(cpy, 3, 3); + cmsPipelineInsertStage(cpy, pipe, cmsAT_BEGIN, StageAllocNegate(cpy)); + + + In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f; + cmsPipelineEvalFloat(cpy, In, Out, pipe); + + rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) && + IsGoodVal("1", Out[1], 1.0-In[1], 0.001) && + IsGoodVal("2", Out[2], 1.0-In[2], 0.001)); + + if (!rc) { + Fail("Pipeline failed"); + goto Error; + } + + if (!cmsWriteTag(cpy, h, cmsSigDToB3Tag, pipe)) { + Fail("Plug-in failed"); + goto Error; + } + + // This cleans the stage as well + cmsPipelineFree(cpy, pipe); + + rc = cmsSaveProfileToMem(cpy, h, NULL, &clen); + if (!rc) { + Fail("Fetch mem size failed"); + goto Error; + } + + + data = (char*) malloc(clen); + if (data == NULL) { + Fail("malloc failed ?!?"); + goto Error; + } + + + rc = cmsSaveProfileToMem(cpy, h, data, &clen); + if (!rc) { + Fail("Save to mem failed"); + goto Error; + } + + cmsCloseProfile(cpy, h); + + cmsSetLogErrorHandler(NULL); + h = cmsOpenProfileFromMem(data, clen); + if (h == NULL) { + Fail("Open profile failed"); + goto Error; + } + + pipe = (cmsPipeline*) cmsReadTag(cpy, h, cmsSigDToB3Tag); + if (pipe != NULL) { + + // Unsupported stage, should fail + Fail("read tag/context switching failed"); + goto Error; + } + + cmsCloseProfile(cpy, h); + + ResetFatalError(); + + h = cmsOpenProfileFromMemTHR(cpy, data, clen); + if (h == NULL) { + Fail("Open profile from mem failed"); + goto Error; + } + + // Get rid of data + free(data); data = NULL; + + pipe = (cmsPipeline*) cmsReadTag(cpy, h, cmsSigDToB3Tag); + if (pipe == NULL) { + Fail("Read tag/conext switching failed (2)"); + return 0; + } + + // Evaluate for negation + In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f; + cmsPipelineEvalFloat(cpy, In, Out, pipe); + + rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) && + IsGoodVal("1", Out[1], 1.0-In[1], 0.001) && + IsGoodVal("2", Out[2], 1.0-In[2], 0.001)); + + cmsCloseProfile(cpy, h); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + return rc; + +Error: + + if (h != NULL) cmsCloseProfile(ctx, h); + if (ctx != NULL) cmsDeleteContext(ctx); + if (cpy != NULL) cmsDeleteContext(cpy); + if (data) free(data); + + return 0; +} + + +// -------------------------------------------------------------------------------------------------- +// Optimization plugin check: +// -------------------------------------------------------------------------------------------------- + +static +void FastEvaluateCurves(cmsContext ContextID, register const cmsUInt16Number In[], + register cmsUInt16Number Out[], + register const void* Data) +{ + Out[0] = In[0]; +} + +static +cmsBool MyOptimize(cmsContext ContextID, cmsPipeline** Lut, + cmsUInt32Number Intent, + cmsUInt32Number* InputFormat, + cmsUInt32Number* OutputFormat, + cmsUInt32Number* dwFlags) +{ + cmsStage* mpe; + _cmsStageToneCurvesData* Data; + + // Only curves in this LUT? All are identities? + for (mpe = cmsPipelineGetPtrToFirstStage(ContextID, *Lut); + mpe != NULL; + mpe = cmsStageNext(ContextID, mpe)) { + + if (cmsStageType(ContextID, mpe) != cmsSigCurveSetElemType) return FALSE; + + // Check for identity + Data = (_cmsStageToneCurvesData*) cmsStageData(ContextID, mpe); + if (Data ->nCurves != 1) return FALSE; + if (cmsEstimateGamma(ContextID, Data->TheCurves[0], 0.1) > 1.0) return FALSE; + + } + + *dwFlags |= cmsFLAGS_NOCACHE; + _cmsPipelineSetOptimizationParameters(ContextID, *Lut, FastEvaluateCurves, NULL, NULL, NULL); + + return TRUE; +} + +cmsPluginOptimization OptimizationPluginSample = { + + {cmsPluginMagicNumber, 2060, cmsPluginOptimizationSig, NULL}, + MyOptimize +}; + + +cmsInt32Number CheckOptimizationPlugin(void) +{ + cmsContext ctx = WatchDogContext(NULL); + cmsContext cpy; + cmsHTRANSFORM xform; + cmsUInt8Number In[]= { 10, 20, 30, 40 }; + cmsUInt8Number Out[4]; + cmsToneCurve* Linear[1]; + cmsHPROFILE h; + int i; + + cmsPluginTHR(ctx, &OptimizationPluginSample); + + cpy = DupContext(ctx, NULL); + + Linear[0] = cmsBuildGamma(cpy, 1.0); + h = cmsCreateLinearizationDeviceLinkTHR(cpy, cmsSigGrayData, Linear); + cmsFreeToneCurve(cpy, Linear[0]); + + xform = cmsCreateTransformTHR(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + cmsCloseProfile(cpy, h); + + cmsDoTransform(cpy, xform, In, Out, 4); + + cmsDeleteTransform(cpy, xform); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + for (i=0; i < 4; i++) + if (In[i] != Out[i]) return 0; + + return 1; +} + + +// -------------------------------------------------------------------------------------------------- +// Check the intent plug-in +// -------------------------------------------------------------------------------------------------- + +/* + This example creates a new rendering intent, at intent number 300, that is identical to perceptual + intent for all color spaces but gray to gray transforms, in this case it bypasses the data. + Note that it has to clear all occurrences of intent 300 in the intents array to avoid + infinite recursion. +*/ + +#define INTENT_DECEPTIVE 300 + +static +cmsPipeline* MyNewIntent(cmsContext ContextID, + cmsUInt32Number nProfiles, + cmsUInt32Number TheIntents[], + cmsHPROFILE hProfiles[], + cmsBool BPC[], + cmsFloat64Number AdaptationStates[], + cmsUInt32Number dwFlags) +{ + cmsPipeline* Result; + cmsUInt32Number ICCIntents[256]; + cmsUInt32Number i; + + for (i=0; i < nProfiles; i++) + ICCIntents[i] = (TheIntents[i] == INTENT_DECEPTIVE) ? INTENT_PERCEPTUAL : + TheIntents[i]; + + if (cmsGetColorSpace(ContextID, hProfiles[0]) != cmsSigGrayData || + cmsGetColorSpace(ContextID, hProfiles[nProfiles-1]) != cmsSigGrayData) + return _cmsDefaultICCintents(ContextID, nProfiles, + ICCIntents, hProfiles, + BPC, AdaptationStates, + dwFlags); + + Result = cmsPipelineAlloc(ContextID, 1, 1); + if (Result == NULL) return NULL; + + cmsPipelineInsertStage(ContextID, Result, cmsAT_BEGIN, + cmsStageAllocIdentity(ContextID, 1)); + + return Result; +} + +static cmsPluginRenderingIntent IntentPluginSample = { + + {cmsPluginMagicNumber, 2060, cmsPluginRenderingIntentSig, NULL}, + + INTENT_DECEPTIVE, MyNewIntent, "bypass gray to gray rendering intent" +}; + +cmsInt32Number CheckIntentPlugin(void) +{ + cmsContext ctx = WatchDogContext(NULL); + cmsContext cpy; + cmsHTRANSFORM xform; + cmsHPROFILE h1, h2; + cmsToneCurve* Linear1; + cmsToneCurve* Linear2; + cmsUInt8Number In[]= { 10, 20, 30, 40 }; + cmsUInt8Number Out[4]; + int i; + + cmsPluginTHR(ctx, &IntentPluginSample); + + cpy = DupContext(ctx, NULL); + + Linear1 = cmsBuildGamma(cpy, 3.0); + Linear2 = cmsBuildGamma(cpy, 0.1); + h1 = cmsCreateLinearizationDeviceLinkTHR(cpy, cmsSigGrayData, &Linear1); + h2 = cmsCreateLinearizationDeviceLinkTHR(cpy, cmsSigGrayData, &Linear2); + + cmsFreeToneCurve(cpy, Linear1); + cmsFreeToneCurve(cpy, Linear2); + + xform = cmsCreateTransformTHR(cpy, h1, TYPE_GRAY_8, h2, TYPE_GRAY_8, INTENT_DECEPTIVE, 0); + cmsCloseProfile(cpy,h1); cmsCloseProfile(cpy, h2); + + cmsDoTransform(cpy, xform, In, Out, 4); + + cmsDeleteTransform(cpy, xform); + cmsDeleteContext(cpy); + cmsDeleteContext(ctx); + + for (i=0; i < 4; i++) + if (Out[i] != In[i]) return 0; + + return 1; +} + + +// -------------------------------------------------------------------------------------------------- +// Check the full transform plug-in +// -------------------------------------------------------------------------------------------------- + +// This is a sample intent that only works for gray8 as output, and always returns '42' +static +void TrancendentalTransform(cmsContext ContextID, struct _cmstransform_struct * CMM, + const void* InputBuffer, + void* OutputBuffer, + cmsUInt32Number Size, + cmsUInt32Number Stride) +{ + cmsUInt32Number i; + + for (i=0; i < Size; i++) + { + ((cmsUInt8Number*) OutputBuffer)[i] = 0x42; + } + +} + + +cmsBool TransformFactory(cmsContext ContextID, _cmsTransformFn* xformPtr, + void** UserData, + _cmsFreeUserDataFn* FreePrivateDataFn, + cmsPipeline** Lut, + cmsUInt32Number* InputFormat, + cmsUInt32Number* OutputFormat, + cmsUInt32Number* dwFlags) + +{ + if (*OutputFormat == TYPE_GRAY_8) + { + // *Lut holds the pipeline to be applied + *xformPtr = TrancendentalTransform; + return TRUE; + } + + return FALSE; +} + + +// The Plug-in entry point +static cmsPluginTransform FullTransformPluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginTransformSig, NULL}, + + TransformFactory +}; + +cmsInt32Number CheckTransformPlugin(void) +{ + cmsContext ctx = WatchDogContext(NULL); + cmsContext cpy; + cmsHTRANSFORM xform; + cmsUInt8Number In[]= { 10, 20, 30, 40 }; + cmsUInt8Number Out[4]; + cmsToneCurve* Linear; + cmsHPROFILE h; + int i; + + cmsPluginTHR(ctx, &FullTransformPluginSample); + + cpy = DupContext(ctx, NULL); + + Linear = cmsBuildGamma(cpy, 1.0); + h = cmsCreateLinearizationDeviceLinkTHR(cpy, cmsSigGrayData, &Linear); + cmsFreeToneCurve(cpy, Linear); + + xform = cmsCreateTransformTHR(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + cmsCloseProfile(cpy, h); + + cmsDoTransform(cpy, xform, In, Out, 4); + + cmsDeleteTransform(cpy, xform); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + for (i=0; i < 4; i++) + if (Out[i] != 0x42) return 0; + + return 1; +} + + +// -------------------------------------------------------------------------------------------------- +// Check the mutex plug-in +// -------------------------------------------------------------------------------------------------- + +typedef struct { + int nlocks; +} MyMtx; + + +static +void* MyMtxCreate(cmsContext id) +{ + MyMtx* mtx = (MyMtx*) _cmsMalloc(id, sizeof(MyMtx)); + mtx ->nlocks = 0; + return mtx; +} + +static +void MyMtxDestroy(cmsContext id, void* mtx) +{ + MyMtx* mtx_ = (MyMtx*) mtx; + + if (mtx_->nlocks != 0) + Die("Locks != 0 when setting free a mutex"); + + _cmsFree(id, mtx); + +} + +static +cmsBool MyMtxLock(cmsContext id, void* mtx) +{ + MyMtx* mtx_ = (MyMtx*) mtx; + mtx_->nlocks++; + + return TRUE; +} + +static +void MyMtxUnlock(cmsContext id, void* mtx) +{ + MyMtx* mtx_ = (MyMtx*) mtx; + mtx_->nlocks--; + +} + + +static cmsPluginMutex MutexPluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginMutexSig, NULL}, + + MyMtxCreate, MyMtxDestroy, MyMtxLock, MyMtxUnlock +}; + + +cmsInt32Number CheckMutexPlugin(void) +{ + cmsContext ctx = WatchDogContext(NULL); + cmsContext cpy; + cmsHTRANSFORM xform; + cmsUInt8Number In[]= { 10, 20, 30, 40 }; + cmsUInt8Number Out[4]; + cmsToneCurve* Linear; + cmsHPROFILE h; + int i; + + + cmsPluginTHR(ctx, &MutexPluginSample); + + cpy = DupContext(ctx, NULL); + + Linear = cmsBuildGamma(cpy, 1.0); + h = cmsCreateLinearizationDeviceLinkTHR(cpy, cmsSigGrayData, &Linear); + cmsFreeToneCurve(cpy, Linear); + + xform = cmsCreateTransformTHR(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0); + cmsCloseProfile(cpy, h); + + cmsDoTransform(cpy, xform, In, Out, 4); + + cmsDeleteTransform(cpy, xform); + cmsDeleteContext(ctx); + cmsDeleteContext(cpy); + + for (i=0; i < 4; i++) + if (Out[i] != In[i]) return 0; + + return 1; +} diff --git a/lcms2mt/testbed/testthread.cpp b/lcms2mt/testbed/testthread.cpp new file mode 100644 index 000000000..b93276120 --- /dev/null +++ b/lcms2mt/testbed/testthread.cpp @@ -0,0 +1,120 @@ + +#include <windows.h> +#include "lcms2_plugin.h" + +static cmsContext ctx; +static cmsHPROFILE prof_cmyk, prof_rgb; +static volatile int rc = 0; + + +static +void* MyMtxCreate(cmsContext id) +{ + return (void*) CreateMutex( NULL, FALSE, NULL); +} + +static +void MyMtxDestroy(cmsContext id, void* mtx) +{ + CloseHandle((HANDLE) mtx); +} + +static +cmsBool MyMtxLock(cmsContext id, void* mtx) +{ + WaitForSingleObject((HANDLE) mtx, INFINITE); + return TRUE; +} + +static +void MyMtxUnlock(cmsContext id, void* mtx) +{ + ReleaseMutex((HANDLE) mtx); +} + + +static cmsPluginMutex MutexPluginSample = { + + { cmsPluginMagicNumber, 2060, cmsPluginMutexSig, NULL}, + + MyMtxCreate, MyMtxDestroy, MyMtxLock, MyMtxUnlock +}; + + +static DWORD WINAPI one_thread(LPVOID lpParameter) +{ + int i, j; + cmsUInt8Number rgb[3*1000]; + cmsUInt8Number cmyk[4*1000]; + + Sleep(rand() % 500 ); + cmsHTRANSFORM xform = cmsCreateTransformTHR(ctx, prof_rgb, TYPE_RGB_8, prof_cmyk, TYPE_CMYK_8, 0, 0); + + for (i=0; i < 100000; i++) { + + for (j=0; j < 1000; j++) + { + rgb[j * 3 ] = 189; + rgb[j * 3 + 1] = 100; + rgb[j * 3 + 2] = 75; + } + cmsDoTransform(xform, rgb, cmyk, 1000); + for (j=0; j < 1000; j++) + { + if (cmyk[j * 4 ] != 37 || + cmyk[j * 4 + 1 ] != 188 || + cmyk[j * 4 + 2 ] != 195 || + cmyk[j * 4 + 3 ] != 7) + { + OutputDebugString(L"ERROR\n"); + rc = 1; + } + + } + + } + + cmsDeleteTransform(xform); + + return 0; +} + +int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) +{ + int i; + cmsContext ctx; + + OutputDebugString(L"Test in progress...\n"); + + ctx = cmsCreateContext(NULL, 0); + + prof_cmyk = cmsOpenProfileFromFileTHR(ctx, "USWebCoatedSWOP.icc", "r"); + prof_rgb = cmsOpenProfileFromFileTHR(ctx, "AdobeRGB1998.icc","r"); + + +#define NWORKERS 10 + + HANDLE workers[NWORKERS]; + + + for (int i=0; i<NWORKERS; ++i) + { + DWORD threadid; + + workers[i] = CreateThread(NULL,0,one_thread,NULL,0,&threadid); + } + + WaitForMultipleObjects(NWORKERS,workers,TRUE,INFINITE); + + for ( i=0;i<NWORKERS;++i) + CloseHandle(workers[i]); + + + cmsCloseProfile(prof_rgb); + cmsCloseProfile(prof_cmyk); + cmsDeleteContext(ctx); + + OutputDebugString(L"Test Done\n"); + + return rc; +} diff --git a/lcms2mt/testbed/toosmall.icc b/lcms2mt/testbed/toosmall.icc Binary files differnew file mode 100755 index 000000000..15e5e4978 --- /dev/null +++ b/lcms2mt/testbed/toosmall.icc diff --git a/lcms2mt/testbed/zoo_icc.c b/lcms2mt/testbed/zoo_icc.c new file mode 100755 index 000000000..05c09d768 --- /dev/null +++ b/lcms2mt/testbed/zoo_icc.c @@ -0,0 +1,309 @@ +//--------------------------------------------------------------------------------- +// +// Little Color Management System +// Copyright (c) 1998-2017 Marti Maria Saguer +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//--------------------------------------------------------------------------------- +// + + +#include "testcms2.h" + + +// ZOO checks ------------------------------------------------------------------------------------------------------------ + + +#ifdef CMS_IS_WINDOWS_ + +static char ZOOfolder[cmsMAX_PATH] = "c:\\colormaps\\"; +static char ZOOwrite[cmsMAX_PATH] = "c:\\colormaps\\write\\"; +static char ZOORawWrite[cmsMAX_PATH] = "c:\\colormaps\\rawwrite\\"; + + +// Read all tags on a profile given by its handle +static +void ReadAllTags(cmsContext ContextID, cmsHPROFILE h) +{ + cmsInt32Number i, n; + cmsTagSignature sig; + + n = cmsGetTagCount(ContextID, h); + for (i=0; i < n; i++) { + + sig = cmsGetTagSignature(ContextID, h, i); + if (cmsReadTag(ContextID, h, sig) == NULL) return; + } +} + + +// Read all tags on a profile given by its handle +static +void ReadAllRAWTags(cmsContext ContextID, cmsHPROFILE h) +{ + cmsInt32Number i, n; + cmsTagSignature sig; + cmsInt32Number len; + + n = cmsGetTagCount(ContextID, h); + for (i=0; i < n; i++) { + + sig = cmsGetTagSignature(ContextID, h, i); + len = cmsReadRawTag(ContextID, h, sig, NULL, 0); + } +} + + +static +void PrintInfo(cmsContext ContextID, cmsHPROFILE h, cmsInfoType Info) +{ + wchar_t* text; + cmsInt32Number len; + cmsContext id = 0; + + len = cmsGetProfileInfo(ContextID, h, Info, "en", "US", NULL, 0); + if (len == 0) return; + + text = _cmsMalloc(id, len); + cmsGetProfileInfo(ContextID, h, Info, "en", "US", text, len); + + wprintf(L"%s\n", text); + _cmsFree(id, text); +} + + +static +void PrintAllInfos(cmsContext ContextID, cmsHPROFILE h) +{ + PrintInfo(ContextID, h, cmsInfoDescription); + PrintInfo(ContextID, h, cmsInfoManufacturer); + PrintInfo(ContextID, h, cmsInfoModel); + PrintInfo(ContextID, h, cmsInfoCopyright); + printf("\n\n"); +} + +static +void ReadAllLUTS(cmsContext ContextID, cmsHPROFILE h) +{ + cmsPipeline* a; + cmsCIEXYZ Black; + + a = _cmsReadInputLUT(ContextID, h, INTENT_PERCEPTUAL); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadInputLUT(ContextID, h, INTENT_RELATIVE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadInputLUT(ContextID, h, INTENT_SATURATION); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadInputLUT(ContextID, h, INTENT_ABSOLUTE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + + a = _cmsReadOutputLUT(ContextID, h, INTENT_PERCEPTUAL); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadOutputLUT(ContextID, h, INTENT_RELATIVE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadOutputLUT(ContextID, h, INTENT_SATURATION); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadOutputLUT(ContextID, h, INTENT_ABSOLUTE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + + a = _cmsReadDevicelinkLUT(ContextID, h, INTENT_PERCEPTUAL); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadDevicelinkLUT(ContextID, h, INTENT_RELATIVE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadDevicelinkLUT(ContextID, h, INTENT_SATURATION); + if (a) cmsPipelineFree(ContextID, a); + + a = _cmsReadDevicelinkLUT(ContextID, h, INTENT_ABSOLUTE_COLORIMETRIC); + if (a) cmsPipelineFree(ContextID, a); + + + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_PERCEPTUAL, 0); + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_SATURATION, 0); + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_ABSOLUTE_COLORIMETRIC, 0); + cmsDetectTAC(ContextID, h); +} + +// Check one specimen in the ZOO + +static +cmsInt32Number CheckSingleSpecimen(cmsContext ContextID, const char* Profile) +{ + char BuffSrc[256]; + char BuffDst[256]; + cmsHPROFILE h; + + sprintf(BuffSrc, "%s%s", ZOOfolder, Profile); + sprintf(BuffDst, "%s%s", ZOOwrite, Profile); + + h = cmsOpenProfileFromFile(BuffSrc, "r"); + if (h == NULL) return 0; + + printf("%s\n", Profile); + + PrintAllInfos(ContextID, h); + ReadAllTags(ContextID, h); + ReadAllLUTS(ContextID, h); + // ReadAllRAWTags(ContextID, h); + + + cmsSaveProfileToFile(ContextID, h, BuffDst); + cmsCloseProfile(ContextID, h); + + h = cmsOpenProfileFromFile(BuffDst, "r"); + if (h == NULL) return 0; + ReadAllTags(ContextID, h); + + + cmsCloseProfile(ContextID, h); + + return 1; +} + +static +cmsInt32Number CheckRAWSpecimen(cmsContext ContextID, const char* Profile) +{ + char BuffSrc[256]; + char BuffDst[256]; + cmsHPROFILE h; + + sprintf(BuffSrc, "%s%s", ZOOfolder, Profile); + sprintf(BuffDst, "%s%s", ZOORawWrite, Profile); + + h = cmsOpenProfileFromFile(BuffSrc, "r"); + if (h == NULL) return 0; + + ReadAllTags(ContextID, h); + ReadAllRAWTags(ContextID, h); + cmsSaveProfileToFile(ContextID, h, BuffDst); + cmsCloseProfile(ContextID, h); + + h = cmsOpenProfileFromFile(BuffDst, "r"); + if (h == NULL) return 0; + ReadAllTags(ContextID, h); + cmsCloseProfile(ContextID, h); + + return 1; +} + + +static int input = 0, + disp = 0, + output = 0, + link = 0, + abst = 0, + color = 0, + named = 0; + +static int rgb = 0, + cmyk = 0, + gray = 0, + other = 0; + + + +static +int count_stats(cmsContext ContextID, const char* Profile) +{ + char BuffSrc[256]; + cmsHPROFILE h; + cmsCIEXYZ Black; + + sprintf(BuffSrc, "%s%s", ZOOfolder, Profile); + + h = cmsOpenProfileFromFile(BuffSrc, "r"); + if (h == NULL) return 0; + + + switch (cmsGetDeviceClass(ContextID, h)) { + + case cmsSigInputClass : input++; break; + case cmsSigDisplayClass : disp++; break; + case cmsSigOutputClass : output++; break; + case cmsSigLinkClass : link++; break; + case cmsSigAbstractClass : abst++; break; + case cmsSigColorSpaceClass : color++; break; + case cmsSigNamedColorClass : named ++; break; + } + + + switch (cmsGetColorSpace(ContextID, h)) { + + case cmsSigRgbData: rgb++; break; + case cmsSigCmykData: cmyk++; break; + case cmsSigGrayData: gray++; break; + default: other++; + } + + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_PERCEPTUAL, 0); + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsDetectDestinationBlackPoint(ContextID, &Black, h, INTENT_SATURATION, 0); + + cmsCloseProfile(ContextID, h); + + return 1; +} + + + +void CheckProfileZOO(cmsContext ContextID) +{ + struct _finddata_t c_file; + intptr_t hFile; + + cmsSetLogErrorHandler(NULL); + + if ( (hFile = _findfirst("c:\\colormaps\\*.*", &c_file)) == -1L ) + printf("No files in current directory"); + else + { + do + { + if (strcmp(c_file.name, ".") != 0 && + strcmp(c_file.name, "..") != 0) { + + CheckSingleSpecimen(ContextID, c_file.name); + CheckRAWSpecimen(ContextID, c_file.name); + + count_stats(ContextID, c_file.name); + + TestMemoryLeaks(FALSE); + + } + + } while ( _findnext(hFile, &c_file) == 0 ); + + _findclose(hFile); + } + + ResetFatalError(); +} + +#endif |