diff options
Diffstat (limited to 'xicc')
-rw-r--r-- | xicc/Jamfile | 172 | ||||
-rw-r--r-- | xicc/License.txt | 662 | ||||
-rw-r--r-- | xicc/Makefile.am | 28 | ||||
-rw-r--r-- | xicc/Readme.txt | 15 | ||||
-rw-r--r-- | xicc/afiles | 68 | ||||
-rw-r--r-- | xicc/cam02.c | 1279 | ||||
-rw-r--r-- | xicc/cam02.h | 208 | ||||
-rw-r--r-- | xicc/cam02plot.c | 845 | ||||
-rw-r--r-- | xicc/cam02ref.h | 621 | ||||
-rw-r--r-- | xicc/cam02test.c | 1325 | ||||
-rw-r--r-- | xicc/cam97s3.c | 596 | ||||
-rw-r--r-- | xicc/cam97s3.h | 169 | ||||
-rw-r--r-- | xicc/cam97test.c | 456 | ||||
-rw-r--r-- | xicc/ccmx.c | 766 | ||||
-rw-r--r-- | xicc/ccmx.h | 120 | ||||
-rw-r--r-- | xicc/ccss.c | 636 | ||||
-rw-r--r-- | xicc/ccss.h | 112 | ||||
-rw-r--r-- | xicc/ccttest.c | 306 | ||||
-rw-r--r-- | xicc/cgatsplot.c | 248 | ||||
-rw-r--r-- | xicc/cv.c | 136 | ||||
-rw-r--r-- | xicc/cvtest.c | 411 | ||||
-rw-r--r-- | xicc/example.sp | 130 | ||||
-rw-r--r-- | xicc/extracticc.c | 219 | ||||
-rw-r--r-- | xicc/extractttag.c | 203 | ||||
-rw-r--r-- | xicc/fakeCMY.c | 493 | ||||
-rw-r--r-- | xicc/fbview.c | 312 | ||||
-rw-r--r-- | xicc/iccgamut.c | 811 | ||||
-rw-r--r-- | xicc/iccjpeg.c | 271 | ||||
-rw-r--r-- | xicc/iccjpeg.h | 73 | ||||
-rw-r--r-- | xicc/icheck.c | 532 | ||||
-rw-r--r-- | xicc/monctest.c | 278 | ||||
-rw-r--r-- | xicc/moncurve.c | 668 | ||||
-rw-r--r-- | xicc/moncurve.h | 121 | ||||
-rw-r--r-- | xicc/mpp.c | 4446 | ||||
-rw-r--r-- | xicc/mpp.h | 268 | ||||
-rw-r--r-- | xicc/mpplu.c | 1355 | ||||
-rw-r--r-- | xicc/revfix.c | 796 | ||||
-rw-r--r-- | xicc/specplot.c | 370 | ||||
-rw-r--r-- | xicc/specsubsamp.c | 232 | ||||
-rw-r--r-- | xicc/spectest.c | 750 | ||||
-rw-r--r-- | xicc/spectest2.c | 270 | ||||
-rw-r--r-- | xicc/tiffgamut.c | 1300 | ||||
-rw-r--r-- | xicc/tiffgmts.c | 1187 | ||||
-rw-r--r-- | xicc/transplot.c | 279 | ||||
-rw-r--r-- | xicc/xcal.c | 496 | ||||
-rw-r--r-- | xicc/xcal.h | 78 | ||||
-rw-r--r-- | xicc/xcam.c | 214 | ||||
-rw-r--r-- | xicc/xcam.h | 82 | ||||
-rw-r--r-- | xicc/xcolorants.c | 768 | ||||
-rw-r--r-- | xicc/xcolorants.h | 326 | ||||
-rw-r--r-- | xicc/xcolorantslu.c | 228 | ||||
-rw-r--r-- | xicc/xdevlin.c | 299 | ||||
-rw-r--r-- | xicc/xdevlin.h | 96 | ||||
-rw-r--r-- | xicc/xdgb.c | 109 | ||||
-rw-r--r-- | xicc/xdgb.h | 71 | ||||
-rw-r--r-- | xicc/xfbview.c | 703 | ||||
-rw-r--r-- | xicc/xfit.c | 2829 | ||||
-rw-r--r-- | xicc/xfit.h | 239 | ||||
-rw-r--r-- | xicc/xicc.c | 3607 | ||||
-rw-r--r-- | xicc/xicc.h | 945 | ||||
-rw-r--r-- | xicc/xicclu.c | 1149 | ||||
-rw-r--r-- | xicc/xlut.c | 4271 | ||||
-rw-r--r-- | xicc/xlutfix.c | 1306 | ||||
-rw-r--r-- | xicc/xmatrix.c | 2022 | ||||
-rw-r--r-- | xicc/xmono.c | 324 | ||||
-rw-r--r-- | xicc/xspect.c | 4714 | ||||
-rw-r--r-- | xicc/xspect.h | 430 | ||||
-rw-r--r-- | xicc/xutils.c | 294 | ||||
-rw-r--r-- | xicc/xutils.h | 69 |
69 files changed, 50212 insertions, 0 deletions
diff --git a/xicc/Jamfile b/xicc/Jamfile new file mode 100644 index 0000000..58e5051 --- /dev/null +++ b/xicc/Jamfile @@ -0,0 +1,172 @@ + +#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on +PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags +#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags +PREF_LINKFLAGS += $(LINKDEBUGFLAG) ; # Link debugging flags + +#Products +Libraries = libxicc libxcolorants libxutils ; +Executables = fakeCMY iccgamut mpplu revfix tiffgamut xicclu extracticc extractttag specplot ; +Headers = xicc.h ; +Samples = License.txt ; + +#Install +InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ; +InstallFile $(DESTDIR)$(PREFIX)/bin : $(Samples) ; +#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ; +#InstallLib $(DESTDIR)$(PREFIX)/lib : $(Libraries) ; + +HDRS = ../h ../icc ../rspl ../cgats ../numlib ../gamut ../spectro ../profile + ../plot ../render $(TIFFINC) $(JPEGINC) $(LibWinH) ; + +# XICC library +Library libxicc : xicc.c xlutfix.c xspect.c xcolorants.c xutils.c iccjpeg.c xdevlin.c + xcam.c cam97s3.c cam02.c mpp.c ccmx.c ccss.c xfit.c xdgb.c moncurve.c xcal.c ; + +# colorant library. Use instead of libxicc +Object xcolorants2 : xcolorants.c ; +LibraryFromObjects libxcolorants : xcolorants2 ; + +# standalone utilities library. Use instead of libxicc +Object xutils2 : xutils.c ; +Object iccjpeg2 : iccjpeg.c ; +LibraryFromObjects libxutils : xutils2 iccjpeg2 ; + +# Utilities / test programs + +LINKLIBS = libxicc ../spectro/libinsttypes ../gamut/libgamut ../rspl/librspl + ../cgats/libcgats ../icc/libicc ../plot/libplot ../plot/libvrml ../numlib/libnum + $(TIFFLIB) $(JPEGLIB) ; + +# Not created yet +#Main xicctest : xicctest.c ; + +# Not created yet +#Main xlutest : xlutest.c ; + +# expanded version of icclu +Main xicclu : xicclu.c ; + +# expanded version of iccgamut - does Jab and ink limiting +Main iccgamut : iccgamut.c ; + +# tiff file gamut utility +Main tiffgamut : tiffgamut.c : : : $(TIFFINC) $(JPEGINC) : : ; + +# diagnostic utility +if [ GLOB . : tiffgmts.c ] { + Main tiffgmts : tiffgmts.c : : : $(TIFFINC) $(JPEGINC) : : ../plot/libvrml ; +} + +# Reverse profile fixer +Main revfix : revfix.c ; + +# MPP lookup test utility +Main mpplu : mpplu.c ; + +# Embedded ICC profile extractor +Main extracticc : extracticc.c : : : $(TIFFINC) $(JPEGINC) : : ; + +# Text tag from ICC profile extracto +Main extractttag : extractttag.c : : : $(TIFFINC) $(JPEGINC) : : ; + +# xcolorant lookup test +Main xcolorantslu : xcolorantslu.c ; + +#test program for viewing inverse algorithm +#Should be in JUNK ?? +Main fbview : fbview.c ; + +Main fakeCMY : fakeCMY.c : : : ../target ; + +Main xfbview : xfbview.c ; + +Main icheck : icheck.c ; + +# Test FWA in xspect +Main spectest : spectest.c ; + +# Test FWA in xspect 2 +Main spectest2 : spectest2.c : : : : : ../spectro/libinsttypes ; + +# Spectral plotting and CCT test +Main ccttest : ccttest.c ; + +# transfer curve plotting +Main transplot : transplot.c ; + +# CGATS .ti3 plotting +Main cgatsplot : cgatsplot.c ; + +# per channel curve testing +Main cv : cv.c ; + +# per channel curve fitting testing +Main cvtest : cvtest.c ; + +# per channel curve fitting testing +Main cvtest : cvtest.c ; + +# Generate sub sampled illuminants or observers */ +Main specsubsamp : specsubsamp.c ; + +# Plot spectrum and test CCT spectrum code and illuminant utility +Main specplot : specplot.c ; + +# CAM test routines +Main cam97test : cam97test.c ; +Main cam02test : cam02test.c ; + +# Test utility for moncurve +Main monctest : monctest.c ; + +#Main cam02vecplot : cam02vecplot.c : : : : : ../plot/libvrml ; + +#Home = ' d:\usr\graeme ' and PWD = ' /src/argyll/xicc ' +if $(HOME) = "d:\\usr\\graeme" && $(PWD) = "/src/argyll/xicc" { + #Create test TIFF file for cam02 conversion + Main cam02plot : cam02plot.c : : : $(TIFFINC) $(JPEGINC) : : ; + Main cam02logplot : cam02logplot.c : : : $(TIFFINC) $(JPEGINC) : : ; + Main cam02delplot : cam02delplot.c : : : $(TIFFINC) $(JPEGINC) : : ; + Main cam02vecplot : cam02vecplot.c : : : : : ../plot/libvrml ; +} + +#Main t : t.c ; + +if $(BUILD_JUNK) { + + LINKLIBS += ../render/librender ; + + Main slocustest : slocustest.c ; + + MainsFromSources t1.c t2.c t22.c t23.c t24.c t3.c ; + + Main test : test.c ; + + Main t : t.c ; + + # CAM97s3 matrix inversion calc + Main cammatrix : cammatrix.c ; + + # test matrix deriv version + Main t : t.c ; + + # Develop conjgrad version of code for profile + Main tt3 : tt3.c ; + + # test icx_icc_cv_to_colorant_comb() + Main tcc : tcc.c ; + + # Develop conjgrad version of code for profile + Main tt4 : tt4.c ; + + # Test viewing conditions extractor + Main vctest : vctest.c ; + + # ~~~ test code + Main lseptest : lseptest.c ; + + # ~~~ test code + Main llseptest : llseptest.c ; +} + diff --git a/xicc/License.txt b/xicc/License.txt new file mode 100644 index 0000000..a871fcf --- /dev/null +++ b/xicc/License.txt @@ -0,0 +1,662 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. + diff --git a/xicc/Makefile.am b/xicc/Makefile.am new file mode 100644 index 0000000..67b34f9 --- /dev/null +++ b/xicc/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/Makefile.shared + +privatelib_LTLIBRARIES = libxicc.la libxutils.la +privatelibdir = $(pkglibdir) + +libxicc_la_SOURCES = xicc.c xlutfix.c xspect.c xcolorants.c \ + xutils.c iccjpeg.c xdevlin.c xcam.c cam97s3.c cam02.c mpp.c \ + ccmx.c ccss.c xfit.c xdgb.c moncurve.c xcal.c +libxicc_la_LIBADD = $(ICC_LIBS) ../gamut/libgamut.la \ + ../numlib/libargyllnum.la ../spectro/libinsttypes.la \ + ../cgats/libcgats.la ../rspl/librspl.la ../libargyll.la -ljpeg $(TIFF_LIBS) + +libxutils_la_SOURCES = xutils.h xutils.c iccjpeg.c iccjpeg.h +libxutils_la_LIBADD = $(TIFF_LIBS) $(ICC_LIBS) -ljpeg + +LDADD = ./libxicc.la ./libxutils.la ../rspl/librspl.la \ + ../numlib/libargyllnum.la ../gamut/libgamut.la \ + ../gamut/libgammap.la ../spectro/libinsttypes.la $(ICC_LIBS) \ + ../cgats/libcgats.la ../plot/libvrml.la ../plot/libplot.la \ + $(TIFF_LIBS) $(X_LIBS) ../libargyll.la -ljpeg + +bin_PROGRAMS = fakeCMY iccgamut mpplu revfix tiffgamut xicclu \ + extracticc extractttag specplot ccttest + +fakeCMY_DEPENDENCIES = ../spectro/libinsttypes.la \ + ../gamut/libgammap.la ../target/libtarget.la + +EXTRA_DIST = xmono.c xmatrix.c xlut.c diff --git a/xicc/Readme.txt b/xicc/Readme.txt new file mode 100644 index 0000000..f4a303a --- /dev/null +++ b/xicc/Readme.txt @@ -0,0 +1,15 @@ +This directory holds the "expansion" icc libraries. + +These suplement the base icc library with enhanced +profile functionality, such as smoothed interpolation, +reverse interpolation, table creation from scattered +data etc. + +Most of this functionality is based on the rspl and icc libraries. +This is where ink limiting and black generation policies +for CMYK devices is implemented. + +xicclu.exe is the analog of the icclib utility icclu, but + expands the capability to reverse lookup the + Lut tables. + diff --git a/xicc/afiles b/xicc/afiles new file mode 100644 index 0000000..8b4341a --- /dev/null +++ b/xicc/afiles @@ -0,0 +1,68 @@ +Readme.txt +License.txt +afiles +Jamfile +xicc.h +xicc.c +xmono.c +xmatrix.c +xlut.c +xlutfix.c +xcal.h +xcal.c +mpp.h +mpp.c +mpplu.c +ccmx.h +ccmx.c +ccss.h +ccss.c +xcolorants.h +xcolorants.c +xcolorantslu.c +xutils.h +xutils.c +iccjpeg.h +iccjpeg.c +xfit.h +xfit.c +xdgb.h +xdgb.c +xspect.h +xspect.c +specplot.c +specsubsamp.c +xdevlin.h +xdevlin.c +example.sp +xicclu.c +iccgamut.c +tiffgamut.c +tiffgmts.c +revfix.c +fakeCMY.c +xcam.h +xcam.c +cam97s3.c +cam97s3.h +cam97test.c +cam02.c +cam02.h +cam02ref.h +cam02test.c +cam02plot.c +moncurve.c +moncurve.h +monctest.c +fbview.c +xfbview.c +icheck.c +transplot.c +cgatsplot.c +spectest.c +spectest2.c +ccttest.c +cv.c +cvtest.c +extracticc.c +extractttag.c diff --git a/xicc/cam02.c b/xicc/cam02.c new file mode 100644 index 0000000..1ab6abc --- /dev/null +++ b/xicc/cam02.c @@ -0,0 +1,1279 @@ + +/* + * cam02 + * + * Color Appearance Model, based on + * CIECAM02, "The CIECAM02 Color Appearance Model" + * by Nathan Moroney, Mark D. Fairchild, Robert W.G. Hunt, Changjun Li, + * M. Ronnier Luo and Todd Newman, IS&T/SID Tenth Color Imaging + * Conference, with the addition of the Viewing Flare + * model described on page 487 of "Digital Color Management", + * by Edward Giorgianni and Thomas Madden, and the + * Helmholtz-Kohlraush effect, using the equation from + * the Bradford-Hunt 96C model as detailed in Mark Fairchild's + * book "Color Appearance Models". + * The Slight modification to the Hunt-Pointer-Estevez matrix + * recommended by Kuo, Zeis and Lai in IS&T/SID 14th Color Imaging + * Conference "Robust CIECAM02 Implementation and Numerical + * Experiment within an ICC Workflow", page 215, together with + * their matrix formulation of inversion has been adopted. + * + * In addition the algorithm has unique extensions to allow + * it to operate reasonably over an unbounded domain. + * + * Author: Graeme W. Gill + * Date: 17/1/2004 + * Version: 1.00 + * + * This file is based on cam97s3.c by Graeme Gill. + * + * Copyright 2004 - 2011 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +/* Note that XYZ values are normalised to 1.0 consistent */ +/* with the ICC convention (not 100.0 as assumed by the CIECAM spec.) */ +/* Note that all whites are assumed to be normalised (ie. Y = 1.0) */ + +/* + Various additions and changes have been made to allow the CAM + conversions to and from an unbounded range of XYZ and Jab values, + in a (somewhat) geometrically consistent maner. This is because + such values arise in the process of gamut mapping, and in scanning through + the grid of PCS values needed to fill in the A2B table of an + ICC profile. Such values have no correlation to a real color + value, but none the less need to be handled without causing an + exception, in a geometrically consistent and reversible + fashion. + + The changes are: + + The Hunt-Pointer-Estevez matrix has been increased in precission by + 1 digit [Kuo, Zeise & Lai, IS&T/SID 14th Color Imaging Conference pp. 215-219] + + The sharpened cone response matrix third row has been changed from + [0.0030, 0.0136, 0.9834] to [0.0000, 0.0000, 1.0000] + [Susstrunk & Brill, IS&T/SID 14th Color Imaging Conference Late Breaking News sublement] + + To prevent wild Jab values as the rgb' values approach zero, a region close to each + primary ground plane is compressed. This expands the region of reasonable + behaviour to encompass XYZ values that are found in practice (ie. ProPhotoRGB). + + To prevent excessive curvature of hue in high saturation blue regions + due to the non-linearity exagerating the difference between + r & b values, a heuristic is introduced that blends the r & b + values, reducing this effect. This degrades the agreement + with the reference CIECAM implementation by about 1DE in this region, + but greatly improves the behaviour in mapping and clipping from + highly saturated blues (ie. ProPhotoRGB). + + The post-adaptation non-linearity response has had a straight + line negative linear extension added. + The positive region has a tangential linear extension added, so + that the range of values is not limited. + An adaptive threshold for the low level linear extension, + to avoid coordinates blowing up when one of the cone responses + is large, while the others become negative. + + Re-arrange ss equation into separated effects, + k1, k2, k3, and then limit these to avoid divide by zero + and sign change errors in fwd and bwd converson, as well + as avoiding the blue saturation doubling back for low + luminance levels. + + To avoid chroma and hue angle information being lost when the + J value used to scale the chroma is 0, and to ensure + that J = 0, a,b != 0 values have a valid mapping into + XYZ space, the J value used to multiply Chroma, is limited + to be equivalent to not less than A == 0.1. + + The Helmholtz-Kohlraush effect is crafted to have resonable + effects over the range of J from 0 to 100, and have more + moderate effects outside this range. + +*/ + +#include <copyright.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "icc.h" +#include "xcam.h" +#include "cam02.h" +#include "numlib.h" + +#define ENABLE_COMPR /* [Def] Enable XYZ compression */ +#define ENABLE_BLUE_ANGLE_FIX /* [Def] Limit maximum blue angle */ +#define ENABLE_DDL /* [Def] Enable k1,k2,k3 overall ss limit values (seems to be the best scheme) */ +#undef ENABLE_SS /* [Undef] Disable overall ss limit values (not the scheme used) */ + +#undef ENTRACE /* [Undef] Enable internal value runtime tracing if s->trace != 0 */ +#undef DOTRACE /* [Undef] Trace anyway (ie. set s->trace = 1) */ +#undef DIAG1 /* [Undef] Print internal value diagnostics for conditions setup */ +#undef DIAG2 /* [Undef] Print internal value diagnostics for each conversion */ +#undef TRACKMINMAX /* [Undef] Track min/max DD & SS limits (run with locus cam02test) */ +#undef DISABLE_MATRIX /* Debug - disable matrix & non-lin, wire XYZ to rgba */ +#undef DISABLE_SCR /* Debug - disable Sharpened Cone Response matrix */ +#undef DISABLE_HPE /* Debug - disable just Hunt-Pointer_Estevez matrix */ +#undef DISABLE_NONLIN /* Debug - wire rgbp to rgba */ +#undef DISABLE_TTD /* Debug - disable ttd vector 'tilt' */ +#undef DISABLE_HHKR /* Debug - disable Helmholtz-Kohlraush */ + +#ifdef ENABLE_COMPR +# define BC_WHMINY 0.3 /* [0.3] Compression direction minimum Y value */ +# define BC_RANGE_R 0.01 /* [0.01] Set compression range as prop. of distance to neutral - red */ +# define BC_RANGE_G 0.05 /* [0.05] Set compression range as prop. of distance to neutral - green*/ +# define BC_RANGE_B 0.10 /* [0.10] Set compression range as prop. of distance to neutral - blue */ +# define BC_MAXRANGE 0.13 /* [0.13] Maximum compression range */ +# define BC_LIMIT 0.7 /* [0.7] Correction limit (abs. rgbp distance shift) */ +#endif /* ENABLE_COMPR */ + +#ifdef ENABLE_BLUE_ANGLE_FIX +# define BLUE_BL_MAX 0.9 /* [0.9] Sets ultimate blue angle, higher = less angle */ +# define BLUE_BL_POW 3.5 /* [3.5] Sets rate at which blue angle is limited */ +#endif + +#define NLDLIMIT 0.00001 /* [0.00001] Non-linearity minimum lower crossover to straight line */ +#define NLDICEPT -0.18 /* [-0.18] Input intercept of straight line with 0.1 output */ + +#define NLULIMIT 1e5 /* Non-linearity upper crossover to straight line */ + +#ifdef ENABLE_SS /* [Undef] */ +# define SSLLIMIT 0.22 /* Overall ab scale lower limit */ +# define SSULIMIT 580.0 /* Overall ab scale upper limit */ +#endif /* ENABLE_SS */ + +#define SYMETRICJ /* [Undef] Use symetric power about zero, else straigt line -ve */ + +#define DDLLIMIT 0.55 /* [0.55] ab component -k3:k2 ratio limit (must be < 1.0) */ +//#define DDULIMIT 0.9993 /* [0.9993] ab component k3:k1 ratio limit (must be < 1.0) */ +#define DDULIMIT 0.34 /* [0.34] ab component k3:k1 ratio limit (must be < 1.0) */ +#define SSMINcJ 0.005 /* [0.005] ab scale cJ minimum value */ +#define JLIMIT 0.005 /* [0.005] J encoding cutover point straight line (0 - 1.0 range) */ +#define HKLIMIT 0.7 /* [0.7] Maximum Helmholtz-Kohlraush lift */ + +#ifdef TRACKMINMAX +double minss = 1e60; +double maxss = -1e60; +double minlrat = 0.0; +double maxurat = 0.0; +#define noslots 103 +double slotsd[noslots]; +double slotsu[noslots]; +double minj = 1e38, maxj = -1e38; +#endif /* TRACKMINMAX */ + +#if defined(ENTRACE) || defined(DOTRACE) +#define TRACE(xxxx) if (s->trace) printf xxxx ; +#else +#define TRACE(xxxx) +#endif + +static void cam_free(cam02 *s); +static int set_view(struct _cam02 *s, ViewingCondition Ev, double Wxyz[3], + double La, double Yb, double Lv, double Yf, double Fxyz[3], + int hk); +static int XYZ_to_cam(struct _cam02 *s, double *Jab, double *xyz); +static int cam_to_XYZ(struct _cam02 *s, double *xyz, double *Jab); + +static double spow(double val, double pp) { + if (val < 0.0) + return -pow(-val, pp); + else + return pow(val, pp); +} + +/* Create a cam02 conversion object, with default viewing conditions */ +cam02 *new_cam02(void) { + cam02 *s; +// double D50[3] = { 0.9642, 1.0000, 0.8249 }; + + if ((s = (cam02 *)calloc(1, sizeof(cam02))) == NULL) { + fprintf(stderr,"cam02: malloc failed allocating object\n"); + exit(-1); + } + + /* Initialise methods */ + s->del = cam_free; + s->set_view = set_view; + s->XYZ_to_cam = XYZ_to_cam; + s->cam_to_XYZ = cam_to_XYZ; + + /* Set default range handling limits */ + s->nldlimit = NLDLIMIT; + s->nldicept = NLDICEPT; + s->nlulimit = NLULIMIT; + s->ddllimit = DDLLIMIT; + s->ddulimit = DDULIMIT; + s->ssmincj = SSMINcJ; + s->jlimit = JLIMIT; + s->hklimit = 1.0 / HKLIMIT; + + /* Set a default viewing condition ?? */ + /* set_view(s, vc_average, D50, 33, 0.2, 0.0, 0.0, D50, 0); */ + +#ifdef DOTRACE + s->trace = 1; +#endif + +#ifdef TRACKMINMAX + { + int i; + for (i = 0; i < noslots; i++) { + slotsd[i] = 0.0; + slotsu[i] = 0.0; + } + } +#endif /* TRACKMINMAX */ + + return s; +} + +static void cam_free(cam02 *s) { + +#ifdef TRACKMINMAX + { + int i; + for (i = 0; i < noslots; i++) { + printf("Slot %d = %f, %f\n",i,slotsd[i], slotsu[i]); + } + printf("minj = %f, maxj = %f\n",minj,maxj); + + printf("minss = %f\n",minss); + printf("maxss = %f\n",maxss); + printf("minlrat = %f\n",minlrat); + printf("maxurat = %f\n",maxurat); + } +#endif /* TRACKMINMAX */ + + if (s != NULL) + free(s); +} + +/* Return value is always 0 */ +static int set_view( +cam02 *s, +ViewingCondition Ev, /* Enumerated Viewing Condition */ +double Wxyz[3], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ +double La, /* Adapting/Surround Luminance cd/m^2 */ +double Yb, /* Relative Luminance of Background to reference white (range 0.0 .. 1.0) */ +double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set to other than vc_none */ +double Yf, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ +double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ +int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ +) { + double tt, t1, t2; + double tm[3][3]; + int i; + + if (Ev == vc_none) { + /* Compute the internal parameters by interpolation */ + int i; + double r, bf; + /* Dark, dim, average, above average */ + double t_C[4] = { 0.525, 0.59, 0.69, 1.0 }; + double t_Nc[4] = { 0.800, 0.95, 1.00, 1.0 }; + double t_F[4] = { 0.800, 0.90, 1.00, 1.0 }; + + if (La < 1e-10) /* Hmm. */ + La = 1e-10; + r = La/Lv; + if (r < 0.0) + r = 0.0; + else if (r > 1.0) + r = 1.0; + + if (r < 0.1) { /* Dark to Dim */ + i = 0; + bf = r/0.1; + } else if (r < 0.2) { /* Dim to Average */ + i = 1; + bf = (r - 0.1)/0.1; + } else { /* Average to above average */ + i = 2; + bf = (r - 0.2)/0.8; + } + s->C = t_C[i] * (1.0 - bf) + t_C[i+1] * bf; + s->Nc = t_Nc[i] * (1.0 - bf) + t_Nc[i+1] * bf; + s->F = t_F[i] * (1.0 - bf) + t_F[i+1] * bf; + } else { + /* Compute the internal parameters by category */ + switch(Ev) { + case vc_dark: + s->C = 0.525; + s->Nc = 0.8; + s->F = 0.8; + break; + case vc_dim: + s->C = 0.59; + s->Nc = 0.95; + s->F = 0.9; + break; + case vc_cut_sheet: + s->C = 0.41; + s->Nc = 0.8; + s->F = 0.8; + break; + default: /* average */ + s->C = 0.69; + s->Nc = 1.0; + s->F = 1.0; + break; + } + } + + /* Transfer parameters to the object */ + s->Ev = Ev; + s->Wxyz[0] = Wxyz[0]; + s->Wxyz[1] = Wxyz[1]; + s->Wxyz[2] = Wxyz[2]; + s->Yb = Yb > 0.005 ? Yb : 0.005; /* Set minimum to avoid divide by 0.0 */ + s->La = La; + s->Yf = Yf; + s->Fxyz[0] = Fxyz[0]; + s->Fxyz[1] = Fxyz[1]; + s->Fxyz[2] = Fxyz[2]; + s->hk = hk; + + /* The rgba vectors */ + s->Va[0] = 1.0; + s->Va[1] = -12.0/11.0; + s->Va[2] = 1.0/11.0; + + s->Vb[0] = 1.0/9.0; + s->Vb[1] = 1.0/9.0; + s->Vb[2] = -2.0/9.0; + + s->VttA[0] = 2.0; + s->VttA[1] = 1.0; + s->VttA[2] = 1.0/20.0; + + /* (Not used) */ + s->Vttd[0] = 1.0; + s->Vttd[1] = 1.0; + s->Vttd[2] = 21.0/20.0; + + /* Vttd in terms of the VttA, Va and Vb vectors */ + s->dcomp[0] = 1.0; + s->dcomp[1] = -11.0/23.0; + s->dcomp[2] = -108.0/23.0; + + /* Compute values that only change with viewing parameters */ + + /* Figure out the Flare contribution to the flareless XYZ input */ + tt = s->Yf * s->Wxyz[1]/s->Fxyz[1]; + s->Fsxyz[0] = tt * s->Fxyz[0]; + s->Fsxyz[1] = tt * s->Fxyz[1]; + s->Fsxyz[2] = tt * s->Fxyz[2]; + + /* Rescale so that the sum of the flare and the input doesn't exceed white */ + s->Fsc = s->Wxyz[1]/(s->Fsxyz[1] + s->Wxyz[1]); + s->Fsxyz[0] *= s->Fsc; + s->Fsxyz[1] *= s->Fsc; + s->Fsxyz[2] *= s->Fsc; + s->Fisc = 1.0/s->Fsc; + +#ifndef DISABLE_SCR + /* Sharpened cone response white values */ + s->rgbW[0] = 0.7328 * s->Wxyz[0] + 0.4296 * s->Wxyz[1] - 0.1624 * s->Wxyz[2]; + s->rgbW[1] = -0.7036 * s->Wxyz[0] + 1.6975 * s->Wxyz[1] + 0.0061 * s->Wxyz[2]; + s->rgbW[2] = 0.0000 * s->Wxyz[0] + 0.0000 * s->Wxyz[1] + 1.0000 * s->Wxyz[2]; +#else + s->rgbW[0] = s->Wxyz[0]; + s->rgbW[1] = s->Wxyz[1]; + s->rgbW[2] = s->Wxyz[2]; +#endif + + /* Degree of chromatic adaptation */ + s->D = s->F * (1.0 - exp((-s->La - 42.0)/92.0)/3.6); + + /* Precompute Chromatic transform values */ + s->Drgb[0] = s->D * (s->Wxyz[1]/s->rgbW[0]) + 1.0 - s->D; + s->Drgb[1] = s->D * (s->Wxyz[1]/s->rgbW[1]) + 1.0 - s->D; + s->Drgb[2] = s->D * (s->Wxyz[1]/s->rgbW[2]) + 1.0 - s->D; + + /* Chromaticaly transformed white value */ + s->rgbcW[0] = s->Drgb[0] * s->rgbW[0]; + s->rgbcW[1] = s->Drgb[1] * s->rgbW[1]; + s->rgbcW[2] = s->Drgb[2] * s->rgbW[2]; + +#ifndef DISABLE_HPE + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + s->rgbpW[0] = 0.7409744840453773 * s->rgbcW[0] + + 0.2180245944753982 * s->rgbcW[1] + + 0.0410009214792244 * s->rgbcW[2]; + s->rgbpW[1] = 0.2853532916858801 * s->rgbcW[0] + + 0.6242015741188157 * s->rgbcW[1] + + 0.0904451341953042 * s->rgbcW[2]; + s->rgbpW[2] = -0.0096276087384294 * s->rgbcW[0] + - 0.0056980312161134 * s->rgbcW[1] + + 1.0153256399545427 * s->rgbcW[2]; +#else + s->rgbpW[0] = s->rgbcW[0]; + s->rgbpW[1] = s->rgbcW[1]; + s->rgbpW[2] = s->rgbcW[2]; +#endif /* DISABLE_HPE */ + +#ifndef DISABLE_SCR + /* Create combined cone and chromatic transform matrix: */ + /* Spectrally sharpened cone responses matrix */ + s->cc[0][0] = 0.7328; s->cc[0][1] = 0.4296; s->cc[0][2] = -0.1624; + s->cc[1][0] = -0.7036; s->cc[1][1] = 1.6975; s->cc[1][2] = 0.0061; + s->cc[2][0] = 0.0000; s->cc[2][1] = 0.0000; s->cc[2][2] = 1.0000; +#else + s->cc[0][0] = 1.0; s->cc[0][1] = 0.0; s->cc[0][2] = 0.0; + s->cc[1][0] = 0.0; s->cc[1][1] = 1.0; s->cc[1][2] = 0.0; + s->cc[2][0] = 0.0; s->cc[2][1] = 0.0; s->cc[2][2] = 1.0; +#endif + + /* Chromaticaly transformed sample value */ + icmSetUnity3x3(tm); + tm[0][0] = s->Drgb[0]; + tm[1][1] = s->Drgb[1]; + tm[2][2] = s->Drgb[2]; + icmMul3x3(s->cc, tm); + +#ifndef DISABLE_HPE + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + tm[0][0] = 0.7409744840453773; + tm[0][1] = 0.2180245944753982; + tm[0][2] = 0.0410009214792244; + tm[1][0] = 0.2853532916858801; + tm[1][1] = 0.6242015741188157; + tm[1][2] = 0.0904451341953042; + tm[2][0] = -0.0096276087384294; + tm[2][1] = -0.0056980312161134; + tm[2][2] = 1.0153256399545427; + icmMul3x3(s->cc, tm); +#endif /* DISABLE_HPE */ + + /* Create inverse combined cone and chromatic transform matrix: */ + icmInverse3x3(s->icc, s->cc); /* Hmm. we assume it cannot fail */ + +#ifdef ENABLE_COMPR + /* Compression ranges */ + s->crange[0] = BC_RANGE_R; + s->crange[1] = BC_RANGE_G; + s->crange[2] = BC_RANGE_B; +#endif /* ENABLE_COMPR */ + + /* Background induction factor */ + s->n = s->Yb/ s->Wxyz[1]; + s->nn = pow(1.64 - pow(0.29, s->n), 0.73); /* Pre computed value */ + + /* Lightness contrast factor ?? */ + { + double k; + + k = 1.0 / (5.0 * s->La + 1.0); + s->Fl = 0.2 * pow(k , 4.0) * 5.0 * s->La + + 0.1 * pow(1.0 - pow(k , 4.0) , 2.0) * pow(5.0 * s->La , 1.0/3.0); + } + + /* Background and Chromatic brightness induction factors */ + s->Nbb = 0.725 * pow(1.0/s->n, 0.2); + s->Ncb = s->Nbb; + + /* Base exponential nonlinearity */ + s->z = 1.48 + pow(s->n , 0.5); + + /* Post-adapted cone response of white */ + tt = pow(s->Fl * s->rgbpW[0], 0.42); + s->rgbaW[0] = 400.0 * tt / (tt + 27.13) + 0.1; + tt = pow(s->Fl * s->rgbpW[1], 0.42); + s->rgbaW[1] = 400.0 * tt / (tt + 27.13) + 0.1; + tt = pow(s->Fl * s->rgbpW[2], 0.42); + s->rgbaW[2] = 400.0 * tt / (tt + 27.13) + 0.1; + + /* Achromatic response of white */ + s->Aw = (s->VttA[0] * s->rgbaW[0] + + s->VttA[1] * s->rgbaW[1] + + s->VttA[2] * s->rgbaW[2] - 0.305) * s->Nbb; + + /* Non-linearity lower crossover output value */ + tt = pow(s->Fl * s->nldlimit, 0.42); + s->nldxval = 400.0 * tt / (tt + 27.13) + 0.1; + + /* Non-linearity lower crossover slope from lower crossover */ + /* to intercept with 0.1 output */ + s->nldxslope = (s->nldxval - 0.1)/(s->nldlimit - s->nldicept); + + /* Non-linearity upper crossover value */ + tt = pow(s->Fl * s->nlulimit, 0.42); + s->nluxval = 400.0 * tt / (tt + 27.13) + 0.1; + + /* Non-linearity upper crossover slope, set to asymtope */ + t1 = s->Fl * s->nlulimit; + t2 = pow(t1, 0.42) + 27.13; + s->nluxslope = 0.42 * s->Fl * 400.0 * 27.13 / (pow(t1,0.58) * t2 * t2); + + + /* Limited A value at J = JLIMIT */ + s->lA = pow(s->jlimit, 1.0/(s->C * s->z)) * s->Aw; + +#ifdef DIAG1 + printf("Scene parameters:\n"); + printf("Viewing condition Ev = %d\n",s->Ev); + printf("Ref white Wxyz = %f %f %f\n", s->Wxyz[0], s->Wxyz[1], s->Wxyz[2]); + printf("Relative liminance of background Yb = %f\n", s->Yb); + printf("Adapting liminance La = %f\n", s->La); + printf("Flare Yf = %f\n", s->Yf); + printf("Flare color Fxyz = %f %f %f\n", s->Fxyz[0], s->Fxyz[1], s->Fxyz[2]); + + printf("Internal parameters:\n"); + printf("Surround Impact C = %f\n", s->C); + printf("Chromatic Induction Nc = %f\n", s->Nc); + printf("Adaptation Degree F = %f\n", s->F); + + printf("Pre-computed values\n"); + printf("Sharpened cone white rgbW = %f %f %f\n", s->rgbW[0], s->rgbW[1], s->rgbW[2]); + printf("Degree of chromatic adaptation D = %f\n", s->D); + printf("Chromatic transform values Drgb = %f %f %f\n", s->Drgb[0], s->Drgb[1], s->Drgb[2]); + printf("Chromatically transformed white rgbcW = %f %f %f\n", s->rgbcW[0], s->rgbcW[1], s->rgbcW[2]); + printf("Hunter-P-E cone response white rgbpW = %f %f %f\n", s->rgbpW[0], s->rgbpW[1], s->rgbpW[2]); + printf("Background induction factor n = %f\n", s->n); + printf(" nn = %f\n", s->nn); + printf("Lightness contrast factor Fl = %f\n", s->Fl); + printf("Background brightness induction factor Nbb = %f\n", s->Nbb); + printf("Chromatic brightness induction factor Ncb = %f\n", s->Ncb); + printf("Base exponential nonlinearity z = %f\n", s->z); + printf("Post adapted cone response white rgbaW = %f %f %f\n", s->rgbaW[0], s->rgbaW[1], s->rgbaW[2]); + printf("Achromatic response of white Aw = %f\n", s->Aw); + printf("\n"); +#endif + return 0; +} + +/* Conversions. Return values are always 0 */ +static int XYZ_to_cam( +struct _cam02 *s, +double Jab[3], +double XYZ[3] +) { + int i; + double xyz[3], rgbp[3], rgba[3]; + double a, b, ja, jb, J, JJ, C, h, e, A, ss; + double ttA, rS, cJ, tt; + double k1, k2, k3; + int clip = 0; + +#ifdef DIAG2 /* Incase in == out */ + double XYZi[3]; + + XYZi[0] = XYZ[0]; + XYZi[1] = XYZ[1]; + XYZi[2] = XYZ[2]; +#endif + + TRACE(("\nForward conversion:\n")) + TRACE(("XYZ = %f %f %f\n",XYZ[0], XYZ[1], XYZ[2])) + +#ifdef DISABLE_MATRIX + + rgba[0] = XYZ[0]; + rgba[1] = XYZ[1]; + rgba[2] = XYZ[2]; + +#else /* !DISABLE_MATRIX */ + + /* Add in flare */ + xyz[0] = s->Fsc * XYZ[0] + s->Fsxyz[0]; + xyz[1] = s->Fsc * XYZ[1] + s->Fsxyz[1]; + xyz[2] = s->Fsc * XYZ[2] + s->Fsxyz[2]; + + TRACE(("XYZ inc flare = %f %f %f\n",xyz[0], xyz[1], xyz[2])) + + /* Spectrally sharpened cone responses, */ + /* Chromaticaly transformed sample value, */ + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space. */ + icmMulBy3x3(rgbp, s->cc, xyz); + + TRACE(("rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2])) + +#ifdef ENABLE_COMPR + /* Try and prevent crazy out of cam02 gamut behaviour, by compressing */ + /* the rgbp so as to prevent it becoming less than zero. */ + { + double tt; /* Temporary */ + double wrgb[3]; /* White target */ + + /* Make white target white point with same Y value */ + tt = xyz[1] > BC_WHMINY ? xyz[1] : BC_WHMINY; /* Limit to minimum Y */ + icmScale3(wrgb, s->rgbpW, tt/s->Wxyz[1]); /* White target at same Y */ + TRACE(("wrgb %f %f %f\n", wrgb[0], wrgb[1], wrgb[2])) + + /* Compress r,g,b in turn */ + for (i = 0; i < 3; i++) { + double cv; /* Compression value */ + double ctv; /* Compression target value */ + double cd; /* Compression displacement needed */ + double cvec[3]; /* Normalized correction vector */ + double isec[3]; /* Intersection with plane */ + double offs; /* Offset of point from orgin*/ + double range; /* Threshold to start compression */ + double asym; /* Compression asymtope */ + + /* Compute compression direction as vector towards white */ + /* (We did try correcting in a blend of limit plane normal and white, */ + /* but compressing towards white seems to be the best.) */ + icmSub3(cvec, wrgb, rgbp); /* Direction of white target */ + + TRACE(("rgbp %f %f %f\n", rgbp[0], rgbp[1], rgbp[2])) + TRACE(("cvec %f %f %f\n", cvec[0], cvec[1], cvec[2])) + + if (cvec[i] < 1e-9) { /* compression direction can't correct this coord */ + TRACE(("Intersection with limit plane failed\n")) + continue; + } + + /* Scale compression vector to make it move a unit in normal direction */ + icmScale3(cvec, cvec, 1.0/cvec[i]); /* Normalized vector to white */ + TRACE(("cvec %f %f %f\n", cvec[0], cvec[1], cvec[2])) + + /* Compute intersection of correction direction with this limit plane */ + /* (This corresponds with finding displacement of rgbp by cvec */ + /* such that the current coord value = 0) */ + icmScale3(isec, cvec, -rgbp[i]); /* (since cvec[i] == 1.0) */ + icmAdd3(isec, isec, rgbp); + TRACE(("isec %f %f %f\n", isec[0], isec[1], isec[2])) + + /* Compute distance from intersection to origin */ + offs = pow(icmNorm3(isec), 0.85); + + range = s->crange[i] * offs; /* Scale range by distance to origin */ + if (range > BC_MAXRANGE) /* so that it tapers down as we approach it */ + range = BC_MAXRANGE; /* and limit maximum */ + + /* Aiming above plane when far from origin, */ + /* but below plane at the origin, so that black isn't affected. */ + asym = range - 0.2 * (range + (0.01 * s->crange[i])); + + ctv = cv = rgbp[i]; /* Distance above/below limit plane */ + + TRACE(("ch %d, offs %f, range %f asym %f, cv %f\n",i, offs,range,asym,cv)) + if (cv < (range - 1e-12)) { /* Need to compress */ + double aa, bb; + aa = 1.0/(range - cv); + bb = 1.0/(range - asym); + ctv = range - 1.0/(aa + bb); + + cd = ctv - cv; /* Displacement needed */ + if (cd > BC_LIMIT) + cd = BC_LIMIT; + TRACE(("ch %d cd = %f, scaled cd %f\n",i,cd,cd)) + + /* Apply correction */ + icmScale3(cvec, cvec, cd); /* Compression vector */ + icmAdd3(rgbp, rgbp, cvec); /* Compress by displacement */ + TRACE(("rgbp after comp. = %f %f %f\n",rgbp[0], rgbp[1], rgbp[2])) + } + } + } +#endif /* ENABLE_COMPR */ + +#ifdef ENABLE_BLUE_ANGLE_FIX + ss = rgbp[0] + rgbp[1] + rgbp[2]; + if (ss < 1e-9) { + ss = 0.0; + } else { + ss = (rgbp[2]/ss - 1.0/3.0) * 3.0/2.0; + if (ss > 0.0) + ss = BLUE_BL_MAX * pow(ss, BLUE_BL_POW); + } + if (ss < 0.0) + ss = 0.0; + else if (ss > 1.0) + ss = 1.0; + tt = 0.5 * (rgbp[0] + rgbp[1]); + rgbp[0] = ss * tt + (1.0 - ss) * rgbp[0]; + rgbp[1] = ss * tt + (1.0 - ss) * rgbp[1]; + TRACE(("rgbp after blue fix ss = %f, rgbp = %f %f %f\n",ss,rgbp[0], rgbp[1], rgbp[2])) +#endif + +#ifdef DISABLE_NONLIN + for (i = 0; i < 3; i++) { + rgba[i] = 400.0/27.13 * rgbp[i]; + } +#else /* !DISABLE_NONLIN */ + /* Post-adapted cone response of sample. */ + /* rgba[] has a minimum value of 0.1 for XYZ[] = 0 and no flare. */ + /* We add a negative linear region, plus a linear segment at */ + /* the end of the +ve conversion to allow numerical handling of a */ + /* very wide range of values. */ + + for (i = 0; i < 3; i++) { + if (rgbp[i] < s->nldlimit) { + rgba[i] = s->nldxval + s->nldxslope * (rgbp[i] - s->nldlimit); + } else { + if (rgbp[i] <= s->nlulimit) { + tt = pow(s->Fl * rgbp[i], 0.42); + rgba[i] = 400.0 * tt / (tt + 27.13) + 0.1; + } else { + rgba[i] = s->nluxval + s->nluxslope * (rgbp[i] - s->nlulimit); + } + } + } +#endif /* !DISABLE_NONLIN */ + +//tt = 0.5 * (rgba[0] + rgba[1]); +//rgba[0] = (rgba[0] - ss * tt)/(1.0 - ss); +//rgba[1] = (rgba[1] - ss * tt)/(1.0 - ss); + +#endif /* !DISABLE_MATRIX */ + + /* Note that the minimum values of rgba[] for XYZ = 0 is 0.1, */ + /* hence magic 0.305 below comes from the following weighting of rgba[], */ + /* to base A at 0.0 */ + /* Deal with the core rgb to A, S & h conversion: */ + + TRACE(("rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2])) + + /* Preliminary Acromatic response */ + ttA = s->VttA[0] * rgba[0] + s->VttA[1] * rgba[1] + s->VttA[2] * rgba[2]; + + /* Achromatic response */ + A = (ttA - 0.305) * s->Nbb; + + /* Preliminary red-green & yellow-blue opponent dimensions */ + /* a, b & ttd form an (almost) orthogonal coordinate set. */ + /* ttA is in a different direction */ + a = s->Va[0] * rgba[0] + s->Va[1] * rgba[1] + s->Va[2] * rgba[2]; + b = s->Vb[0] * rgba[0] + s->Vb[1] * rgba[1] + s->Vb[2] * rgba[2]; + + /* restricted Saturation to stop divide by zero */ + /* (The exact value isn't important because the numerator dominates as a,b aproach 0 */ + rS = sqrt(a * a + b * b); + if (rS < DBL_EPSILON) + rS = DBL_EPSILON; + TRACE(("ttA = %f, a = %f, b = %f, rS = %f, A = %f\n", ttA,a,b,rS,A)) + + /* Lightness J, Derived directly from Acromatic response. */ + /* Cuttover to a straight line segment when J < 0.005, */ +#ifndef SYMETRICJ /* Cut to a straight line */ + if (A >= s->lA) { + J = pow(A/s->Aw, s->C * s->z); /* J/100 - keep Sign */ + } else { + J = s->jlimit/s->lA * A; /* Straight line */ + TRACE(("limited Acromatic to straight line\n")) + } +#else /* Symetric */ + if (A >= 0.0) { + J = pow(A/s->Aw, s->C * s->z); /* J/100 - keep Sign */ + } else { + J = -pow(-A/s->Aw, s->C * s->z); /* J/100 - keep Sign */ + TRACE(("symetric Acromatic\n")) + } +#endif + + /* Constraied (+ve, non-zero) J */ + if (A > 0.0) { + cJ = pow(A/s->Aw, s->C * s->z); + if (cJ < s->ssmincj) + cJ = s->ssmincj; + } else { + cJ = s->ssmincj; + } + + TRACE(("J = %f, cJ = %f\n",J,cJ)) + + /* Final hue angle */ + h = (180.0/DBL_PI) * atan2(b,a); + h = (h < 0.0) ? h + 360.0 : h; + + /* Eccentricity factor */ + e = (12500.0/13.0 * s->Nc * s->Ncb * (cos(h * DBL_PI/180.0 + 2.0) + 3.8)); + + /* ab scale components */ + k1 = pow(s->nn, 1.0/0.9) * e * pow(cJ, 1.0/1.8)/pow(rS, 1.0/9.0); + k2 = pow(cJ, 1.0/(s->C * s->z)) * s->Aw/s->Nbb + 0.305; + k3 = s->dcomp[1] * a + s->dcomp[2] * b; + + TRACE(("Raw k1 = %f, k2 = %f, k3 = %f, raw ss = %f\n",k1, k2, k3, pow(k1/(k2 + k3), 0.9))) + +#ifdef TRACKMINMAX + { + int sno; + double lrat, urat; + + ss = pow(k1/(k2 + k3), 0.9); + + if (ss < minss) + minss = ss; + if (ss > maxss) + maxss = ss; + + lrat = -k3/k2; + urat = k3 * pow(ss, 10.0/9.0) / k1; + if (lrat > minlrat) + minlrat = lrat; + if (urat > maxurat) + maxurat = urat; + + /* Record distribution of ss min/max vs. J for */ + /* regions outside a,b == 0 */ + + sno = (int)(J * 100.0 + 0.5); + if (sno < 0) { + sno = 101; + if (J < minj) + minj = J; + } else if (sno > 100) { + sno = 102; + if (J > maxj) + maxj = J; + } + if (slotsd[sno] < lrat) + slotsd[sno] = lrat; + if (slotsu[sno] < urat) + slotsu[sno] = urat; + } +#endif /* TRACKMINMAX */ + +#ifdef ENABLE_DDL + + /* Limit ratio of k3 to k2 to stop zero or -ve ss */ + if (k3 < -k2 * s->ddllimit) { + k3 = -k2 * s->ddllimit; + TRACE(("k3 limited to %f due to k3:k2 ratio, ss = %f\n",k3,pow(k1/(k2 + k3), 0.9))) + clip = 1; + } + + /* See if there is going to be a problem in bwd, and limit k3 if there is */ + if (k3 > (k2 * s->ddulimit/(1.0 - s->ddulimit))) { + k3 = (k2 * s->ddulimit/(1.0 - s->ddulimit)); + TRACE(("k3 limited to %f to allow for bk3:bk1 bwd limit\n",k3)) + clip = 1; + } + +#endif /* !ENABLE_DDL */ + +#ifdef DISABLE_TTD + + ss = pow((k1/k2), 0.9); + +#else /* !TRACKMINMAX */ + + /* Compute the ab scale factor */ + ss = pow(k1/(k2 + k3), 0.9); + +#endif /* !ENABLE_DDL */ + +#ifdef ENABLE_SS + if (ss < SSLLIMIT) + ss = SSLLIMIT; + else if (ss > SSULIMIT) + ss = SSULIMIT; +#endif /* ENABLE_SS */ + +#ifdef NEVER +// ------------------------------------------- + /* Show ss components */ + if (s->retss) { + Jab[0] = 1.0; + if (clip) + Jab[0] = 0.0; + Jab[1] = (log10(ss) - +1.5)/(2.0 - +1.5); + Jab[2] = (log10(ss) - +1.0)/(2.5 - +1.0); + + for (i = 0; i < 3; i++) { + if (Jab[i] < 0.0) + Jab[i] = 0.0; + else if (Jab[i] > 1.0) + Jab[i] = 1.0; + } + return 0; + } +// ------------------------------------------- +#endif /* NEVER */ + + ja = a * ss; + jb = b * ss; + + /* Chroma - always +ve, used just for HHKR */ + C = sqrt(ja * ja + jb * jb); + + TRACE(("ss = %f, A = %f, J = %f, C = %f, h = %f\n",ss,A,J,C,h)) + + JJ = J; +#ifndef DISABLE_HHKR + /* Helmholtz-Kohlraush effect */ + if (s->hk && J < 1.0) { + double kk = C/300.0 * sin(DBL_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 1e-6) /* Limit kk to a reasonable range */ + kk = 1.0/(s->hklimit + 1.0/kk); + JJ = J + (1.0 - (J > 0.0 ? J : 0.0)) * kk; + TRACE(("JJ = %f from J = %f, kk = %f\n",JJ,J,kk)) + } +#endif /* DISABLE_HHKR */ + + JJ *= 100.0; /* Scale J */ + + /* Compute Jab value */ + Jab[0] = JJ; + Jab[1] = ja; + Jab[2] = jb; + +#ifdef NEVER /* Brightness/Colorfulness */ + { + double M, Q; + + Q = (4.0/s->C) * sqrt(JJ/100.0) * (s->Aw + 4.0) * pow(s->Fl, 0.25); + M = C * pow(s->Fl, 0.25); + + printf("Lightness = %f, Chroma = %f\n",J,C); + printf("Brightness = %f, Colorfulness = %f\n",M,Q); + } +#endif /* NEVER */ + + TRACE(("Jab %f %f %f\n",Jab[0], Jab[1], Jab[2])) + TRACE(("\n")) + +#ifdef DIAG2 + printf("Processing XYZ->Jab:\n"); + printf("XYZ = %f %f %f\n", XYZi[0], XYZi[1], XYZi[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("Huntpace rgbpP-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Prelim red green a = %f, b = %f\n", a, b); + printf("Hue angle h = %f\n", h); + printf("Eccentricity factor e = %f\n", e); + printf("Achromatic response A = %f\n", A); + printf("Lightness J = %f, H.K. Lightness = %f\n", J * 100, JJ); + printf("Prelim Saturation ss = %f\n", ss); + printf("Chroma C = %f\n", C); + printf("Jab = %f %f %f\n", Jab[0], Jab[1], Jab[2]); + printf("\n"); +#endif + return 0; +} + +static int cam_to_XYZ( +struct _cam02 *s, +double XYZ[3], +double Jab[3] +) { + int i; + double xyz[3], rgbp[3], rgba[3]; + double a, b, ja, jb, J, JJ, C, rC, h, e, A, ss; + double tt, cJ, ttA; + double k1, k2, k3; /* (k1 & k3 are different to the fwd k1 & k3) */ + +#ifdef DIAG2 /* Incase in == out */ + double Jabi[3]; + + Jabi[0] = Jab[0]; + Jabi[1] = Jab[1]; + Jabi[2] = Jab[2]; + +#endif + + TRACE(("\nReverse conversion:\n")) + TRACE(("Jab %f %f %f\n",Jab[0], Jab[1], Jab[2])) + + JJ = Jab[0] * 0.01; /* J/100 */ + ja = Jab[1]; + jb = Jab[2]; + + /* Convert Jab to core A, S & h values: */ + + /* Compute hue angle */ + h = (180.0/DBL_PI) * atan2(jb, ja); + h = (h < 0.0) ? h + 360.0 : h; + + /* Compute chroma value */ + C = sqrt(ja * ja + jb * jb); /* Must be Always +ve, Can be NZ even if J == 0 */ + + /* Preliminary Restricted chroma, always +ve and NZ */ + /* (The exact value isn't important because the numerator dominates as a,b aproach 0) */ + rC = C; + if (rC < DBL_EPSILON) + rC = DBL_EPSILON; + + J = JJ; +#ifndef DISABLE_HHKR + /* Undo Helmholtz-Kohlraush effect */ + if (s->hk && J < 1.0) { + double kk = C/300.0 * sin(DBL_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 1e-6) /* Limit kk to a reasonable range */ + kk = 1.0/(s->hklimit + 1.0/kk); + J = (JJ - kk)/(1.0 - kk); + if (J < 0.0) + J = JJ - kk; + TRACE(("J = %f from JJ = %f, kk = %f\n",J,JJ,kk)) + } +#endif /* DISABLE_HHKR */ + + /* Achromatic response */ +#ifndef SYMETRICJ /* Cut to a straight line */ + if (J >= s->jlimit) { + A = pow(J, 1.0/(s->C * s->z)) * s->Aw; + } else { /* In the straight line segment */ + A = s->lA/s->jlimit * J; + TRACE(("Undo Acromatic straight line\n")) + } +#else /* Symetric */ + if (J >= 0.0) { + A = pow(J, 1.0/(s->C * s->z)) * s->Aw; + } else { /* In the straight line segment */ + A = -pow(-J, 1.0/(s->C * s->z)) * s->Aw; + TRACE(("Undo symetric Acromatic\n")) + } +#endif + + /* Preliminary Acromatic response +ve */ + ttA = (A/s->Nbb)+0.305; + + if (A > 0.0) { + cJ = pow(A/s->Aw, s->C * s->z); + if (cJ < s->ssmincj) + cJ = s->ssmincj; + } else { + cJ = s->ssmincj; + } + TRACE(("C = %f, A = %f from J = %f, cJ = %f\n",C, A,J,cJ)) + + /* Eccentricity factor */ + e = (12500.0/13.0 * s->Nc * s->Ncb * (cos(h * DBL_PI/180.0 + 2.0) + 3.8)); + + /* ab scale components */ + k1 = pow(s->nn, 1.0/0.9) * e * pow(cJ, 1.0/1.8)/pow(rC, 1.0/9.0); + k2 = pow(cJ, 1.0/(s->C * s->z)) * s->Aw/s->Nbb + 0.305; + k3 = s->dcomp[1] * ja + s->dcomp[2] * jb; + + TRACE(("Raw k1 = %f, k2 = %f, k3 = %f, raw ss = %f\n",k1, k2, k3, (k1 - k3)/k2)) + +#ifdef ENABLE_DDL + + /* Limit ratio of k3 to k1 to stop zero or -ve ss */ + if (k3 > (k1 * s->ddulimit)) { + k3 = k1 * s->ddulimit; + TRACE(("k3 limited to %f due to k3:k1 ratio, ss = %f\n",k3,(k1 - k3)/k2)) + } + + /* See if there is going to be a problem in fwd */ + if (k3 < -k1 * s->ddllimit/(1.0 - s->ddllimit)) { + /* Adjust ss to allow for fwd limitd computation */ + k3 = -k1 * s->ddllimit/(1.0 - s->ddllimit); + TRACE(("k3 set to %f to allow for fk3:fk2 fwd limit\n",k3)) + } + +#endif /* ENABLE_DDL */ + +#ifdef DISABLE_TTD + + ss = k1/k2; + +#else /* !DISABLE_TTD */ + + /* Compute the ab scale factor */ + ss = (k1 - k3)/k2; + +#endif /* !DISABLE_TTD */ + +#ifdef ENABLE_SS + if (ss < SSLLIMIT) + ss = SSLLIMIT; + else if (ss > SSULIMIT) + ss = SSULIMIT; +#endif /* ENABLE_SS */ + + /* Unscale a and b */ + a = ja / ss; + b = jb / ss; + + TRACE(("ss = %f, ttA = %f, a = %f, b = %f\n",ss,ttA,a,b)) + + /* Solve for post-adapted cone response of sample */ + /* using inverse matrix on ttA, a, b */ + rgba[0] = (20.0/61.0) * ttA + + ((41.0 * 11.0)/(61.0 * 23.0)) * a + + ((288.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[1] = (20.0/61.0) * ttA + - ((81.0 * 11.0)/(61.0 * 23.0)) * a + - ((261.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[2] = (20.0/61.0) * ttA + - ((20.0 * 11.0)/(61.0 * 23.0)) * a + - ((20.0 * 315.0)/(61.0 * 23.0)) * b; + + TRACE(("rgba %f %f %f\n",rgba[0], rgba[1], rgba[2])) + +#ifdef DISABLE_MATRIX + + XYZ[0] = rgba[0]; + XYZ[1] = rgba[1]; + XYZ[2] = rgba[2]; + +#else /* !DISABLE_MATRIX */ + +#ifdef DISABLE_NONLIN + rgbp[0] = 27.13/400.0 * rgba[0]; + rgbp[1] = 27.13/400.0 * rgba[1]; + rgbp[2] = 27.13/400.0 * rgba[2]; +#else /* !DISABLE_NONLIN */ + + /* Hunt-Pointer_Estevez cone space */ + /* (with linear segment at the +ve end) */ + for (i = 0; i < 3; i++) { + if (rgba[i] < s->nldxval) { + rgbp[i] = s->nldlimit + (rgba[i] - s->nldxval)/s->nldxslope; + } else if (rgba[i] <= s->nluxval) { + tt = rgba[i] - 0.1; + rgbp[i] = pow((27.13 * tt)/(400.0 - tt), 1.0/0.42)/s->Fl; +// rgbp[i] = pow((27.13 * tt)/(400.0 - tt), 1.0/1.0)/s->Fl; + } else { + rgbp[i] = s->nlulimit + (rgba[i] - s->nluxval)/s->nluxslope; + } + } +#endif /* !DISABLE_NONLIN */ + + TRACE(("rgbp %f %f %f\n",rgbp[0], rgbp[1], rgbp[2])) + +#ifdef ENABLE_BLUE_ANGLE_FIX + ss = rgbp[0] + rgbp[1] + rgbp[2]; + if (ss < 1e-9) + ss = 0.0; + else { + ss = (rgbp[2]/ss - 1.0/3.0) * 3.0/2.0; + if (ss > 0.0) + ss = BLUE_BL_MAX * pow(ss, BLUE_BL_POW); + } + if (ss < 0.0) + ss = 0.0; + else if (ss > 1.0) + ss = 1.0; + tt = 0.5 * (rgbp[0] + rgbp[1]); + rgbp[0] = (rgbp[0] - ss * tt)/(1.0 - ss); + rgbp[1] = (rgbp[1] - ss * tt)/(1.0 - ss); + + TRACE(("rgbp after blue fix %f = %f %f %f\n",ss,rgbp[0], rgbp[1], rgbp[2])) +#endif + + +#ifdef ENABLE_COMPR + /* Undo soft limiting */ + { + double tt; /* Temporary */ + double wrgb[3]; /* White target */ + + /* Make white target white point with same Y value */ + tt = rgbp[0] * s->icc[1][0] + rgbp[1] * s->icc[1][1] + rgbp[2] * s->icc[1][2]; + tt = tt > BC_WHMINY ? tt : BC_WHMINY; /* Limit to minimum Y */ + icmScale3(wrgb, s->rgbpW, tt/s->Wxyz[1]); /* White target at same Y */ + TRACE(("wrgb %f %f %f\n", wrgb[0], wrgb[1], wrgb[2])) + + /* Un-limit b,g,r in turn */ + for (i = 2; i >= 0; i--) { + double cv; /* Compression value */ + double ctv; /* Compression target value */ + double cd; /* Compression displacement needed */ + double cvec[3]; /* Normalized correction vector */ + double isec[3]; /* Intersection with plane */ + double offs; /* Offset of point from orgin*/ + double range; /* Threshold to start compression */ + double asym; /* Compression asymtope */ + + /* Compute compression direction as vector towards white */ + /* (We did try correcting in a blend of limit plane normal and white, */ + /* but compressing towards white seems to be the best.) */ + icmSub3(cvec, wrgb, rgbp); /* Direction of white target */ + + TRACE(("rgbp %f %f %f\n", rgbp[0], rgbp[1], rgbp[2])) + TRACE(("cvec %f %f %f\n", cvec[0], cvec[1], cvec[2])) + + if (cvec[i] < 1e-9) { /* compression direction can't correct this coord */ + TRACE(("Intersection with limit plane failed\n")) + continue; + } + + /* Scale compression vector to make it move a unit in normal direction */ + icmScale3(cvec, cvec, 1.0/cvec[i]); /* Normalized vector to white */ + TRACE(("cvec %f %f %f\n", cvec[0], cvec[1], cvec[2])) + + /* Compute intersection of correction direction with this limit plane */ + /* (This corresponds with finding displacement of rgbp by cvec */ + /* such that the current coord value = 0) */ + icmScale3(isec, cvec, -rgbp[i]); /* (since cvec[i] == 1.0) */ + icmAdd3(isec, isec, rgbp); + TRACE(("isec %f %f %f\n", isec[0], isec[1], isec[2])) + + /* Compute distance from intersection to origin */ + offs = pow(icmNorm3(isec), 0.85); + + range = s->crange[i] * offs; /* Scale range by distance to origin */ + if (range > BC_MAXRANGE) /* so that it tapers down as we approach it */ + range = BC_MAXRANGE; /* and limit maximum */ + + /* Aiming above plane when far from origin, */ + /* but below plane at the origin, so that black isn't affected. */ + asym = range - 0.2 * (range + (0.01 * s->crange[i])); + + ctv = cv = rgbp[i]; /* Distance above/below limit plane */ + + TRACE(("ch %d, offs %f, range %f asym %f, cv %f\n",i, offs,range,asym,cv)) + + if (ctv < (range - 1e-12)) { /* Need to expand */ + + if (ctv <= asym) { + cd = BC_LIMIT; + TRACE(("ctv %f < asym %f\n",ctv,asym)) + } else { + double aa, bb; + aa = 1.0/(range - ctv); + bb = 1.0/(range - asym); + if (aa > (bb + 1e-12)) + cv = range - 1.0/(aa - bb); + cd = ctv - cv; /* Displacement needed */ + } + if (cd > BC_LIMIT) + cd = BC_LIMIT; + TRACE(("ch %d cd = %f, scaled cd %f\n",i,cd,cd)) + + if (cd > 1e-9) { + icmScale3(cvec, cvec, -cd); /* Compression vector */ + icmAdd3(rgbp, rgbp, cvec); /* Compress by displacement */ + TRACE(("rgbp after decomp. = %f %f %f\n",rgbp[0], rgbp[1], rgbp[2])) + } + } + } + } +#endif /* ENABLE_COMPR */ + + /* Chromaticaly transformed sample value */ + /* Spectrally sharpened cone responses */ + /* XYZ values */ + icmMulBy3x3(xyz, s->icc, rgbp); + + TRACE(("XYZ = %f %f %f\n",xyz[0], xyz[1], xyz[2])) + + /* Subtract flare */ + XYZ[0] = s->Fisc * (xyz[0] - s->Fsxyz[0]); + XYZ[1] = s->Fisc * (xyz[1] - s->Fsxyz[1]); + XYZ[2] = s->Fisc * (xyz[2] - s->Fsxyz[2]); + +#endif /* !DISABLE_MATRIX */ + + TRACE(("XYZ after flare = %f %f %f\n",XYZ[0], XYZ[1], XYZ[2])) + TRACE(("\n")) + +#ifdef DIAG2 + printf("Processing:\n"); + printf("Jab = %f %f %f\n", Jabi[0], Jabi[1], Jabi[2]); + printf("Chroma C = %f\n", C); + printf("Preliminary Saturation ss = %f\n", ss); + printf("Lightness J = %f, H.K. Lightness = %f\n", J * 100, JJ * 100); + printf("Achromatic response A = %f\n", A); + printf("Eccentricity factor e = %f\n", e); + printf("Hue angle h = %f\n", h); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Hunundeft-P-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("XYZ = %f %f %f\n", XYZ[0], XYZ[1], XYZ[2]); + printf("\n"); +#endif + return 0; +} + diff --git a/xicc/cam02.h b/xicc/cam02.h new file mode 100644 index 0000000..9491572 --- /dev/null +++ b/xicc/cam02.h @@ -0,0 +1,208 @@ + +/* + * cam02 + * + * Color Appearance Model, based on + * CIECAM02, "The CIECAM02 Color Appearance Model" + * by Nathan Moroney, Mark D. Fairchild, Robert W.G. Hunt, Changjun Li, + * M. Ronnier Luo and Todd Newman, IS&T/SID Tenth Color Imaging + * Conference, with the addition of the Viewing Flare + * model described on page 487 of "Digital Color Management", + * by Edward Giorgianni and Thomas Madden, and the + * Helmholtz-Kohlraush effect, using the equation + * the Bradford-Hunt 96C model as detailed in Mark Fairchilds + * book "Color Appearance Models". + * + * Author: Graeme W. Gill + * Date: 17/1/2004 + * Version: 1.00 + * + * This file is based on cam97s3.h by Graeme Gill. + * + * Copyright 2004 - 2011 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* Definitions assumed here: + + Stimulus field is the 2 degrees field of view of the sample + being characterised. + + Viewing/Scene/Image field is the area of the whole image or + display surface that the stimulus is part of. + + Background field is 10-12 degree field immediately surrounding the stimulus field. + This may be within, overlap or encompass the Viewing/Scene/Image field. + + Visual field is the 130 degree angular field that is seen by the eyes. + + Surround/Adapting field is the visual field minus the background field, + and is what is assumed to be setting the viewers light adaptation level. + + Ambient field is the whole surrounding environmental light field. + + Illuminating field is the field that illuminates the reflective + Scene/Image. It may be the same as the Ambient field or it could + be a specific light source that is directed to the viewing scene.. + + NOTE: In "Digital Color Management", Giorgianni and Madden use the term + "Surround" to mean the same thing as "Background" in the CIECAM02 terminology. + The ICC standard doesn't define what it means by "Surround illumination". + +*/ + +/* Rules of Thumb: */ + +/* Ambient Luminance (Lamb, cd/m^2) is Ambient Illuminance (Eamb, Lux) divided by PI. */ +/* i.e. Lamb = Eamb/PI */ /* (1 foot candle = 0.0929 lux) */ + +/* Illuminating field Luminance (Li, cd/m^2) is the Illuminating field Illuminance (Ei, Lux) */ +/* divide by PI. i.e. Li = Ei/PI */ + +/* The Adapting/Surround Luminance is La often taken to be */ +/* the 20% of the Ambient Luminance (gray world, 50% perceptual) */ +/* i.e. La = Lamb/5 = Eamb/15.7 */ +/* If the Illuminating field covers the Adapting/surround, the it will be 20% of the */ +/* Illuminating field. */ + +/* For a reflective print, the Viewing/Scene/Image luminance (Lv, cd/m^2), */ +/* will be the Illuminating Luminance (Li, cd/m^2) or the Ambient Luminance (Lamb, cd/m^2) */ +/* reflected by the media white point (Yw) */ + +/* If there is no special illumination for a reflective print, */ +/* then the Illuminating Luminance (Li) will be the Ambient Luminance (Lamb) */ + +/* An emisive display will have an independently determined Lv. */ + +/* The classification of the type of surround is */ +/* determined by comparing the Adapting/Surround luminance (La, cd/m^2) */ +/* with the average luminance of the Viewing/Scene/Image field (Lv, cd/m^2) */ + +/* La/Lv == 0%, dark */ +/* La/Lv 0 - 20%, dim */ +/* La/Lv > 20%, average */ +/* special, cut sheet */ + +/* The Background relative luminance Yb is typically assumed to */ +/* be 0.18 .. 0.2, and is assumed to be grey. */ + +/* The source of flare light depends on the type of display system. */ +/* For a CRT, it will be the Ambient light reflecting off the glass surface. */ +/* (This implies Yf = Lamb * reflectance/Lv) */ +/* For a reflection print, it will be the Illuminant or Ambient reflecting from the media */ +/* surface. (Yf = Li * reflectance) */ +/* For a projected image, it will be stray projector light, scattered by the */ +/* surround, screen and air particles. (Yf = Li * reflectance_and_scattering) */ + +/* + +Typical adapting field luminances and white luminance in reflective setup: + +E = illuminance in Lux +Lv = White luminance assuming 100% reflectance +La = Adapting field luminance in cd/m^2, assuming 20% reflectance from surround + + E La Lv Condition + 11 0.7 4 Twilight + 32 2 10 Subdued indoor lighting + 64 4 20 Less than typical office light; sometimes recommended for + display-only workplaces (sRGB) + 350 22 111 Typical Office (sRGB annex D) + 500 32 160 Practical print evaluationa (ISO-3664 P2) + 1000 64 318 Good Print evaluation (CIE 116-1995) + 1000 64 318 Television Studio lighting + 1000 64 318 Overcast Outdoors + 2000 127 637 Critical print evaluation (ISO-3664 P1) + 10000 637 3183 Typical outdoors, full daylight + 50000 3185 15915 Bright summers day + +*/ + +/* ---------------------------------- */ + +struct _cam02 { +/* Public: */ + void (*del)(struct _cam02 *s); /* We're done with it */ + + int (*set_view)( + struct _cam02 *s, + ViewingCondition Ev, /* Enumerated Viewing Condition */ + double Wxyz[3], /* Reference/Adapted White XYZ (Y scale 1.0) */ + double La, /* Adapting/Surround Luminance cd/m^2 */ + double Yb, /* Luminance of Background relative to reference white (range 0.0 .. 1.0) */ + double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set */ + double Yf, /* Flare as a fraction of the reference white (range 0.0 .. 1.0) */ + double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ + int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ + ); + + /* Conversions. Return nz on error */ + int (*XYZ_to_cam)(struct _cam02 *s, double *out, double *in); + int (*cam_to_XYZ)(struct _cam02 *s, double *out, double *in); + +/* Private: */ + /* Scene parameters */ + ViewingCondition Ev; /* Enumerated Viewing Condition */ + double La; /* Adapting/Surround Luminance cd/m^2 */ + double Wxyz[3]; /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + double Yb; /* Relative Luminance of Background to reference white (Y range 0.0 .. 1.0) */ + double Yf; /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + double Fxyz[3]; /* The Flare white coordinates (typically the Ambient color) */ + + /* Internal parameters */ + double C; /* Surround Impact */ + double Nc; /* Chromatic Induction */ + double F; /* Adaptation Degree */ + + /* Pre-computed values */ + double cc[3][3]; /* Forward cone and chromatic transform */ + double icc[3][3]; /* Reverse cone and chromatic transform */ + double crange[3]; /* ENABLE_COMPR compression range */ + double Va[3], Vb[3], VttA[3], Vttd[3]; /* rgba vectors */ + double dcomp[3]; /* Vttd in terms of VttA, Va, Vb */ + double Fsc; /* Flare scale */ + double Fisc; /* Inverse flare scale */ + double Fsxyz[3]; /* Scaled Flare color coordinates */ + double rgbW[3]; /* Sharpened cone response white values */ + double D; /* Degree of chromatic adaption */ + double Drgb[3]; /* Chromatic transformation value */ + double rgbcW[3]; /* Chromatically transformed white value */ + double rgbpW[3]; /* Hunt-Pointer-Estevez cone response space white */ + double n; /* Background induction factor */ + double nn; /* Precomuted function of n */ + double Fl; /* Lightness contrast factor ?? */ + double Nbb; /* Background brightness induction factors */ + double Ncb; /* Chromatic brightness induction factors */ + double z; /* Base exponential nonlinearity */ + double rgbaW[3]; /* Post-adapted cone response of white */ + double Aw; /* Achromatic response of white */ + double nldxval; /* Non-linearity output value at lower crossover to linear */ + double nldxslope; /* Non-linearity slope at lower crossover to linear */ + double nluxval; /* Non-linearity value at upper crossover to linear */ + double nluxslope; /* Non-linearity slope at upper crossover to linear */ + double lA; /* JLIMIT Limited A */ + + /* Option flags, code not always enabled */ + int hk; /* Use Helmholtz-Kohlraush effect */ + int trace; /* Trace values through computation */ + int retss; /* Return ss rather than Jab */ + int range; /* (for cam02ref.h) return on range error */ + + double nldlimit; /* value of NLDLIMIT, sets non-linearity lower limit */ + double nldicept; /* value of NLDLICEPT, sets straight line intercept with 0.1 output */ + double nlulimit; /* value of NLULIMIT, sets non-linearity upper limit (tangent) */ + double ddllimit; /* value of DDLLIMIT, sets fwd k3 to k2 limit */ + double ddulimit; /* value of DDULIMIT, sets bwd k3 to k1 limit */ + double ssmincj; /* value of SSMINJ, sets cJ minimum value */ + double jlimit; /* value of JLIMIT, sets cutover to straight line for J point */ + double hklimit; /* value of HKLIMIT, sets HK factor upper limit */ + +}; typedef struct _cam02 cam02; + + +/* Create a cam02 conversion class, with default viewing conditions */ +cam02 *new_cam02(void); + diff --git a/xicc/cam02plot.c b/xicc/cam02plot.c new file mode 100644 index 0000000..ca68b52 --- /dev/null +++ b/xicc/cam02plot.c @@ -0,0 +1,845 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 11/11/2004 + * Version: 1.00 + * + * Copyright 2000-2004 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the CIECAM02 continuity. + * This test creates file "cam02plot.tif" containing values that + * have been converted to and back from CIECAM02 space. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xcam.h" +#include "cam02.h" +#include "tiffio.h" +#include "cam02ref.h" + +#define DEFRES 100 /* Default cube resolution */ + + +#define NORMAL_XYZ2RGB +#undef NIEVE_XYZ2RGB +#undef COMPLEX_XYZ2RGB + +#define SCALE 1.0 /* Compress RGB by scale towards grey */ + +#ifndef _isnan +#define _isnan(x) ((x) != (x)) +#define _finite(x) ((x) == 0.0 || (x) * 1.0000001 != (x)) +#endif + +static void +Lab2XYZ(double *out, double *in) { + double L = in[0], a = in[1], b = in[2]; + double x,y,z,fx,fy,fz; + + if (L > 8.0) { + fy = (L + 16.0)/116.0; + y = pow(fy,3.0); + } else { + y = L/903.2963058; + fy = 7.787036979 * y + 16.0/116.0; + } + + fx = a/500.0 + fy; + if (fx > 24.0/116.0) + x = pow(fx,3.0); + else + x = (fx - 16.0/116.0)/7.787036979; + + fz = fy - b/200.0; + if (fz > 24.0/116.0) + z = pow(fz,3.0); + else + z = (fz - 16.0/116.0)/7.787036979; + + out[0] = x * 0.9642; + out[1] = y * 1.0000; + out[2] = z * 0.8249; +} + +/* CIE XYZ to perceptual Lab */ +static void +XYZ2Lab(double *out, double *in) { + double X = in[0], Y = in[1], Z = in[2]; + double x,y,z,fx,fy,fz; + + x = X/0.9642; + y = Y/1.0000; + z = Z/0.8249; + + if (x > 0.008856451586) + fx = pow(x,1.0/3.0); + else + fx = 7.787036979 * x + 16.0/116.0; + + if (y > 0.008856451586) + fy = pow(y,1.0/3.0); + else + fy = 7.787036979 * y + 16.0/116.0; + + if (z > 0.008856451586) + fz = pow(z,1.0/3.0); + else + fz = 7.787036979 * z + 16.0/116.0; + + out[0] = 116.0 * fy - 16.0; + out[1] = 500.0 * (fx - fy); + out[2] = 200.0 * (fy - fz); +} + +#ifdef NORMAL_XYZ2RGB + +static double sign(double x) { + if (x < 0.0) + return -1.0; + else + return 1.0; +} + +/* Version with reasonable clipping. Converts to Lab, */ +/* clips the Lab an then converts to sRGB */ +/* Convert from XYZ to sRGB */ +void XYZ2sRGB(double *out, double *in, int trace) { + double lab[3]; + double tmp[3]; + double scale = SCALE; /* Scale RGB towards grey */ + double mat[3][3] = + { + { 3.2410, -1.5374, -0.4986 }, + { -0.9692, 1.8760, 0.0416 }, + { 0.0556, -0.2040, 1.0570 } + }; + int i; + +// ~~999 + trace = 0; + + /* Copy from input */ + for(i = 0; i < 3; i++) + tmp[i] = in[i]; + + if (trace) printf("XYZ to RGB\n"); + if (trace) printf("input XYZ %f %f %f\n",tmp[0],tmp[1],tmp[2]); + /* Clip to reasonable range */ +// if (tmp[1] <= 0.0) +// tmp[0] = tmp[2] = 0.0; + XYZ2Lab(lab, tmp); + if (trace) printf("Lab %f %f %f\n",tmp[0],tmp[1],tmp[2]); + icmClipLab(lab, lab); + if (trace) printf("Clipped Lab %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* We're going to assume that L == 0 is always black */ + if (lab[0] <= 0.0) { + lab[1] = lab[2] = 0.0; + } + if (trace) printf("Clipped Lab2 %f %f %f\n",tmp[0],tmp[1],tmp[2]); + Lab2XYZ(tmp, lab); + if (trace) printf("XYZ %f %f %f\n",tmp[0],tmp[1],tmp[2]); + icmClipXYZ(tmp, tmp); + if (trace) printf("clipped XYZ %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Now convert to sRGB assuming D50 (??) white point */ + icmMulBy3x3(tmp, mat, tmp); + + /* Scale */ + for(i = 0; i < 3; i++) { + tmp[i] = ((tmp[i] - 0.5) * scale) + 0.5; + } + + if (trace) printf("raw RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Apply gamma */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.00304) { + tmp[i] = tmp[i] * 12.92; + } else { + tmp[i] = 1.055 * pow(tmp[i], 1.0/2.4) - 0.055; + } + } + + if (trace) printf("gamma corrected RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + +#ifndef NEVER + /* Clip to nearest */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.0) + tmp[i] = 0.0; + else if (tmp[i] > 1.0) + tmp[i] = 1.0; + } +#else + /* Clip towards center */ + { + double biggest = -100.0; + for(i = 0; i < 3; i++) { + tmp[i] -= 0.5; + if (fabs(tmp[i]) > biggest) + biggest = fabs(tmp[i]); + } + for(i = 0; i < 3; i++) { + if (biggest > 0.5) + tmp[i] *= 0.5/biggest; + tmp[i] += 0.5; + } + } +#endif + if (trace) printf("output clipped RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Copy to output */ + for(i = 0; i < 3; i++) + out[i] = tmp[i]; + +} + +#endif /* NORMAL_XYZ2RGB */ + +#ifdef NIEVE_XYZ2RGB + +/* Nieve version with RGB clipping */ +/* Convert from XYZ to sRGB */ +void XYZ2sRGB(double *out, double *in, int trace) { + double tmp[3]; + double scale = SCALE; /* Scale RGB towards grey */ + double mat[3][3] = + { + { 3.2410, -1.5374, -0.4986 }, + { -0.9692, 1.8760, 0.0416 }, + { 0.0556, -0.2040, 1.0570 } + }; + int i; + + if (trace) printf("XYZ to RGB\n"); + if (trace) printf("input XYZ %f %f %f\n",in[0],in[1],in[2]); + + /* Now convert to sRGB assuming D50 (??) white point */ + icmMulBy3x3(tmp, mat, in); + + /* Scale */ + for(i = 0; i < 3; i++) { + tmp[i] = ((tmp[i] - 0.5) * scale) + 0.5; + } + + if (trace) printf("raw RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Clip to nearest */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.0) + tmp[i] = 0.0; + else if (tmp[i] > 1.0) + tmp[i] = 1.0; + } + + if (trace) printf("clipped RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Apply gamma */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.00304) { + tmp[i] = tmp[i] * 12.92; + } else { + tmp[i] = 1.055 * pow(tmp[i], 1.0/2.4) - 0.055; + } + } + + if (trace) printf("output gamma corrected RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Copy to output */ + for(i = 0; i < 3; i++) + out[i] = tmp[i]; +} + +#endif /* NIEVE_XYZ2RGB */ + +#ifdef COMPLEX_XYZ2RGB + +/* Complex version using powell to ensure good clipping */ +/* Callback context */ +typedef struct { + double scale; + double lab[3]; + double imat[3][3]; +} ctx; + +double clipf(void *fdata, double tp[]) { + ctx *x = (ctx *)fdata; + double lab[3], tmp[3], tt; + int k; + double rv = 0.0; + +//printf("\n"); +//printf("~1 clipf got %f %f %f\n",tp[0],tp[1],tp[2]); + + /* Penalize for being out of gamut */ + for (k = 0; k < 3; k++) { + if (tp[k] < 0.0) { + tt = 100000.0 * (0.0 - tp[k]); + rv += tt * tt; + } else if (tp[k] > 1.0) { + tt = 100000.0 * (tp[k] - 1.0); + rv += tt * tt; + } + } +//printf("~1 rv out of gamut = %f\n",rv); + + /* Unscale it */ + for(k = 0; k < 3; k++) + tmp[k] = ((tp[k] - 0.5) / x->scale) + 0.5; +//printf("~1 unscale %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + icmMulBy3x3(tmp, x->imat, tmp); /* To XYZ */ +//printf("~1 xyz %f %f %f\n",tmp[0],tmp[1],tmp[2]); + XYZ2Lab(lab, tmp); /* To Lab */ +//printf("~1 lab = %f %f %f\n",lab[0],lab[1],lab[2]); +//printf("~1 target = %f %f %f\n",x->lab[0],x->lab[1],x->lab[2]); + + /* Compute an error to the goal */ + tt = 10000.0 * (lab[0] - x->lab[0]); + rv += tt * tt; + + tt = 1.0 * (lab[1] - x->lab[1]); + rv += tt * tt; + tt = 1.0 * (lab[2] - x->lab[2]); + rv += tt * tt; + +//printf("~1 rv = %f\n",rv); + + return rv; +} + +/* Convert from XYZ to sRGB */ +void XYZ2sRGB(double *out, double *in, int trace) { + double lab[3], tmp[3]; + double scale = SCALE; /* Scale RGB towards grey */ + double mat[3][3] = + { + { 3.2410, -1.5374, -0.4986 }, + { -0.9692, 1.8760, 0.0416 }, + { 0.0556, -0.2040, 1.0570 } + }; + int i; + + /* Copy from input */ + for(i = 0; i < 3; i++) + tmp[i] = in[i]; + + if (trace) printf("XYZ to RGB\n"); + + if (trace) printf("input XYZ %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + XYZ2Lab(lab, tmp); + + if (trace) printf("Lab %f %f %f\n",lab[0],lab[1],lab[2]); + + icmClipLab(lab, lab); + + if (trace) printf("Clipped Lab %f %f %f\n",lab[0],lab[1],lab[2]); + + /* Now convert to sRGB assuming D50 (??) white point */ + Lab2XYZ(tmp, lab); + icmMulBy3x3(tmp, mat, tmp); + + /* Scale */ + for(i = 0; i < 3; i++) { + tmp[i] = ((tmp[i] - 0.5) * scale) + 0.5; + } + + if (trace) printf("raw RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* See if it need clipping */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.0 || tmp[i] > 1.0) + break; + } + /* It needs more clipping */ + if (i < 3) { + ctx x; + double ss[3] = { 0.1, 0.1, 0.1 }; + x.lab[0] = lab[0]; + x.lab[1] = lab[1]; + x.lab[2] = lab[2]; + x.scale = scale; + icmInverse3x3(x.imat, mat); + + if (powell(NULL, 3, tmp, ss, 1e-6, 1000, clipf, (void *)&x) < 0.0) + error("RGB clip failed"); + } + if (trace) printf("RGB clip %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Clip to nearest */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.0) + tmp[i] = 0.0; + else if (tmp[i] > 1.0) + tmp[i] = 1.0; + } + if (trace) printf("clip to nearest RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Apply gamma */ + for(i = 0; i < 3; i++) { + if (tmp[i] < 0.00304) { + tmp[i] = tmp[i] * 12.92; + } else { + tmp[i] = 1.055 * pow(tmp[i], 1.0/2.4) - 0.055; + } + } + if (trace) printf("gamma corrected RGB %f %f %f\n",tmp[0],tmp[1],tmp[2]); + + /* Copy to output */ + for(i = 0; i < 3; i++) + out[i] = tmp[i]; + +} +#endif /* COMPLEX_XYZ2RGB */ + +void usage(char *diag) { + fprintf(stderr,"Create a 3D slice plot of XYZ/Lab <-> Jab, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + if (diag != NULL) + fprintf(stderr,"Diagnostic '%s'\n",diag); + fprintf(stderr,"usage: cam02plot [-options] x y\n"); + fprintf(stderr,"'cam02plot -l -o' shows problem best\n"); + fprintf(stderr," -v level Verbosity level 0 - 2 (default = 1)\n"); + fprintf(stderr," -n Don't use Helmholtz-Kohlraush flag\n"); + fprintf(stderr," -f Use the reference CIECAM transform\n"); + fprintf(stderr," -r res Resolution (default %d)\n",DEFRES); + fprintf(stderr," -l Lab source cube (default XYZ source cube)\n"); + fprintf(stderr," -i XYZ/Lab -> Jab (default XYZ/Lab -> Lab)\n"); + fprintf(stderr," -o Jab -> XYZ -> RGB (defult Lab -> XYZ -> RGB\n"); + fprintf(stderr," -s XYZ/Lab -> ab scale as RGB\n"); + fprintf(stderr," -x Cross section plots\n"); + fprintf(stderr," x y Return input color for this coordinate\n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char *tiffname = "cam02plot.tif"; + TIFF *wh = NULL; + uint16 resunits = RESUNIT_INCH; + float resx = 75.0, resy = 75.0; + tdata_t *obuf; + unsigned char *ob; + int verb = 0; + int transl = 0; /* Translate x,y into input color */ + int tx = 0, ty = 0; /* coordinate to translate */ + int use_hk = 1; /* Use Helmholtz-Kohlraush flag */ + int use_ref = 0; /* Use reference transform rather than working code */ + int lab_plot = 0; /* Lab <-> Jab plot, else XYZ <-> Jab */ + int to_jab = 0; /* Convert to Jab, else convert to Lab */ + int from_jab = 0; /* Convert from Jab, else convert from Lab */ + int to_ss = 0; /* Convert from XYZ/Lab to abs scale ss */ + int xsect = 0; /* Cross section plots */ + + double white[9][3] = { + { 0.964242, 1.000000, 0.825124 }, /* 0: D50 */ + { 1.098494, 1.000000, 0.355908 }, /* 1: A */ + { 0.908731, 1.000000, 0.987233 }, /* 2: F5 */ + { 1.128981, 1.000000, 0.305862 }, /* 3: D25 */ + { 0.950471, 1.000000, 1.088828 }, /* 4: D65 */ + { 0.949662, 1.000000, 1.160438 }, /* 5: D80 */ + { 0.949662, 1.000000, 1.160438 }, /* 6: D80 */ + { 0.951297, 1.000000, 1.338196 }, /* 7: D85 */ + { 0.952480, 1.000000, 1.386693 } /* 8: D90 */ + }; + +#ifdef NEVER + double La[6] = { 10.0, 31.83, 100.0, 318.31, 1000.83, 3183.1 }; + + ViewingCondition Vc[4] = { + vc_average, + vc_dark, + vc_dim, + vc_cut_sheet + }; +#endif /* NEVER */ + + int i, j, k, m; + double xyz[3], Jab[3], rgb[3]; + cam02 *cam1, *cam2; + + int res = DEFRES; /* Resolution of scan through space */ + int ares; /* Array of 2d sub-rasters */ + int w, h; /* Width and height of raster */ + int x, y; + int sp = 5; /* Spacing beween resxres sub-images */ + + error_program = argv[0]; + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage("Requested usage"); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + fa = nfa; + if (na == NULL) + verb = 2; + else { + if (na[0] == '0') + verb = 0; + else if (na[0] == '1') + verb = 1; + else if (na[0] == '2') + verb = 2; + else + usage("Illegal verbosity level"); + } + } + + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) + usage("Need resolution after -r"); + res = atoi(na); + if (res < 2 || res > 1000) + usage("Resolution is out of range"); + } + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + use_hk = 0; + } + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + use_ref = 1; + } + else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') { + lab_plot = 1; + } + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + to_jab = 1; + } + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + from_jab = 1; + } + else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + to_ss = 1; + } + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + xsect = 1; + } + else + usage("Unknown flag"); + } else + break; + } + + if ((fa+1) < argc) { + tx = atoi(argv[fa]); + ty = atoi(argv[fa+1]); + transl = 1; + } + + + /* Setup cam to convert to Jab */ + if (use_ref) + cam1 = new_cam02ref(); + else + cam1 = new_cam02(); + cam1->set_view( + cam1, + vc_average, /* Enumerated Viewing Condition */ + white[4], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + 1000.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[4], /* The Flare color coordinates (typically the Ambient color) */ + use_hk /* use Helmholtz-Kohlraush flag */ + ); + + /* Setup cam to convert from Jab */ + if (use_ref) + cam2 = new_cam02ref(); + else + cam2 = new_cam02(); + cam2->set_view( + cam2, + vc_average, /* Enumerated Viewing Condition */ + white[4], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + 1000.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[4], /* The Flare color coordinates (typically the Ambient color) */ + use_hk /* use Helmholtz-Kohlraush flag */ + ); + + /* Figure out the size of the raster */ + ares = (int)ceil(sqrt((double)res)); + if (verb) + printf("For res %d, ares = %d\n",res,ares); + + w = ares * res + sp * (ares+1); + h = ares * res + sp * (ares+1); + + if (to_ss) { + cam1->retss = 1; + } + + if (transl) { + int ix,iy,iz; + double dx,dy,dz; + double tmp[3], lab[3]; + + cam1->trace = 1; + cam2->trace = 1; + +//printf("~1 translate %d %d\n",tx,ty); + tx -= sp; /* Border at base */ + ty -= sp; + +//printf("~1 offset %d %d\n",tx,ty); + ix = tx % (res + sp); + iy = ty % (res + sp); + iz = (ty / (res + sp)) * ares + (tx / (res + sp)); + +//printf("~1 x y z = %d %d %d\n", ix,iy,iz); + + dx = ix/(res-1.0); + dy = iy/(res-1.0); + dz = iz/((ares * ares)-1.0); + +//printf("~1 X Y Z = %f %f %f\n", dx,dy,dz); + + /* Source range is extended L*a*b* type */ + if (lab_plot) { + if (xsect) { + lab[0] = 180.0 * dz - 40.0; + lab[1] = 300.0 * dy - 150.0; + lab[2] = 300.0 * dx - 150.0; + } else { + lab[0] = 180.0 * dx - 40.0; + lab[1] = 300.0 * dy - 150.0; + lab[2] = 300.0 * dz - 150.0; + } +//printf("~1 Lab = %f %f %f\n", lab[0],lab[1],lab[2]); + Lab2XYZ(tmp, lab); + printf("======================================================\n"); + printf("%d,%d -> Lab %f %f %f\n", tx+sp, ty+sp, lab[0],lab[1],lab[2]); + printf(" == XYZ %f %f %f\n", tmp[0],tmp[1],tmp[2]); + + /* Else source range is extended XYZ type */ + } else { + if (xsect) { + tmp[0] = 1.4 * dz - 0.2; + tmp[1] = 1.4 * dy - 0.2; + tmp[2] = 1.6 * dx - 0.2; + } else { + tmp[0] = 1.4 * dx - 0.2; + tmp[1] = 1.4 * dy - 0.2; + tmp[2] = 1.6 * dz - 0.2; + } + printf("%d,%d -> Input color XYZ %f %f %f\n", tx, ty, tmp[0],tmp[1],tmp[2]); + } + + if (to_ss) { + cam1->XYZ_to_cam(cam1, Jab, tmp); + printf(" XYZ -> ss %f %f %f\n", Jab[0],Jab[1],Jab[2]); + + /* Convert XYZ through cam and back, then to RGB */ + } else if (to_jab) { + cam1->XYZ_to_cam(cam1, Jab, tmp); + if (to_ss) { + printf(" XYZ -> ss %f %f %f\n", Jab[0],Jab[1],Jab[2]); + } else { + printf(" XYZ -> ss %f %f %f\n", Jab[0],Jab[1],Jab[2]); + } + + /* Else convert XYZ to L*a*b* */ + } else { + XYZ2Lab(Jab, tmp); + printf(" XYZ -> Lab %f %f %f\n", Jab[0],Jab[1],Jab[2]); + } + + /* Convert from Jab back to XYZ */ + if (from_jab) { + cam2->cam_to_XYZ(cam2, rgb, Jab); + printf(" Jab -> XYZ %f %f %f\n", rgb[0],rgb[1],rgb[2]); + + /* Else convert from Lab back to XYZ */ + } else { + Lab2XYZ(rgb, Jab); + printf(" Lab -> XYZ %f %f %f\n", rgb[0],rgb[1],rgb[2]); + } + + XYZ2Lab(tmp, rgb); + printf(" == Lab %f %f %f\n", tmp[0],tmp[1],tmp[2]); + + /* Interpret XYZ as RGB color */ + XYZ2sRGB(rgb, rgb, 1); + printf(" XYZ -> RGB %f %f %f\n", rgb[0],rgb[1],rgb[2]); + + cam1->trace = 0; + cam2->trace = 0; + + printf("\n"); + exit(0); + } + + if (verb) + printf("Raster width = %d, height = %d\n",w,h); + + /* Setup the tiff file */ + if ((wh = TIFFOpen(tiffname, "w")) == NULL) + error("Can\'t create TIFF file '%s'!",tiffname); + + TIFFSetField(wh, TIFFTAG_IMAGEWIDTH, w); + TIFFSetField(wh, TIFFTAG_IMAGELENGTH, h); + TIFFSetField(wh, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(wh, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(wh, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(wh, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(wh, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(wh, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(wh, TIFFTAG_RESOLUTIONUNIT, resunits); + TIFFSetField(wh, TIFFTAG_XRESOLUTION, resx); + TIFFSetField(wh, TIFFTAG_YRESOLUTION, resy); + TIFFSetField(wh, TIFFTAG_IMAGEDESCRIPTION, "cam02plot"); + + obuf = _TIFFmalloc(TIFFScanlineSize(wh)); + ob = (unsigned char *)obuf; + + y = 0; + + /* Fill sp lines with black */ + for (x = 0, m = 0; m < w; m++) { + ob[x] = ob[x+1] = ob[x+2] = 0; + x += 3; + } + for (j = 0; j < sp; j++, y++) { + TIFFWriteScanline(wh, ob, y, 0); + } + + for (i = 0; i < ares; i++) { /* Vertical blocks (rows) */ + for (j = 0; j < res; j++, y++) { /* Vertical in block (y = Y/a*) */ + xyz[1] = j/(res-1.0); + x = 0; + + /* Fill sp pixels with black */ + for (m = 0; m < sp; m++) { + ob[x] = ob[x+1] = ob[x+2] = 0; + x += 3; + } + for (k = 0; k < ares; k++) { /* Horizontal blocks (columns) */ + int zv = i * ares + k; + if (zv >= res) { + /* Fill res pixels with black */ + for (m = 0; m < res; m++) { + ob[x] = ob[x+1] = ob[x+2] = 0; + x += 3; + } + } else { + xyz[2] = zv/(res-1.0); /* z = block = Z/b* */ + for (m = 0; m < res; m++) { /* Horizontal in block (x = X/L*) */ + double tmp[3], xyz2[3]; + xyz[0] = m/(res-1.0); + + if (lab_plot) { + if (xsect) { + tmp[0] = 180.0 * xyz[2] - 40.0; + tmp[1] = 300.0 * xyz[1] - 150.0; + tmp[2] = 300.0 * xyz[0] - 150.0; + } else { + tmp[0] = 180.0 * xyz[0] - 40.0; + tmp[1] = 300.0 * xyz[1] - 150.0; + tmp[2] = 300.0 * xyz[2] - 150.0; + } + Lab2XYZ(tmp, tmp); + + } else { + if (xsect) { + tmp[0] = 1.4 * xyz[2] - 0.2; + tmp[1] = 1.4 * xyz[1] - 0.2; + tmp[2] = 1.6 * xyz[0] - 0.2; + } else { + tmp[0] = 1.4 * xyz[0] - 0.2; + tmp[1] = 1.4 * xyz[1] - 0.2; + tmp[2] = 1.6 * xyz[2] - 0.2; + } + } + + /* Convert XYZ through cam and back, then to RGB */ + if (to_jab || to_ss) + cam1->XYZ_to_cam(cam1, Jab, tmp); + else + XYZ2Lab(Jab, tmp); + + if (to_ss) { + rgb[0] = Jab[0]; + rgb[1] = Jab[1]; + rgb[2] = Jab[2]; + } else { + if (from_jab) + cam2->cam_to_XYZ(cam2, xyz2, Jab); + else + Lab2XYZ(xyz2, Jab); + + XYZ2sRGB(rgb, xyz2, 0); + } + + /* Fill with pixel value */ + ob[x+0] = (int)(rgb[0] * 255.0 + 0.5); + ob[x+1] = (int)(rgb[1] * 255.0 + 0.5); + ob[x+2] = (int)(rgb[2] * 255.0 + 0.5); + x += 3; + } + } + /* Fill sp pixels with black */ + for (m = 0; m < sp; m++) { + ob[x] = ob[x+1] = ob[x+2] = 0; + x += 3; + } + } + TIFFWriteScanline(wh, ob, y, 0); + } + /* Fill sp lines with black */ + for (x = m = 0; m < w; m++) { + ob[x] = ob[x+1] = ob[x+2] = 0; + x += 3; + } + for (j = 0; j < sp; j++, y++) { + TIFFWriteScanline(wh, ob, y, 0); + } + } + + cam1->del(cam1); + cam2->del(cam2); + + /* Write TIFF file */ + TIFFClose(wh); + + return 0; +} + diff --git a/xicc/cam02ref.h b/xicc/cam02ref.h new file mode 100644 index 0000000..de75f6b --- /dev/null +++ b/xicc/cam02ref.h @@ -0,0 +1,621 @@ + +/* + * cam02, unoptimised, untweaked reference version for testing. + * with optional trace/range error flags. + * + * Color Appearance Model. + * + * Author: Graeme W. Gill + * Date: 17/1/2004 + * Version: 1.00 + * + * This file is based on cam97s3.c by Graeme Gill. + * + * Copyright 2004, 2007 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +/* Note that XYZ values are normalised to 1.0 consistent */ +/* with the ICC convention (not 100.0 as assumed by the CIECAM spec.) */ +/* Note that all whites are assumed to be normalised (ie. Y = 1.0) */ + +#undef DIAG /* Print internal value diagnostics for each conversion */ + +/* ---------------------------------- */ + +#ifdef NEVER + +struct _cam02ref { +/* Public: */ + void (*del)(struct _cam02ref *s); /* We're done with it */ + + int (*set_view)( + struct _cam02ref *s, + ViewingCondition Ev, /* Enumerated Viewing Condition */ + double Wxyz[3], /* Reference/Adapted White XYZ (Y scale 1.0) */ + double La, /* Adapting/Surround Luminance cd/m^2 */ + double Yb, /* Luminance of Background relative to reference white (range 0.0 .. 1.0) */ + double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set */ + double Yf, /* Flare as a fraction of the reference white (range 0.0 .. 1.0) */ + double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ + int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ + ); + + /* Conversions */ + int (*XYZ_to_cam)(struct _cam02ref *s, double *out, double *in); + +/* Private: */ + /* Scene parameters */ + ViewingCondition Ev; /* Enumerated Viewing Condition */ + double Wxyz[3]; /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + double Yb; /* Relative Luminance of Background to reference white (Y range 0.0 .. 1.0) */ + double La; /* Adapting/Surround Luminance cd/m^2 */ + double Yf; /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + double Fxyz[3]; /* The Flare white coordinates (typically the Ambient color) */ + + /* Internal parameters */ + double C; /* Surround Impact */ + double Nc; /* Chromatic Induction */ + double F; /* Adaptation Degree */ + + /* Pre-computed values */ + double Fsc; /* Flare scale */ + double Fisc; /* Inverse flare scale */ + double Fsxyz[3]; /* Scaled Flare color coordinates */ + double rgbW[3]; /* Sharpened cone response white values */ + double D; /* Degree of chromatic adaption */ + double Drgb[3]; /* Chromatic transformation value */ + double rgbcW[3]; /* Chromatically transformed white value */ + double rgbpW[3]; /* Hunt-Pointer-Estevez cone response space white */ + double n; /* Background induction factor */ + double nn; /* Precomuted function of n */ + double Fl; /* Lightness contrast factor ?? */ + double Nbb; /* Background brightness induction factors */ + double Ncb; /* Chromatic brightness induction factors */ + double z; /* Base exponential nonlinearity */ + double rgbaW[3]; /* Post-adapted cone response of white */ + double Aw; /* Achromatic response of white */ + + /* Option flags */ + int hk; /* Use Helmholtz-Kohlraush effect */ + int trace; /* Trace internal values */ + double range; /* Return error if there is a range error */ + double nldlimit; /* range error if nlinear is less than this */ + double jlimit; /* range error if J is less than this */ + +}; typedef struct _cam02ref cam02ref; + +#else + +typedef struct _cam02 cam02ref; + +#endif + +/* ---------------------------------- */ + +/* Utility function */ +/* Return a viewing condition enumeration from the given Ambient and */ +/* Adapting/Surround Luminance. */ +static ViewingCondition cam02ref_Ambient2VC( +double La, /* Ambient Luminence (cd/m^2) */ +double Lv /* Luminence of white in the Viewing/Scene/Image field (cd/m^2) */ +) { + double r; + + if (fabs(La) < 1e-10) /* Hmm. */ + r = 1.0; + else + r = La/Lv; + + if (r < 0.01) + return vc_dark; + if (r < 0.2) + return vc_dim; + return vc_average; +} + +static void cam02ref_free(cam02ref *s); +static int cam02ref_set_view(cam02ref *s, ViewingCondition Ev, double Wxyz[3], + double Yb, double La, double Lv, double Yf, double Fxyz[3], int hk); +static int cam02ref_XYZ_to_cam(cam02ref *s, double *Jab, double *xyz); +static int cam02ref_cam_to_XYZ(cam02ref *s, double XYZ[3], double Jab[3]); + +/* Create a cam02 conversion object, with default viewing conditions */ +cam02ref *new_cam02ref(void) { + cam02ref *s; + + if ((s = (cam02ref *)malloc(sizeof(cam02ref))) == NULL) { + fprintf(stderr,"cam02: malloc failed allocating object\n"); + exit(-1); + } + + /* Initialise methods */ + s->del = cam02ref_free; + s->set_view = cam02ref_set_view; + s->XYZ_to_cam = cam02ref_XYZ_to_cam; + s->cam_to_XYZ = cam02ref_cam_to_XYZ; + + return s; +} + +static void cam02ref_free(cam02ref *s) { + if (s != NULL) + free(s); +} + +static int cam02ref_set_view( +cam02ref *s, +ViewingCondition Ev, /* Enumerated Viewing Condition */ +double Wxyz[3], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ +double La, /* Adapting/Surround Luminance cd/m^2 */ +double Yb, /* Relative Luminence of Background to reference white */ +double Lv, /* Luminence of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set to other than vc_none */ +double Yf, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ +double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ +int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ +) { + double tt; + + if (Ev == vc_none) /* Compute enumerated viewing condition */ + Ev = cam02ref_Ambient2VC(La, Lv); + /* Transfer parameters to the object */ + s->Ev = Ev; + s->Wxyz[0] = Wxyz[0]; + s->Wxyz[1] = Wxyz[1]; + s->Wxyz[2] = Wxyz[2]; + s->Yb = Yb > 0.005 ? Yb : 0.005; /* Set minimum to avoid divide by 0.0 */ + s->La = La; + s->Yf = Yf; + s->Fxyz[0] = Fxyz[0]; + s->Fxyz[1] = Fxyz[1]; + s->Fxyz[2] = Fxyz[2]; + s->hk = hk; + + /* Compute the internal parameters by category */ + switch(s->Ev) { + case vc_dark: + s->C = 0.525; + s->Nc = 0.8; + s->F = 0.8; + break; + case vc_dim: + s->C = 0.59; + s->Nc = 0.95; + s->F = 0.9; + break; + case vc_cut_sheet: + s->C = 0.41; + s->Nc = 0.8; + s->F = 0.8; + break; + default: /* average */ + s->C = 0.69; + s->Nc = 1.0; + s->F = 1.0; + break; + } + + /* Compute values that only change with viewing parameters */ + + /* Figure out the Flare contribution to the flareless XYZ input */ + tt = s->Yf * s->Wxyz[1]/s->Fxyz[1]; + s->Fsxyz[0] = tt * s->Fxyz[0]; + s->Fsxyz[1] = tt * s->Fxyz[1]; + s->Fsxyz[2] = tt * s->Fxyz[2]; + + /* Rescale so that the sum of the flare and the input doesn't exceed white */ + s->Fsc = s->Wxyz[1]/(s->Fsxyz[1] + s->Wxyz[1]); + s->Fsxyz[0] *= s->Fsc; + s->Fsxyz[1] *= s->Fsc; + s->Fsxyz[2] *= s->Fsc; + s->Fisc = 1.0/s->Fsc; + + /* Sharpened cone response white values */ + s->rgbW[0] = 0.7328 * s->Wxyz[0] + 0.4296 * s->Wxyz[1] - 0.1624 * s->Wxyz[2]; + s->rgbW[1] = -0.7036 * s->Wxyz[0] + 1.6975 * s->Wxyz[1] + 0.0061 * s->Wxyz[2]; + s->rgbW[2] = 0.0000 * s->Wxyz[0] + 0.0000 * s->Wxyz[1] + 1.0000 * s->Wxyz[2]; + + /* Degree of chromatic adaption */ + s->D = s->F * (1.0 - exp((-s->La - 42.0)/92.0)/3.6); + + /* Precompute Chromatic transform values */ + s->Drgb[0] = s->D * (s->Wxyz[1]/s->rgbW[0]) + 1.0 - s->D; + s->Drgb[1] = s->D * (s->Wxyz[1]/s->rgbW[1]) + 1.0 - s->D; + s->Drgb[2] = s->D * (s->Wxyz[1]/s->rgbW[2]) + 1.0 - s->D; + + /* Chromaticaly transformed white value */ + s->rgbcW[0] = s->Drgb[0] * s->rgbW[0]; + s->rgbcW[1] = s->Drgb[1] * s->rgbW[1]; + s->rgbcW[2] = s->Drgb[2] * s->rgbW[2]; + + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + s->rgbpW[0] = 0.7409744840453773 * s->rgbcW[0] + + 0.2180245944753982 * s->rgbcW[1] + + 0.0410009214792244 * s->rgbcW[2]; + s->rgbpW[1] = 0.2853532916858801 * s->rgbcW[0] + + 0.6242015741188157 * s->rgbcW[1] + + 0.0904451341953042 * s->rgbcW[2]; + s->rgbpW[2] = -0.0096276087384294 * s->rgbcW[0] + - 0.0056980312161134 * s->rgbcW[1] + + 1.0153256399545427 * s->rgbcW[2]; + + + /* Background induction factor */ + s->n = s->Yb/ s->Wxyz[1]; + s->nn = pow(1.64 - pow(0.29, s->n), 0.73); /* Pre computed value */ + + /* Lightness contrast factor ?? */ + { + double k; + + k = 1.0 / (5.0 * s->La + 1.0); + s->Fl = 0.2 * pow(k , 4.0) * 5.0 * s->La + + 0.1 * pow(1.0 - pow(k , 4.0) , 2.0) * pow(5.0 * s->La , 1.0/3.0); + } + + /* Background and Chromatic brightness induction factors */ + s->Nbb = 0.725 * pow(1.0/s->n, 0.2); + s->Ncb = s->Nbb; + + /* Base exponential nonlinearity */ + s->z = 1.48 + pow(s->n , 0.5); + + /* Post-adapted cone response of white */ + tt = pow(s->Fl * s->rgbpW[0], 0.42); + s->rgbaW[0] = (400.1 * tt + 2.713) / (tt + 27.13); + tt = pow(s->Fl * s->rgbpW[1], 0.42); + s->rgbaW[1] = (400.1 * tt + 2.713) / (tt + 27.13); + tt = pow(s->Fl * s->rgbpW[2], 0.42); + s->rgbaW[2] = (400.1 * tt + 2.713) / (tt + 27.13); + + /* Achromatic response of white */ + s->Aw = (2.0 * s->rgbaW[0] + s->rgbaW[1] + (1.0/20.0) * s->rgbaW[2] - 0.305) * s->Nbb; + +#ifdef DIAG + printf("Ref. Scene parameters:\n"); + printf("Viewing condition Ev = %d\n",s->Ev); + printf("Ref white Wxyz = %f %f %f\n", s->Wxyz[0], s->Wxyz[1], s->Wxyz[2]); + printf("Relative liminance of background Yb = %f\n", s->Yb); + printf("Adapting liminance La = %f\n", s->La); + printf("Flare Yf = %f\n", s->Yf); + printf("Flare color Fxyz = %f %f %f\n", s->Fxyz[0], s->Fxyz[1], s->Fxyz[2]); + + printf("Internal parameters:\n"); + printf("Surround Impact C = %f\n", s->C); + printf("Chromatic Induction Nc = %f\n", s->Nc); + printf("Adaption Degree F = %f\n", s->F); + + printf("Pre-computed values\n"); + printf("Sharpened cone white rgbW = %f %f %f\n", s->rgbW[0], s->rgbW[1], s->rgbW[2]); + printf("Degree of chromatic adaption D = %f\n", s->D); + printf("Chromatic transform values Drgb = %f %f %f\n", s->Drgb[0], s->Drgb[1], s->Drgb[2]); + printf("Chromatically transformed white rgbcW = %f %f %f\n", s->rgbcW[0], s->rgbcW[1], s->rgbcW[2]); + printf("Hunter-P-E cone response white rgbpW = %f %f %f\n", s->rgbpW[0], s->rgbpW[1], s->rgbpW[2]); + printf("Background induction factor n = %f\n", s->n); + printf("Lightness contrast factor Fl = %f\n", s->Fl); + printf("Background brightness induction factor Nbb = %f\n", s->Nbb); + printf("Chromatic brightness induction factor Ncb = %f\n", s->Ncb); + printf("Base exponential nonlinearity z = %f\n", s->z); + printf("Post adapted cone response white rgbaW = %f %f %f\n", s->rgbaW[0], s->rgbaW[1], s->rgbaW[2]); + printf("Achromatic response of white Aw = %f\n", s->Aw); + printf("\n"); +#endif + return 0; +} + +/* A version of the pow() function that preserves the */ +/* sign of its first argument. */ +static double spow(double x, double y) { + return x < 0.0 ? -pow(-x,y) : pow(x,y); +} + + +#define REFTRACE(xxxx) if (s->trace) printf xxxx ; + +/* Conversion. Returns NZ and -1, -1, -1 if input is out of range */ +static int cam02ref_XYZ_to_cam( +cam02ref *s, +double Jab[3], +double XYZ[3] +) { + int i; + double xyz[3], rgb[3], rgbp[3], rgba[3], rgbaW[3], rgbc[3], rgbcW[3]; + double a, b, rS, J, C, h, e, A, ss; + double ttd, tt; + + REFTRACE(("\nReference forward conversion:\n")) + REFTRACE(("XYZ %f %f %f\n",XYZ[0], XYZ[1], XYZ[2])) + + /* Add in flare */ + xyz[0] = s->Fsc * XYZ[0] + s->Fsxyz[0]; + xyz[1] = s->Fsc * XYZ[1] + s->Fsxyz[1]; + xyz[2] = s->Fsc * XYZ[2] + s->Fsxyz[2]; + + REFTRACE(("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2])) + + /* Spectrally sharpened cone responses */ + rgb[0] = 0.7328 * xyz[0] + 0.4296 * xyz[1] - 0.1624 * xyz[2]; + rgb[1] = -0.7036 * xyz[0] + 1.6975 * xyz[1] + 0.0061 * xyz[2]; + rgb[2] = 0.0000 * xyz[0] + 0.0000 * xyz[1] + 1.0000 * xyz[2]; + + REFTRACE(("Sharpened cone sample rgb = %f %f %f\n", rgb[0], rgb[1], rgb[2])) + + /* Chromaticaly transformed sample value */ + rgbc[0] = s->Drgb[0] * rgb[0]; + rgbc[1] = s->Drgb[1] * rgb[1]; + rgbc[2] = s->Drgb[2] * rgb[2]; + + REFTRACE(("Chromatically transformed sample value rgbc = %f %f %f\n", rgb[0], rgb[1], rgb[2])) + + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + rgbp[0] = 0.7409744840453773 * rgbc[0] + + 0.2180245944753982 * rgbc[1] + + 0.0410009214792244 * rgbc[2]; + rgbp[1] = 0.2853532916858801 * rgbc[0] + + 0.6242015741188157 * rgbc[1] + + 0.0904451341953042 * rgbc[2]; + rgbp[2] = -0.0096276087384294 * rgbc[0] + - 0.0056980312161134 * rgbc[1] + + 1.0153256399545427 * rgbc[2]; + + REFTRACE(("rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2])) + + /* Post-adapted cone response of sample. */ + /* rgba[] has a minimum value of 0.1 for XYZ[] = 0 and no flare. */ + /* We add a symetric negative compression region */ + for (i = 0; i < 3; i++) { + if (s->range && (rgbp[i] < 0.0 || rgbp[i] < s->nldlimit)) { + Jab[0] = Jab[1] = Jab[2] = -1.0; + return 1; + } + if (rgbp[i] < 0.0) { + tt = pow(s->Fl * -rgbp[i], 0.42); + rgba[i] = (2.713 - 397.387 * tt) / (tt + 27.13); + + } else { + tt = pow(s->Fl * rgbp[i], 0.42); + rgba[i] = (400.1 * tt + 2.713) / (tt + 27.13); + } + } + + REFTRACE(("rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2])) + + /* Preliminary red-green & yellow-blue opponent dimensions */ + a = rgba[0] - 12.0 * rgba[1]/11.0 + rgba[2]/11.0; + b = (1.0/9.0) * (rgba[0] + rgba[1] - 2.0 * rgba[2]); + rS = sqrt(a * a + b * b); /* Normalised a, b */ + + /* Preliminary Saturation */ + /* Note that the minimum values for rgba[] for XYZ = 0 is 0.1 */ + /* Hence magic 0.305 below comes from the following weighting of rgba[] */ + ttd = rgba[0] + rgba[1] + (21.0/20.0) * rgba[2]; + + /* Achromatic response */ + /* Note that the minimum values of rgba[] for XYZ = 0 is 0.1, */ + /* hence magic 0.305 below comes from the following weighting of rgba[], */ + /* to base A at 0.0 */ + A = (2.0 * rgba[0] + rgba[1] + (1.0/20.0) * rgba[2] - 0.305) * s->Nbb; + + REFTRACE(("a = %f, b = %f, ttd = %f, rS = %f, A = %f\n", a, b, ttd, rS, A)) + + /* Lightness */ + J = pow(A/s->Aw, s->C * s->z); /* J/100 - keep Sign */ + + /* Hue angle */ + h = (180.0/DBL_PI) * atan2(b,a); + h = (h < 0.0) ? h + 360.0 : h; + + /* Eccentricity factor */ + e = (cos(h * DBL_PI/180.0 + 2.0) + 3.8); + + if (s->range && (J < DBL_EPSILON || J < s->jlimit || ttd < DBL_EPSILON)) { + REFTRACE(("J = %f, ttd = %f, exit with error\n", J, ttd)) + Jab[0] = Jab[1] = Jab[2] = -1.0; + return 1; + } + + ss = (12500.0/13.0 * s->Nc * s->Ncb * rS * e) / ttd; + + /* Chroma */ + C = pow(ss, 0.9) * sqrt(J) * s->nn; + + REFTRACE(("ss = %f, C = %f\n", ss, C)) + + /* Helmholtz-Kohlraush effect */ + if (s->hk && J < 1.0) { + double JJ, kk = C/300.0 * sin(DBL_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 0.9) /* Limit kk to a reasonable range */ + kk = 0.9; + JJ = J + (1.0 - J) * kk; + REFTRACE(("JJ = %f from J = %f, kk = %f\n",JJ,J,kk)) + J = JJ; + } + + J *= 100.0; /* Scale J */ + + /* Compute Jab value */ + Jab[0] = J; + if (rS >= DBL_EPSILON) { + Jab[1] = C * a/rS; + Jab[2] = C * b/rS; + } else { + Jab[1] = 0.0; + Jab[2] = 0.0; + } + + REFTRACE(("Returning Jab %f %f %f\n", Jab[0],Jab[1],Jab[2])) + +#ifdef DIAG + printf("Processing:\n"); + printf("XYZ = %f %f %f\n", XYZ[0], XYZ[1], XYZ[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("Sharpened cone sample rgb = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + printf("Chromatically transformed sample value rgbc = %f %f %f\n", rgbc[0], rgbc[1], rgbc[2]); + printf("Hunt-P-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Prelim red green a = %f, b = %f\n", a, b); + printf("Hue angle h = %f\n", h); + printf("Eccentricity factor e = %f\n", e); + printf("Achromatic response A = %f\n", A); + printf("Lightness J = %f\n", J); + printf("Prelim Saturation ss = %f\n", ss); + printf("Chroma C = %f\n", C); + printf("Jab = %f %f %f\n", Jab[0], Jab[1], Jab[2]); + printf("\n"); +#endif + return 0; +} + +static int cam02ref_cam_to_XYZ( +cam02ref *s, +double XYZ[3], +double Jab[3] +) { + int i; + double xyz[3], rgb[3], rgbp[3], rgba[3], rgbaW[3], rgbc[3], rgbcW[3]; + double ja, jb, aa, ab, a, b, J, C, h, e, A, ss; + double tt, ttA, tte; + + J = Jab[0] * 0.01; /* J/100 */ + ja = Jab[1]; + jb = Jab[2]; + + /* Compute hue angle */ + h = (180.0/DBL_PI) * atan2(jb, ja); + h = (h < 0.0) ? h + 360.0 : h; + + /* Compute chroma value */ + C = sqrt(ja * ja + jb * jb); /* Must be Always +ve */ + + /* Helmholtz-Kohlraush effect */ + if (s->hk && J < 1.0) { + double kk = C/300.0 * sin(DBL_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 0.9) /* Limit kk to a reasonable range */ + kk = 0.9; + J = (J - kk)/(1.0 - kk); + } + + /* Eccentricity factor */ + e = (cos(h * DBL_PI/180.0 + 2.0) + 3.8); + + /* Achromatic response */ + A = spow(J, 1.0/(s->C * s->z)) * s->Aw; /* Keep sign of J */ + + /* Preliminary Saturation - keep +ve */ + tt = fabs(J); + ss = pow(C/(sqrt(tt) * s->nn), 1.0/0.9); /* keep +ve */ + + /* Compute a & b, taking care of numerical problems */ + aa = fabs(ja); + ab = fabs(jb); + ttA = (A/s->Nbb)+0.305; /* Common factor */ + tte = 12500.0/13.0 * e * s->Nc * s->Ncb; /* Common factor */ + + if (aa < 1e-10 && ab < 1e-10) { + a = ja; + b = jb; + } else if (aa > ab) { + double tanh = jb/ja; + double sign = (h > 90.0 && h <= 270.0) ? -1.0 : 1.0; + + if (ttA < 0.0) + sign = -sign; + + a = (ss * ttA) + / (sign * sqrt(1.0 + tanh * tanh) * tte + (ss * (11.0/23.0 + (108.0/23.0) * tanh))); + b = a * tanh; + + } else { /* ab > aa */ + double itanh = ja/jb; + double sign = (h > 180.0 && h <= 360.0) ? -1.0 : 1.0; + + if (ttA < 0.0) + sign = -sign; + + b = (ss * ttA) + / (sign * sqrt(1.0 + itanh * itanh) * tte + (ss * (108.0/23.0 + (11.0/23.0) * itanh))); + a = b * itanh; + } + + /* Post-adapted cone response of sample */ + rgba[0] = (20.0/61.0) * ttA + + ((41.0 * 11.0)/(61.0 * 23.0)) * a + + ((288.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[1] = (20.0/61.0) * ttA + - ((81.0 * 11.0)/(61.0 * 23.0)) * a + - ((261.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[2] = (20.0/61.0) * ttA + - ((20.0 * 11.0)/(61.0 * 23.0)) * a + - ((20.0 * 315.0)/(61.0 * 23.0)) * b; + + /* Hunt-Pointer_Estevez cone space */ + tt = 1.0/s->Fl; + for (i = 0; i < 3; i++) { + if (rgba[i] < 0.1) { + double ta = rgba[i] > -396.387 ? rgba[i] : -396.387; + rgbp[i] = -tt * pow((2.713 - 27.13 * rgba[i] )/(397.387 + ta), 1.0/0.42); + } else { + double ta = rgba[i] < 399.1 ? rgba[i] : 399.1; + rgbp[i] = tt * pow((27.13 * rgba[i] -2.713)/(400.1 - ta), 1.0/0.42); + } + } + + /* Chromaticaly transformed sample value */ + rgbc[0] = 1.5591523979049677 * rgbp[0] + - 0.5447226796590880 * rgbp[1] + - 0.0144453097698588 * rgbp[2]; + rgbc[1] = -0.7143267176368630 * rgbp[0] + + 1.8503099728895096 * rgbp[1] + - 0.1359761119854705 * rgbp[2]; + rgbc[2] = 0.0107755117023383 * rgbp[0] + + 0.0052187662221759 * rgbp[1] + + 0.9840056143203700 * rgbp[2]; + + /* Spectrally sharpened cone responses */ + rgb[0] = rgbc[0]/s->Drgb[0]; + rgb[1] = rgbc[1]/s->Drgb[1]; + rgb[2] = rgbc[2]/s->Drgb[2]; + + /* XYZ values */ + xyz[0] = 1.0961238208355140 * rgb[0] + - 0.2788690002182872 * rgb[1] + + 0.1827451793827730 * rgb[2]; + xyz[1] = 0.4543690419753590 * rgb[0] + + 0.4735331543074120 * rgb[1] + + 0.0720978037172291 * rgb[2]; + xyz[2] = -0.0096276087384294 * rgb[0] + - 0.0056980312161134 * rgb[1] + + 1.0153256399545427 * rgb[2]; + + /* Subtract flare */ + XYZ[0] = s->Fisc * (xyz[0] - s->Fsxyz[0]); + XYZ[1] = s->Fisc * (xyz[1] - s->Fsxyz[1]); + XYZ[2] = s->Fisc * (xyz[2] - s->Fsxyz[2]); + +#ifdef DIAG + printf("Processing:\n"); + printf("Jab = %f %f %f\n", Jab[0], Jab[1], Jab[2]); + printf("Chroma C = %f\n", C); + printf("Preliminary Saturation ss = %f\n", ss); + printf("Lightness J = %f\n", J * 100.0); + printf("Achromatic response A = %f\n", A); + printf("Eccentricity factor e = %f\n", e); + printf("Hue angle h = %f\n", h); + printf("Prelim red green a = %f, b = %f\n", a, b); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Hunt-P-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Chromatically transformed sample value rgbc = %f %f %f\n", rgbc[0], rgbc[1], rgbc[2]); + printf("Sharpened cone sample rgb = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("XYZ = %f %f %f\n", XYZ[0], XYZ[1], XYZ[2]); + printf("\n"); +#endif + return 0; +} + + diff --git a/xicc/cam02test.c b/xicc/cam02test.c new file mode 100644 index 0000000..3dafc9c --- /dev/null +++ b/xicc/cam02test.c @@ -0,0 +1,1325 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 18/1/2004 + * Version: 1.00 + * + * Copyright 2000-2011 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the CIECAM02 functionality. + */ + + +#include <stdio.h> +#include <math.h> +#include "icc.h" +#include "xcam.h" +#include "cam02.h" +#include "numlib.h" +#include "cam02ref.h" + + /* ** = usual tests */ + +#undef DIAG /* Print internal value diagnostics for each spot test conversion */ + /* and print diagnostics for excessive errors, nans etc. */ +#undef VERBOSE /* Print diagnostic values for every conversion */ +#define SPOTTEST /* ** Test known spot colors */ +#undef TROUBLE /* Test trouble spot colors XYZ -> Jab -> XYZ */ +#undef TROUBLE2 /* Test trouble spot colors Jab -> XYZ -> Jab */ +#undef SPECIAL /* Special exploration code */ + +#undef LOCUSTEST /* [def] ** Test spectrum locus points */ +#undef LOCUSRAND /* [def] ** Test random spectrum locus points */ + +#define INVTEST /* [def] ** -100 -> 100 XYZ cube to Jab to XYZ */ +#undef INVTEST1 /* [undef] Single value */ +#undef INVTEST2 /* [undef] Powell search for invers */ + +#define TESTINV /* [def] ** Jab cube to XYZ to Jab */ +#undef TESTINV1 /* [undef] Single Jab value */ +#undef TESTINV2 /* [undef] J = 0 test values */ + +//#define TRES 41 /* Grid resolution */ +#define TRES 17 /* Grid resolution */ +#define USE_HK 0 /* Use Helmholtz-Kohlraush in testing */ +#define EXIT_ON_ERROR /* and also trace */ + +//#define MAX_SPOT_ERR 0.05 +//#define MAX_REF_ERR 0.1 /* Maximum permitted error to reference transform in delta Jab */ +/* The blue fix throws this out */ +#define MAX_SPOT_ERR 2.0 +#define MAX_REF_ERR 2.0 /* Maximum permitted error to reference transform in delta Jab */ + +#ifndef _isnan +#define _isnan(x) ((x) != (x)) +#define _finite(x) ((x) == 0.0 || (x) * 1.0000001 != (x)) +#endif + + +#ifdef INVTEST1 +static void +Lab2XYZ(double *out, double *in) { + double L = in[0], a = in[1], b = in[2]; + double x,y,z,fx,fy,fz; + + if (L > 8.0) { + fy = (L + 16.0)/116.0; + y = pow(fy,3.0); + } else { + y = L/903.2963058; + fy = 7.787036979 * y + 16.0/116.0; + } + + fx = a/500.0 + fy; + if (fx > 24.0/116.0) + x = pow(fx,3.0); + else + x = (fx - 16.0/116.0)/7.787036979; + + fz = fy - b/200.0; + if (fz > 24.0/116.0) + z = pow(fz,3.0); + else + z = (fz - 16.0/116.0)/7.787036979; + + out[0] = x * 0.9642; + out[1] = y * 1.0000; + out[2] = z * 0.8249; +} +#endif /* INVTEST1 */ + +/* CIE XYZ to perceptual Lab */ +static void +XYZ2Lab(double *out, double *in) { + double X = in[0], Y = in[1], Z = in[2]; + double x,y,z,fx,fy,fz; + + x = X/0.9642; /* D50 ? */ + y = Y/1.0000; + z = Z/0.8249; + + if (x > 0.008856451586) + fx = pow(x,1.0/3.0); + else + fx = 7.787036979 * x + 16.0/116.0; + + if (y > 0.008856451586) + fy = pow(y,1.0/3.0); + else + fy = 7.787036979 * y + 16.0/116.0; + + if (z > 0.008856451586) + fz = pow(z,1.0/3.0); + else + fz = 7.787036979 * z + 16.0/116.0; + + out[0] = 116.0 * fy - 16.0; + out[1] = 500.0 * (fx - fy); + out[2] = 200.0 * (fy - fz); +} + + +/* Return maximum difference */ +double maxdiff(double in1[3], double in2[3]) { + int i; + double tt, rv = 0.0; + + /* Deal with the possibility that we have nan's */ + for (i = 0; i < 3; i++) { + tt = fabs(in1[i] - in2[i]); + if (!_finite(tt)) + return tt; + if (tt > rv) + rv = tt; + } + return rv; +} + +/* Return absolute difference */ +double absdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + tt = in1[0] - in2[0]; + rv += tt * tt; + tt = in1[1] - in2[1]; + rv += tt * tt; + tt = in1[2] - in2[2]; + rv += tt * tt; + return sqrt(rv); +} + +/* Return maximum Lab difference of XYZ */ +double maxxyzdiff(double i1[3], double i2[3]) { + int i; + double tt, rv = 0.0; + double in1[3], in2[3]; + + XYZ2Lab(in1, i1); + XYZ2Lab(in2, i2); + + /* Deal with the possibility that we have nan's */ + for (i = 0; i < 3; i++) { + tt = fabs(in1[i] - in2[i]); + if (!_finite(tt)) + return tt; + if (tt > rv) + rv = tt; + } + return rv; +} + +#ifdef INVTEST2 + +/* Powell callback function and struct */ +typedef struct { + cam02 *cam; + double Jab[3]; +} cntx; + +static double opt1(void *fdata, double tp[]) { + cntx *x = (cntx *)fdata; + double Jab[3]; + double tt, rv = 0.0; + + x->cam->XYZ_to_cam(x->cam, Jab, tp); + + tt = Jab[0] - x->Jab[0]; + rv += tt * tt; + tt = Jab[1] - x->Jab[1]; + rv += tt * tt; + tt = Jab[2] - x->Jab[2]; + rv += tt * tt; + return rv; +} + +#endif /* INVTEST2 */ + +/* 2 degree spectrum locus in xy coordinates */ +/* nm, x, y, Y CMC */ +double sl[65][4] = { + { 380, 0.1741, 0.0050, 0.000039097450 }, + { 385, 0.1740, 0.0050, 0.000065464490 }, + { 390, 0.1738, 0.0049, 0.000121224052 }, + { 395, 0.1736, 0.0049, 0.000221434140 }, + { 400, 0.1733, 0.0048, 0.000395705080 }, + { 405, 0.1730, 0.0048, 0.000656030940 }, + { 410, 0.1726, 0.0048, 0.001222776600 }, + { 415, 0.1721, 0.0048, 0.002210898200 }, + { 420, 0.1714, 0.0051, 0.004069952000 }, + { 425, 0.1703, 0.0058, 0.007334133400 }, + { 430, 0.1689, 0.0069, 0.011637600000 }, + { 435, 0.1669, 0.0086, 0.016881322000 }, + { 440, 0.1644, 0.0109, 0.023015402000 }, + { 445, 0.1611, 0.0138, 0.029860866000 }, + { 450, 0.1566, 0.0177, 0.038072300000 }, + { 455, 0.1510, 0.0227, 0.048085078000 }, + { 460, 0.1440, 0.0297, 0.060063754000 }, + { 465, 0.1355, 0.0399, 0.074027114000 }, + { 470, 0.1241, 0.0578, 0.091168598000 }, + { 475, 0.1096, 0.0868, 0.112811680000 }, + { 480, 0.0913, 0.1327, 0.139122260000 }, + { 485, 0.0686, 0.2007, 0.169656160000 }, + { 490, 0.0454, 0.2950, 0.208513180000 }, + { 495, 0.0235, 0.4127, 0.259083420000 }, + { 500, 0.0082, 0.5384, 0.323943280000 }, + { 505, 0.0039, 0.6548, 0.407645120000 }, + { 510, 0.0139, 0.7502, 0.503483040000 }, + { 515, 0.0389, 0.8120, 0.608101540000 }, + { 520, 0.0743, 0.8338, 0.709073280000 }, + { 525, 0.1142, 0.8262, 0.792722560000 }, + { 530, 0.1547, 0.8059, 0.861314320000 }, + { 535, 0.1929, 0.7816, 0.914322820000 }, + { 540, 0.2296, 0.7543, 0.953482260000 }, + { 545, 0.2658, 0.7243, 0.979818740000 }, + { 550, 0.3016, 0.6923, 0.994576720000 }, + { 555, 0.3373, 0.6589, 0.999604300000 }, + { 560, 0.3731, 0.6245, 0.994513460000 }, + { 565, 0.4087, 0.5896, 0.978204680000 }, + { 570, 0.4441, 0.5547, 0.951588260000 }, + { 575, 0.4788, 0.5202, 0.915060800000 }, + { 580, 0.5125, 0.4866, 0.869647940000 }, + { 585, 0.5448, 0.4544, 0.816076000000 }, + { 590, 0.5752, 0.4242, 0.756904640000 }, + { 595, 0.6029, 0.3965, 0.694818180000 }, + { 600, 0.6270, 0.3725, 0.630997820000 }, + { 605, 0.6482, 0.3514, 0.566802360000 }, + { 610, 0.6658, 0.3340, 0.503096860000 }, + { 615, 0.6801, 0.3197, 0.441279360000 }, + { 620, 0.6915, 0.3083, 0.380961920000 }, + { 625, 0.7006, 0.2993, 0.321156580000 }, + { 630, 0.7079, 0.2920, 0.265374180000 }, + { 635, 0.7140, 0.2859, 0.217219520000 }, + { 640, 0.7190, 0.2809, 0.175199900000 }, + { 645, 0.7230, 0.2770, 0.138425720000 }, + { 650, 0.7260, 0.2740, 0.107242628000 }, + { 655, 0.7283, 0.2717, 0.081786794000 }, + { 660, 0.7300, 0.2700, 0.061166218000 }, + { 665, 0.7311, 0.2689, 0.044729418000 }, + { 670, 0.7320, 0.2680, 0.032160714000 }, + { 675, 0.7327, 0.2673, 0.023307860000 }, + { 680, 0.7334, 0.2666, 0.017028548000 }, + { 685, 0.7340, 0.2660, 0.011981432000 }, + { 690, 0.7344, 0.2656, 0.008259734600 }, + { 695, 0.7346, 0.2654, 0.005758363200 }, + { 700, 0.7347, 0.2653, 0.004117206200 } +}; + +int +main(void) { + int ok = 1; + +#define NO_WHITES 9 + double white[9][3] = { + { 0.964242, 1.000000, 0.825124 }, /* 0: D50 */ + { 1.098494, 1.000000, 0.355908 }, /* 1: A */ + { 0.908731, 1.000000, 0.987233 }, /* 2: F5 */ + { 1.128981, 1.000000, 0.305862 }, /* 3: D25 */ + { 0.950471, 1.000000, 1.088828 }, /* 4: D65 */ + { 0.949662, 1.000000, 1.160438 }, /* 5: D80 */ + { 0.949662, 1.000000, 1.160438 }, /* 6: D80 */ + { 0.951297, 1.000000, 1.338196 }, /* 7: D85 */ + { 0.952480, 1.000000, 1.386693 } /* 8: D90 */ + }; +#define NO_LAS 6 + double La[6] = { 10.0, 31.83, 100.0, 318.31, 1000.83, 3183.1 }; + +#define NO_VCS 4 + + ViewingCondition Vc[4] = { + vc_average, + vc_dark, + vc_dim, + vc_cut_sheet + }; + +#ifdef SPOTTEST +#define NO_SPOTS 5 + double sp_white[6][3] = { + { 0.9505, 1.000, 1.0888 }, + { 0.9505, 1.000, 1.0888 }, + { 1.0985, 1.000, 0.3558 }, + { 1.0985, 1.000, 0.3558 }, + { 0.9505, 1.000, 1.0888 }, + { 0.9642, 1.000, 0.8249 } /* D50 for inversion tests */ + }; + double sp_La[6] = { 318.31, 31.83, 318.31, 31.83, 318.31, 150.0 }; + + double sample[5][3] = { + { 0.1901, 0.2000, 0.2178 }, + { 0.5706, 0.4306, 0.3196 }, + { 0.0353, 0.0656, 0.0214 }, + { 0.1901, 0.2000, 0.2178 }, + { 0.9505, 1.000, 1.0888 } /* Check white */ + }; + + /* Reference values (NOT correct anymore, as HK params have changed!!!) */ +#if USE_HK == 1 + double correct[5][4] = { /* Hue range is last two */ + { 41.75, 0.10, 217.0, 219.0 }, + { 69.11, 48.60, 19.9, 19.91 }, + { 30.22, 46.94, 177.1, 177.2 }, + { 52.64, 53.07, 249.5, 249.6 }, + { 100.00, 0.14, 0.0, 360.0 } + }; +#else + double correct[5][4] = { /* Hue range is last two */ + { 41.73, 0.10, 217.0, 219.0 }, + { 65.94, 48.60, 19.90, 19.91 }, + { 21.79, 46.94, 177.1, 177.2 }, + { 42.66, 53.07, 249.5, 249.6 }, + { 100.0, 0.14, 0.0, 360.0 } /* Check white */ + }; +#endif /* USE_HK */ +// Original values +//#if USE_HK == 1 +// double correct[5][4] = { /* Hue range is last two */ +// { 41.75, 0.10, 217.0, 219.0 }, +// { 69.13, 48.57, 19.56, 19.56 }, +// { 30.22, 46.94, 177.1, 177.1 }, +// { 52.31, 51.92, 248.9, 248.9 }, +// { 100.00, 0.14, 0.0, 360.0 } +// }; +#//else +// double correct[5][4] = { /* Hue range is last two */ +// { 41.73, 0.10, 217.0, 219.0 }, +// { 65.96, 48.57, 19.6, 19.6 }, +// { 21.79, 46.94, 177.1, 177.1 }, +// { 42.53, 51.92, 248.9, 248.9 }, +// { 100.0, 0.14, 0.0, 360.0 } /* Check white */ +// }; +//#endif /* USE_HK */ +#endif /* SPOTTEST */ + +#if defined(TROUBLE) || defined(TROUBLE2) || defined(SPECIAL) +#define NO_TROUBLE 1 + double twhite[3][3] = { + { 0.9642, 1.000, 0.8249 }, /* D50 */ + { 0.949662, 1.000000, 1.160438 }, /* 5: D80 */ + { 0.9642, 1.000, 0.8249 } /* D50 */ + }; + ViewingCondition tVc[3] = { vc_average, vc_average, vc_average }; + double tLa[3] = { 10.0, 3183.1, 50.0 }; + double tFlair[3] = { 0.00, 0.01, 0.01 }; + double tsample[4][3] = { + { -1.0, -1.0, -1.0 }, /* Inv Problem */ + { 34.485160, 0.982246, 164.621489 }, /* Inv Problem */ + { 0.031339, 0.000000, 0.824895 }, /* ProPhoto Blue - OK */ + { 0.041795, 0.006211, 0.652597 } /* ProPhoto nearly Blue - fails */ + }; + double tsample2[1][3] = { + { 3.625000, -121.600000, -64.000000, } /* Inv Problem */ + }; +#endif /* TROUBLE || TROUBLE2 */ + + cam02ref *camr; + cam02 *cam; + int c, d, e; + + cam = new_cam02(); + camr = new_cam02ref(); + camr->range = 1; /* Return on range error */ +#ifdef VERBOSE + cam->trace = 1; + camr->trace = 1; +#endif + + /* ================= Trouble colors ===================== */ +#if defined(TROUBLE) || defined(TROUBLE2) || defined(SPECIAL) + for (c = 0; c < NO_TROUBLE; c++) { + +#ifdef DIAG + printf("Case %d:\n",c); +#endif /* DIAG */ + camr->set_view( + camr, + tVc[c], /* Enumerated Viewing Condition */ + twhite[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + tLa[c], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + tFlair[c], /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + twhite[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + cam->set_view( + cam, + tVc[c], /* Enumerated Viewing Condition */ + twhite[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + tLa[c], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + tFlair[c], /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + twhite[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + camr->nldlimit = cam->nldlimit; + camr->jlimit = cam->jlimit; + +#ifdef DIAG + printf("\n"); +#endif /* DIAG */ + +#ifdef SPECIAL + /* See what happens as we approach a neutral color */ + { + double j1[3] = { 50, +0.1, +0.1 }, x1[3]; + double j2[3] = { 0, +0.1, +0.1 }, x2[3]; + double Jab[3], xyz[3]; + double rr = 1.0; + int i; + + /* Establish the XYZ */ + cam->cam_to_XYZ(cam, x1, j1); + cam->cam_to_XYZ(cam, x2, j2); + + cam->trace = 1; + + /* Approach it by halves */ + for (i = 0; i < 100; i++, rr *= 0.5) { + xyz[0] = rr * x1[0] + (1.0 - rr) * x2[0]; + xyz[1] = rr * x1[1] + (1.0 - rr) * x2[1]; + xyz[2] = rr * x1[2] + (1.0 - rr) * x2[2]; + + cam->XYZ_to_cam(cam, Jab, xyz); + + printf("XYZ %f %f %f -> Jab is %f %f %f\n", + xyz[0], xyz[1], xyz[2], + Jab[0], Jab[1], Jab[2]); + } + exit(0); + } +#endif /* SPECIAL */ + +#ifdef TROUBLE + { + double Jab[3], jabr[3], xyz[3]; + + cam->XYZ_to_cam(cam, Jab, tsample[c]); + + if (camr->XYZ_to_cam(camr, jabr, tsample[c])) { +#ifdef DIAG + printf("Reference XYZ2Jab returned error from XYZ %f %f %f\n",tsample[c][0],tsample[c][1],tsample[c][2]); +#endif /* DIAG */ + } else if (maxdiff(Jab, jabr) > MAX_REF_ERR) { + printf("Spottest: Excessive error to reference for %f %f %f\n",tsample[c][0],tsample[c][1],tsample[c][2]); + printf("Spottest: Error %f, Got %f %f %f\n expected %f %f %f\n", maxdiff(Jab, jabr), Jab[0], Jab[1], Jab[2], jabr[0], jabr[1], jabr[2]); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + + +#ifdef DIAG + printf("XYZ %f %f %f -> Jab is %f %f %f\n", + tsample[c][0], tsample[c][1], tsample[c][2], + Jab[0], Jab[1], Jab[2]); +#endif /* DIAG */ + + cam->cam_to_XYZ(cam, xyz, Jab); + +#ifdef DIAG + printf("XYZ %f %f %f -> Jab is %f %f %f\n", + Jab[0], Jab[1], Jab[2], + xyz[0], xyz[1], xyz[2]); +#endif /* DIAG */ + + if (maxdiff(tsample[c], xyz) > 1e-5) { + printf("Spottest trouble 1: Excessive error in inversion %f\n",maxdiff(tsample[c], xyz)); + printf("Is %f %f %f\n",xyz[0],xyz[1],xyz[2]); + printf("Should be %f %f %f\n",tsample[c][0],tsample[c][1],tsample[c][2]); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + } +#endif /* TROUBLE */ + +#ifdef TROUBLE2 + { + double Jab[3], xyz[3]; + + cam->cam_to_XYZ(cam, xyz, tsample2[c]); + +#ifdef DIAG + printf("Lab %f %f %f -> XYZ is %f %f %f\n", + tsample2[c][0], tsample2[c][1], tsample2[c][2], + xyz[0], xyz[1], xyz[2]); +#endif /* DIAG */ + + cam->XYZ_to_cam(cam, Jab, xyz); + +#ifdef DIAG + printf("XYZ %f %f %f -> Jab is %f %f %f\n", + xyz[0], xyz[1], xyz[2], + Jab[0], Jab[1], Jab[2]); +#endif /* DIAG */ + + if (maxdiff(tsample2[c], Jab) > 1e-3) { + printf("Spottest trouble2: Excessive error in inversion %f\n",maxdiff(tsample2[c], Jab)); + printf("Is %f %f %f\n",Jab[0],Jab[1],Jab[2]); + printf("Should be %f %f %f\n",tsample2[c][0],tsample2[c][1],tsample2[c][2]); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + } +#endif /* TROUBLE2 */ + } +#endif /* TROUBLE || TROUBLE 2 */ + /* =============================================== */ + + /* ================= SpotTest ===================== */ +#ifdef SPOTTEST + { + double mrerr = 0.0, mxerr = 0.0; + for (c = 0; c < NO_SPOTS; c++) { + +#ifdef DIAG + printf("Case %d:\n",c); +#endif /* DIAG */ + camr->set_view( + camr, + vc_average, /* Enumerated Viewing Condition */ + sp_white[c],/* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + sp_La[c], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + sp_white[c],/* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + sp_white[c],/* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + sp_La[c], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + sp_white[c],/* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + + camr->nldlimit = cam->nldlimit; + camr->jlimit = cam->jlimit; + +#ifdef DIAG + printf("\n"); +#endif /* DIAG */ + + { + double Jab[3], JCh[3], jabr[3], res[3], target[3]; + double mde; + + cam->XYZ_to_cam(cam, Jab, sample[c]); + + if (camr->XYZ_to_cam(camr, jabr, sample[c])) + printf("Reference XYZ2Jab returned error\n"); + else if ((mde = maxdiff(Jab, jabr)) > MAX_REF_ERR) { + printf("Spottest: Excessive error to reference for %f %f %f\n",sample[c][0],sample[c][1],sample[c][2]); + printf("Spottest: Error %f, Got %f %f %f\n expected %f %f %f\n",maxdiff(Jab, jabr), + Jab[0], Jab[1], Jab[2], jabr[0], jabr[1], jabr[2]); + ok = 0; +#ifdef EXIT_ON_ERROR + camr->trace = 1; + camr->XYZ_to_cam(camr, jabr, sample[c]); + camr->trace = 0; + cam->trace = 1; + cam->XYZ_to_cam(cam, Jab, sample[c]); + cam->trace = 0; + fflush(stdout); + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + if (mde > mrerr) + mrerr = mde; + + /* Convert to JCh for checking */ + JCh[0] = Jab[0]; + + /* Compute hue angle */ + JCh[2] = (180.0/3.14159265359) * atan2(Jab[2], Jab[1]); + JCh[2] = (JCh[2] < 0.0) ? JCh[2] + 360.0 : JCh[2]; + + /* Compute chroma value */ + JCh[1] = sqrt(Jab[1] * Jab[1] + Jab[2] * Jab[2]); + + target[0] = correct[c][0]; + target[1] = correct[c][1]; + if (JCh[2] >= correct[c][2] && JCh[2] <= correct[c][3]) { + target[2] = JCh[2]; + } else { + if (fabs(JCh[2] - correct[c][2]) < fabs(JCh[2] - correct[c][3])) + target[2] = correct[c][2]; + else + target[2] = correct[c][3]; + } + + if ((mde = maxdiff(JCh, target)) > MAX_SPOT_ERR) { + printf("Spottest: Excessive error for %f %f %f\n",sample[c][0],sample[c][1],sample[c][2]); + printf("Spottest: Excessive error in conversion to CAM %f\n",maxdiff(JCh, target)); + printf("Jab is %f %f %f\nJch is %f %f %f\n", + Jab[0], Jab[1], Jab[2], + JCh[0], JCh[1], JCh[2]); + + printf("JCh should be %f %f %f - %f\n", + correct[c][0], correct[c][1], correct[c][2], correct[c][3]); + + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + if (mde > mxerr) + mxerr = mde; + + cam->cam_to_XYZ(cam, res, Jab); + + if (maxdiff(sample[c], res) > 1e-5) { + printf("Spottest: Excessive error in inversion %f\n",maxdiff(sample[c], res)); + ok = 0; +#ifdef EXIT_ON_ERROR + cam->trace = 1; + cam->XYZ_to_cam(cam, Jab, sample[c]); + cam->cam_to_XYZ(cam, res, Jab); + cam->trace = 0; + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } +#ifdef DIAG + printf("Jab is %f %f %f\nJch is %f %f %f\n", + Jab[0], Jab[1], Jab[2], + JCh[0], JCh[1], JCh[2]); + + printf("JCh should be %f %f %f - %f\n", + correct[c][0], correct[c][1], correct[c][2], correct[c][3]); + + printf("XYZ is %f %f %f\n",res[0], res[1], res[2]); + printf("Inversion error = %f\n",maxdiff(sample[c], res)); + printf("\n\n"); +#endif /* DIAG */ + } + } + if (ok == 0) { + printf("Spottest testing FAILED\n"); + } else { + printf("Spottest testing OK\n"); + printf("Spottest maximum DE to ref = %f, to expected = %f\n",mrerr,mxerr); + } + } +#endif /* SPOTTEST */ + /* =============================================== */ + + /* ================= LocusTest ===================== */ +#ifdef LOCUSTEST +#define LT_YRES 30 + { + double mxlocde = -100.0; /* maximum locus test delta E */ + + for (c = 0; c < NO_WHITES; c++) { + printf("Locus: next white reference\n"); + for (d = 0; d < NO_LAS; d++) { + for (e = 0; e < NO_VCS; e++) { + int i, j; + double Yxy[3], xyz[3], Jab[3], jabr[3], checkxyz[3]; + double mxd; /* Maximum delta of Lab any component */ + double ltde; /* Locus test delta E */ + + camr->set_view( + camr, + Vc[e], /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + La[d], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + + cam->set_view( + cam, + Vc[e], /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + La[d], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + + /* Make reference return error where it's going to disagree with implementation */ + camr->nldlimit = cam->nldlimit; + camr->jlimit = cam->jlimit; + + /* The monochromic boundary with a unit energy monochromic source */ + for (j = 0; j < LT_YRES; j++) { + double Y = j/(LT_YRES - 1.0); + + Y = Y * Y * 0.999 + 0.001; + + for (i = 0; i < 65; i++) { + Yxy[0] = Y * sl[i][3]; + Yxy[1] = sl[i][1]; + Yxy[2] = sl[i][2]; + + icmYxy2XYZ(xyz, Yxy); + + cam->XYZ_to_cam(cam, Jab, xyz); + + if (camr->XYZ_to_cam(camr, jabr, xyz)) { +#ifdef DIAG + printf("Reference XYZ2Jab returned error from XYZ %f %f %f\n",xyz[0],xyz[1],xyz[2]); +#endif /* DIAG */ + } else if (maxdiff(Jab, jabr) > MAX_REF_ERR) { + printf("Locustest1: Excessive error to reference for %f %f %f\n",xyz[0],xyz[1],xyz[2]); + printf("Locustest1: Error %f, Got %f %f %f\n expected %f %f %f\n",maxdiff(Jab, jabr), + Jab[0], Jab[1], Jab[2], jabr[0], jabr[1], jabr[2]); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + + cam->cam_to_XYZ(cam, checkxyz, Jab); + +#ifdef DIAG + printf("Inversion error = %f\n",maxdiff(checkxyz, xyz)); +#endif /* DIAG */ + + /* Check the result */ + ltde = icmXYZLabDE(&icmD50, checkxyz, xyz); + if (ltde > mxlocde) + mxlocde = ltde; + mxd = maxxyzdiff(checkxyz, xyz); + if (mxd > 0.05) { + printf("XYZ %f %f %f\n ->",xyz[0],xyz[1],xyz[2]); + printf("Jab %f %f %f\n ->",Jab[0],Jab[1],Jab[2]); + printf("XYZ %f %f %f\n",checkxyz[0],checkxyz[1],checkxyz[2]); + printf("Locustest1: Excessive roundtrip error %f\n",mxd); + printf("c = %d, d = %d, e = %d\n",c,d,e); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + } + + /* Do the purple line */ + for (i = 0; i < 20; i++) { + double b = i/(20 - 1.0); + + Yxy[0] = (b * sl[0][3] + (1.0 - b) * sl[64][3]) * Y; + Yxy[1] = b * sl[0][1] + (1.0 - b) * sl[64][1]; + Yxy[2] = b * sl[0][2] + (1.0 - b) * sl[64][2]; + icmYxy2XYZ(xyz, Yxy); + + cam->XYZ_to_cam(cam, Jab, xyz); + + if (camr->XYZ_to_cam(camr, jabr, xyz)) { +#ifdef DIAG + printf("Reference XYZ2Jab returned error from XYZ %f %f %f\n",xyz[0],xyz[1],xyz[2]); +#endif /* DIAG */ + } else if (maxdiff(Jab, jabr) > MAX_REF_ERR) { + printf("Locustest2: Excessive error to reference for %f %f %f\n",xyz[0],xyz[1],xyz[2]); + printf("Locustest2: Error %f, Got %f %f %f,\n expected %f %f %f\n",maxdiff(Jab, jabr), + Jab[0], Jab[1], Jab[2], jabr[0], jabr[1], jabr[2]); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + + cam->cam_to_XYZ(cam, checkxyz, Jab); + +#ifdef DIAG + printf("Inversion error = %f\n",maxdiff(checkxyz, xyz)); +#endif /* DIAG */ + + /* Check the result */ + ltde = icmXYZLabDE(&icmD50, checkxyz, xyz); + if (ltde > mxlocde) + mxlocde = ltde; + mxd = maxxyzdiff(checkxyz, xyz); + if (mxd > 0.05) { + printf("XYZ %f %f %f\n ->",xyz[0],xyz[1],xyz[2]); + printf("Jab %f %f %f\n ->",Jab[0],Jab[1],Jab[2]); + printf("XYZ %f %f %f\n",checkxyz[0],checkxyz[1],checkxyz[2]); + printf("Locustest2: Excessive roundtrip error %f\n",mxd); + printf("c = %d, d = %d, e = %d\n",c,d,e); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + } + } + +#ifdef LOCUSRAND + for (i = 0; i < 20000 ; i++) { +// for (i = 0; i < 1000000 ; i++) { + int i1, i2, i3; + double Y, bb1, bb2, bb3; + + i1 = i_rand(0, 64); + i2 = i_rand(0, 64); + i3 = i_rand(0, 64); + bb1 = d_rand(0.0, 1.0); + bb2 = d_rand(0.0, 1.0 - bb1); + bb3 = 1.0 - bb1 - bb2; + Y = d_rand(0.001, 1.0); +//printf("~1 i1 = %d, i2 = %d, i3 = %d\n",i1,i2,i3); +//printf("~1 bb1 = %f, bb2 = %f, bb3 = %f\n",bb1,bb2,bb3); +//printf("~1 Y = %f\n",Y); + + Yxy[0] = bb1 * sl[i1][3] + bb2 * sl[i2][3] + bb3 * sl[i3][3]; + Yxy[1] = bb1 * sl[i1][1] + bb2 * sl[i2][1] + bb3 * sl[i3][1]; + Yxy[2] = bb1 * sl[i1][2] + bb2 * sl[i2][2] + bb3 * sl[i3][2]; + Yxy[0] = Y; /* ??? */ + icmYxy2XYZ(xyz, Yxy); + + Yxy[0] = (bb1 * sl[i1][3] + bb2 * sl[i2][3] + bb3 * sl[i3][3]) * Y; +//printf("~1 xyz = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + + cam->XYZ_to_cam(cam, Jab, xyz); + + if (camr->XYZ_to_cam(camr, jabr, xyz)) { +#ifdef DIAG + printf("Reference XYZ2Jab returned error from XYZ %f %f %f\n",xyz[0],xyz[1],xyz[2]); +#endif /* DIAG */ + } else if (maxdiff(Jab, jabr) > MAX_REF_ERR) { + printf("Locustest3: Excessive error to reference for %f %f %f\n",xyz[0],xyz[1],xyz[2]); + printf("Locustest3: Error %f, Got %f %f %f, expected %f %f %f\n",maxdiff(Jab, jabr), + Jab[0], Jab[1], Jab[2], jabr[0], jabr[1], jabr[2]); + printf("c = %d, d = %d, e = %d\n",c,d,e); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + + cam->cam_to_XYZ(cam, checkxyz, Jab); + +#ifdef DIAG + printf("Inversion error = %f\n",maxdiff(checkxyz, xyz)); +#endif /* DIAG */ + + /* Check the result */ + ltde = icmXYZLabDE(&icmD50, checkxyz, xyz); + if (ltde > mxlocde) + mxlocde = ltde; + mxd = maxxyzdiff(checkxyz, xyz); + if (mxd > 0.05) { + printf("XYZ %f %f %f\n ->",xyz[0],xyz[1],xyz[2]); + printf("Jab %f %f %f\n ->",Jab[0],Jab[1],Jab[2]); + printf("XYZ %f %f %f\n",checkxyz[0],checkxyz[1],checkxyz[2]); + printf("Locustest3: Excessive roundtrip error %f\n",mxd); + printf("c = %d, d = %d, e = %d\n",c,d,e); + ok = 0; +#ifdef EXIT_ON_ERROR + fflush(stdout); + exit(-1); +#endif /* EXIT_ON_ERROR */ + } + } +#endif + + } /* Viewing condition */ + } /* Luninence */ + } /* Whites */ + if (ok == 0) { + printf("Locustest FAILED\n"); + } else { + printf("Locustest OK, max DE = %e\n",mxlocde); + } + } +#endif /* LOCUSTEST */ + /* =============================================== */ + + /* ================= XYZ->Jab->XYZ ================== */ +#if defined(INVTEST) || defined(INVTEST1) || defined(INVTEST2) + { + /* Get the range of Jab values */ + double xmin[3] = { 1e38, 1e38, 1e38 }; + double xmax[3] = { -1e38, -1e38, -1e38 }; + double jmin[3] = { 1e38, 1e38, 1e38 }; + double jmax[3] = { -1e38, -1e38, -1e38 }; + double merr = 0.0; + for (c = 0; c < 6; c++) { + int co0, co1, co2; /* (using co[3] triggers compiler bug) */ + double xyz[3], Lab[3], Jab[3], checkxyz[3]; + double mxd; + + + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + 34.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.01, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + +#ifdef INVTEST1 + /* Test case */ + Lab[0] = 0.0; Lab[1] = -128.0; Lab[2] = 36.571429; + Lab2XYZ(xyz, Lab); + cam->XYZ_to_cam(cam, Jab, xyz); + cam->cam_to_XYZ(cam, checkxyz, Jab); + + /* Check the result */ + mxd = maxxyzdiff(checkxyz, xyz); + if (_finite(merr) && (!_finite(mxd) || mxd > merr)) + merr = mxd; +#ifdef DIAG +#ifndef VERBOSE + if (!_finite(mxd) || mxd > 0.1) +#endif /* VERBOSE */ + { + printf("\n"); + printf("c = %d, #### Lab = %f %f %f\n",c, Lab[0], Lab[1], Lab[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + xyz[0],xyz[1],xyz[2],Jab[0],Jab[1],Jab[2], + checkxyz[0],checkxyz[1],checkxyz[2], + maxxyzdiff(checkxyz, xyz)); +#ifdef EXIT_ON_ERROR + if (!_finite(mxd) || mxd > 0.1) { + fflush(stdout); + exit(-1); + } +#endif /* EXIT_ON_ERROR */ + } +#endif /* DIAG */ + +#endif /* INVTEST1 */ + +#ifdef INVTEST2 + { + int rv; + cntx x; + double xyz[3], ss[3], Jab[3]; + + x.cam = cam; + x.Jab[0] = 0.0; + x.Jab[1] = -50.0; + x.Jab[2] = -50.0; + + xyz[0] = -0.3; + xyz[1] = -0.3; + xyz[2] = -0.3; + + ss[0] = 0.3; + ss[1] = 0.3; + ss[2] = 0.3; + + rv = powell(NULL, 3, xyz, ss, 1e-12, 10000, opt1, (void *)&x); + if (rv) + printf("Powell failed\n"); + else { + printf("XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + } + cam->cam_to_XYZ(cam, xyz, x.Jab); + printf("Inverted XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + cam->XYZ_to_cam(cam, Jab, xyz); + printf("Check Jab %f %f %f\n",Jab[0], Jab[1], Jab[2]); + c = 6; + } +#endif /* INVTEST2 */ + +#ifdef INVTEST + /* itterate through -20 to +120 XYZ cube space */ + for (co0 = 0; co0 < TRES; co0++) { + xyz[0] = co0/(TRES-1.0); + xyz[0] = xyz[0] * 1.4 - 0.2; + for (co1 = 0; co1 < TRES; co1++) { + xyz[1] = co1/(TRES-1.0); + xyz[1] = xyz[1] * 1.4 - 0.2; + for (co2 = 0; co2 < TRES; co2++) { + int i; + xyz[2] = co2/(TRES-1.0); + xyz[2] = xyz[2] * 1.4 - 0.2; + + for (i = 0; i < 3; i++) { + if (xyz[i] < xmin[i]) + xmin[i] = xyz[i]; + if (xyz[i] > xmax[i]) + xmax[i] = xyz[i]; + } + cam->XYZ_to_cam(cam, Jab, xyz); + + for (i = 0; i < 3; i++) { + if (Jab[i] < jmin[i]) + jmin[i] = Jab[i]; + if (Jab[i] > jmax[i]) + jmax[i] = Jab[i]; + } + cam->cam_to_XYZ(cam, checkxyz, Jab); + + /* Check the result */ + mxd = maxxyzdiff(checkxyz, xyz); + if (_finite(merr) && (!_finite(mxd) || mxd > merr)) + merr = mxd; + +#if defined(DIAG) || defined(VERBOSE) || defined(EXIT_ON_ERROR) + +#if defined(DIAG) || defined(EXIT_ON_ERROR) + if (!_finite(mxd) || mxd > 0.5) /* Delta E */ +#endif /* DIAG || EXIT_ON_ERROR */ + { + double oLab[3]; + + printf("\n"); + printf("#### XYZ = %f %f %f -> %f %f %f\n", + xyz[0], xyz[1], xyz[2], checkxyz[0], checkxyz[1], checkxyz[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + xyz[0],xyz[1],xyz[2],Jab[0],Jab[1],Jab[2], + checkxyz[0],checkxyz[1],checkxyz[2], + maxxyzdiff(checkxyz, xyz)); + printf("c = %d\n",c); +#ifdef EXIT_ON_ERROR + if (!_finite(mxd) || mxd > 0.1) { + cam->trace = 1; + cam->XYZ_to_cam(cam, Jab, xyz); + cam->cam_to_XYZ(cam, checkxyz, Jab); + cam->trace = 0; + fflush(stdout); + exit(-1); + } +#endif /* EXIT_ON_ERROR */ + } +#endif /* DIAG || VERBOSE || EXIT_ON_ERROR */ + } + } + } +#endif /* INVTEST */ + } + if (!_finite(merr) || merr > 0.15) { + printf("INVTEST: Excessive error in roundtrip check %f DE\n",merr); + ok = 0; + } + printf("\n"); + printf("XYZ -> Jab -> XYZ\n"); + printf("Inversion check complete, peak error = %e DE\n",merr); + printf("Range of XYZ values was:\n"); + printf("X: %f -> %f\n", xmin[0], xmax[0]); + printf("Y: %f -> %f\n", xmin[1], xmax[1]); + printf("Z: %f -> %f\n", xmin[2], xmax[2]); + printf("Range of Jab values was:\n"); + printf("J: %f -> %f\n", jmin[0], jmax[0]); + printf("a: %f -> %f\n", jmin[1], jmax[1]); + printf("b: %f -> %f\n", jmin[2], jmax[2]); + } +#endif /* INVTEST || INVTEST1 || INVTEST2 */ + /* =============================================== */ + + /* ===================Jab->XYX->Jab ============================ */ + /* Note that this is expected to fail if BLUECOMPR is enabled */ +#if defined(TESTINV) || defined(TESTINV1) || defined(TESTINV2) + { + /* Get the range of XYZ values */ + double xmin[3] = { 1e38, 1e38, 1e38 }; + double xmax[3] = { -1e38, -1e38, -1e38 }; + double jmin[3] = { 1e38, 1e38, 1e38 }; + double jmax[3] = { -1e38, -1e38, -1e38 }; + double merr = 0.0; + + for (c = 0; c < 6; c++) { + int i, j; + int co0, co1, co2; /* (using co[3] triggers compiler bug) */ + double xyz[3], Jab[3], checkJab[3]; + double mxd; + + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + 34.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.01, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK /* use Helmholtz-Kohlraush flag */ + ); + +#ifdef TESTINV1 + /* Double sample test case */ + for (j = 0; j < 2; j++) { + if (j == 0) { +// Jab[0] = -2.5; Jab[1] = 128.0; Jab[2] = 128.0; + Jab[0] = 0.0; Jab[1] = -7.683075; Jab[2] = -7.683075; + } else { +// Jab[0] = -2.5; Jab[1] = -128.0; Jab[2] = -128.0; + Jab[0] = 0.0; Jab[1] = -128.0; Jab[2] = -128.0; + } +// Jab[0] = 21.25; Jab[1] = 0.0; Jab[2] = -64.0; +// Jab[0] = 1.05; Jab[1] = 0.0; Jab[2] = -64.0; +// Jab[0] = 0.0; Jab[1] = -128.0; Jab[2] = -128.0; + + for (i = 0; i < 3; i++) { + if (Jab[i] < jmin[i]) + jmin[i] = Jab[i]; + if (Jab[i] > jmax[i]) + jmax[i] = Jab[i]; + } + cam->cam_to_XYZ(cam, xyz, Jab); + for (i = 0; i < 3; i++) { + if (xyz[i] < xmin[i]) + xmin[i] = xyz[i]; + if (xyz[i] > xmax[i]) + xmax[i] = xyz[i]; + } + cam->XYZ_to_cam(cam, checkJab, xyz); + + /* Check the result */ + mxd = maxdiff(checkJab, Jab); + if (_finite(merr) && (!_finite(mxd) || mxd > merr)) + merr = mxd; +#ifdef DIAG +#ifndef VERBOSE + if (!_finite(mxd) || mxd > 0.1) +#endif /* VERBOSE */ + { + printf("\n"); + printf("#### Jab = %f %f %f\n",Jab[0], Jab[1], Jab[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + Jab[0],Jab[1],Jab[2], xyz[0],xyz[1],xyz[2], + checkJab[0],checkJab[1],checkJab[2], + maxdiff(checkJab, Jab)); + printf("c = %d\n",c); +#ifdef EXIT_ON_ERROR + if (!_finite(mxd) || mxd > 0.1) { + fflush(stdout); + exit(-1); + } +#endif /* EXIT_ON_ERROR */ + } +#endif /* DIAG */ + } +#endif /* TESTINV1 */ + +#ifdef TESTINV2 + { /* J = 0 test cases */ + int i; + for (i = 1; i < 128; i++) { + Jab[0] = 0.0; + Jab[1] = (double)i/2; + Jab[2] = (double)i; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)-i/2; + Jab[2] = (double)i; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)i/2; + Jab[2] = (double)-i; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)-i/2; + Jab[2] = (double)-i; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)i; + Jab[2] = (double)i/2; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)-i; + Jab[2] = (double)i/2; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)i; + Jab[2] = (double)-i/2; + cam->cam_to_XYZ(cam, xyz, Jab); + + Jab[0] = 0.0; + Jab[1] = (double)-i; + Jab[2] = (double)-i/2; + cam->cam_to_XYZ(cam, xyz, Jab); + } + } +#endif /* TESTINV2 */ + +#ifdef TESTINV + /* itterate through Jab space */ + for (co0 = 0; co0 < TRES; co0++) { + Jab[0] = co0/(TRES-1.0); + Jab[0] = -50.0 + Jab[0] * 165.0; + for (co1 = 0; co1 < TRES; co1++) { + Jab[1] = co1/(TRES-1.0); + Jab[1] = (Jab[1] - 0.5) * 256.0; + for (co2 = 0; co2 < TRES; co2++) { + int i; + Jab[2] = co2/(TRES-1.0); + Jab[2] = (Jab[2] - 0.5) * 256.0; + + for (i = 0; i < 3; i++) { + if (Jab[i] < jmin[i]) + jmin[i] = Jab[i]; + if (Jab[i] > jmax[i]) + jmax[i] = Jab[i]; + } + cam->cam_to_XYZ(cam, xyz, Jab); + for (i = 0; i < 3; i++) { + if (xyz[i] < xmin[i]) + xmin[i] = xyz[i]; + if (xyz[i] > xmax[i]) + xmax[i] = xyz[i]; + } + cam->XYZ_to_cam(cam, checkJab, xyz); + + /* Check the result */ + mxd = maxdiff(checkJab, Jab); + if (_finite(merr) && (!_finite(mxd) || mxd > merr)) + merr = mxd; +#if defined(DIAG) || defined(VERBOSE) || defined(EXIT_ON_ERROR) + +#if defined(DIAG) || defined(EXIT_ON_ERROR) + if (!_finite(mxd) || mxd > 0.1) +#endif /* DIAG || EXIT_ON_ERROR */ + { + printf("\n"); + printf("#### Jab = %f %f %f\n",Jab[0], Jab[1], Jab[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + Jab[0],Jab[1],Jab[2], xyz[0],xyz[1],xyz[2], + checkJab[0],checkJab[1],checkJab[2], + maxdiff(checkJab, Jab)); + printf("c = %d\n",c); +#ifdef EXIT_ON_ERROR + if (!_finite(mxd) || mxd > 0.1) { + cam->trace = 1; + cam->cam_to_XYZ(cam, xyz, Jab); + cam->XYZ_to_cam(cam, checkJab, xyz); + cam->trace = 0; + fflush(stdout); + exit(-1); + } +#endif /* EXIT_ON_ERROR */ + } +#endif /* DIAG || VERBOSE || EXIT_ON_ERROR */ + } + } + } +#endif /* TESTINV */ + } + if (!_finite(merr) || merr > 1.0) { + printf("TESTINV: Excessive error in roundtrip check %f\n",merr); + ok = 0; + } + printf("\n"); + printf("Jab -> XYX -> Jab\n"); + printf("Inversion check 2 complete, peak error = %e DE Jab\n",merr); + printf("Range of Jab values was:\n"); + printf("J: %f -> %f\n", jmin[0], jmax[0]); + printf("a: %f -> %f\n", jmin[1], jmax[1]); + printf("b: %f -> %f\n", jmin[2], jmax[2]); + printf("Range of XYZ values was:\n"); + printf("X: %f -> %f\n", xmin[0], xmax[0]); + printf("Y: %f -> %f\n", xmin[1], xmax[1]); + printf("Z: %f -> %f\n", xmin[2], xmax[2]); + } +#endif /* TESTINV || TESTINV1 TESTINV2 */ + /* =============================================== */ + + printf("\n"); + if (ok == 0) { + printf("Cam testing FAILED\n"); + } else { + printf("Cam testing OK\n"); + } + + cam->del(cam); + + return 0; +} + diff --git a/xicc/cam97s3.c b/xicc/cam97s3.c new file mode 100644 index 0000000..d75b33e --- /dev/null +++ b/xicc/cam97s3.c @@ -0,0 +1,596 @@ + +/* + * cam97s3hk + * + * Color Appearance Model, based on + * CIECAM97, "Revision for Practical Applications" + * by Mark D. Fairchild, with the addition of the Viewing Flare + * model described on page 487 of "Digital Color Management", + * by Edward Giorgianni and Thomas Madden, and the + * Helmholtz-Kohlraush effect, using the equation + * the Bradford-Hunt 96C model as detailed in Mark Fairchilds + * book "Color Appearance Models". + * + * Author: Graeme W. Gill + * Date: 5/10/00 + * Version: 1.20 + * + * Copyright 2000, 2002 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* Note that XYZ values are normalised to 1.0 consistent */ +/* with the ICC convention (not 100.0 as assumed by the CIECAM spec.) */ +/* Note that all whites are assumed to be normalised (ie. Y = 1.0) */ + +/* Various changes have been made to allow the CAM conversions to */ +/* function over a much greater range of XYZ and Jab values that */ +/* the functions are described in the above references. This is */ +/* because such values arise in the process of gamut mapping, and */ +/* in scanning through the grid of PCS values needed to fill in */ +/* the A2B table of an ICC profile. Such values have no correlation */ +/* to a real color value, but none the less need to be handled without */ +/* causing an exception, in a geometrically consistent and reversible */ +/* fashion. */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "xcam.h" +#include "cam97s3.h" + +#undef DIAG /* Print internal value diagnostics for each conversion */ + +#define CAM_PI 3.14159265359 + +/* Utility function */ +/* Return a viewing condition enumeration from the given Ambient and */ +/* Adapting/Surround Luminance. */ +static ViewingCondition cam97_Ambient2VC( +double La, /* Ambient Luminance (cd/m^2) */ +double Lv /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ +) { + double r; + + if (fabs(La) < 1e-10) /* Hmm. */ + r = 1.0; + else + r = La/Lv; + + if (r < 0.01) + return vc_dark; + if (r < 0.2) + return vc_dim; + return vc_average; +} + +static void cam_free(cam97s3 *s); +static int set_view(struct _cam97s3 *s, ViewingCondition Ev, double Wxyz[3], + double La, double Yb, double Lv, double Yf, double Fxyz[3], + int hk); +static int XYZ_to_cam(struct _cam97s3 *s, double *Jab, double *xyz); +static int cam_to_XYZ(struct _cam97s3 *s, double *xyz, double *Jab); + +/* Create a cam97s3 conversion object, with default viewing conditions */ +cam97s3 *new_cam97s3(void) { + cam97s3 *s; +// double D50[3] = { 0.9642, 1.0000, 0.8249 }; + + if ((s = (cam97s3 *)calloc(1, sizeof(cam97s3))) == NULL) { + fprintf(stderr,"cam97s3: malloc failed allocating object\n"); + exit(-1); + } + + /* Initialise methods */ + s->del = cam_free; + s->set_view = set_view; + s->XYZ_to_cam = XYZ_to_cam; + s->cam_to_XYZ = cam_to_XYZ; + + /* Set a default viewing condition ?? */ + /* set_view(s, vc_average, D50, 33.0, 0.2, 0.0, 0.0, D50, 0); */ + + return s; +} + +static void cam_free(cam97s3 *s) { + if (s != NULL) + free(s); +} + +/* A version of the pow() function that preserves the */ +/* sign of its first argument. */ +static double spow(double x, double y) { + return x < 0.0 ? -pow(-x,y) : pow(x,y); +} + +static int set_view( +cam97s3 *s, +ViewingCondition Ev, /* Enumerated Viewing Condition */ +double Wxyz[3], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ +double La, /* Adapting/Surround Luminance cd/m^2 */ +double Yb, /* Relative Luminance of Background to reference white */ +double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set to other than vc_none */ +double Yf, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ +double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ +int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ +) { + double tt; + + if (Ev == vc_none) /* Compute enumerated viewing condition */ + Ev = cam97_Ambient2VC(La, Lv); + /* Transfer parameters to the object */ + s->Ev = Ev; + s->Wxyz[0] = Wxyz[0]; + s->Wxyz[1] = Wxyz[1]; + s->Wxyz[2] = Wxyz[2]; + s->Yb = Yb > 0.005 ? Yb : 0.005; /* Set minimum to avoid divide by 0.0 */ + s->La = La; + s->Yf = Yf; + s->Fxyz[0] = Fxyz[0]; + s->Fxyz[1] = Fxyz[1]; + s->Fxyz[2] = Fxyz[2]; + s->hk = hk; + + /* Compute the internal parameters by category */ + switch(s->Ev) { + case vc_dark: + s->C = 0.525; + s->Nc = 0.8; + s->F = 0.9; + break; + case vc_dim: + s->C = 0.59; + s->Nc = 0.95; + s->F = 0.9; + break; + case vc_cut_sheet: + s->C = 0.41; + s->Nc = 0.8; + s->F = 0.9; + break; + default: /* average */ + s->C = 0.69; + s->Nc = 1.0; + s->F = 1.0; + break; + } + + /* Compute values that only change with viewing parameters */ + + /* Figure out the Flare contribution to the flareless XYZ input */ + tt = s->Yf * s->Wxyz[1]/s->Fxyz[1]; + s->Fsxyz[0] = tt * s->Fxyz[0]; + s->Fsxyz[1] = tt * s->Fxyz[1]; + s->Fsxyz[2] = tt * s->Fxyz[2]; + + /* Rescale so that the sum of the flare and the input doesn't exceed white */ + s->Fsc = s->Wxyz[1]/(s->Fsxyz[1] + s->Wxyz[1]); + s->Fsxyz[0] *= s->Fsc; + s->Fsxyz[1] *= s->Fsc; + s->Fsxyz[2] *= s->Fsc; + s->Fisc = 1.0/s->Fsc; + + /* Sharpened cone response white values */ + s->rgbW[0] = 0.8562 * s->Wxyz[0] + 0.3372 * s->Wxyz[1] - 0.1934 * s->Wxyz[2]; + s->rgbW[1] = -0.8360 * s->Wxyz[0] + 1.8327 * s->Wxyz[1] + 0.0033 * s->Wxyz[2]; + s->rgbW[2] = 0.0357 * s->Wxyz[0] - 0.0469 * s->Wxyz[1] + 1.0112 * s->Wxyz[2]; + + /* Degree of chromatic adaptation */ + s->D = s->F - (s->F / (1.0 + 2.0 * pow(s->La, 0.25) + s->La * s->La / 300.0) ); + + /* Chromaticaly transformed white value */ + s->rgbcW[0] = (s->D * (1.0/s->rgbW[0]) + 1.0 - s->D ) * s->rgbW[0]; + s->rgbcW[1] = (s->D * (1.0/s->rgbW[1]) + 1.0 - s->D ) * s->rgbW[1]; + s->rgbcW[2] = (s->D * (1.0/s->rgbW[2]) + 1.0 - s->D ) * s->rgbW[2]; + + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + s->rgbpW[0] = 0.6962394300923847 * s->rgbcW[0] + + 0.2492311682812913 * s->rgbcW[1] + + 0.0545394016263241 * s->rgbcW[2]; + s->rgbpW[1] = 0.3054822636273227 * s->rgbcW[0] + + 0.5921282520433844 * s->rgbcW[1] + + 0.1023894843292929 * s->rgbcW[2]; + s->rgbpW[2] = -0.0139683251072516 * s->rgbcW[0] + + 0.0278065725014340 * s->rgbcW[1] + + 0.9861617526058175 * s->rgbcW[2]; + + /* Background induction factor */ + s->n = s->Yb/ s->Wxyz[1]; + s->nn = pow((1.64 - pow(0.29, s->n)), 1.41); /* Pre computed value */ + + /* Lightness contrast factor ?? */ + { + double k; + + k = 1.0 / (5.0 * s->La + 1.0); + s->Fl = 0.2 * pow(k , 4.0) * 5.0 * s->La + + 0.1 * pow(1.0 - pow(k , 4.0) , 2.0) * pow(5.0 * s->La , 1.0/3.0); + } + + /* Background and Chromatic brightness induction factors */ + s->Nbb = 0.725 * pow(1.0/s->n, 0.2); + s->Ncb = s->Nbb; + + /* Base exponential nonlinearity */ + s->z = 1.0 + pow(s->n , 0.5); + + /* Post-adapted cone response of white */ + tt = pow(s->Fl * s->rgbpW[0], 0.73); + s->rgbaW[0] = (40.0 * tt / (tt + 2.0)) + 1.0; + tt = pow(s->Fl * s->rgbpW[1], 0.73); + s->rgbaW[1] = (40.0 * tt / (tt + 2.0)) + 1.0; + tt = pow(s->Fl * s->rgbpW[2], 0.73); + s->rgbaW[2] = (40.0 * tt / (tt + 2.0)) + 1.0; + + /* Achromatic response of white */ + s->Aw = (2.0 * s->rgbaW[0] + s->rgbaW[1] + (1.0/20.0) * s->rgbaW[2] - 3.05) * s->Nbb; + +#ifdef DIAG + printf("Scene parameters:\n"); + printf("Viewing condition Ev = %d\n",s->Ev); + printf("Ref white Wxyz = %f %f %f\n", s->Wxyz[0], s->Wxyz[1], s->Wxyz[2]); + printf("Relative liminance of background Yb = %f\n", s->Yb); + printf("Adapting liminance La = %f\n", s->La); + printf("Flare Yf = %f\n", s->Yf); + printf("Flare color Fxyz = %f %f %f\n", s->Fxyz[0], s->Fxyz[1], s->Fxyz[2]); + + printf("Internal parameters:\n"); + printf("Surround Impact C = %f\n", s->C); + printf("Chromatic Induction Nc = %f\n", s->Nc); + printf("Adaptation Degree F = %f\n", s->F); + + printf("Pre-computed values\n"); + printf("Sharpened cone white rgbW = %f %f %f\n", s->rgbW[0], s->rgbW[1], s->rgbW[2]); + printf("Degree of chromatic adaptation D = %f\n", s->D); + printf("Chromatically transformed white rgbcW = %f %f %f\n", s->rgbcW[0], s->rgbcW[1], s->rgbcW[2]); + printf("Hunter-P-E cone response white rgbpW = %f %f %f\n", s->rgbpW[0], s->rgbpW[1], s->rgbpW[2]); + printf("Background induction factor n = %f\n", s->n); + printf("Lightness contrast factor Fl = %f\n", s->Fl); + printf("Background brightness induction factor Nbb = %f\n", s->Nbb); + printf("Chromatic brightness induction factor Ncb = %f\n", s->Ncb); + printf("Base exponential nonlinearity z = %f\n", s->z); + printf("Post adapted cone response white rgbaW = %f %f %f\n", s->rgbaW[0], s->rgbaW[1], s->rgbaW[2]); + printf("Achromatic response of white Aw = %f\n", s->Aw); +#endif + return 0; +} + +/* Conversions */ +static int XYZ_to_cam( +struct _cam97s3 *s, +double Jab[3], +double XYZ[3] +) { + int i; + double xyz[3], rgb[3], rgbp[3], rgba[3], rgbc[3]; + double a, b, nab, J, C, h, e, A, ss; + double ttd, tt; + + /* Add in flare */ + xyz[0] = s->Fsc * XYZ[0] + s->Fsxyz[0]; + xyz[1] = s->Fsc * XYZ[1] + s->Fsxyz[1]; + xyz[2] = s->Fsc * XYZ[2] + s->Fsxyz[2]; + + /* Spectrally sharpened cone responses */ + rgb[0] = 0.8562 * xyz[0] + 0.3372 * xyz[1] - 0.1934 * xyz[2]; + rgb[1] = -0.8360 * xyz[0] + 1.8327 * xyz[1] + 0.0033 * xyz[2]; + rgb[2] = 0.0357 * xyz[0] - 0.0469 * xyz[1] + 1.0112 * xyz[2]; + + /* Chromaticaly transformed sample value */ + rgbc[0] = (s->D * (1.0/s->rgbW[0]) + 1.0 - s->D ) * rgb[0]; + rgbc[1] = (s->D * (1.0/s->rgbW[1]) + 1.0 - s->D ) * rgb[1]; + rgbc[2] = (s->D * (1.0/s->rgbW[2]) + 1.0 - s->D ) * rgb[2]; + + /* Transform from spectrally sharpened, to Hunt-Pointer_Estevez cone space */ + rgbp[0] = 0.6962394300923847 * rgbc[0] + + 0.2492311682812913 * rgbc[1] + + 0.0545394016263241 * rgbc[2]; + rgbp[1] = 0.3054822636273227 * rgbc[0] + + 0.5921282520433844 * rgbc[1] + + 0.1023894843292929 * rgbc[2]; + rgbp[2] = -0.0139683251072516 * rgbc[0] + + 0.0278065725014340 * rgbc[1] + + 0.9861617526058175 * rgbc[2]; + + /* Post-adapted cone response of sample. */ + /* rgba[] has a minimum value of 1.0 for XYZ[] = 0 and no flare. */ + /* We add linear segments at the ends of this conversion to */ + /* allow numerical handling of a wider range of values */ + for (i = 0; i < 3; i++) { + if (rgbp[i] < 0.0) { + tt = pow(s->Fl * -rgbp[i], 0.73); + if (tt < 78.0) + rgba[i] = (2.0 - 39.0 * tt) / (tt + 2.0); + else + rgba[i] = (2.0 - tt) / 2.0; + + } else { + tt = pow(s->Fl * rgbp[i], 0.73); + if (tt < 78.0) + rgba[i] = (41.0 * tt + 2.0) / (tt + 2.0); + else + rgba[i] = (tt + 2.0) / 2.0; + } + } + + /* Preliminary red-green & yellow-blue opponent dimensions */ + a = rgba[0] - 12.0 * rgba[1]/11.0 + rgba[2]/11.0; + b = (1.0/9.0) * (rgba[0] + rgba[1] - 2.0 * rgba[2]); + nab = sqrt(a * a + b * b); /* Normalised a, b */ + + /* Hue angle */ + h = (180.0/CAM_PI) * atan2(b,a); + h = (h < 0.0) ? h + 360.0 : h; + + /* Eccentricity factor */ + { + double r, e1, e2, h1, h2; + + if (h <= 20.14) + e1 = 0.8565, e2 = 0.8, h1 = 0.0, h2 = 20.14; + else if (h <= 90.0) + e1 = 0.8, e2 = 0.7, h1 = 20.14, h2 = 90.0; + else if (h <= 164.25) + e1 = 0.7, e2 = 1.0, h1 = 90.0, h2 = 164.25; + else if (h <= 237.53) + e1 = 1.0, e2 = 1.2, h1 = 164.25, h2 = 237.53; + else + e1 = 1.2, e2 = 0.8565, h1 = 237.53, h2 = 360.0; + + r = (h-h1)/(h2-h1); +#ifdef CIECAM97S3_SPLINE_E + r = r * r * (3.0 - 2.0 * r); +#endif + e = e1 + r * (e2-e1); + } + + /* Achromatic response */ + /* Note that the minimum values of rgba[] for XYZ = 0 is 1.0, */ + /* hence magic 3.05 below comes from the following weighting of rgba[], */ + /* to base A at 0.0 */ + A = (2.0 * rgba[0] + rgba[1] + (1.0/20.0) * rgba[2] - 3.05) * s->Nbb; + + /* Lightness */ + J = spow(A/s->Aw, s->C * s->z); /* J/100 - keep Sign */ + + /* Saturation */ + /* Note that the minimum values for rgba[] for XYZ = 0 is 1.0 */ + /* Hence magic 3.05 below comes from the following weighting of rgba[] */ + ttd = rgba[0] + rgba[1] + (21.0/20.0) * rgba[2]; + ttd = fabs(ttd); + if (ttd < 3.05) { /* If not physically realisable, limit denominator */ + ttd = 3.05; /* hence limit max ss value */ + } + ss = (50000.0/13.0 * s->Nc * s->Ncb * nab * e) / ttd; + + /* Chroma - Keep C +ve and make sure J doesn't force it to 0 */ + tt = fabs(J); + if (tt < 0.01) + tt = 0.01; + C = 0.7487 * pow(ss, 0.973) * pow(tt, 0.945 * s->n) * s->nn; + + /* Helmholtz-Kohlraush effect */ + if (s->hk) { + double kk = C/300.0 * sin(CAM_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 0.9) /* Limit kk to a reasonable range */ + kk = 0.9; + J = J + (1.0 - J) * kk; + } + + J *= 100.0; /* Scale J */ + + /* Compute Jab value */ + Jab[0] = J; + if (nab > 1e-10) { + Jab[1] = C * a/nab; + Jab[2] = C * b/nab; + } else { + Jab[1] = 0.0; + Jab[2] = 0.0; + } + +#ifdef DIAG + printf("Processing:\n"); + printf("XYZ = %f %f %f\n", XYZ[0], XYZ[1], XYZ[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("Sharpened cone sample rgb = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + printf("Chromatically transformed sample value rgbc = %f %f %f\n", rgbc[0], rgbc[1], rgbc[2]); + printf("Hunt-P-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Prelim red green a = %f, b = %f\n", a, b); + printf("Hue angle h = %f\n", h); + printf("Eccentricity factor e = %f\n", e); + printf("Achromatic response A = %f\n", A); + printf("Lightness J = %f\n", J); + printf("Saturation ss = %f\n", ss); + printf("Chroma C = %f\n", C); + printf("Jab = %f %f %f\n", Jab[0], Jab[1], Jab[2]); +#endif + return 0; +} + +static int cam_to_XYZ( +struct _cam97s3 *s, +double XYZ[3], +double Jab[3] +) { + int i; + double xyz[3], rgb[3], rgbp[3], rgba[3], rgbc[3]; + double ja, jb, aa, ab, a, b, J, C, h, e, A, ss; + double tt, ttA, tte; + + J = Jab[0] * 0.01; /* J/100 */ + ja = Jab[1]; + jb = Jab[2]; + + /* Compute hue angle */ + h = (180.0/CAM_PI) * atan2(jb, ja); + h = (h < 0.0) ? h + 360.0 : h; + + /* Compute chroma value */ + C = sqrt(ja * ja + jb * jb); /* Must be Always +ve */ + + /* Helmholtz-Kohlraush effect */ + if (s->hk) { + double kk = C/300.0 * sin(CAM_PI * fabs(0.5 * (h - 90.0))/180.0); + if (kk > 0.9) /* Limit kk to a reasonable range */ + kk = 0.9; + J = (J - kk)/(1.0 - kk); + } + + /* Eccentricity factor */ + { + double r, e1, e2, h1, h2; + + if (h <= 20.14) + e1 = 0.8565, e2 = 0.8, h1 = 0.0, h2 = 20.14; + else if (h <= 90.0) + e1 = 0.8, e2 = 0.7, h1 = 20.14, h2 = 90.0; + else if (h <= 164.25) + e1 = 0.7, e2 = 1.0, h1 = 90.0, h2 = 164.25; + else if (h <= 237.53) + e1 = 1.0, e2 = 1.2, h1 = 164.25, h2 = 237.53; + else + e1 = 1.2, e2 = 0.8565, h1 = 237.53, h2 = 360.0; + + r = (h-h1)/(h2-h1); +#ifdef CIECAM97S3_SPLINE_E + r = r * r * (3.0 - 2.0 * r); +#endif + e = e1 + r * (e2-e1); + } + + /* Achromatic response */ + A = spow(J, 1.0/(s->C * s->z)) * s->Aw; /* Keep sign of J */ + + /* Saturation - keep +ve and make sure J = 0 doesn't blow it up. */ + tt = fabs(J); + if (tt < 0.01) + tt = 0.01; + ss = pow(C/(0.7487 * pow(tt, 0.945 * s->n) * s->nn), 1.0/0.973); /* keep +ve */ + + /* Compute a & b, taking care of numerical problems */ + aa = fabs(ja); + ab = fabs(jb); + ttA = (A/s->Nbb)+3.05; /* Common factor */ + tte = 50000.0/13.0 * e * s->Nc * s->Ncb; /* Common factor */ + + if (aa < 1e-10 && ab < 1e-10) { + a = ja; + b = jb; + } else if (aa > ab) { + double tanh = jb/ja; + double sign = (h > 90.0 && h <= 270.0) ? -1.0 : 1.0; + + if (ttA < 0.0) + sign = -sign; + + a = (ss * ttA) + / (sign * sqrt(1.0 + tanh * tanh) * tte + (ss * (11.0/23.0 + (108.0/23.0) * tanh))); + b = a * tanh; + + } else { /* ab > aa */ + double itanh = ja/jb; + double sign = (h > 180.0 && h <= 360.0) ? -1.0 : 1.0; + + if (ttA < 0.0) + sign = -sign; + + b = (ss * ttA) + / (sign * sqrt(1.0 + itanh * itanh) * tte + (ss * (108.0/23.0 + (11.0/23.0) * itanh))); + a = b * itanh; + } + + { /* Check if we have a limited saturation because it is non-realisable */ + double tts; + double nab = sqrt(a * a + b * b); /* Normalised a, b */ + tts = (nab * tte) / 3.05; /* Limited saturation number */ + if (tts < ss) { /* Saturation exceeds it anyway so must have limited denom. */ + a *= ss/tts; /* Rescale a & b to account for extra ss */ + b *= ss/tts; /* even though denom was limited (since nab was in numerator). */ + } + } + + /* Post-adapted cone response of sample */ + rgba[0] = (20.0/61.0) * ttA + + ((41.0 * 11.0)/(61.0 * 23.0)) * a + + ((288.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[1] = (20.0/61.0) * ttA + - ((81.0 * 11.0)/(61.0 * 23.0)) * a + - ((261.0 * 1.0)/(61.0 * 23.0)) * b; + rgba[2] = (20.0/61.0) * ttA + - ((20.0 * 11.0)/(61.0 * 23.0)) * a + - ((20.0 * 315.0)/(61.0 * 23.0)) * b; + + /* Hunt-Pointer_Estevez cone space */ + /* (with linear segments at the ends0 */ + tt = 1.0/s->Fl; + for (i = 0; i < 3; i++) { + if (rgba[i] < 1.0) { + double ta = rgba[i] > -38.0 ? rgba[i] : -38.0; + rgbp[i] = -tt * pow((2.0 - 2.0 * rgba[i] )/(39.0+ ta), 1.0/0.73); + } else { + double ta = rgba[i] < 40.0 ? rgba[i] : 40.0; + rgbp[i] = tt * pow((2.0 * rgba[i] -2.0)/(41.0 - ta), 1.0/0.73); + } + } + + /* Chromaticaly transformed sample value */ + rgbc[0] = 1.7605948990728097 * rgbp[0] + - 0.7400833814121892 * rgbp[1] + - 0.0205291236096116 * rgbp[2]; + rgbc[1] = -0.9170843265341294 * rgbp[0] + + 2.0826033118941054 * rgbp[1] + - 0.1655098145167107 * rgbp[2]; + rgbc[2] = 0.0507964678367941 * rgbp[0] + - 0.0692054676442407 * rgbp[1] + + 1.0184084918427683 * rgbp[2]; + + /* Spectrally sharpened cone responses */ + rgb[0] = rgbc[0]/(s->D * (1.0/s->rgbW[0]) + 1.0 - s->D); + rgb[1] = rgbc[1]/(s->D * (1.0/s->rgbW[1]) + 1.0 - s->D); + rgb[2] = rgbc[2]/(s->D * (1.0/s->rgbW[2]) + 1.0 - s->D); + + /* XYZ values */ + xyz[0] = 0.9873999149199270 * rgb[0] + - 0.1768250198556842 * rgb[1] + + 0.1894251049357572 * rgb[2]; + xyz[1] = 0.4504351090445316 * rgb[0] + + 0.4649328977527109 * rgb[1] + + 0.0846319932027575 * rgb[2]; + xyz[2] = -0.0139683251072516 * rgb[0] + + 0.0278065725014340 * rgb[1] + + 0.9861617526058175 * rgb[2]; + + /* Subtract flare */ + XYZ[0] = s->Fisc * (xyz[0] - s->Fsxyz[0]); + XYZ[1] = s->Fisc * (xyz[1] - s->Fsxyz[1]); + XYZ[2] = s->Fisc * (xyz[2] - s->Fsxyz[2]); + +#ifdef DIAG + printf("Processing:\n"); + printf("Jab = %f %f %f\n", Jab[0], Jab[1], Jab[2]); + printf("Chroma C = %f\n", C); + printf("Saturation ss = %f\n", ss); + printf("Lightness J = %f\n", J * 100.0); + printf("Achromatic response A = %f\n", A); + printf("Eccentricity factor e = %f\n", e); + printf("Hue angle h = %f\n", h); + printf("Prelim red green a = %f, b = %f\n", a, b); + printf("Post adapted cone response rgba = %f %f %f\n", rgba[0], rgba[1], rgba[2]); + printf("Hunt-P-E cone space rgbp = %f %f %f\n", rgbp[0], rgbp[1], rgbp[2]); + printf("Chromatically transformed sample value rgbc = %f %f %f\n", rgbc[0], rgbc[1], rgbc[2]); + printf("Sharpened cone sample rgb = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + printf("Including flare XYZ = %f %f %f\n", xyz[0], xyz[1], xyz[2]); + printf("XYZ = %f %f %f\n", XYZ[0], XYZ[1], XYZ[2]); +#endif + return 0; +} + + + + diff --git a/xicc/cam97s3.h b/xicc/cam97s3.h new file mode 100644 index 0000000..9893ea4 --- /dev/null +++ b/xicc/cam97s3.h @@ -0,0 +1,169 @@ + +/* + * cam97s3 + * + * Color Appearance Model, based on + * CIECAM97, "Revision for Practical Applications" + * by Mark D. Fairchild, with the addition of the Viewing Flare + * model described on page 487 of "Digital Color Management", + * by Edward Giorgianni and Thomas Madden, and the + * Helmholtz-Kohlraush effect, using the equation + * the Bradford-Hunt 96C model as detailed in Mark Fairchilds + * book "Color Appearance Models". + * + * Author: Graeme W. Gill + * Date: 5/10/00 + * Version: 1.20 + * + * Copyright 2000 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* Algorithm tweaks */ +#define CIECAM97S3_SPLINE_E /* Use spline interpolation for eccentricity factor */ + +/* Definitions assumed here: + + Stimulus field is the 2 degrees field of view of the sample + being characterised. + + Viewing/Scene/Image field is the area of the whole image or + display surface that the stimulus is part of. + + Background field is 10-12 degree field surrounding the stimulus field. + This may be within, overlap or encompass the Viewing/Scene/Image field. + + Surround/Adapting field is the visual field minus the background field. + + Visual field is the 130 degree angular field that is seen by the eyes. + + Illuminating field is the field that illuminates the reflective + Scene/Image. It may be the same as the Ambient field. + + Ambient field is the whole surrounding environmental light field. + + NOTE: In "Digital Color Management", Giorgianni and Madden use the term + "Surround" to mean the same thing as "Background" in the CIECAM97 terminology. + The ICC standard doesn't define what it means by "Surround illumination". + +*/ + +/* Rules of Thumb: */ +/* Ambient Luminance (Lat, cd/m^2) is Ambient Illuminance (Lux) divided by PI. */ +/* i.e. Lat = Iat/PI */ /* (1 foot candle = 0.0929 lux) */ + +/* The Adapting/Surround Luminance is often taken to be */ +/* the 20% of the Ambient Luminance. (gray world) */ +/* i.e. La = Lat/5 = Iat/15.7 */ + +/* For a reflective print, the Viewing/Scene/Image luminance (Lv, cd/m^2), */ +/* will be the Illuminating Luminance (Li, cd/m^2) reflected by the */ +/* media white point (Yw) */ + +/* If there is no special illumination for a reflective print, */ +/* then the Illuminating Luminance (Li) will be the Ambient Luminance (Lat) */ + +/* An emisive display will have an independently determined Lv. */ + +/* The classification of the type of surround is */ +/* determined by comparing the Adapting/Surround luminance (La, cd/m^2) */ +/* with the average luminance of the Viewing/Scene/Image field (Lv, cd/m^2) */ + +/* La/Lv == 0%, dark */ +/* La/Lv 0 - 20%, dim */ +/* La/Lv > 20%, average */ +/* special, cut sheet */ + +/* The Background relative luminance Yb is typically assumed to */ +/* be 0.18 .. 0.2, and is assumed to be grey. */ + +/* The source of flare light depends on the type of display system. */ +/* For a CRT, it will be the Ambient light reflecting off the glass surface. */ +/* (This implies Yf = Lat * reflectance/Lv) */ +/* For a reflection print, it will be the Illuminant reflecting from the media */ +/* surface. (Yf = Li * reflectance) */ +/* For a projected image, it will be stray projector light, scattered by the */ +/* surround, screen and air particles. (Yf = Li * reflectance_and_scattering) */ + +/* + + Typical Ambient Illuminance brightness + (Lux) La Condition + 11 1 Twilight + 32 2 Subdued indoor lighting + 64 4 Less than typical office light; sometimes recommended for + display-only workplaces (sRGB) + 350 22 Typical Office (sRGB annex D) + 500 32 Practical print evaluationa (ISO-3664 P2) + 1000 64 Good Print evaluation (CIE 116-1995) + 1000 64 Overcast Outdoors + 2000 127 Critical print evaluation (ISO-3664 P1) + 10000 637 Typical outdoors, full daylight + 50000 3185 Bright summers day + +*/ + +/* ---------------------------------- */ +struct _cam97s3 { +/* Public: */ + void (*del)(struct _cam97s3 *s); /* We're done with it */ + + int (*set_view)( + struct _cam97s3 *s, + ViewingCondition Ev, /* Enumerated Viewing Condition */ + double Wxyz[3], /* Reference/Adapted White XYZ (Y scale 1.0) */ + double La, /* Adapting/Surround Luminance cd/m^2 */ + double Yb, /* Luminance of Background relative to reference white (range 0.0 .. 1.0) */ + double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set */ + double Yf, /* Flare as a fraction of the reference white (range 0.0 .. 1.0) */ + double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ + int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ + ); + + /* Conversions */ + int (*XYZ_to_cam)(struct _cam97s3 *s, double *out, double *in); + int (*cam_to_XYZ)(struct _cam97s3 *s, double *out, double *in); + +/* Private: */ + /* Scene parameters */ + ViewingCondition Ev; /* Enumerated Viewing Condition */ + double Wxyz[3]; /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + double Yb; /* Relative Luminance of Background to reference white (Y range 0.0 .. 1.0) */ + double La; /* Adapting/Surround Luminance cd/m^2 */ + double Yf; /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + double Fxyz[3]; /* The Flare white coordinates (typically the Ambient color) */ + + /* Internal parameters */ + double C; /* Surround Impact */ + double Nc; /* Chromatic Induction */ + double F; /* Adaptation Degree */ + + /* Pre-computed values */ + double Fsc; /* Flare scale */ + double Fisc; /* Inverse flare scale */ + double Fsxyz[3]; /* Scaled Flare color coordinates */ + double rgbW[3]; /* Sharpened cone response white values */ + double D; /* Degree of chromatic adaption */ + double rgbcW[3]; /* Chromatically transformed white value */ + double rgbpW[3]; /* Hunt-Pointer-Estevez cone response space white */ + double n; /* Background induction factor */ + double nn; /* Precomuted function of n */ + double Fl; /* Lightness contrast factor ?? */ + double Nbb; /* Background brightness induction factors */ + double Ncb; /* Chromatic brightness induction factors */ + double z; /* Base exponential nonlinearity */ + double rgbaW[3]; /* Post-adapted cone response of white */ + double Aw; /* Achromatic response of white */ + + /* Flags */ + int hk; /* If NZ, use Helmholtz-Kohlraush effect */ + int trace; /* Trace values through computation */ +}; typedef struct _cam97s3 cam97s3; + + +/* Create a cam97s3 conversion class, with default viewing conditions */ +cam97s3 *new_cam97s3(void); + diff --git a/xicc/cam97test.c b/xicc/cam97test.c new file mode 100644 index 0000000..ff71bc8 --- /dev/null +++ b/xicc/cam97test.c @@ -0,0 +1,456 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 1/5/01 + * Version: 1.20 + * + * Copyright 2000-2004 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the CIECAM97s3 functionality. + */ + + +#include <stdio.h> +#include <math.h> +#include "xcam.h" +#include "cam97s3.h" + +#define DIAG /* Print internal value diagnostics for each conversion */ +#define SPOTTEST /* Test known spot colors */ +#undef INVTEST /* Lab cube to XYZ to Jab to XYZ */ +#undef INVTEST2 /* Jab cube to XYZ to Jab */ +#define TRES 33 +#define USE_HK 0 /* Use Helmholtz-Kohlraush */ +#define NOCAMCLIP 1 /* Don't clip before XYZ2Jab */ + +#ifndef _isnan +#define _isnan(x) ((x) != (x)) +#define _finite(x) ((x) == (x)) +#endif + +static void +Lab2XYZ(double *out, double *in) { + double L = in[0], a = in[1], b = in[2]; + double x,y,z,fx,fy,fz; + + if (L > 8.0) { + fy = (L + 16.0)/116.0; + y = pow(fy,3.0); + } else { + y = L/903.2963058; + fy = 7.787036979 * y + 16.0/116.0; + } + + fx = a/500.0 + fy; + if (fx > 24.0/116.0) + x = pow(fx,3.0); + else + x = (fx - 16.0/116.0)/7.787036979; + + fz = fy - b/200.0; + if (fz > 24.0/116.0) + z = pow(fz,3.0); + else + z = (fz - 16.0/116.0)/7.787036979; + + out[0] = x * 0.9642; + out[1] = y * 1.0000; + out[2] = z * 0.8249; +} + +/* CIE XYZ to perceptual Lab */ +static void +XYZ2Lab(double *out, double *in) { + double X = in[0], Y = in[1], Z = in[2]; + double x,y,z,fx,fy,fz; + + x = X/0.9642; + y = Y/1.0000; + z = Z/0.8249; + + if (x > 0.008856451586) + fx = pow(x,1.0/3.0); + else + fx = 7.787036979 * x + 16.0/116.0; + + if (y > 0.008856451586) + fy = pow(y,1.0/3.0); + else + fy = 7.787036979 * y + 16.0/116.0; + + if (z > 0.008856451586) + fz = pow(z,1.0/3.0); + else + fz = 7.787036979 * z + 16.0/116.0; + + out[0] = 116.0 * fy - 16.0; + out[1] = 500.0 * (fx - fy); + out[2] = 200.0 * (fy - fz); +} + + +/* Return maximum difference */ +double maxdiff(double in1[3], double in2[3]) { + int i; + double tt, rv = 0.0; + + /* Deal with the possibility that we have nan's */ + for (i = 0; i < 3; i++) { + tt = fabs(in1[i] - in2[i]); + if (_isnan(tt)) + return tt; + if (tt > rv) + rv = tt; + } + return rv; +} + +/* Return absolute difference */ +double absdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + tt = in1[0] - in2[0]; + rv += tt * tt; + tt = in1[1] - in2[1]; + rv += tt * tt; + tt = in1[2] - in2[2]; + rv += tt * tt; + return sqrt(rv); +} + +/* Return maximum Lab difference of XYZ */ +double maxxyzdiff(double i1[3], double i2[3]) { + int i; + double tt, rv = 0.0; + double in1[3], in2[3]; + + XYZ2Lab(in1, i1); + XYZ2Lab(in2, i2); + + /* Deal with the possibility that we have nan's */ + for (i = 0; i < 3; i++) { + tt = fabs(in1[i] - in2[i]); + if (_isnan(tt)) + return tt; + if (tt > rv) + rv = tt; + } + return rv; +} + +int +main(void) { + int ok = 1; + double white[6][3] = { + { 0.9505, 1.000, 1.0888 }, + { 0.9505, 1.000, 1.0888 }, + { 1.0985, 1.000, 0.3558 }, + { 1.0985, 1.000, 0.3558 }, + { 0.9505, 1.000, 1.0888 }, + { 0.9642, 1.000, 0.8249 } /* D50 for inversion tests */ + }; + double La[6] = { 318.31, 31.83, 318.31, 31.83, 318.31, 150.0 }; + double sample[5][3] = { + { 0.1901, 0.2000, 0.2178 }, + { 0.5706, 0.4306, 0.3196 }, + { 0.0353, 0.0656, 0.0214 }, + { 0.1901, 0.2000, 0.2178 }, + { 0.9505, 1.000, 1.0888 } /* Check white */ + }; + +#ifdef CIECAM97S3_HK +#ifdef CIECAM97S3_SPLINE_E + double correct[5][4] = { /* Hue range is last two */ + { 41.14, 0.06, 225.3, 251.9 }, + { 69.04, 71.04, 19.4, 19.4 }, + { 35.09, 87.15, 175.3, 175.3 }, + { 55.64, 82.43, 252.5, 252.5 }, + { 100.00000, 0.0, 0.0, 360.0 } + }; +#else + double correct[5][4] = { /* Hue range is last two */ + { 41.14, 0.06, 225.3, 251.9 }, + { 69.056048, 71.20763, 19.4, 19.4 }, + { 35.365009, 88.65238, 175.3, 175.3 }, + { 55.266184, 80.55128, 252.5, 252.5 }, + { 100.00000, 0.0, 0.0, 360.0 } + }; +#endif +#else + double correct[5][4] = { /* Hue range is last two */ + { 41.13, 0.05, 225.3, 251.9 }, + { 64.14, 71.22, 19.4, 19.4 }, + { 19.18, 88.64, 175.3, 175.3 }, + { 39.11, 80.55, 252.5, 252.5 }, + { 100.0, 0.00, 0.0, 360.0 } /* Check white */ + }; +#endif + + double Jab[3]; + double res[3]; + cam97s3 *cam; + int c; + + cam = new_cam97s3(); + +#ifdef SPOTTEST + for (c = 0; c < 5; c++) { + +#ifdef DIAG + printf("Case %d:\n",c); +#endif /* DIAG */ + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + La[c], /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.00, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK + ); +#ifdef DIAG + printf("\n"); +#endif /* DIAG */ + + { + double JCh[3], target[3]; + cam->XYZ_to_cam(cam, Jab, sample[c]); + + /* Convert to JCh for checking */ + JCh[0] = Jab[0]; + + /* Compute hue angle */ + JCh[2] = (180.0/3.14159265359) * atan2(Jab[2], Jab[1]); + JCh[2] = (JCh[2] < 0.0) ? JCh[2] + 360.0 : JCh[2]; + + /* Compute chroma value */ + JCh[1] = sqrt(Jab[1] * Jab[1] + Jab[2] * Jab[2]); + + target[0] = correct[c][0]; + target[1] = correct[c][1]; + if (JCh[2] >= correct[c][2] && JCh[2] <= correct[c][3]) { + target[2] = JCh[2]; + } else { + if (fabs(JCh[2] - correct[c][2]) < fabs(JCh[2] - correct[c][3])) + target[2] = correct[c][2]; + else + target[2] = correct[c][3]; + } + + cam->cam_to_XYZ(cam, res, Jab); + + if (maxdiff(JCh, target) > 0.05) { + printf("Spottest: Excesive error in conversion to CAM %f\n",maxdiff(JCh, target)); + ok = 0; + } + if (maxdiff(sample[c], res) > 1e-5) { + printf("Spottest: Excessive error in inversion %f\n",maxdiff(sample[c], res)); + ok = 0; + } +#ifdef DIAG + printf("Jab is %f %f %f, Jch is %f %f %f\n", + Jab[0], Jab[1], Jab[2], + JCh[0], JCh[1], JCh[2]); + + printf("Error to expected value = %f\n",maxdiff(JCh, target)); + + printf("XYZ is %f %f %f\n",res[0], res[1], res[2]); + printf("Inversion error = %f\n",maxdiff(sample[c], res)); +#endif /* DIAG */ + } + } +#endif /* SPOTTEST */ + +#ifdef INVTEST + for (c = 5; c < 6; c++) { + /* Get the range of Jab values */ + double min[3] = { 1e6, 1e6, 1e6 }; + double max[3] = { -1e6, -1e6, -1e6 }; + + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + 34.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.01, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK + ); + + { + int co0, co1, co2; /* (using co[3] triggers compiler bug) */ + double merr = 0.0; + double xyz[3], Lab[3], Jab[3], checkxyz[3]; + + +#ifdef NEVER + /* Test case */ + Lab[0] = 0.0; + Lab[1] = -180.0; + Lab[2] = -180.0; + Lab2XYZ(xyz, Lab); + cam->XYZ_to_cam(cam, Jab, xyz); + cam->cam_to_XYZ(cam, checkxyz, Jab); + +#else /* !NEVER */ + + /* itterate through Lab space */ + for (co0 = 0; co0 < TRES; co0++) { + Lab[0] = co0/(TRES-1.0); + Lab[0] = Lab[0] * 100.0; + for (co1 = 0; co1 < TRES; co1++) { + Lab[1] = co1/(TRES-1.0); + Lab[1] = (Lab[1] - 0.5) * 256.0; + for (co2 = 0; co2 < TRES; co2++) { + double mxd; + int i; + Lab[2] = co2/(TRES-1.0); + Lab[2] = (Lab[2] - 0.5) * 256.0; + + Lab2XYZ(xyz, Lab); + cam->XYZ_to_cam(cam, Jab, xyz); + for (i = 0; i < 3; i++) { + if (Jab[i] < min[i]) + min[i] = Jab[i]; + if (Jab[i] > max[i]) + max[i] = Jab[i]; + } + cam->cam_to_XYZ(cam, checkxyz, Jab); + + /* Check the result */ + mxd = maxxyzdiff(checkxyz, xyz); + if (_finite(merr) && (_isnan(mxd) || mxd > merr)) + merr = mxd; +#ifdef DIAG + if (_isnan(mxd) || mxd > 0.1) { + printf("\n"); + printf("Lab = %f %f %f\n",Lab[0], Lab[1], Lab[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + xyz[0],xyz[1],xyz[2],Jab[0],Jab[1],Jab[2], + checkxyz[0],checkxyz[1],checkxyz[2], + maxxyzdiff(checkxyz, xyz)); + } +#endif /* DIAG */ + } + } + } + if (_isnan(merr) || merr > 0.1) { + printf("Excessive error in inversion check %f\n",merr); + ok = 0; + } +#ifdef DIAG + printf("Inversion check complete, peak error = %f\n",merr); +#endif /* !NEVER */ + +#endif /* DIAG */ + printf("\n"); + printf("Range of Jab values was:\n"); + printf("J: %f -> %f\n", min[0], max[0]); + printf("a: %f -> %f\n", min[1], max[1]); + printf("b: %f -> %f\n", min[2], max[2]); + } + } +#endif /* INVTEST */ + +#ifdef INVTEST2 + for (c = 5; c < 6; c++) { + cam->set_view( + cam, + vc_average, /* Enumerated Viewing Condition */ + white[c], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */ + 34.0, /* Adapting/Surround Luminance cd/m^2 */ + 0.20, /* Relative Luminance of Background to reference white */ + 0.0, /* Luminance of white in image - not used */ + 0.01, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + white[c], /* The Flare color coordinates (typically the Ambient color) */ + USE_HK + ); + + { + int co0, co1, co2; /* (using co[3] triggers compiler bug) */ + double merr = 0.0; + double xyz[3], Jab[3], checkJab[3]; + +#ifdef NEVER + /* Test case */ +#ifdef NEVER + Jab[0] = 0.0; + Jab[1] = -180.0; + Jab[2] = -180.0; + cam->cam_to_XYZ(cam, xyz, Jab); + cam->XYZ_to_cam(cam, checkJab, xyz); +#else + xyz[0] = 555.365115; + xyz[1] = -518.091908; + xyz[2] = -1591.731701; + cam->XYZ_to_cam(cam, Jab, xyz); + cam->cam_to_XYZ(cam, xyz, Jab); +#endif + +#else /* !NEVER */ + + /* itterate through Jab space */ + for (co0 = 0; co0 < TRES; co0++) { + Jab[0] = co0/(TRES-1.0); + Jab[0] = 0.0 + Jab[0] * 100.0; + for (co1 = 0; co1 < TRES; co1++) { + Jab[1] = co1/(TRES-1.0); + Jab[1] = (Jab[1] - 0.5) * 256.0; + for (co2 = 0; co2 < TRES; co2++) { + double mxd; + Jab[2] = co2/(TRES-1.0); + Jab[2] = (Jab[2] - 0.5) * 256.0; + + cam->cam_to_XYZ(cam, xyz, Jab); + cam->XYZ_to_cam(cam, checkJab, xyz); + + /* Check the result */ + mxd = maxdiff(checkJab, Jab); + if (_finite(merr) && (_isnan(mxd) || mxd > merr)) + merr = mxd; +#ifdef DIAG + if (_isnan(mxd) || mxd > 0.1) { + printf("\n"); + printf("Jab = %f %f %f\n",Jab[0], Jab[1], Jab[2]); + printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n", + Jab[0],Jab[1],Jab[2], xyz[0],xyz[1],xyz[2], + checkJab[0],checkJab[1],checkJab[2], + maxdiff(checkJab, Jab)); + } +#endif /* DIAG */ + } + } + } + if (_isnan(merr) || merr > 1.0) { + printf("Excessive error in inversion check 2 %f\n",merr); + ok = 0; + } +#ifdef DIAG + printf("Inversion check 2 complete, peak error = %f\n",merr); +#endif /* DIAG */ +#endif /* !NEVER */ + } + } +#endif /* INVTEST2 */ + + printf("\n"); + if (ok == 0) { + printf("Cam testing FAILED\n"); + } else { + printf("Cam testing OK\n"); + } + + return 0; +} + diff --git a/xicc/ccmx.c b/xicc/ccmx.c new file mode 100644 index 0000000..b1244af --- /dev/null +++ b/xicc/ccmx.c @@ -0,0 +1,766 @@ + +/* + * Argyll Color Correction System + * Colorimeter Correction Matrix + * + * Author: Graeme W. Gill + * Date: 19/8/2010 + * + * Copyright 2010 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + * + * NOTE though that if SALONEINSTLIB is not defined, that this file depends + * on other libraries that are licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3. + */ + +/* + * TTBD: + */ + +#undef DEBUG + +#define verbo stdout + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <sys/types.h> +#include <time.h> +#ifndef SALONEINSTLIB +#include "numlib.h" +#include "icc.h" +#else +#include "numsup.h" +#include "conv.h" +#endif +#include "cgats.h" +#include "ccmx.h" + +#ifdef NT /* You'd think there might be some standards.... */ +# ifndef __BORLANDC__ +# define stricmp _stricmp +# endif +#else +# define stricmp strcasecmp +#endif + +/* Forward declarations */ + +/* Utilities */ + +/* Method implimentations */ + +/* Write out the ccmx to a CGATS format .ccmx file */ +/* Return nz on error */ +static int create_ccmx_cgats( +ccmx *p, /* This */ +cgats **pocg /* return CGATS structure */ +) { + int i, j, n; + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *ocg; /* CGATS structure */ + char buf[100]; + + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CCMX"); /* our special type is Colorimeter Correction Matrix */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + if (p->desc != NULL) + ocg->add_kword(ocg, 0, "DESCRIPTOR", p->desc,NULL); + ocg->add_kword(ocg, 0, "INSTRUMENT",p->inst, NULL); + if (p->disp != NULL) + ocg->add_kword(ocg, 0, "DISPLAY",p->disp, NULL); + if (p->tech != NULL) + ocg->add_kword(ocg, 0, "TECHNOLOGY",p->tech, NULL); + if (p->disp == NULL && p->tech == NULL) { +#ifdef DEBUG + fprintf(stdout, "write_ccmx: ccmx doesn't contain display or techology strings"); +#endif + sprintf(p->err, "write_ccmx: ccmx doesn't contain display or techology strings"); + ocg->del(ocg); + return 1; + } + if (p->cbid != 0) { + sprintf(buf, "%d", p->cbid); + ocg->add_kword(ocg, 0, "DISPLAY_TYPE_BASE_ID", buf, NULL); + } + if (p->refrmode >= 0) + ocg->add_kword(ocg, 0, "DISPLAY_TYPE_REFRESH", p->refrmode ? "YES" : "NO", NULL); + if (p->sel != NULL) + ocg->add_kword(ocg, 0, "UI_SELECTORS", p->sel, NULL); + if (p->ref != NULL) + ocg->add_kword(ocg, 0, "REFERENCE",p->ref, NULL); + + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll ccmx", NULL); + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + ocg->add_kword(ocg, 0, "COLOR_REP", "XYZ", NULL); + + /* Add fields for the matrix */ + ocg->add_field(ocg, 0, "XYZ_X", r_t); + ocg->add_field(ocg, 0, "XYZ_Y", r_t); + ocg->add_field(ocg, 0, "XYZ_Z", r_t); + + /* Write out the matrix values */ + for (i = 0; i < 3; i++) { + ocg->add_set(ocg, 0, p->matrix[i][0], p->matrix[i][1], p->matrix[i][2]); + } + + if (pocg != NULL) + *pocg = ocg; + + return 0; +} + +/* Write out the ccmx to a CGATS format .ccmx file */ +/* Return nz on error */ +static int write_ccmx( +ccmx *p, /* This */ +char *outname /* Filename to write to */ +) { + int rv; + cgats *ocg; /* CGATS structure */ + + /* Create CGATS elements */ + if ((rv = create_ccmx_cgats(p, &ocg)) != 0) { + return rv; + } + + /* Write it to file */ + if (ocg->write_name(ocg, outname)) { + strcpy(p->err, ocg->err); + ocg->del(ocg); /* Clean up */ + return 1; + } + ocg->del(ocg); /* Clean up */ + + return 0; +} + +/* write to a CGATS .ccmx file to a memory buffer. */ +/* return nz on error, with message in err[] */ +static int buf_write_ccmx( +ccmx *p, +unsigned char **buf, /* Return allocated buffer */ +int *len /* Return length */ +) { + int rv; + cgats *ocg; /* CGATS structure */ + cgatsFile *fp; + + /* Create CGATS elements */ + if ((rv = create_ccmx_cgats(p, &ocg)) != 0) { + return rv; + } + + if ((fp = new_cgatsFileMem(NULL, 0)) == NULL) { + strcpy(p->err, "new_cgatsFileMem failed"); + return 2; + } + + /* Write it to file */ + if (ocg->write(ocg, fp)) { + strcpy(p->err, ocg->err); + ocg->del(ocg); /* Clean up */ + fp->del(fp); + return 1; + } + + /* Get the buffer the ccmx has been written to */ + if (fp->get_buf(fp, buf, (size_t *)len)) { + strcpy(p->err, "cgatsFileMem get_buf failed"); + return 2; + } + + ocg->del(ocg); /* Clean up */ + fp->del(fp); + + return 0; +} + +/* Read in the ccmx CGATS .ccmx file */ +/* Return nz on error */ +static int read_ccmx_cgats( +ccmx *p, /* This */ +cgats *icg /* input cgats structure */ +) { + int i, j, n, ix; + int ti; /* Temporary CGATs index */ + int spi[3]; /* CGATS indexes for each band */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + + if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0) { + sprintf(p->err, "read_ccmx: Input file isn't a CCMX format file"); + icg->del(icg); + return 1; + } + if (icg->ntables != 1) { + sprintf(p->err, "Input file doesn't contain exactly one table"); + icg->del(icg); + return 1; + } + if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0) { + sprintf(p->err, "read_ccmx: Input file doesn't contain keyword COLOR_REP"); + icg->del(icg); + return 1; + } + + if (strcmp(icg->t[0].kdata[ti],"XYZ") != 0) { + sprintf(p->err, "read_ccmx: Input file doesn't have COLOR_REP of XYZ"); + icg->del(icg); + return 1; + } + + if ((ti = icg->find_kword(icg, 0, "DESCRIPTOR")) >= 0) { + if ((p->desc = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + } + + if ((ti = icg->find_kword(icg, 0, "INSTRUMENT")) < 0) { + sprintf(p->err, "read_ccmx: Input file doesn't contain keyword INSTRUMENT"); + icg->del(icg); + return 1; + } + if ((p->inst = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + + if ((ti = icg->find_kword(icg, 0, "DISPLAY")) >= 0) { + if ((p->disp = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + } + if ((ti = icg->find_kword(icg, 0, "TECHNOLOGY")) >= 0) { + if ((p->tech = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + } + if (p->disp == NULL && p->tech == NULL) { + sprintf(p->err, "read_ccmx: Input file doesn't contain keyword DISPLAY or TECHNOLOGY"); + icg->del(icg); + return 1; + } + if ((ti = icg->find_kword(icg, 0, "DISPLAY_TYPE_REFRESH")) >= 0) { + if (stricmp(icg->t[0].kdata[ti], "YES") == 0) + p->refrmode = 1; + else if (stricmp(icg->t[0].kdata[ti], "NO") == 0) + p->refrmode = 0; + } else { + p->refrmode = -1; + } + if ((ti = icg->find_kword(icg, 0, "DISPLAY_TYPE_BASE_ID")) >= 0) { + p->cbid = atoi(icg->t[0].kdata[ti]); + } else { + p->cbid = 0; + } + + if ((ti = icg->find_kword(icg, 0, "UI_SELECTORS")) >= 0) { + if ((p->sel = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + } + + if ((ti = icg->find_kword(icg, 0, "REFERENCE")) >= 0) { + if ((p->ref = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccmx: malloc failed"); + icg->del(icg); + return 2; + } + } + + /* Locate the fields */ + for (i = 0; i < 3; i++) { /* XYZ fields */ + if ((spi[i] = icg->find_field(icg, 0, xyzfname[i])) < 0) { + sprintf(p->err, "read_ccmx: Input file doesn't contain field %s", xyzfname[i]); + icg->del(icg); + return 1; + } + if (icg->t[0].ftype[spi[i]] != r_t) { + sprintf(p->err, "read_ccmx: Input file field %s is wrong type", xyzfname[i]); + icg->del(icg); + return 1; + } + } + + if (icg->t[0].nsets != 3) { + sprintf(p->err, "read_ccmx: Input file doesn't have exactly 3 sets"); + icg->del(icg); + return 1; + } + + /* Read the matrix values */ + for (ix = 0; ix < icg->t[0].nsets; ix++) { + p->matrix[ix][0] = *((double *)icg->t[0].fdata[ix][spi[0]]); + p->matrix[ix][1] = *((double *)icg->t[0].fdata[ix][spi[1]]); + p->matrix[ix][2] = *((double *)icg->t[0].fdata[ix][spi[2]]); + } + + return 0; +} + +/* Read in the ccmx CGATS .ccmx file */ +/* Return nz on error */ +static int read_ccmx( +ccmx *p, /* This */ +char *inname /* Filename to read from */ +) { + int rv; + cgats *icg; /* input cgats structure */ + + /* Open and look at the .ccmx */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + sprintf(p->err, "read_ccmx: new_cgats() failed"); + return 2; + } + icg->add_other(icg, "CCMX"); /* our special type is Model Printer Profile */ + + if (icg->read_name(icg, inname)) { + strcpy(p->err, icg->err); + icg->del(icg); + return 1; + } + + if ((rv = read_ccmx_cgats(p, icg)) != 0) { + icg->del(icg); + return rv; + } + + icg->del(icg); /* Clean up */ + + return 0; +} + +/* Read in the ccmx CGATS .ccmx file from a memory buffer */ +/* Return nz on error */ +static int buf_read_ccmx( +ccmx *p, /* This */ +unsigned char *buf, +int len +) { + int rv; + cgatsFile *fp; + cgats *icg; /* input cgats structure */ + + if ((fp = new_cgatsFileMem(buf, len)) == NULL) { + strcpy(p->err, "new_cgatsFileMem failed"); + return 2; + } + + /* Open and look at the .ccmx file */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + sprintf(p->err, "read_ccmx: new_cgats() failed"); + fp->del(fp); + return 2; + } + icg->add_other(icg, "CCMX"); /* our special type is Model Printer Profile */ + + if (icg->read(icg, fp)) { + strcpy(p->err, icg->err); + icg->del(icg); + fp->del(fp); + return 1; + } + fp->del(fp); + + if ((rv = read_ccmx_cgats(p, icg)) != 0) { + icg->del(icg); /* Clean up */ + return rv; + } + + icg->del(icg); /* Clean up */ + + return 0; +} + +/* Lookup an XYZ or Lab color */ +static void xform( +ccmx *p, /* This */ +double *out, /* Output XYZ */ +double *in /* Input XYZ */ +) { + icmMulBy3x3(out, p->matrix, in); +} + + +/* Set the contents of the ccmx. return nz on error. */ +static int set_ccmx(ccmx *p, +char *desc, /* General description (optional) */ +char *inst, /* Instrument description to copy from */ +char *disp, /* Display make and model (optional if tech) */ +char *tech, /* Display technology description (optional if disp) */ +int refrmode, /* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */ +int cbid, /* Display type calibration base ID, 0 = unknown */ +char *sel, /* UI selector characters - NULL for none */ +char *refd, /* Reference spectrometer description (optional) */ +double mtx[3][3] /* Transform matrix to copy from */ +) { + if ((p->desc = desc) != NULL && (p->desc = strdup(desc)) == NULL) { + sprintf(p->err, "set_ccmx: malloc failed"); + return 2; + } + if ((p->inst = inst) != NULL && (p->inst = strdup(inst)) == NULL) { + sprintf(p->err, "set_ccmx: malloc failed"); + return 2; + } + if ((p->disp = disp) != NULL && (p->disp = strdup(disp)) == NULL) { + sprintf(p->err, "set_ccmx: malloc failed"); + return 2; + } + if ((p->tech = tech) != NULL && (p->tech = strdup(tech)) == NULL) { + sprintf(p->err, "set_ccmx: malloc failed"); + return 2; + } + + p->refrmode = refrmode; + p->cbid = cbid; + + if (sel != NULL) { + if ((p->sel = strdup(sel)) == NULL) { + sprintf(p->err, "set_ccmx: malloc sel failed"); + return 2; + } + } + if ((p->ref = refd) != NULL && (p->ref = strdup(refd)) == NULL) { + sprintf(p->err, "set_ccmx: malloc failed"); + return 2; + } + + icmCpy3x3(p->matrix, mtx); + + return 0; +} + +#ifndef SALONEINSTLIB + +/* ------------------------------------------- */ +/* Modified version that de-weights Luminance errors by 5: */ +/* Return the CIE94 Delta E color difference measure, squared */ +static double wCIE94sq(double Lab0[3], double Lab1[3]) { + double desq, dhsq; + double dlsq, dcsq; + double c12; + + { + double dl, da, db; + dl = Lab0[0] - Lab1[0]; + dlsq = dl * dl; /* dl squared */ + da = Lab0[1] - Lab1[1]; + db = Lab0[2] - Lab1[2]; + + /* Compute normal Lab delta E squared */ + desq = dlsq + da * da + db * db; + } + + { + double c1, c2, dc; + + /* Compute chromanance for the two colors */ + c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]); + c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]); + c12 = sqrt(c1 * c2); /* Symetric chromanance */ + + /* delta chromanance squared */ + dc = c2 - c1; + dcsq = dc * dc; + } + + /* Compute delta hue squared */ + if ((dhsq = desq - dlsq - dcsq) < 0.0) + dhsq = 0.0; + { + double sc, sh; + + /* Weighting factors for delta chromanance & delta hue */ + sc = 1.0 + 0.048 * c12; + sh = 1.0 + 0.014 * c12; + return 0.2 * 0.2 * dlsq + dcsq/(sc * sc) + dhsq/(sh * sh); + } +} + +/* ------------------------------------------- */ + +/* Context for optimisation callback */ +typedef struct { + int npat; + double (*refs)[3]; /* Array of XYZ values from spectrometer */ + double (*cols)[3]; /* Array of XYZ values from colorimeter */ + int wix; /* White index */ + icmXYZNumber wh; /* White reference */ +} cntx; + + +/* Optimisation function */ +/* Compute the sum of delta E's squared for the */ +/* tp will be the 9 matrix values */ +/* It's not clear if the white weighting is advantagous or not. */ +double optf(void *fdata, double *tp) { + cntx *cx = (cntx *)fdata; + int i; + double de; + double m[3][3]; + + m[0][0] = tp[0]; + m[0][1] = tp[1]; + m[0][2] = tp[2]; + m[1][0] = tp[3]; + m[1][1] = tp[4]; + m[1][2] = tp[5]; + m[2][0] = tp[6]; + m[2][1] = tp[7]; + m[2][2] = tp[8]; + + for (de = 0.0, i = 0; i < cx->npat; i++) { + double tlab[3], xyz[3], lab[3]; + icmXYZ2Lab(&cx->wh, tlab, cx->refs[i]); + + icmMulBy3x3(xyz, m, cx->cols[i]); + icmXYZ2Lab(&cx->wh, lab, xyz); + + if (i == cx->wix) + de += cx->npat/4.0 * wCIE94sq(tlab, lab); /* Make white weight = 1/4 all others */ + else + de += wCIE94sq(tlab, lab); +//printf("~1 %d: txyz %f %f %f, tlab %f %f %f\n", i,cx->refs[i][0], cx->refs[i][1], cx->refs[i][2], tlab[0], tlab[1], tlab[2]); +//printf("~1 %d: xyz %f %f %f\n", i,cx->cols[i][0], cx->cols[i][1], cx->cols[i][2]); +//printf("~1 %d: mxyz %f %f %f, lab %f %f %f\n", i,xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]); +//printf("~1 %d: de %f\n", i,wCIE94(tlab, lab)); + } + + de /= cx->npat; +#ifdef DEBUG +// printf("~1 return values = %f\n",de); +#endif + + return de; +} + +/* Create a ccmx from measurements. return nz on error. */ +static int create_ccmx(ccmx *p, +char *desc, /* General description (optional) */ +char *inst, /* Instrument description to copy from */ +char *disp, /* Display make and model (optional if tech) */ +char *tech, /* Display technology description (optional if disp) */ +int refrmode, /* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */ +int cbid, /* Display type calibration base index, 0 = unknown */ +char *sel, /* UI selector characters - NULL for none */ +char *refd, /* Reference spectrometer description (optional) */ +int npat, /* Number of samples in following arrays */ +double (*refs)[3], /* Array of XYZ values from spectrometer */ +double (*cols)[3] /* Array of XYZ values from colorimeter */ +) { + int i, mxix; + double maxy = -1e6; + cntx cx; + double cp[9], sa[9]; + + if ((p->desc = desc) != NULL && (p->desc = strdup(desc)) == NULL) { + sprintf(p->err, "create_ccmx: malloc failed"); + return 2; + } + if ((p->inst = inst) != NULL && (p->inst = strdup(inst)) == NULL) { + sprintf(p->err, "create_ccmx: malloc failed"); + return 2; + } + if ((p->disp = disp) != NULL && (p->disp = strdup(disp)) == NULL) { + sprintf(p->err, "create_ccmx: malloc failed"); + return 2; + } + if ((p->tech = tech) != NULL && (p->tech = strdup(tech)) == NULL) { + sprintf(p->err, "create_ccmx: malloc failed"); + return 2; + } + p->refrmode = refrmode; + p->cbid = cbid; + + if (sel != NULL) { + if ((p->sel = strdup(sel)) == NULL) { + sprintf(p->err, "create_ccmx: malloc sel failed"); + return 2; + } + } + if ((p->ref = refd) != NULL && (p->ref = strdup(refd)) == NULL) { + sprintf(p->err, "create_ccmx: malloc failed"); + return 2; + } + + /* Find the white patch */ + + cx.npat = npat; + cx.refs = refs; + cx.cols = cols; + + for (i = 0; i < npat; i++) { + if (refs[i][1] > maxy) { + maxy = refs[i][1]; + cx.wix = i; + } + } +#ifdef DEBUG + printf("white = %f %f %f\n",refs[cx.wix][0],refs[cx.wix][1],refs[cx.wix][1]); +#endif + cx.wh.X = refs[cx.wix][0]; + cx.wh.Y = refs[cx.wix][1]; + cx.wh.Z = refs[cx.wix][2]; + + /* Starting matrix */ + cp[0] = 1.0; cp[1] = 0.0; cp[2] = 0.0; + cp[3] = 0.0; cp[4] = 1.0; cp[5] = 0.0; + cp[6] = 0.0; cp[7] = 0.0; cp[8] = 1.0; + + for (i = 0; i < 9; i++) + sa[i] = 0.1; + + if (powell(NULL, 9, cp, sa, 1e-6, 2000, optf, &cx, NULL, NULL) < 0.0) { + sprintf(p->err, "create_ccmx: powell() failed"); + return 1; + } + + p->matrix[0][0] = cp[0]; + p->matrix[0][1] = cp[1]; + p->matrix[0][2] = cp[2]; + p->matrix[1][0] = cp[3]; + p->matrix[1][1] = cp[4]; + p->matrix[1][2] = cp[5]; + p->matrix[2][0] = cp[6]; + p->matrix[2][1] = cp[7]; + p->matrix[2][2] = cp[8]; + + /* Compute the average and max errors */ + p->av_err = p->mx_err = 0.0; + for (i = 0; i < npat; i++) { + double tlab[3], xyz[3], lab[3], de; + icmXYZ2Lab(&cx.wh, tlab, cx.refs[i]); + icmMulBy3x3(xyz, p->matrix, cx.cols[i]); + icmXYZ2Lab(&cx.wh, lab, xyz); + de = icmCIE94(tlab, lab); + p->av_err += de; + if (de > p->mx_err) { + p->mx_err = de; + mxix = i; + } +//printf("~1 %d: de %f, tlab %f %f %f, lab %f %f %f\n",i,de,tlab[0], tlab[1], tlab[2], lab[0], lab[1], lab[2]); + } + p->av_err /= (double)npat; + +//printf("~1 max error is index %d\n",mxix); + +#ifdef DEBUG + printf("Average error %f, max %f\n",p->av_err,p->mx_err); + printf("Correction matrix is:\n"); + printf(" %f %f %f\n", cp[0], cp[1], cp[2]); + printf(" %f %f %f\n", cp[3], cp[4], cp[5]); + printf(" %f %f %f\n", cp[6], cp[7], cp[8]); +#endif + + return 0; +} + +#else /* SALONEINSTLIB */ + +/* Create a ccmx from measurements. return nz on error. */ +static int create_ccmx(ccmx *p, +char *desc, /* General description (optional) */ +char *inst, /* Instrument description to copy from */ +char *disp, /* Display make and model (optional if tech) */ +char *tech, /* Display technology description (optional if disp) */ +int refrmode, /* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */ +int cbid, /* Display type calibration base ID, 0 = unknown */ +char *sel, /* UI selector characters - NULL for none */ +char *refd, /* Reference spectrometer description (optional) */ +int npat, /* Number of samples in following arrays */ +double (*refs)[3], /* Array of XYZ values from spectrometer */ +double (*cols)[3] /* Array of XYZ values from colorimeter */ +) { + sprintf(p->err, "create_ccmx: not implemented in ccmx.c"); + return 1; +} + +#endif /* SALONEINSTLIB */ + +/* Delete it */ +static void del_ccmx(ccmx *p) { + if (p != NULL) { + if (p->desc != NULL) + free(p->desc); + if (p->inst != NULL) + free(p->inst); + if (p->disp != NULL) + free(p->disp); + if (p->tech != NULL) + free(p->tech); + if (p->sel != NULL) + free(p->sel); + if (p->ref != NULL) + free(p->ref); + free(p); + } +} + +/* Allocate a new, uninitialised ccmx */ +/* Note thate black and white points aren't allocated */ +ccmx *new_ccmx(void) { + ccmx *p; + + if ((p = (ccmx *)calloc(1, sizeof(ccmx))) == NULL) + return NULL; + + p->refrmode = -1; + p->cbid = 0; + + /* Init method pointers */ + p->del = del_ccmx; + p->set_ccmx = set_ccmx; + p->create_ccmx = create_ccmx; + p->write_ccmx = write_ccmx; + p->buf_write_ccmx = buf_write_ccmx; + p->read_ccmx = read_ccmx; + p->buf_read_ccmx = buf_read_ccmx; + p->xform = xform; + + return p; +} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/ccmx.h b/xicc/ccmx.h new file mode 100644 index 0000000..b0ca01a --- /dev/null +++ b/xicc/ccmx.h @@ -0,0 +1,120 @@ +#ifndef CCMX_H +#define CCMX_H + +/* + * Argyll Color Correction System + * Colorimeter Correction Matrix support. + * + * Author: Graeme W. Gill + * Date: 19/8/2010 + * + * Copyright 2010 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + * + * NOTE though that if SALONEINSTLIB is not defined, that this file depends + * on other libraries that are licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3. + * + */ + +/* + * This object provides storage and application of a 3x3 XYZ + * corretion matrix suitable for corrected a particular + * display colorimeter for a particular display. + */ + +/* ------------------------------------------------------------------------------ */ + +struct _ccmx { + + /* Public: */ + void (*del)(struct _ccmx *p); + + /* Set the contents of the ccmx. return nz on error. */ + int (*set_ccmx)(struct _ccmx *p, char *desc, char *inst, char *disp, char *tech, + int refrmode, int cbid, char *sel, char *refd, double mtx[3][3]); + + /* Create a ccmx from measurements. return nz on error. */ + int (*create_ccmx)(struct _ccmx *p, char *desc, char *inst, char *disp, char *tech, + int refrmode, int cbid, char *sel, char *refd, + int nsamples, double refs[][3], double cols[][3]); + + /* write to a CGATS .ccmx file */ + int (*write_ccmx)(struct _ccmx *p, char *filename); + + /* write a CGATS .ccmx file to a memory buffer. */ + /* return nz on error, with message in err[] */ + int (*buf_write_ccmx)(struct _ccmx *p, unsigned char **buf, int *len); + + /* read from a CGATS .ccmx file */ + int (*read_ccmx)(struct _ccmx *p, char *filename); + + /* read from a CGATS .ccmx file from a memory buffer. */ + int (*buf_read_ccmx)(struct _ccmx *p, unsigned char *buf, int len); + + /* Correct an XYZ value */ + void (*xform) (struct _ccmx *p, + double *out, /* Output XYZ */ + double *in); /* Input XYZ */ + + /* Private: */ + /* (All char * are owned by ccmx) */ + char *desc; /* Desciption (optional) */ + char *inst; /* Name of colorimeter instrument */ + char *disp; /* Name of display (optional if tech) */ + char *tech; /* Technology (CRT, LCD + backlight type etc.) (optional if disp) */ + int cbid; /* Calibration display type base ID, 0 if not known */ + int refrmode; /* Refresh mode, -1 if unknown, 0 of no, 1 if yes */ + char *sel; /* Optional UI selector characters. May be NULL */ + char *ref; /* Name of spectrometer instrument (optional) */ + double matrix[3][3]; /* Transform matrix */ + double av_err; /* Average error of fit */ + double mx_err; /* Maximum error of fit */ + + /* Houskeeping */ + int errc; /* Error code */ + char err[200]; /* Error message */ +}; typedef struct _ccmx ccmx; + +/* Create a new, uninitialised ccmx */ +ccmx *new_ccmx(void); + +#endif /* CCMX_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/ccss.c b/xicc/ccss.c new file mode 100644 index 0000000..cd9b4e4 --- /dev/null +++ b/xicc/ccss.c @@ -0,0 +1,636 @@ + +/* + * Argyll Color Correction System + * Colorimeter Correction Matrix + * + * Author: Graeme W. Gill + * Date: 1r9/8/2010 + * + * Copyright 2010 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + */ + +/* + * TTBD: + */ + +#undef DEBUG + +#define verbo stdout + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <sys/types.h> +#include <time.h> +#ifndef SALONEINSTLIB +#include "numlib.h" +#include "icc.h" +#else +#include "numsup.h" +#include "conv.h" +#endif +#include "cgats.h" +#include "xspect.h" +#include "ccss.h" + +#ifdef NT /* You'd think there might be some standards.... */ +# ifndef __BORLANDC__ +# define stricmp _stricmp +# endif +#else +# define stricmp strcasecmp +#endif + +/* Forward declarations */ +static void free_ccss(ccss *p); + +/* Utilities */ + +/* Method implimentations */ + +/* Write out the ccss to a CGATS format object */ +/* Return nz on error */ +static int create_ccss_cgats( +ccss *p, /* This */ +cgats **pocg /* return CGATS structure */ +) { + int i, j; + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *ocg; /* CGATS structure */ + int nsetel = 0; + cgats_set_elem *setel; /* Array of set value elements */ + char buf[100]; + + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CCSS"); /* Type is Colorimeter Calibration Spectral Set */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + if (p->desc != NULL) + ocg->add_kword(ocg, 0, "DESCRIPTOR", p->desc,NULL); + + if (p->orig != NULL) + ocg->add_kword(ocg, 0, "ORIGINATOR", p->orig, NULL); + else + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll ccss", NULL); + if (p->crdate != NULL) + ocg->add_kword(ocg, 0, "CREATED",p->crdate, NULL); + else + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + if (p->disp) + ocg->add_kword(ocg, 0, "DISPLAY",p->disp, NULL); + if (p->tech) + ocg->add_kword(ocg, 0, "TECHNOLOGY", p->tech,NULL); + if (p->disp == NULL && p->tech == NULL) { + sprintf(p->err, "write_ccss: ccss doesn't contain display or techology strings"); + ocg->del(ocg); + return 1; + } + if (p->refrmode >= 0) + ocg->add_kword(ocg, 0, "DISPLAY_TYPE_REFRESH", p->refrmode ? "YES" : "NO", NULL); + if (p->sel != NULL) + ocg->add_kword(ocg, 0, "UI_SELECTORS", p->sel, NULL); + if (p->ref != NULL) + ocg->add_kword(ocg, 0, "REFERENCE",p->ref, NULL); + + sprintf(buf,"%d", p->samples[0].spec_n); + ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL); + sprintf(buf,"%f", p->samples[0].spec_wl_short); + ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL); + sprintf(buf,"%f", p->samples[0].spec_wl_long); + ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL); + sprintf(buf,"%f", p->samples[0].norm); + ocg->add_kword(ocg, 0, "SPECTRAL_NORM",buf, NULL); + + /* Fields we want */ + ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); + nsetel += 1; /* For id */ + + /* Generate fields for spectral values */ + for (i = 0; i < p->samples[0].spec_n; i++) { + int nm; + + /* Compute nearest integer wavelength */ + nm = (int)(p->samples[0].spec_wl_short + ((double)i/(p->samples[0].spec_n-1.0)) + * (p->samples[0].spec_wl_long - p->samples[0].spec_wl_short) + 0.5); + + sprintf(buf,"SPEC_%03d",nm); + ocg->add_field(ocg, 0, buf, r_t); + } + nsetel += p->samples[0].spec_n; /* Spectral values */ + + if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) { + strcpy(p->err, "Malloc failed!"); + ocg->del(ocg); /* Clean up */ + return 2; + } + + /* Write out the patch info to the output CGATS file */ + for (i = 0; i < p->no_samp; i++) { + int k = 0; + + sprintf(buf,"%d",i+1); + setel[k++].c = buf; + + for (j = 0; j < p->samples[i].spec_n; j++) + setel[k++].d = p->samples[i].spec[j]; + + ocg->add_setarr(ocg, 0, setel); + } + free(setel); + + if (pocg != NULL) + *pocg = ocg; + + return 0; +} + +/* Write out the ccss to a CGATS format .ccss file */ +/* Return nz on error */ +static int write_ccss( +ccss *p, /* This */ +char *outname /* Filename to write to */ +) { + int rv; + cgats *ocg; /* CGATS structure */ + + if (p->no_samp < 3) { + strcpy(p->err, "Need at least three spectral samples"); + return 1; + } + + /* Create CGATS elements */ + if ((rv = create_ccss_cgats(p, &ocg)) != 0) { + return rv; + } + + /* Write it to file */ + if (ocg->write_name(ocg, outname)) { + strcpy(p->err, ocg->err); + ocg->del(ocg); /* Clean up */ + return 1; + } + ocg->del(ocg); /* Clean up */ + + return 0; +} + +/* write to a CGATS .ccss file to a memory buffer. */ +/* return nz on error, with message in err[] */ +static int buf_write_ccss( +ccss *p, +unsigned char **buf, /* Return allocated buffer */ +int *len /* Return length */ +) { + int rv; + cgats *ocg; /* CGATS structure */ + cgatsFile *fp; + + if (p->no_samp < 3) { + strcpy(p->err, "Need at least three spectral samples"); + return 1; + } + + /* Create CGATS elements */ + if ((rv = create_ccss_cgats(p, &ocg)) != 0) { + return rv; + } + + if ((fp = new_cgatsFileMem(NULL, 0)) == NULL) { + strcpy(p->err, "new_cgatsFileMem failed"); + return 2; + } + + /* Write it to file */ + if (ocg->write(ocg, fp)) { + strcpy(p->err, ocg->err); + ocg->del(ocg); /* Clean up */ + fp->del(fp); + return 1; + } + + /* Get the buffer the ccss has been written to */ + if (fp->get_buf(fp, buf, (size_t *)len)) { + strcpy(p->err, "cgatsFileMem get_buf failed"); + return 2; + } + + ocg->del(ocg); /* Clean up */ + fp->del(fp); + + return 0; +} + +/* Read in the ccss CGATS .ccss file from cgats */ +/* Return nz on error */ +static int read_ccss_cgats( +ccss *p, /* This */ +cgats *icg /* input cgats structure */ +) { + int i, j; + int ti, ii; /* Temporary CGATs index */ + int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */ + xspect sp; + + if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0) { + sprintf(p->err, "read_ccss: Input file isn't a CCSS format file"); + icg->del(icg); + return 1; + } + if (icg->ntables != 1) { + sprintf(p->err, "Input file doesn't contain exactly one table"); + icg->del(icg); + return 1; + } + + free_ccss(p); + + if ((ti = icg->find_kword(icg, 0, "DESCRIPTOR")) >= 0) { + if ((p->desc = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + if ((ti = icg->find_kword(icg, 0, "ORIGINATOR")) >= 0) { + if ((p->orig = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + if ((ti = icg->find_kword(icg, 0, "CREATED")) >= 0) { + if ((p->crdate = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + + if ((ti = icg->find_kword(icg, 0, "DISPLAY")) >= 0) { + if ((p->disp = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + if ((ti = icg->find_kword(icg, 0, "TECHNOLOGY")) >= 0) { + if ((p->tech = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + if (p->disp == NULL && p->tech == NULL) { + sprintf(p->err, "read_ccss: Input file doesn't contain keyword DISPLAY or TECHNOLOGY"); + icg->del(icg); + return 1; + } + if ((ti = icg->find_kword(icg, 0, "DISPLAY_TYPE_REFRESH")) >= 0) { + if (stricmp(icg->t[0].kdata[ti], "YES") == 0) + p->refrmode = 1; + else if (stricmp(icg->t[0].kdata[ti], "NO") == 0) + p->refrmode = 0; + } + + if ((ti = icg->find_kword(icg, 0, "UI_SELECTORS")) >= 0) { + if ((p->sel = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + + if ((ti = icg->find_kword(icg, 0, "REFERENCE")) >= 0) { + if ((p->ref = strdup(icg->t[0].kdata[ti])) == NULL) { + sprintf(p->err, "read_ccss: malloc failed"); + icg->del(icg); + return 2; + } + } + + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0) { + sprintf(p->err,"Input file doesn't contain keyword SPECTRAL_BANDS"); + icg->del(icg); + return 1; + } + sp.spec_n = atoi(icg->t[0].kdata[ii]); + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0) { + sprintf(p->err,"Input file doesn't contain keyword SPECTRAL_START_NM"); + icg->del(icg); + return 1; + } + sp.spec_wl_short = atof(icg->t[0].kdata[ii]); + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0) { + sprintf(p->err,"Input file doesn't contain keyword SPECTRAL_END_NM"); + icg->del(icg); + return 1; + } + sp.spec_wl_long = atof(icg->t[0].kdata[ii]); + + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_NORM")) < 0) { + sp.norm = 1.0; /* Older versions don't have SPECTRAL_NORM */ + } else { + sp.norm = atof(icg->t[0].kdata[ii]); + } + + /* Find the fields for spectral values */ + for (j = 0; j < sp.spec_n; j++) { + char buf[100]; + int nm; + + /* Compute nearest integer wavelength */ + nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0)) + * (sp.spec_wl_long - sp.spec_wl_short) + 0.5); + + sprintf(buf,"SPEC_%03d",nm); + + if ((spi[j] = icg->find_field(icg, 0, buf)) < 0) { + sprintf(p->err,"Input file doesn't contain field %s",buf); + icg->del(icg); + return 1; + } + } + + if ((p->no_samp = icg->t[0].nsets) < 3) { + sprintf(p->err, "Input file doesn't contain at least three spectral samples"); + p->no_samp = 0; + icg->del(icg); /* Clean up */ + return 1; + } + + /* Allocate spectral array */ + if ((p->samples = (xspect *)malloc(sizeof(xspect) * p->no_samp)) == NULL) { + strcpy(p->err, "Malloc failed!"); + p->no_samp = 0; + icg->del(icg); /* Clean up */ + return 2; + } + + /* Fill sample array with the data */ + for (i = 0; i < p->no_samp; i++) { + + XSPECT_COPY_INFO(&p->samples[i], &sp); + + /* Read the spectral values for this patch */ + for (j = 0; j < sp.spec_n; j++) { + p->samples[i].spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]); + } + } + + return 0; +} + +/* Read in the ccss CGATS .ccss file */ +/* Return nz on error */ +static int read_ccss( +ccss *p, /* This */ +char *inname /* Filename to read from */ +) { + int rv; + cgats *icg; /* input cgats structure */ + + /* Open and look at the .ccss file */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + sprintf(p->err, "read_ccss: new_cgats() failed"); + return 2; + } + icg->add_other(icg, "CCSS"); /* our special type is Model Printer Profile */ + + if (icg->read_name(icg, inname)) { + strcpy(p->err, icg->err); + icg->del(icg); + return 1; + } + + if ((rv = read_ccss_cgats(p, icg)) != 0) { + icg->del(icg); /* Clean up */ + return rv; + } + + icg->del(icg); /* Clean up */ + + return 0; +} + +/* Read in the ccss CGATS .ccss file from a memory buffer */ +/* Return nz on error */ +static int buf_read_ccss( +ccss *p, /* This */ +unsigned char *buf, +int len +) { + int rv; + cgatsFile *fp; + cgats *icg; /* input cgats structure */ + + if ((fp = new_cgatsFileMem(buf, len)) == NULL) { + strcpy(p->err, "new_cgatsFileMem failed"); + return 2; + } + + /* Open and look at the .ccss file */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + sprintf(p->err, "read_ccss: new_cgats() failed"); + fp->del(fp); + return 2; + } + icg->add_other(icg, "CCSS"); /* our special type is Model Printer Profile */ + + if (icg->read(icg, fp)) { + strcpy(p->err, icg->err); + icg->del(icg); + fp->del(fp); + return 1; + } + fp->del(fp); + + if ((rv = read_ccss_cgats(p, icg)) != 0) { + icg->del(icg); /* Clean up */ + return rv; + } + + icg->del(icg); /* Clean up */ + + return 0; +} + +/* Set the contents of the ccss. return nz on error. */ +/* (Copy all the values) */ +static int set_ccss(ccss *p, +char *orig, /* Originator (May be NULL) */ +char *crdate, /* Creation date in ctime() format (May be NULL) */ +char *desc, /* General description (optional) */ +char *disp, /* Display make and model (optional if tech) */ +char *tech, /* Display technology description (optional if disp) */ +int refrmode, /* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */ +char *sel, /* UI selector characters - NULL for none */ +char *refd, /* Reference spectrometer description (optional) */ +xspect *samples, /* Arry of spectral samples. All assumed to be same dim as first */ +int no_samp /* Number of spectral samples */ +) { + int i; + + free_ccss(p); + if (orig != NULL) { + if ((p->orig = strdup(orig)) == NULL) { + sprintf(p->err, "set_ccss: malloc orig failed"); + return 2; + } + } + if (desc != NULL) { + if ((p->desc = strdup(desc)) == NULL) { + sprintf(p->err, "set_ccss: malloc desc failed"); + return 2; + } + } + if (crdate != NULL) { + if ((p->crdate = strdup(crdate)) == NULL) { + sprintf(p->err, "set_ccss: malloc crdate failed"); + return 2; + } + } + if (disp != NULL) { + if ((p->disp = strdup(disp)) == NULL) { + sprintf(p->err, "set_ccss: malloc disp failed"); + return 2; + } + } + if (tech != NULL) { + if ((p->tech = strdup(tech)) == NULL) { + sprintf(p->err, "set_ccss: malloc tech failed"); + return 2; + } + } + p->refrmode = refrmode; + if (sel != NULL) { + if ((p->sel = strdup(sel)) == NULL) { + sprintf(p->err, "set_ccss: malloc sel failed"); + return 2; + } + } + if (refd != NULL) { + if ((p->ref = strdup(refd)) == NULL) { + sprintf(p->err, "set_ccss: malloc ref failed"); + return 2; + } + } + + if (p->samples != NULL) { + free(p->samples); + p->samples = NULL; + } + + if ((p->no_samp = no_samp) < 3) { + strcpy(p->err, "Must be at least three spectral samples"); + p->no_samp = 0; + return 1; + } + + /* Allocate spectral array */ + if ((p->samples = (xspect *)malloc(sizeof(xspect) * p->no_samp)) == NULL) { + strcpy(p->err, "Malloc failed!"); + p->no_samp = 0; + return 2; + } + + /* And copy all the values */ + for (i = 0; i < p->no_samp; i++) { + p->samples[i] = samples[i]; + } + + return 0; +} + +/* Free the contents */ +static void free_ccss(ccss *p) { + if (p != NULL) { + if (p->desc != NULL) + free(p->desc); + p->desc = NULL; + if (p->orig != NULL) + free(p->orig); + p->orig = NULL; + if (p->crdate != NULL) + free(p->crdate); + p->crdate = NULL; + if (p->disp != NULL) + free(p->disp); + p->disp = NULL; + if (p->tech != NULL) + free(p->tech); + p->tech = NULL; + if (p->sel != NULL) + free(p->sel); + p->sel = NULL; + if (p->ref != NULL) + free(p->ref); + p->ref = NULL; + if (p->samples != NULL) + free(p->samples); + p->samples = NULL; + p->no_samp = 0; + } +} + +/* Delete it */ +static void del_ccss(ccss *p) { + if (p != NULL) { + free_ccss(p); + free(p); + } +} + +/* Allocate a new, uninitialised ccss */ +/* Note thate black and white points aren't allocated */ +ccss *new_ccss(void) { + ccss *p; + + if ((p = (ccss *)calloc(1, sizeof(ccss))) == NULL) + return NULL; + + /* Init method pointers */ + p->del = del_ccss; + p->set_ccss = set_ccss; + p->write_ccss = write_ccss; + p->buf_write_ccss = buf_write_ccss; + p->read_ccss = read_ccss; + p->buf_read_ccss = buf_read_ccss; + + return p; +} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/ccss.h b/xicc/ccss.h new file mode 100644 index 0000000..0d2e128 --- /dev/null +++ b/xicc/ccss.h @@ -0,0 +1,112 @@ +#ifndef CCSS_H +#define CCSS_H + +/* + * Argyll Color Correction System + * Colorimeter Calibration Spectral Set support. + * + * Author: Graeme W. Gill + * Date: 18/8/2011 + * + * Copyright 2010 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + * + * Based on ccmx.h + */ + +/* + * This object provides storage and application of emisive spectral + * samples that can be used to compute calibration for suitable + * colorimeters (such as the i1d3) tuned for particular types of displays. + */ + +/* ------------------------------------------------------------------------------ */ + +struct _ccss { + + /* Public: */ + void (*del)(struct _ccss *p); + + /* Set the contents of the ccss. return nz on error. */ + /* (Makes copies of all parameters) */ + int (*set_ccss)(struct _ccss *p, char *orig, char *cdate, + char *desc, char *disp, char *tech, int refrmode, char *sel, + char *ref, xspect *samples, int no_samp); + + /* write to a CGATS .ccss file */ + /* return nz on error, with message in err[] */ + int (*write_ccss)(struct _ccss *p, char *filename); + + /* write a CGATS .ccss file to a memory buffer. */ + /* return nz on error, with message in err[] */ + int (*buf_write_ccss)(struct _ccss *p, unsigned char **buf, int *len); + + /* read from a CGATS .ccss file */ + /* return nz on error, with message in err[] */ + int (*read_ccss)(struct _ccss *p, char *filename); + + /* read from a CGATS .ccss file from a memory buffer. */ + /* return nz on error, with message in err[] */ + int (*buf_read_ccss)(struct _ccss *p, unsigned char *buf, int len); + + /* Private: */ + /* (All char * are owned by ccss) */ + char *orig; /* Originator. May be NULL */ + char *crdate; /* Creation date (in ctime() format). May be NULL */ + char *desc; /* General Description (optional) */ + char *disp; /* Description of the display (Manfrr and Model No) (optional if tech) */ + char *tech; /* Technology (CRT, LCD + backlight type etc.) (optional if disp) */ + int refrmode; /* Refresh mode, -1 if unknown, 0 of no, 1 if yes */ + char *sel; /* Optional UI selector characters. May be NULL */ + char *ref; /* Name of reference spectrometer instrument (optional) */ + xspect *samples; /* Set of spectral samples */ + int no_samp; /* Number of samples */ + + /* Houskeeping - should switch this to a1log ? */ + int errc; /* Error code */ + char err[200]; /* Error message */ +}; typedef struct _ccss ccss; + +/* Create a new, uninitialised ccss */ +ccss *new_ccss(void); + +#endif /* CCSS_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/ccttest.c b/xicc/ccttest.c new file mode 100644 index 0000000..9662678 --- /dev/null +++ b/xicc/ccttest.c @@ -0,0 +1,306 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2006/5/9 + * Version: 1.00 + * + * Copyright 2006 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the Daylight and Plankian spectra, + * Correlated and Visual Color Temperatures, and CRI. + */ + +#include <stdio.h> +#include <math.h> +#include "xspect.h" +#include "numlib.h" +#include "plot.h" + +#define PLANKIAN +#define XRES 500 + + +#ifdef PLANKIAN +#define BBTYPE icxIT_Ptemp +#else +#define BBTYPE icxIT_Dtemp +#endif + +static int do_spec(char *name, xspect *sp) { + int i; + double xyz[3]; /* Color temperature */ + double Yxy[3]; + double xx[XRES]; + double y1[XRES]; + double cct, vct; + double cct_xyz[3], vct_xyz[3]; + double cct_lab[3], vct_lab[3]; + icmXYZNumber wp; + double de; + + printf("\n"); + + /* Compute XYZ of illuminant */ + if (icx_ill_sp2XYZ(xyz, icxOT_CIE_1931_2, NULL, icxIT_custom, 0, sp) != 0) + error ("icx_sp_temp2XYZ returned error"); + + icmXYZ2Yxy(Yxy, xyz); + + printf("Type = %s\n",name); + printf("XYZ = %f %f %f, x,y = %f %f\n", xyz[0], xyz[1], xyz[2], Yxy[1], Yxy[2]); + + /* Compute CCT */ + if ((cct = icx_XYZ2ill_ct(cct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 0)) < 0) + error ("Got bad cct\n"); + + /* Compute VCT */ + if ((vct = icx_XYZ2ill_ct(vct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 1)) < 0) + error ("Got bad vct\n"); + +#ifdef PLANKIAN + printf("CCT = %f, VCT = %f\n",cct, vct); +#else + printf("CDT = %f, VDT = %f\n",cct, vct); +#endif + + { + int invalid = 0; + double cri; + cri = icx_CIE1995_CRI(&invalid, sp); + printf("CRI = %.1f%s\n",cri,invalid ? " (Invalid)" : ""); + } + + /* Use modern color difference - gives a better visual match */ + icmAry2XYZ(wp, vct_xyz); + icmXYZ2Lab(&wp, cct_lab, cct_xyz); + icmXYZ2Lab(&wp, vct_lab, vct_xyz); + de = icmCIE2K(cct_lab, vct_lab); + printf("CIEDE2000 Delta E = %f\n",de); + + /* Plot spectrum out */ + for (i = 0; i < XRES; i++) { + double ww; + + ww = (sp->spec_wl_long - sp->spec_wl_short) + * ((double)i/(XRES-1.0)) + sp->spec_wl_short; + + xx[i] = ww; + y1[i] = value_xspect(sp, ww); + } + do_plot(xx,y1,NULL,NULL,i); + + return 0; +} + + +void usage(void) { + fprintf(stderr,"Plot spectrum and calculate CCT and VCT\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: ccttest [infile.sp]\n"); + fprintf(stderr," [infile.sp] spectrum to plot\n"); + fprintf(stderr," default is all built in illuminants\n"); + exit(1); +} + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int k; + int verb = 0; + char in_name[100] = { '\000' }; /* Spectrum name */ + double temp; + xspect sp; /* Spectra */ + icxIllumeType ilType; + char buf[200]; + + error_program = argv[0]; + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } else { + usage(); + } + } + else + break; + } + + if (fa < argc && argv[fa][0] != '-') + strcpy(in_name,argv[fa]); + +#ifndef NEVER /* hack test */ + { +#define NTESTS 5 + int i; + double cct, vct; + double de1, de2; + double xyz[NTESTS][3] = { + { 92.250000, 97.750000, 89.650000 }, /* Rogers spotread verification result */ + { 94.197915, 97.686362, 80.560411, }, /* Rogers target */ + { 93.51, 97.12, 80.86 }, /* Rogers test ? */ + { 75.31, 78.20, 64.59 }, /* Mac LCD test */ + { 42.98, 44.62, 36.95 } /* Hitachie CRT test */ + }; + double axyz[3]; + double lab[3], alab[3]; + + for (i = 0; i < NTESTS; i++) { + /* Compute CCT */ + if ((cct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 0)) < 0) + error ("Got bad cct\n"); + + axyz[0] /= axyz[1]; + axyz[2] /= axyz[1]; + axyz[1] /= axyz[1]; + icmXYZ2Lab(&icmD50, alab, axyz); + axyz[0] = xyz[i][0] / xyz[i][1]; + axyz[2] = xyz[i][2] / xyz[i][1]; + axyz[1] = xyz[i][1] / xyz[i][1]; + icmXYZ2Lab(&icmD50, lab, axyz); + de1 = icmCIE2K(lab, alab); + + /* Compute VCT */ + if ((vct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 1)) < 0) + error ("Got bad vct\n"); + axyz[0] /= axyz[1]; + axyz[2] /= axyz[1]; + axyz[1] /= axyz[1]; + icmXYZ2Lab(&icmD50, alab, axyz); + axyz[0] = xyz[i][0] / xyz[i][1]; + axyz[2] = xyz[i][2] / xyz[i][1]; + axyz[1] = xyz[i][1] / xyz[i][1]; + icmXYZ2Lab(&icmD50, lab, axyz); + de2 = icmCIE2K(lab, alab); + + printf("XYZ %f %f %f, CCT = %f de %f, VCT = %f de %f\n",xyz[i][0], xyz[i][1], xyz[i][2], cct, de1, vct, de1); + + /* Compute CCT */ + if ((cct = icx_XYZ2ill_ct(axyz, icxIT_Dtemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 0)) < 0) + error ("Got bad cct\n"); + axyz[0] /= axyz[1]; + axyz[2] /= axyz[1]; + axyz[1] /= axyz[1]; + icmXYZ2Lab(&icmD50, alab, axyz); + axyz[0] = xyz[i][0] / xyz[i][1]; + axyz[2] = xyz[i][2] / xyz[i][1]; + axyz[1] = xyz[i][1] / xyz[i][1]; + icmXYZ2Lab(&icmD50, lab, axyz); + de1 = icmCIE2K(lab, alab); + + /* Compute VCT */ + if ((vct = icx_XYZ2ill_ct(axyz, icxIT_Dtemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 1)) < 0) + error ("Got bad vct\n"); + + axyz[0] /= axyz[1]; + axyz[2] /= axyz[1]; + axyz[1] /= axyz[1]; + icmXYZ2Lab(&icmD50, alab, axyz); + axyz[0] = xyz[i][0] / xyz[i][1]; + axyz[2] = xyz[i][2] / xyz[i][1]; + axyz[1] = xyz[i][1] / xyz[i][1]; + icmXYZ2Lab(&icmD50, lab, axyz); + de2 = icmCIE2K(lab, alab); + + printf("XYZ %f %f %f, CDT = %f de %f, VDT = %f de %f\n",xyz[i][0], xyz[i][1], xyz[i][2], cct, de1, vct, de2); + } + } +#endif + + if (in_name[0] != '\000') { + if (read_xspect(&sp, in_name) != 0) + error ("Unable to read custom spectrum '%s'",in_name); + + sprintf(buf, "File '%s'",in_name); + + do_spec(buf, &sp); + + } else { + + /* For each standard illuminant */ + for (ilType = icxIT_A; ilType <= icxIT_F10; ilType++) { + char *inm = NULL; + + switch (ilType) { + case icxIT_A: + inm = "A"; break; + case icxIT_C: + inm = "C"; break; + case icxIT_D50: + inm = "D50"; break; + case icxIT_D50M2: + inm = "D50M2"; break; + case icxIT_D65: + inm = "D65"; break; + case icxIT_F5: + inm = "F5"; break; + case icxIT_F8: + inm = "F8"; break; + case icxIT_F10: + inm = "F10"; break; + default: + inm = "Unknown"; break; + break; + } + + if (standardIlluminant(&sp, ilType, 0) != 0) + error ("standardIlluminant returned error"); + + do_spec(inm, &sp); + } + + /* For each material and illuminant */ + for (temp = 2500; temp <= 9000; temp += 500) { + + for (k = 0; k < 2; k++) { + + ilType = k == 0 ? icxIT_Dtemp : icxIT_Ptemp; + + if (standardIlluminant(&sp, ilType, temp) != 0) + error ("standardIlluminant returned error"); + + sprintf(buf, "%s at %f", k == 0 ? "Daylight" : "Black body", temp); + + do_spec(buf, &sp); + } + } + + } + return 0; +} + + + + + + + + diff --git a/xicc/cgatsplot.c b/xicc/cgatsplot.c new file mode 100644 index 0000000..9fadf89 --- /dev/null +++ b/xicc/cgatsplot.c @@ -0,0 +1,248 @@ + +/* + * Very simple, dumb plot of .ti3 data. + * + * Author: Graeme W. Gill + * Date: 2005/10/12 + * Version: 1.0 + * + * Copyright 2000 - 2005 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "numlib.h" +#include "icc.h" +#include "cgats.h" +#include "plot.h" +#include "xcolorants.h" +#include "sort.h" + +void usage(void) { + fprintf(stderr,"Simple 2D plot of CGATS .ti3 data\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: cgatssplot [-v] infile\n"); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -0 .. -9 Choose channel to plot against\n"); + exit(1); +} + +/* Patch value type */ +typedef struct { + double d[ICX_MXINKS]; /* Device values */ + double v[3]; /* CIE value */ +} pval; + + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int chan = 0; /* Chosen channel to plot against */ + char in_name[100]; + + char *buf, *outc; + int ti; + cgats *cgf = NULL; /* cgats file data */ + int isLab = 0; /* cgats output is Lab, else XYZ */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" }; + int npat; /* Number of patches */ + inkmask nmask; /* Device inkmask */ + int nchan; /* Number of input chanels */ + char *bident; /* Base ident */ + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int pcsix[3]; /* Device chanel indexes */ + pval *pat; /* patch values */ + int i, j; + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + else if (argv[fa][1] >= '0' && argv[fa][1] <= '9') { + chan = argv[fa][1] - '0'; + } + else if (argv[fa][1] == '?') + usage(); + else + usage(); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(in_name,argv[fa]); + + /* Open CIE target values */ + cgf = new_cgats(); /* Create a CGATS structure */ + cgf->add_other(cgf, "CTI3");/* our special input type is Calibration Target Information 3 */ + if (cgf->read_name(cgf, in_name)) + error("CGATS file read error %s on file '%s'",cgf->err, in_name); + + if (cgf->ntables == 0 || cgf->t[0].tt != tt_other || cgf->t[0].oi != 0) + error ("Profile file '%s' isn't a CTI3 format file",in_name); + + if (cgf->ntables < 1) + error ("Input file '%s' doesn't contain at least one table",in_name); + + if ((ti = cgf->find_kword(cgf, 0, "COLOR_REP")) < 0) + error("Input file doesn't contain keyword COLOR_REPS"); + + if ((buf = strdup(cgf->t[0].kdata[ti])) == NULL) + error("Malloc failed"); + + /* Split COLOR_REP into device and PCS space */ + if ((outc = strchr(buf, '_')) == NULL) + error("COLOR_REP '%s' invalid", cgf->t[0].kdata[ti]); + *outc++ = '\000'; + + if (strcmp(outc, "XYZ") == 0) { + isLab = 0; + } else if (strcmp(outc, "LAB") == 0) { + isLab = 1; + } else + error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", cgf->t[0].kdata[ti]); + + if ((nmask = icx_char2inkmask(buf)) == 0) { + error ("File '%s' keyword COLOR_REPS has unknown device value '%s'",in_name,buf); + } + + free(buf); + + nchan = icx_noofinks(nmask); + bident = icx_inkmask2char(nmask, 0); /* Base ident (No possible 'i') */ + + /* Find device fields */ + for (j = 0; j < nchan; j++) { + int ii, imask; + char fname[100]; + + imask = icx_index2ink(nmask, j); + sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident, + icx_ink2char(imask)); + + if ((ii = cgf->find_field(cgf, 0, fname)) < 0) + error ("Input file doesn't contain field %s",fname); + if (cgf->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + chix[j] = ii; + } + + /* Find PCS fields */ + for (j = 0; j < 3; j++) { + int ii; + + if ((ii = cgf->find_field(cgf, 0, isLab ? labfname[j] : xyzfname[j])) < 0) + error ("Input file doesn't contain field %s",isLab ? labfname[j] : xyzfname[j]); + if (cgf->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",isLab ? labfname[j] : xyzfname[j]); + pcsix[j] = ii; + } + + npat = cgf->t[0].nsets; /* Number of patches */ + + if (npat <= 0) + error("No sets of data in file '%s'",in_name); + + /* Allocate arrays to hold test patch input and output values */ + if ((pat = (pval *)malloc(sizeof(pval) * npat)) == NULL) + error("Malloc failed - pat[]"); + + /* Grab all the values */ + for (i = 0; i < npat; i++) { + pat[i].v[0] = *((double *)cgf->t[0].fdata[i][pcsix[0]]); + pat[i].v[1] = *((double *)cgf->t[0].fdata[i][pcsix[1]]); + pat[i].v[2] = *((double *)cgf->t[0].fdata[i][pcsix[2]]); + if (!isLab) { + pat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */ + pat[i].v[1] /= 100.0; + pat[i].v[2] /= 100.0; + } + if (!isLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */ + icmXYZ2Lab(&icmD50, pat[i].v, pat[i].v); + } + for (j = 0; j < nchan; j++) { + pat[i].d[j] = *((double *)cgf->t[0].fdata[i][chix[j]]); + } + } + + /* Sort by the selected channel */ +#define HEAP_COMPARE(A,B) (A.d[chan] < B.d[chan]) + HEAPSORT(pval, pat, npat); +#undef HEAP_COMPARE + + /* Create the plot */ + { + int i; + double *xx; + double *y0; + double *y1; + double *y2; + + if ((xx = (double *)malloc(sizeof(double) * npat)) == NULL) + error("Malloc failed - xx[]"); + if ((y0 = (double *)malloc(sizeof(double) * npat)) == NULL) + error("Malloc failed - y0[]"); + if ((y1 = (double *)malloc(sizeof(double) * npat)) == NULL) + error("Malloc failed - y1[]"); + if ((y2 = (double *)malloc(sizeof(double) * npat)) == NULL) + error("Malloc failed - y2[]"); + + for (i = 0; i < npat; i++) { + xx[i] = pat[i].d[chan]; + y0[i] = pat[i].v[0]; + y1[i] = 50 + pat[i].v[1]/2.0; + y2[i] = 50 + pat[i].v[2]/2.0; + +// printf("~1 %d: xx = %f, y = %f %f %f\n",i,xx[i],y0[i],y1[i],y2[i]); + } + do_plot6(xx,y0,y1,NULL,NULL,y2,NULL,npat); + + free(y2); + free(y1); + free(y0); + free(xx); + } + + free(pat); + cgf->del(cgf); + + return 0; +} diff --git a/xicc/cv.c b/xicc/cv.c new file mode 100644 index 0000000..5271bc4 --- /dev/null +++ b/xicc/cv.c @@ -0,0 +1,136 @@ + +/**********************************************************/ +/* Investigate Graphics GEMS transfer curve function parameters */ +/**********************************************************/ + +/* Author: Graeme Gill + * Date: 2003/12/1 + * + * Copyright 2003 Graeme W. Gill + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <numlib.h> +#include "plot.h" + +void usage(void); + +#define XRES 100 /* Plot resolution */ + +/* Per transfer function */ +static double tfunc( +double *v, /* Pointer to first parameter */ +int luord, /* Curve order n..MPP_MXTCORD */ +double vv /* Source of value */ +) { + double g; + int ord; + + if (vv < 0.0) + vv = 0.0; + else if (vv > 1.0) + vv = 1.0; + + /* Process all the shaper orders from high to low. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* can't be non-monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. ] */ + for (ord = luord-1; ord >= 0; ord--) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + +// nsec = 1 << ord; /* Double sections for each order */ + nsec = ord + 1; /* Sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} + + +#define MAX_PARM 10 + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int np = 0; /* Current number of input parameters */ + double params[MAX_PARM] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int i; + double x; + double xx[XRES]; + double y1[XRES]; + + error_program = "cv"; + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (np >= MAX_PARM) + break; + + params[np++] = atof(argv[fa]); + } + + if (np == 0) + np = 1; + + printf("There are %d parameters:\n",np); fflush(stdout); + for (i = 0; i < np; i++) { + printf("Paramter %d = %f\n",i, params[i]); fflush(stdout); + } + + /* Display the result */ + for (i = 0; i < XRES; i++) { + x = i/(double)(XRES-1); + + xx[i] = x; + y1[i] = tfunc(params, np, x); + + if (y1[i] < -0.2) + y1[i] = -0.2; + else if (y1[i] > 1.2) + y1[i] = 1.2; + } + do_plot(xx,y1,NULL,NULL,XRES); + + return 0; +} + +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +void +usage(void) { + puts("usage: cv param0 [param1] [param2] ... "); + exit(1); +} + + + + + diff --git a/xicc/cvtest.c b/xicc/cvtest.c new file mode 100644 index 0000000..6d24572 --- /dev/null +++ b/xicc/cvtest.c @@ -0,0 +1,411 @@ + +/**********************************************************/ +/* Investigate GEMS transfer curve function approximation */ +/**********************************************************/ + +/* Standard test + Random testing */ + +/* Author: Graeme Gill + * Date: 2003/12/1 + * + * Copyright 2003 Graeme W. Gill + * Parts derived from rspl/c1.c + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +#undef DIAG +#undef TEST_SYM /* Test forcing center to zero (a*, b* constraint) */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "numlib.h" +#include "plot.h" + +#define MAX_PARM 40 /* Make > SHAPE_ORDS */ + +#define POWTOL 1e-5 +#define MAXITS 10000 + +/* SHAPE_BASE doesn't seem extremely critical. It is centered in +/- 1 magnitude */ +/* 10 x more filters out noise reasonably heaviliy, 10 x less gives noticable */ +/* overshoot Range 0.00001 .. 0.001 */ +/* SHAPE_HBASE is more critical. */ +/* Range 0.00005 .. 0.001 */ +//#define SHAPE_BASE 0.00001 /* 0 & 1 harmonic weight */ +//#define SHAPE_HBASE 0.0002 /* 2nd and higher additional weight */ +//#define SHAPE_ORDS 20 +#define SHAPE_BASE 0.00001 /* 0 & 1 harmonic weight */ +#define SHAPE_HBASE 0.0001 /* 2nd and higher additional weight */ +#define SHAPE_ORDS 20 + +/* Interface coordinate value */ +typedef struct { + double p; /* coordinate position */ + double v; /* function values */ +} co; + +double lin(double x, double xa[], double ya[], int n); +static double tfunc(double *v, int luord, double vv); +void fit(double *params, int np, co *test_points, int pnts); +void usage(void); + +#define TRIALS 40 /* Number of random trials */ +#define SKIP 0 /* Number of random trials to skip */ + +#define ABS_MAX_PNTS 100 + +#define MIN_PNTS 2 +#define MAX_PNTS 20 + +#define MIN_RES 20 +#define MAX_RES 500 + +double xa[ABS_MAX_PNTS]; +double ya[ABS_MAX_PNTS]; + +#define XRES 100 + +#define TSETS 4 +#define PNTS 11 +#define GRES 100 +int t1p[TSETS] = { + 4, + 11, + 11, + 11 +}; + +double t1xa[TSETS][PNTS] = { + { 0.0, 0.2, 0.8, 1.0 }, + { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }, + { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }, + { 0.0, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75, 1.0 } +}; + +double t1ya[TSETS][PNTS] = { + { 0.0, 0.5, 0.6, 1.0 }, + { 0.0, 0.10, 0.22, 0.35, 0.52, 0.65, 0.78, 0.91, 1.0, 0.9, 0.85 }, + { 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.8, 1.0, 1.0, 1.0 }, + { 0.0, 0.35, 0.4, 0.41, 0.42, 0.46, 0.5, 0.575, 0.48, 0.75, 1.0 } +}; + + +co test_points[ABS_MAX_PNTS]; + +double lin(double x, double xa[], double ya[], int n); + +int main(void) { + int i, n; + double x; + double xx[XRES]; + double y1[XRES]; + double y2[XRES]; + int np = SHAPE_ORDS; /* Number of harmonics */ + double params[MAX_PARM]; /* Harmonic parameters */ + + error_program = "Curve1"; + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + for (n = 0; n < TRIALS; n++) { + double lrand; /* Amount of level randomness */ + int pnts; + + if (n < TSETS) /* Standard versions */ { + pnts = t1p[n]; + for (i = 0; i < pnts; i++) { + xa[i] = t1xa[n][i]; + ya[i] = t1ya[n][i]; + } + } else if (n == TSETS) { /* Exponential function aproximation */ + double ex = 2.2; + pnts = MAX_PNTS; + + printf("Trial %d, no points = %d, exponential %f\n",n,pnts,ex); + + /* Create X values */ + for (i = 0; i < pnts; i++) + xa[i] = i/(pnts-1.0); + + for (i = 0; i < pnts; i++) + ya[i] = pow(xa[i], ex); + + } else if (n == (TSETS+1)) { /* Exponential function aproximation */ + double ex = 1.0/2.2; + pnts = MAX_PNTS; + + printf("Trial %d, no points = %d, exponential %f\n",n,pnts,ex); + + /* Create X values */ + for (i = 0; i < pnts; i++) + xa[i] = i/(pnts-1.0); + + for (i = 0; i < pnts; i++) + ya[i] = pow(xa[i], ex); + + } else { /* Random versions */ + lrand = d_rand(0.0,0.2); /* Amount of level randomness */ + lrand *= lrand; + pnts = i_rand(MIN_PNTS,MAX_PNTS); + + printf("Trial %d, no points = %d, level randomness = %f\n",n,pnts,lrand); + + /* Create X values */ + xa[0] = 0.0; + for (i = 1; i < pnts; i++) + xa[i] = xa[i-1] + d_rand(0.5,1.0); + for (i = 0; i < pnts; i++) /* Divide out */ + xa[i] = (xa[i]/xa[pnts-1]); + + /* Create y values */ + ya[0] = xa[0]; + for (i = 1; i < pnts; i++) + ya[i] = ya[i-1] + d_rand(0.1,1.0) + d_rand(-0.1,0.4) + d_rand(-0.4,0.5); + for (i = 0; i < pnts; i++) { /* Divide out */ + ya[i] = (ya[i]/ya[pnts-1]); + if (ya[i] < 0.0) + ya[i] = 0.0; + else if (ya[i] > 1.0) + ya[i] = 1.0; + } + } + + if (n < SKIP) + continue; + + for (i = 0; i < pnts; i++) { + test_points[i].p = xa[i]; + test_points[i].v = ya[i]; + } + + for (np = 2; np <= SHAPE_ORDS; np++) { + + /* Fit to scattered data */ + fit(params, /* Parameters to return */ + np, /* Number of parameters */ + test_points, /* Test points */ + pnts /* Number of test points */ + ); + + printf("Number params = %d\n",np); + for (i = 0; i < np; i++) { + printf("Param %d = %f\n",i,params[i]); + } + + /* Display the result */ + for (i = 0; i < XRES; i++) { + x = i/(double)(XRES-1); + xx[i] = x; + y1[i] = lin(x,xa,ya,pnts); + y2[i] = tfunc(params, np, x); + if (y2[i] < -0.2) + y2[i] = -0.2; + else if (y2[i] > 1.2) + y2[i] = 1.2; + } + + do_plot(xx,y1,y2,NULL,XRES); + } + + } /* next trial */ + return 0; +} + + +double lin( +double x, +double xa[], +double ya[], +int n) { + int i; + double y; + + if (x < xa[0]) + return ya[0]; + else if (x > xa[n-1]) + return ya[n-1]; + + for (i = 0; i < (n-1); i++) + if (x >=xa[i] && x <= xa[i+1]) + break; + + x = (x - xa[i])/(xa[i+1] - xa[i]); + + y = ya[i] + (ya[i+1] - ya[i]) * x; + + return y; + } + + +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + +void +usage(void) { + puts("usage: curve"); + exit(1); + } + +/*******************************************************************/ +/* Grapic gems based, monotonic 1D function transfer curve. */ + +/* Per transfer function */ +static double tfunc( +double *v, /* Pointer to first parameter */ +int luord, /* Curve order n..MPP_MXTCORD */ +double vv /* Source of value */ +) { + double g; + int ord; + + if (vv < 0.0) + vv = 0.0; + else if (vv > 1.0) + vv = 1.0; + + /* Process all the shaper orders from high to low. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* can't be non-monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. ] */ + for (ord = 0; ord < luord; ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} + +/* return the sum of the squares of the current shaper parameters */ +static double shapmag( +double *v, /* Pointer to first parameter */ +int luord +) { + double tt, w, tparam = 0.0; + int f; + + for (f = 0; f < luord; f++) { + tt = v[f]; + tt *= tt; + + /* Weigh to suppress ripples */ + if (f <= 1) + w = SHAPE_BASE; + else { + w = SHAPE_BASE + (f-1) * SHAPE_HBASE; + } + + tparam += w * tt; + } + return tparam; +} + +/* Context for optimising function fit */ +typedef struct { + int luord; /* shaper order */ + co *points; /* List of test points */ + int nodp; /* Number of data points */ +} luopt; + +/* Shaper+Matrix optimisation function handed to powell() */ +static double luoptfunc(void *edata, double *v) { + luopt *p = (luopt *)edata; + double rv = 0.0, smv; + double out; + int i; + + /* For all our data points */ + for (i = 0; i < p->nodp; i++) { + double ev; + + /* Apply our function */ + out = tfunc(v, p->luord, p->points[i].p); + + ev = out - p->points[i].v; + +#ifdef NEVER + ev = fabs(ev); + rv += ev * ev * ev; +#else + rv += ev * ev; +#endif + + } + + /* Normalise error to be an average delta E squared */ + rv /= (double)p->nodp; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + smv = shapmag(v, p->luord); + rv += smv; + +#ifdef TEST_SYM + { + double tt; + tt = tfunc(v, p->luord, 0.5) - 0.5; + tt *= tt; + rv += 200.0 * tt; + } +#endif + + printf("~1 rv = %f (%f)\n",rv,smv); + return rv; +} + +/* Fitting function */ +void fit( +double *params, /* Parameters to return */ +int np, /* Number of parameters */ +co *test_points, /* Test points */ +int pnts /* Number of test points */ +) { + int i; + double sa[MAX_PARM]; /* Search area */ + luopt os; + + os.luord = np; + os.points= test_points; + os.nodp = pnts; + + for (i = 0; i < np; i++) { + sa[i] = 0.5; + params[i] = 0.0; + } + + if (powell(NULL, np, params, sa, POWTOL, MAXITS, luoptfunc, (void *)&os, NULL, NULL) != 0) + error ("Powell failed"); +} + diff --git a/xicc/example.sp b/xicc/example.sp new file mode 100644 index 0000000..57cfcfc --- /dev/null +++ b/xicc/example.sp @@ -0,0 +1,130 @@ +SPECT + +DESCRIPTOR "Argyll Example Spectral power/reflectance information (D50)" +ORIGINATOR "Argyll CMS" +CREATED "Fri Jul 06 17:49:57 2001" +KEYWORD "SPECTRAL_BANDS" +SPECTRAL_BANDS "107" +KEYWORD "SPECTRAL_START_NM" +SPECTRAL_START_NM "300.000000" +KEYWORD "SPECTRAL_END_NM" +SPECTRAL_END_NM "830.000000" +KEYWORD "SPECTRAL_NORM" +SPECTRAL_NORM "100.000000" + +KEYWORD "SPEC_300" +KEYWORD "SPEC_305" +KEYWORD "SPEC_310" +KEYWORD "SPEC_315" +KEYWORD "SPEC_320" +KEYWORD "SPEC_325" +KEYWORD "SPEC_330" +KEYWORD "SPEC_335" +KEYWORD "SPEC_340" +KEYWORD "SPEC_345" +KEYWORD "SPEC_350" +KEYWORD "SPEC_355" +KEYWORD "SPEC_360" +KEYWORD "SPEC_365" +KEYWORD "SPEC_370" +KEYWORD "SPEC_375" +KEYWORD "SPEC_380" +KEYWORD "SPEC_385" +KEYWORD "SPEC_390" +KEYWORD "SPEC_395" +KEYWORD "SPEC_400" +KEYWORD "SPEC_405" +KEYWORD "SPEC_410" +KEYWORD "SPEC_415" +KEYWORD "SPEC_420" +KEYWORD "SPEC_425" +KEYWORD "SPEC_430" +KEYWORD "SPEC_435" +KEYWORD "SPEC_440" +KEYWORD "SPEC_445" +KEYWORD "SPEC_450" +KEYWORD "SPEC_455" +KEYWORD "SPEC_460" +KEYWORD "SPEC_465" +KEYWORD "SPEC_470" +KEYWORD "SPEC_475" +KEYWORD "SPEC_480" +KEYWORD "SPEC_485" +KEYWORD "SPEC_490" +KEYWORD "SPEC_495" +KEYWORD "SPEC_500" +KEYWORD "SPEC_505" +KEYWORD "SPEC_510" +KEYWORD "SPEC_515" +KEYWORD "SPEC_520" +KEYWORD "SPEC_525" +KEYWORD "SPEC_530" +KEYWORD "SPEC_535" +KEYWORD "SPEC_540" +KEYWORD "SPEC_545" +KEYWORD "SPEC_550" +KEYWORD "SPEC_555" +KEYWORD "SPEC_560" +KEYWORD "SPEC_565" +KEYWORD "SPEC_570" +KEYWORD "SPEC_575" +KEYWORD "SPEC_580" +KEYWORD "SPEC_585" +KEYWORD "SPEC_590" +KEYWORD "SPEC_595" +KEYWORD "SPEC_600" +KEYWORD "SPEC_605" +KEYWORD "SPEC_610" +KEYWORD "SPEC_615" +KEYWORD "SPEC_620" +KEYWORD "SPEC_625" +KEYWORD "SPEC_630" +KEYWORD "SPEC_635" +KEYWORD "SPEC_640" +KEYWORD "SPEC_645" +KEYWORD "SPEC_650" +KEYWORD "SPEC_655" +KEYWORD "SPEC_660" +KEYWORD "SPEC_665" +KEYWORD "SPEC_670" +KEYWORD "SPEC_675" +KEYWORD "SPEC_680" +KEYWORD "SPEC_685" +KEYWORD "SPEC_690" +KEYWORD "SPEC_695" +KEYWORD "SPEC_700" +KEYWORD "SPEC_705" +KEYWORD "SPEC_710" +KEYWORD "SPEC_715" +KEYWORD "SPEC_720" +KEYWORD "SPEC_725" +KEYWORD "SPEC_730" +KEYWORD "SPEC_735" +KEYWORD "SPEC_740" +KEYWORD "SPEC_745" +KEYWORD "SPEC_750" +KEYWORD "SPEC_755" +KEYWORD "SPEC_760" +KEYWORD "SPEC_765" +KEYWORD "SPEC_770" +KEYWORD "SPEC_775" +KEYWORD "SPEC_780" +KEYWORD "SPEC_785" +KEYWORD "SPEC_790" +KEYWORD "SPEC_795" +KEYWORD "SPEC_800" +KEYWORD "SPEC_805" +KEYWORD "SPEC_810" +KEYWORD "SPEC_815" +KEYWORD "SPEC_820" +KEYWORD "SPEC_825" +KEYWORD "SPEC_830" +NUMBER_OF_FIELDS 107 +BEGIN_DATA_FORMAT +SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830 +END_DATA_FORMAT + +NUMBER_OF_SETS 1 +BEGIN_DATA +0.020000 1.0300 2.0500 4.9100 7.7800 11.260 14.750 16.350 17.950 19.480 21.010 22.480 23.940 25.450 26.960 25.720 24.490 27.180 29.870 39.590 49.310 52.910 56.510 58.270 60.030 58.930 57.820 66.320 74.820 81.040 87.250 88.930 90.610 90.990 91.370 93.240 95.110 93.540 91.960 93.840 95.720 96.170 96.610 96.870 97.130 99.610 102.10 101.43 100.75 101.54 102.32 101.16 100.00 98.870 97.740 98.330 98.920 96.210 93.500 95.590 97.690 98.480 99.270 99.160 99.040 97.380 95.720 97.290 98.860 97.260 95.670 96.930 98.190 100.60 103.00 101.07 99.130 93.260 87.380 89.490 91.600 92.250 92.890 84.870 76.850 81.680 86.510 89.550 92.580 85.400 78.230 67.960 57.690 70.310 82.920 80.600 78.270 78.910 79.550 76.480 73.400 68.660 63.920 67.350 70.780 72.610 74.440 +END_DATA diff --git a/xicc/extracticc.c b/xicc/extracticc.c new file mode 100644 index 0000000..5fc24af --- /dev/null +++ b/xicc/extracticc.c @@ -0,0 +1,219 @@ + +/* + * Extract an ICC profile from a TIFF file. + * + * Author: Graeme W. Gill + * Date: 2008/5/18 + * Version: 1.00 + * + * Copyright 2008 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* + * TTBD: + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <setjmp.h> +#include <string.h> +#include <math.h> +#include "numlib.h" +#include "tiffio.h" +#include "jpeglib.h" +#include "iccjpeg.h" +#include "copyright.h" +#include "aconfig.h" +#include "icc.h" + + +void usage(char *diag, ...) { + int i; + fprintf(stderr,"Extract an ICC profile from a TIFF or JPEG file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + if (diag != NULL) { + va_list args; + fprintf(stderr,"Diagnostic: "); + va_start(args, diag); + vfprintf(stderr, diag, args); + va_end(args); + fprintf(stderr,"\n"); + } + fprintf(stderr,"usage: extracticc [-v] infile.tif/jpg outfile%s\n",ICC_FILE_EXT); + fprintf(stderr," -v Verbose\n"); + exit(1); +} + +/* ------------------------------------------------------ */ + +/* JPEG error information */ +typedef struct { + jmp_buf env; /* setjmp/longjmp environment */ + char message[JMSG_LENGTH_MAX]; +} jpegerrorinfo; + +/* JPEG error handler */ +static void jpeg_error(j_common_ptr cinfo) { + jpegerrorinfo *p = (jpegerrorinfo *)cinfo->client_data; + (*cinfo->err->format_message) (cinfo, p->message); + longjmp(p->env, 1); +} + +/* ------------------------------------------------------ */ +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char in_name[MAXNAMEL+1]; /* TIFF input name */ + char out_name[MAXNAMEL+1]; /* ICC output name */ + int verb = 0; + TIFFErrorHandler olderrh, oldwarnh; + TIFFErrorHandlerExt olderrhx, oldwarnhx; + TIFF *rh; + int size = 0; + void *buf = NULL; + icmFile *fp; + int rv = 0; + + error_program = argv[0]; + + if (argc < 3) + usage("Too few parameters"); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(NULL); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + else + usage("Unknown flag '%c'",argv[fa][1]); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Missing input TIFF file"); + strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing output ICC profile"); + strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + + /* - - - - - - - - - - - - - - - */ + /* Open up input tiff file ready for reading */ + /* Got arguments, so setup to process the file */ + olderrh = TIFFSetErrorHandler(NULL); + oldwarnh = TIFFSetWarningHandler(NULL); + olderrhx = TIFFSetErrorHandlerExt(NULL); + oldwarnhx = TIFFSetWarningHandlerExt(NULL); + if ((rh = TIFFOpen(in_name, "r")) != NULL) { + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + + if (TIFFGetField(rh, TIFFTAG_ICCPROFILE, &size, &buf) == 0 || size == 0) { + error("unable to find ICC profile tag in TIFF file '%s'",in_name); + } + + if ((fp = new_icmFileStd_name(out_name, "w")) == NULL) { + error("unable to open output ICC profile '%s'",out_name); + } + + if (fp->write(fp, buf, 1, size) != size) { + error("error writing file '%s'",out_name); + } + + if (fp->del(fp) != 0) { + error("error closing file '%s'",out_name); + } + + TIFFClose(rh); /* Close Input file and free field buffer */ + + } else { + jpegerrorinfo jpeg_rerr; + FILE *rf = NULL; + struct jpeg_decompress_struct rj; + struct jpeg_error_mgr jerr; + + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + + /* We cope with the horrible ijg jpeg library error handling */ + /* by using a setjmp/longjmp. */ + jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error; + if (setjmp(jpeg_rerr.env)) { + jpeg_destroy_decompress(&rj); + fclose(rf); + error("error opening read file '%s'",in_name); + } + + rj.err = &jerr; + rj.client_data = &jpeg_rerr; + jpeg_create_decompress(&rj); + +#if defined(O_BINARY) || defined(_O_BINARY) + if ((rf = fopen(in_name,"rb")) == NULL) +#else + if ((rf = fopen(in_name,"r")) == NULL) +#endif + { + jpeg_destroy_decompress(&rj); + error("error opening read file '%s'",in_name); + } + + jpeg_stdio_src(&rj, rf); + setup_read_icc_profile(&rj); + + /* we'll longjmp on error */ + jpeg_read_header(&rj, TRUE); + + if (!read_icc_profile(&rj, (unsigned char **)&buf, (unsigned int *)&size)) { + jpeg_destroy_decompress(&rj); + fclose(rf); + error("unable to find ICC profile marker in JPEG file '%s'",in_name); + } + jpeg_destroy_decompress(&rj); + fclose(rf); + + if ((fp = new_icmFileStd_name(out_name, "w")) == NULL) { + error("unable to open output ICC profile '%s'",out_name); + } + + if (fp->write(fp, buf, 1, size) != size) { + error("error writing file '%s'",out_name); + } + + if (fp->del(fp) != 0) { + error("error closing file '%s'",out_name); + } + free(buf); + } + return 0; +} diff --git a/xicc/extractttag.c b/xicc/extractttag.c new file mode 100644 index 0000000..79b4085 --- /dev/null +++ b/xicc/extractttag.c @@ -0,0 +1,203 @@ + +/* + * Extract a CGATS file from an ICC profile tag. + * + * Author: Graeme W. Gill + * Date: 2008/5/18 + * Version: 1.00 + * + * Copyright 2008 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* + * TTBD: + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xicc.h" + +#define MXTGNMS 30 + +void usage(char *diag, ...) { + int i; + fprintf(stderr,"Extract a text tag from an ICC profile, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + if (diag != NULL) { + va_list args; + fprintf(stderr,"Diagnostic: "); + va_start(args, diag); + vfprintf(stderr, diag, args); + va_end(args); + fprintf(stderr,"\n"); + } + fprintf(stderr,"usage: extractttag [-v] infile%s outfile\n",ICC_FILE_EXT); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -t tag Extract this tag rather than default 'targ'\n"); + fprintf(stderr," -c Extract calibration file from 'targ' tag\n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char in_name[MAXNAMEL+1]; /* TIFF input name */ + char out_name[MAXNAMEL+1]; /* ICC output name */ + char tag_name[MXTGNMS] = { 't','a','r','g' }; + int docal = 0; + icc *icco; + icTagSignature sig; + icmText *ro; + icmFile *ifp, *ofp; + int verb = 0; + int size = 0; + void *buf = NULL; + int rv = 0; + + error_program = argv[0]; + + if (argc < 3) + usage("Too few parameters"); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(NULL); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + /* Tag name */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + fa = nfa; + if (na == NULL) usage("Expect tag name after -t"); + strncpy(tag_name,na,4); + tag_name[4] = '\000'; + } + + /* Calibration */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + docal = 1; + } + + else + usage("Unknown flag '%c'",argv[fa][1]); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Missing input ICC profile"); + strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing output filename"); + strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + + /* - - - - - - - - - - - - - - - */ + + /* Open up the file for reading */ + if ((ifp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Can't open file '%s'",in_name); + + if ((icco = new_icc()) == NULL) + error ("Creation of ICC object failed"); + + if ((rv = icco->read(icco,ifp,0)) != 0) + error ("%d, %s",rv,icco->err); + + sig = str2tag(tag_name); + + if ((ro = (icmText *)icco->read_tag(icco, sig)) == NULL) + error("%d, %s",icco->errc, icco->err); + + if (ro->ttype != icSigTextType) { + error("Tag isn't TextType"); + } + + if (docal) { + cgatsFile *cgf; + cgats *icg; + int tab, oi; + xcal *cal; + + if ((icg = new_cgats()) == NULL) { + error("new_cgats() failed"); + } + if ((cgf = new_cgatsFileMem(ro->data, ro->size)) == NULL) { + error("new_cgatsFileMem() failed"); + } + icg->add_other(icg, "CTI3"); + oi = icg->add_other(icg, "CAL"); + + if (icg->read(icg, cgf) != 0) { + error("failed to parse tag contents as a CGATS file"); + } + + for (tab = 0; tab < icg->ntables; tab++) { + if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) { + break; + } + } + if (tab >= icg->ntables) { + error("Failed to locate CAL table in CGATS"); + } + + if ((cal = new_xcal()) == NULL) { + error("new_xcal() failed"); + } + if (cal->read_cgats(cal, icg, tab, in_name) != 0) { + error("Parsing CAL table failed"); + } + icg->del(icg); + cgf->del(cgf); + + if (cal->write(cal, out_name) != 0) { + error("writing to file '%s' failed\n",out_name); + } + } else { + if ((ofp = new_icmFileStd_name(out_name, "w")) == NULL) { + error("unable to open output file '%s'",out_name); + } + + if (ofp->write(ofp, ro->data, 1, ro->size-1) != (ro->size-1)) { + error("writing to file '%s' failed",out_name); + } + + if (ofp->del(ofp) != 0) { + error("closing file '%s' failed",out_name); + } + } + + icco->del(icco); + ifp->del(ifp); + + return 0; +} diff --git a/xicc/fakeCMY.c b/xicc/fakeCMY.c new file mode 100644 index 0000000..4387cd8 --- /dev/null +++ b/xicc/fakeCMY.c @@ -0,0 +1,493 @@ + +/* + * Create a synthetic .ti3 file for a CMY "device", + * that exactly maps to the surface points and neutral + * axis of the CMYK profile it is derived from. + * + * Author: Graeme W. Gill + * Date: 2004/6/27 + * Version: 1.00 + * + * Copyright 2004 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + * Add a flag to create RGB instead, by simply inverting the CMY values + * when writing to the .ti3 file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xicc.h" +#include "cgats.h" +#include "targen.h" + +#define EXTRA_NEUTRAL 50 /* Crude way of increasing weighting */ + +/* ---------------------------------------- */ + +void usage(char *diag) { + fprintf(stderr,"Create a fake CMY data file from a CMYK profile, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: fakeCMY [option] profile.icm fake.ti3\n"); + if (diag != NULL) + fprintf(stderr,"Diagnostic: %s\n",diag); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -r res set surface point resolution (default 3)\n"); + fprintf(stderr," -l tlimit set total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set black ink limit, 0 - 100%% (estimate by default)\n"); + exit(1); +} + +/* Fake up a vector direction clip in Lab space */ +static void visect( + icxLuBase *luo, /* xicc lookup object */ + double *pout, /* PCS result (may be NULL) */ + double *dout, /* Device result (may be NULL) */ + double *orig, /* Origin of vector (PCS) */ + double *vec /* Input vector (PCS) */ +) { + int i; + int rv; + double sum, step, inc[3]; /* 1 Delta E increment */ + double try[3]; /* Trial PCS */ + double res[4]; /* Trial result device values */ + + for (sum = 0.0, i = 0; i < 3; i++) { + double tt = vec[i] - orig[i]; + sum += tt * tt; + } + sum = sqrt(sum); + for (i = 0; i < 3; i++) { + inc[i] = (vec[i] - orig[i])/sum; + } +//printf("~1 orig %f %f %f, vec %f %f %f\n",orig[0],orig[1],orig[2],vec[0],vec[1],vec[3]); +//printf("~1 inc %f %f %f\n",inc[0],inc[1],inc[2]); + + /* Increment in vector direction until we clip */ + for (i = 0; i < 3; i++) + try[i] = 0.5 * (orig[i] + vec[i]); + + for (step = 20.0;;) { + rv = luo->inv_lookup(luo, res, try); + if (rv > 1) + error ("inv_lookup failed"); +//printf("~1 trial %f %f %f returned %d\n",try[0],try[1],try[2],rv); + if (rv == 1) { + if (step <= 0.1) + break; + /* Back off, and use smaller steps */ + for (i = 0; i < 3; i++) + try[i] -= step * inc[i]; + step *= 0.5; + } + for (i = 0; i < 3; i++) + try[i] += step * inc[i]; + } + + if (dout != NULL) + for (i = 0; i < 3; i++) + dout[i] = res[i]; + + /* Lookup the clipped PCS */ + if (pout != NULL) + luo->lookup(luo, pout, res); +} + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + double tlimit = -1.0; /* Total ink limit */ + double klimit = -1.0; /* Black ink limit */ + int tres = 3; /* Resolution of suface grid */ + int gres = tres+2; /* Resolution of grey points */ + char in_name[100]; + char out_name[100]; + icmFile *rd_fp; + icc *rd_icco; + int rv = 0; + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + xicc *xicco; + icxLuBase *luo; + icxInk ink; /* Ink parameters */ + fxpos *fxlist = NULL; /* Fixed point list for full spread */ + int fxlist_a = 0; /* Fixed point list allocation */ + int fxno = 0; /* The number of fixed points */ + int gc[MXTD]; /* Grid coordinate */ + + cgats *ocg; /* output cgats structure */ + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + + int i, j; + + error_program = argv[0]; + + if (argc < 2) + usage("Not enough arguments"); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + /* Ink limits */ + else if (argv[fa][1] == 'l') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -l"); + tlimit = atoi(na)/100.0; + } + + else if (argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -L"); + klimit = atoi(na)/100.0; + } + + /* Resolution */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -r"); + tres = atoi(na); + } + + else if (argv[fa][1] == '?') + usage(NULL); + else + usage("Unknown flag"); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Expected input profile name"); + strcpy(in_name,argv[fa++]); + + if (fa >= argc || argv[fa][0] == '-') usage("Expected output .ti3 name"); + strcpy(out_name,argv[fa++]); + + /* Open up the file for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((rd_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + /* Read the header and tag list */ + if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) + error ("Read: %d, %s",rv,rd_icco->err); + + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(rd_icco)) == NULL) + error ("Creation of xicc failed"); + + /* Setup ink limit */ + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(xicco, &ink.tlimit, tlimit, &ink.klimit, klimit); + + if (verb) { + if (ink.tlimit >= 0.0) + printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); + if (ink.klimit >= 0.0) + printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); + } + + ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */ + ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + ink.c.Kstle = 0.5; /* Min K at white end */ + ink.c.Kstpo = 0.5; /* Start of transition is at white */ + ink.c.Kenle = 0.5; /* Max K at black end */ + ink.c.Kenpo = 0.5; /* End transition at black */ + ink.c.Kshap = 1.0; /* Linear transition */ + + gres = tres + 2; + + /* Get a Device to PCS conversion object */ + if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) { + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + /* Get details of conversion */ + luo->spaces(luo, &ins, NULL, &outs, NULL, NULL, NULL, NULL, NULL); + + if (ins != icSigCmykData) { + error("Expecting CMYK device"); + } + + /* Make a default allocation */ + fxlist_a = 4; + if ((fxlist = (fxpos *)malloc(sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + + /* init coords */ + for (j = 0; j < 3; j++) + gc[j] = 0; + + for (;;) { /* For all grid points */ + + /* Check if this position is on the CMY surface */ + for (j = 0; j < 3; j++) { + if (gc[j] == 0 || gc[j] == (tres-1)) + break; + } + if (j < 3) { /* On the surface */ + + /* Make sure there is room */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (j = 0; j < 3; j++) + fxlist[fxno].p[j] = gc[j]/(tres-1.0); + fxlist[fxno].p[3] = 0.0; + fxno++; + } + + /* Increment grid index and position */ + for (j = 0; j < 3; j++) { + gc[j]++; + if (gc[j] < tres) + break; /* No carry */ + gc[j] = 0; + } + if (j >= 3) + break; /* Done grid */ + } + + for (i = 1; i < ((EXTRA_NEUTRAL * gres)-1); i++) { /* For all grey axis points */ + + /* Make sure there is room */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (j = 0; j < 3; j++) + fxlist[fxno].p[j] = i/((EXTRA_NEUTRAL * gres)-1.0); + fxlist[fxno].p[3] = 0.0; + fxno++; + } + + /* Convert CMY values into CMY0 Lab values */ + for (i = 0; i < fxno; i++) { + if ((rv = luo->lookup(luo, fxlist[i].v, fxlist[i].p)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); +//printf("~1 initial lookup %f %f %f -> %f %f %f\n", fxlist[i].p[0], fxlist[i].p[1], fxlist[i].p[2], fxlist[i].v[0], fxlist[i].v[1], fxlist[i].v[2]); + } + + /* Figure out the general scale at the black end */ + { + double dval[4]; + double wt[3] = { 100.0, 0, 0 }; /* Approximate white PCS */ + double ac[3] = { 50.0, 0, 0 }; /* Adjustment center */ + + double lab_wt[3]; /* Media Whitepoint PCS */ + double xyz_wt[3]; /* Media Whitepoint PCS */ + icmXYZNumber XYZ_WT; + double k_bk[3]; /* K black PCS */ + double cmyk_bk[3]; /* CMYK black PCS */ + double toAbs[3][3]; + double fromAbs[3][3]; + + /* Discover the Lab of the CMYK media */ + for (j = 0; j < 4; j++) + dval[j] = 0.0; + luo->lookup(luo, lab_wt, dval); + icmLab2XYZ(&icmD50, xyz_wt, lab_wt); + icmAry2XYZ(XYZ_WT, xyz_wt); + + /* Create the XYZ chromatic transform from relative to absolute and back */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, XYZ_WT, icmD50, toAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, XYZ_WT, fromAbs); + + /* Discover the Lab of the CMY black and CMYK black */ + for (j = 0; j < 3; j++) + dval[j] = 0.0; + dval[j] = 1.0; + luo->lookup(luo, k_bk, dval); + visect(luo, cmyk_bk, NULL, wt, k_bk); + if (verb) + printf("K black %f %f %f, CMYK black %f %f %f\n", k_bk[0], k_bk[1], k_bk[2], cmyk_bk[0], cmyk_bk[1], cmyk_bk[2]); + + /* Scale the CMY values that will be influenced by the K component to a darker */ + /* black. */ + + for (i = 0; i < fxno; i++) { + + /* Treat grey axis points differently */ + if (fxlist[i].p[0] != 0.0 + && fxlist[i].p[0] == fxlist[i].p[1] + && fxlist[i].p[0] == fxlist[i].p[2]) { + double xyz[3]; /* Temporary */ + double lab[3]; /* Temporary */ + +//printf("~1 scaled neutral L value from %f",fxlist[i].v[0]); + + /* Scale L value from K to CMYK black */ + fxlist[i].v[0] = lab_wt[0] + (fxlist[i].v[0] - lab_wt[0]) * + (cmyk_bk[0] - lab_wt[0])/(k_bk[0] - lab_wt[0]); +//printf(" to %f\n",fxlist[i].v[0]); + +#ifdef NEVER + /* Make a & b values same as CMYK black */ + /* Pivot around white point */ + fxlist[i].v[1] = wt[1] + (cmyk_bk[1] - wt[1]) * + (fxlist[i].v[0] - wt[0])/(cmyk_bk[0] - wt[0]); + fxlist[i].v[2] = wt[2] + (cmyk_bk[2] - wt[2]) * + (fxlist[i].v[0] - wt[0])/(cmyk_bk[0] - wt[0]); +#else + /* Convert absolute target Lab to relative Lab */ + icmLab2XYZ(&icmD50, xyz, fxlist[i].v); + icmMulBy3x3(xyz, fromAbs, xyz); + icmXYZ2Lab(&icmD50, lab, xyz); + + /* Make sure the equivalent relative value is neutral */ + lab[1] = lab[2] = 0.0; + + /* Convert back to absolute Lab */ + icmLab2XYZ(&icmD50, xyz, lab); + icmMulBy3x3(xyz, toAbs, xyz); + icmXYZ2Lab(&icmD50, lab, xyz); + +//printf("~1 corrected neutral value from Lab %f %f %f to %f %f %f\n", fxlist[i].v[0], fxlist[i].v[1], fxlist[i].v[2], lab[0], lab[1], lab[2]); + + for (j = 0; j < 3; j++) + fxlist[i].v[j] = lab[j]; +#endif + + } else { + + for (j = 0; j < 3; j++) { + if (fxlist[i].p[j] == 0.0) + break; /* Not a dark color */ + } + if (j >= 3) { + /* Scale by CMYK/K black vector */ + for (j = 0; j < 3; j++) { + fxlist[i].v[j] = lab_wt[j] + (fxlist[i].v[j] - lab_wt[j]) * + (cmyk_bk[j] - lab_wt[j])/(k_bk[j] - lab_wt[j]); + } + } + +//printf("[%4.2f %4.2f %4.2f] %f %f %f becomes", +//fxlist[i].p[0], fxlist[i].p[1], fxlist[i].p[2], fxlist[i].v[0], fxlist[i].v[1], fxlist[i].v[2]); + /* Now clip non-neutrals to the gamut surface */ + visect(luo, fxlist[i].v, NULL, ac, fxlist[i].v); +//printf("%f %f %f\n", fxlist[i].v[0], fxlist[i].v[1], fxlist[i].v[2]); + } + } + } + + if (verb) { + for (i = 0; i < fxno; i++) { + printf("Point %d = %f %f %f -> %f %f %f\n",i, + fxlist[i].p[0], fxlist[i].p[1], fxlist[i].p[2], + fxlist[i].v[0], fxlist[i].v[1], fxlist[i].v[2]); + } + } + + /* Setup output cgats file */ + { + char buf[1000]; + cgats_set_elem *setel; /* Array of set value elements */ + + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL); + ocg->add_kword(ocg, 0, NULL, NULL, "Fake CMY device data for CMY->CMYK link"); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll fakeCMY", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); + + ocg->add_kword(ocg, 0, "COLOR_REP","CMY_LAB", NULL); + + if (tlimit >= 0) { + sprintf(buf,"%.0f",tlimit); + ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",buf, NULL); + } + + if (klimit >= 0) { + sprintf(buf,"%.0f",klimit); + ocg->add_kword(ocg, 0, "BLACK_INK_LIMIT",buf, NULL); + } + + /* Fields we want */ + ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); + ocg->add_field(ocg, 0, "CMY_C", r_t); + ocg->add_field(ocg, 0, "CMY_M", r_t); + ocg->add_field(ocg, 0, "CMY_Y", r_t); + ocg->add_field(ocg, 0, "LAB_L", r_t); + ocg->add_field(ocg, 0, "LAB_A", r_t); + ocg->add_field(ocg, 0, "LAB_B", r_t); + + if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 7)) == NULL) + error("Malloc failed!"); + + /* Write out test values. */ + for (i = 0; i < fxno; i++) { + + sprintf(buf, "%d", i+1); + setel[0].c = buf; + + setel[1].d = 100.0 * fxlist[i].p[0]; + setel[2].d = 100.0 * fxlist[i].p[1]; + setel[3].d = 100.0 * fxlist[i].p[2]; + setel[4].d = fxlist[i].v[0]; + setel[5].d = fxlist[i].v[1]; + setel[6].d = fxlist[i].v[2]; + + ocg->add_setarr(ocg, 0, setel); + } + + free(setel); + if (ocg->write_name(ocg, out_name)) + error("Write error : %s",ocg->err); + ocg->del(ocg); + } + + luo->del(luo); + xicco->del(xicco); + rd_icco->del(rd_icco); + rd_fp->del(rd_fp); + + return 0; +} + diff --git a/xicc/fbview.c b/xicc/fbview.c new file mode 100644 index 0000000..53da99e --- /dev/null +++ b/xicc/fbview.c @@ -0,0 +1,312 @@ + +/* + * International Color Consortium Format Library (icclib) + * View the gamut clipping implemented by the bwd profile. + * + * Author: Graeme W. Gill + * Date: 2000/12/8 + * Version: 1.23 + * + * Copyright 2000 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" + +#define TRES 43 + +/* - - - - - - - - - - - - - */ + +/* Return maximum difference */ +double maxdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + if ((tt = fabs(in1[0] - in2[0])) > rv) + rv = tt; + if ((tt = fabs(in1[1] - in2[1])) > rv) + rv = tt; + if ((tt = fabs(in1[2] - in2[2])) > rv) + rv = tt; + return rv; +} + +/* Return absolute difference */ +double absdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + tt = in1[0] - in2[0]; + rv += tt * tt; + tt = in1[1] - in2[1]; + rv += tt * tt; + tt = in1[2] - in2[2]; + rv += tt * tt; + return sqrt(rv); +} + +/* ---------------------------------------- */ + +void usage(void) { + fprintf(stderr,"View bwd table clipping of an ICC file, , Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: fbtest [-v] infile\n"); + fprintf(stderr," -v verbose\n"); + exit(1); +} + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int doaxes = 0; + char in_name[100]; + char *xl, out_name[100]; + icmFile *rd_fp; + icc *rd_icco; + int rv = 0; + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + + /* Check variables */ + int co[4]; + double in[4], out[4], check[4], temp[4]; + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + else if (argv[fa][1] == '?') + usage(); + else + usage(); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(in_name,argv[fa]); + + strcpy(out_name, in_name); + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + strcpy(xl,".wrl"); + + /* Open up the file for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((rd_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + /* Read the header and tag list */ + if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) + error ("Read: %d, %s",rv,rd_icco->err); + + /* Run the target Lab values through the bwd and fwd tables, */ + /* to compute the overall error. */ + { +#define GAMUT_LCENT 50.0 + double merr = 0.0; + double aerr = 0.0; + double nsamps = 0.0; + icmLuBase *luo1, *luo2; + FILE *wrl; + struct { + double x, y, z; + double wx, wy, wz; + double r, g, b; + } axes[5] = { + { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */ + { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */ + { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */ + { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */ + { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */ + }; + int i, j; + + + /* Get a Device to PCS conversion object */ + if ((luo1 = rd_icco->get_luobj(rd_icco, icmFwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((luo1 = rd_icco->get_luobj(rd_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + /* Get details of conversion */ + luo1->spaces(luo1, &ins, NULL, &outs, NULL, NULL, NULL, NULL, NULL, NULL); + + if (ins != icSigCmykData) { + error("Expecting CMYK device"); + } + + /* Get a PCS to Device conversion object */ + if ((luo2 = rd_icco->get_luobj(rd_icco, icmBwd, icAbsoluteColorimetric, + icSigLabData, icmLuOrdNorm)) == NULL) { + if ((luo2 = rd_icco->get_luobj(rd_icco, icmBwd, icmDefaultIntent, + icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + + if ((wrl = fopen(out_name,"w")) == NULL) { + fprintf(stderr,"Error opening output file '%s'\n",out_name); + return 2; + } + + /* Spit out a VRML 2 Object surface of gamut */ + + fprintf(wrl,"#VRML V2.0 utf8\n"); + fprintf(wrl,"\n"); + fprintf(wrl,"# Created by the Argyll CMS\n"); + fprintf(wrl,"Transform {\n"); + fprintf(wrl,"children [\n"); + fprintf(wrl," NavigationInfo {\n"); + fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n"); + fprintf(wrl," } # We'll add our own light\n"); + fprintf(wrl,"\n"); + fprintf(wrl," DirectionalLight {\n"); + fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n"); + fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," Viewpoint {\n"); + fprintf(wrl," position 0 0 340 # Position we view from\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + if (doaxes != 0) { + fprintf(wrl,"# Lab axes as boxes:\n"); + for (i = 0; i < 5; i++) { + fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z); + fprintf(wrl,"\tchildren [\n"); + fprintf(wrl,"\t\tShape{\n"); + fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n", + axes[i].wx, axes[i].wy, axes[i].wz); + fprintf(wrl,"\t\t\tappearance Appearance { material Material "); + fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b); + fprintf(wrl,"\t\t}\n"); + fprintf(wrl,"\t]\n"); + fprintf(wrl,"}\n"); + } + fprintf(wrl,"\n"); + } + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); + + i = 0; +// for (co[0] = 0; co[0] < TRES; co[0]++) { + for (co[1] = 0; co[1] < TRES; co[1]++) { + for (co[2] = 0; co[2] < TRES; co[2]++) { + int rv1, rv2; + double mxd, absd; + +// temp[0] = co[0]/(TRES-1.0); + temp[1] = co[1]/(TRES-1.0); + temp[2] = co[2]/(TRES-1.0); + +// in[0] = temp[0] * 100.0; + in[0] = 0.0; + in[1] = 200.0 * temp[1] -100.0; + in[2] = 200.0 * temp[2] -100.0; + + + /* PCS -> Device */ + if ((rv2 = luo2->lookup(luo2, out, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* Device -> PCS */ + if ((rv1 = luo1->lookup(luo1, check, out)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (verb) { + printf("%f %f %f -> %f %f %f %f -> %f %f %f [%f]\n", + in[0],in[1],in[2],out[0],out[1],out[2],out[3],check[0],check[1],check[2], + maxdiff(check, in)); + } + + /* Check the result */ + mxd = maxdiff(check, in); + if (mxd > merr) + merr = mxd; + + nsamps++; + absd = absdiff(check, in); + aerr += absd; + + if (absd > 3.0) { + fprintf(wrl,"%f %f %f,\n",in[1], in[2], in[0]-GAMUT_LCENT); + fprintf(wrl,"%f %f %f,\n",check[1], check[2], check[0]-GAMUT_LCENT); + i++; + } + } + } +// } + + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (j = 0; j < i; j++) { + fprintf(wrl,"%d, %d, -1,\n", j * 2, j * 2 + 1); + } + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"} # end shape\n"); + + fprintf(wrl,"\n"); + fprintf(wrl," ] # end of children for world\n"); + fprintf(wrl,"}\n"); + + if (fclose(wrl) != 0) { + fprintf(stderr,"Error closing output file '%s'\n",out_name); + return 2; + } + + printf("bwd to fwd check complete, peak err = %f, avg err = %f\n",merr,aerr/nsamps); + + /* Done with lookup object */ + luo1->del(luo1); + luo2->del(luo2); + } + + rd_icco->del(rd_icco); + rd_fp->del(rd_fp); + + return 0; +} + diff --git a/xicc/iccgamut.c b/xicc/iccgamut.c new file mode 100644 index 0000000..7bd3b4c --- /dev/null +++ b/xicc/iccgamut.c @@ -0,0 +1,811 @@ + +/* + * iccgamut + * + * Produce color surface gamut of an ICC profile. + * + * Author: Graeme W. Gill + * Date: 19/3/00 + * Version: 1.00 + * + * Copyright 2000 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + + +/* + * TTBD: + * To support CIACAM02 properly, need to cope with viewing parameters ? + */ + +#define SURFACE_ONLY +#define GAMRES 10.0 /* Default surface resolution */ + +#define USE_CAM_CLIP_OPT /* Use CAM space to clip in */ + +#define RGBRES 33 /* 33 */ +#define CMYKRES 17 /* 17 */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xicc.h" +#include "gamut.h" +#include "counters.h" + +static void diag_gamut(icxLuBase *p, double detail, int doaxes, + double tlimit, double klimit, char *outname); + +void usage(char *diag) { + int i; + fprintf(stderr,"Create Lab/Jab gamut plot Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: iccgamut [options] profile\n"); + if (diag != NULL) + fprintf(stderr,"Diagnostic: %s\n",diag); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -d sres Surface resolution details 1.0 - 50.0\n"); + fprintf(stderr," -w emit VRML .wrl file as well as CGATS .gam file\n"); + fprintf(stderr," -n Don't add VRML axes or white/black point\n"); + fprintf(stderr," -k Add VRML markers for prim. & sec. \"cusp\" points\n"); + fprintf(stderr," -f function f = forward*, b = backwards\n"); + fprintf(stderr," -i intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute (default), d = profile default\n"); +// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n"); + fprintf(stderr," -p oride l = Lab_PCS (default), j = %s Appearance Jab\n",icxcam_description(cam_default)); + fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n"); + fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n"); + fprintf(stderr," -l tlimit set total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," -c viewcond set viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or a series of parameter:value changes\n"); + for (i = 0; ; i++) { + icxViewCond vc; + if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999) + break; + + fprintf(stderr," %s\n",vc.desc); + } + fprintf(stderr," s:surround n = auto, a = average, m = dim, d = dark,\n"); + fprintf(stderr," c = transparency (default average)\n"); + fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white)\n"); + fprintf(stderr," w:x:y Adapted white point as x, y\n"); + fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n"); + fprintf(stderr," b:background Background %% of image luminance (default 20)\n"); + fprintf(stderr," l:scenewhite Scene white in cd.m^2 if surround = auto (default 250)\n"); + fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n"); + fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white)\n"); + fprintf(stderr," f:x:y Flare color as x, y\n"); + fprintf(stderr," -s Create special cube surface topology plot\n"); + fprintf(stderr,"\n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char prof_name[100]; + char *xl, out_name[100]; + icmFile *fp; + icc *icco; + xicc *xicco; + gamut *gam; + int verb = 0; + int rv = 0; + int vrml = 0; + int doaxes = 1; + int docusps = 0; + double gamres = GAMRES; /* Surface resolution */ + int special = 0; /* Special surface plot */ + int fl = 0; /* luobj flags */ + icxInk ink; /* Ink parameters */ + int tlimit = -1; /* Total ink limit as a % */ + int klimit = -1; /* Black ink limit as a % */ + icxViewCond vc; /* Viewing Condition for CIECAM */ + int vc_e = -1; /* Enumerated viewing condition */ + int vc_s = -1; /* Surround override */ + double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */ + double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */ + double vc_a = -1.0; /* Adapted luminance */ + double vc_b = -1.0; /* Background % overide */ + double vc_l = -1.0; /* Scene luminance override */ + double vc_f = -1.0; /* Flare % overide */ + double vc_fXYZ[3] = {-1.0, -1.0, -1.0}; /* Flare color override in XYZ */ + double vc_fxy[2] = {-1.0, -1.0}; /* Flare color override in x,y */ + + icxLuBase *luo; + + /* Lookup parameters */ + icmLookupFunc func = icmFwd; /* Default */ + icRenderingIntent intent = -1; /* Default */ + icColorSpaceSignature pcsor = icSigLabData; /* Default */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + + error_program = argv[0]; + + if (argc < 2) + usage("Too few parameters"); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(NULL); + + /* function */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -f"); + switch (na[0]) { + case 'f': + case 'F': + func = icmFwd; + break; + case 'b': + case 'B': + func = icmBwd; + break; + default: + usage("Unrecognised parameter after flag -f"); + } + } + + /* Intent */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -i"); + switch (na[0]) { + case 'd': + intent = icmDefaultIntent; + break; + case 'a': + intent = icAbsoluteColorimetric; + break; + case 'p': + intent = icPerceptual; + break; + case 'r': + intent = icRelativeColorimetric; + break; + case 's': + intent = icSaturation; + break; + /* Argyll special intents to check spaces underlying */ + /* icxPerceptualAppearance & icxSaturationAppearance */ + case 'P': + intent = icmAbsolutePerceptual; + break; + case 'S': + intent = icmAbsoluteSaturation; + break; + default: + usage("Unrecognised parameter after flag -i"); + } + } + + /* Search order */ + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -o"); + switch (na[0]) { + case 'n': + case 'N': + order = icmLuOrdNorm; + break; + case 'r': + case 'R': + order = icmLuOrdRev; + break; + default: + usage("Unrecognised parameter after flag -o"); + } + } + + /* PCS override */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -p"); + switch (na[0]) { + case 'l': + pcsor = icSigLabData; + break; + case 'j': + pcsor = icxSigJabData; + break; + default: + usage("Unrecognised parameter after flag -p"); + } + } + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + /* VRML output */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + vrml = 1; + } + /* No axis output in vrml */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + doaxes = 0; + } + /* Do cusp markers in vrml */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + docusps = 1; + } + /* Special */ + else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + special = 1; + } + /* Ink limit */ + else if (argv[fa][1] == 'l') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -l"); + tlimit = atoi(na); + } + + else if (argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -L"); + klimit = atoi(na); + } + + + /* Surface Detail */ + else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -d"); + gamres = atof(na); + if (gamres < 0.1 || gamres > 50.0) + usage("Parameter after flag -d seems out of range"); + } + + /* Viewing conditions */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -c"); +#ifdef NEVER + if (na[0] >= '0' && na[0] <= '9') { + vc_e = atoi(na); + } else +#endif + if (na[1] != ':') { + if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Urecognised Enumerated Viewing conditions"); + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage("Unrecognised parameters after -cs"); + if (na[2] == 'n' || na[2] == 'N') { + vc_s = vc_none; /* Automatic from Lv */ + } else if (na[2] == 'a' || na[2] == 'A') { + vc_s = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc_s = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc_s = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc_s = vc_cut_sheet; + } else + usage("Unrecognised parameters after -cs:"); + } else if (na[0] == 'w' || na[0] == 'W') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_wxy[0] = x; vc_wxy[1] = y; + } else + usage("Unrecognised parameters after -cw"); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage("Unrecognised parameters after -ca"); + vc_a = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage("Unrecognised parameters after -cb"); + vc_b = atof(na+2); + } else if (na[0] == 'l' || na[0] == 'L') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]l) missing ':'"); + vc_l = atof(na+2); + } else if (na[0] == 'f' || na[0] == 'F') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_fXYZ[0] = x; vc_fXYZ[1] = y; vc_fXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_fxy[0] = x; vc_fxy[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc_f = x; + } else + usage("Unrecognised parameters after -cf"); + } else + usage("Unrecognised parameters after -c"); + } + else + usage("Unknown flag"); + } else + break; + } + + if (intent == -1) { + if (pcsor == icxSigJabData) + intent = icRelativeColorimetric; /* Default to icxAppearance */ + else + intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */ + } + + if (fa >= argc || argv[fa][0] == '-') usage("Expected profile name"); + strcpy(prof_name,argv[fa]); + + /* Open up the profile for reading */ + if ((fp = new_icmFileStd_name(prof_name,"r")) == NULL) + error ("Can't open file '%s'",prof_name); + + if ((icco = new_icc()) == NULL) + error ("Creation of ICC object failed"); + + if ((rv = icco->read(icco,fp,0)) != 0) + error ("%d, %s",rv,icco->err); + + if (verb) { + icmFile *op; + if ((op = new_icmFileStd_fp(stdout)) == NULL) + error ("Can't open stdout"); + icco->header->dump(icco->header, op, 1); + op->del(op); + } + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(icco)) == NULL) + error ("Creation of xicc failed"); + + /* Set the ink limits */ + icxDefaultLimits(xicco, &ink.tlimit, tlimit/100.0, &ink.klimit, klimit/100.0); + + if (verb) { + if (ink.tlimit >= 0.0) + printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); + if (ink.klimit >= 0.0) + printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); + } + + /* Setup a safe ink generation (not used) */ + ink.KonlyLmin = 0; /* Use normal black Lmin for locus */ + ink.k_rule = icxKluma5k; + ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */ + ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + ink.c.Kstle = 0.0; /* Min K at white end */ + ink.c.Kstpo = 0.0; /* Start of transition is at white */ + ink.c.Kenle = 1.0; /* Max K at black end */ + ink.c.Kenpo = 1.0; /* End transition at black */ + ink.c.Kshap = 1.0; /* Linear transition */ + + /* Setup the default viewing conditions */ + if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -2) + error ("%d, %s",xicco->errc, xicco->err); + + if (vc_e != -1) + if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -2) + error ("%d, %s",xicco->errc, xicco->err); + if (vc_s >= 0) + vc.Ev = vc_s; + if (vc_wXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; + vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; + } + if (vc_wxy[0] >= 0.0) { + double x = vc_wxy[0]; + double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Wxyz[0] = x/y * vc.Wxyz[1]; + vc.Wxyz[2] = z/y * vc.Wxyz[1]; + } + if (vc_a >= 0.0) + vc.La = vc_a; + if (vc_b >= 0.0) + vc.Yb = vc_b/100.0; + if (vc_l >= 0.0) + vc.Lv = vc_l; + if (vc_f >= 0.0) + vc.Yf = vc_f/100.0; + if (vc_fXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; + vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; + } + if (vc_fxy[0] >= 0.0) { + double x = vc_fxy[0]; + double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Fxyz[0] = x/y * vc.Fxyz[1]; + vc.Fxyz[2] = z/y * vc.Fxyz[1]; + } + + fl |= ICX_CLIP_NEAREST; /* Don't setup rev uncessarily */ + +#ifdef USE_CAM_CLIP_OPT + fl |= ICX_CAM_CLIP; +#endif + +#ifdef NEVER + printf("~1 output space flags = 0x%x\n",fl); + printf("~1 output space intent = %s\n",icx2str(icmRenderingIntent,intent)); + printf("~1 output space pcs = %s\n",icx2str(icmColorSpaceSignature,pcsor)); + printf("~1 output space viewing conditions =\n"); xicc_dump_viewcond(&vc); + printf("~1 output space inking =\n"); xicc_dump_inking(&ink); +#endif + + strcpy(out_name, prof_name); + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + + strcpy(xl,".gam"); + + /* Get a expanded color conversion object */ + if ((luo = xicco->get_luobj(xicco, fl, func, intent, pcsor, order, &vc, &ink)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + if (special) { + if (func != icmFwd) + error("Must be forward direction for special plot"); + strcpy(xl,".wrl"); + diag_gamut(luo, gamres, doaxes, tlimit/100.0, klimit/100.0, out_name); + } else { + /* Creat a gamut surface */ + if ((gam = luo->get_gamut(luo, gamres)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + if (gam->write_gam(gam,out_name)) + error ("write gamut failed on '%s'",out_name); + + if (vrml) { + strcpy(xl,".wrl"); + if (gam->write_vrml(gam,out_name, doaxes, docusps)) + error ("write vrml failed on '%s'",out_name); + } + + if (verb) { + printf("Total volume of gamut is %f cubic colorspace units\n",gam->volume(gam)); + } + gam->del(gam); + } + + luo->del(luo); /* Done with lookup object */ + + xicco->del(xicco); /* Expansion wrapper */ + icco->del(icco); /* Icc */ + fp->del(fp); + + + return 0; +} + +/* -------------------------------------------- */ +/* Code for special gamut surface plot */ + +#define GAMUT_LCENT 50 + +/* Create a diagnostic gamut, illustrating */ +/* device space "fold-over" */ +static void diag_gamut( +icxLuBase *p, /* Lookup object */ +double detail, /* Gamut resolution detail */ +int doaxes, /* Do Lab axes */ +double tlimit, /* Total ink limit */ +double klimit, /* K ink limit */ +char *outname /* Output VRML file */ +) { + int i, j; + FILE *wrl; + struct { + double x, y, z; + double wx, wy, wz; + double r, g, b; + } axes[5] = { + { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */ + { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */ + { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */ + { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */ + { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */ + }; + int vix; /* Vertex index */ + DCOUNT(coa, MXDI, p->inputChan, 0, 0, 2); + + double col[1 << MXDI][3]; /* Color asigned to each major vertex */ + int res; + + if (tlimit < 0.0) + tlimit = p->inputChan; + if (klimit < 0.0) + klimit = 1.0; + + /* Asign some colors to the combination nodes */ + for (i = 0; i < (1 << p->inputChan); i++) { + int a, b, c, j; + double h; + + j = (i ^ 0x5a5a5a5a) % (1 << p->inputChan); + h = (double)j/((1 << p->inputChan)-1); + + /* Make fully saturated with chosen hue */ + if (h < 1.0/3.0) { + a = 0; + b = 1; + c = 2; + } else if (h < 2.0/3.0) { + a = 1; + b = 2; + c = 0; + h -= 1.0/3.0; + } else { + a = 2; + b = 0; + c = 1; + h -= 2.0/3.0; + } + h *= 3.0; + + col[i][a] = (1.0 - h); + col[i][b] = h; + col[i][c] = d_rand(0.0, 1.0); + } + + if (detail > 0.0) + res = (int)(100.0/detail); /* Establish an appropriate sampling density */ + else + res = 4; + + if (res < 2) + res = 2; + + if ((wrl = fopen(outname,"w")) == NULL) + error("Error opening wrl output file '%s'",outname); + + /* Spit out a VRML 2 Object surface of gamut */ + fprintf(wrl,"#VRML V2.0 utf8\n"); + fprintf(wrl,"\n"); + fprintf(wrl,"# Created by the Argyll CMS\n"); + fprintf(wrl,"Transform {\n"); + fprintf(wrl,"children [\n"); + fprintf(wrl," NavigationInfo {\n"); + fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n"); + fprintf(wrl," } # We'll add our own light\n"); + fprintf(wrl,"\n"); + fprintf(wrl," DirectionalLight {\n"); + fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n"); + fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," Viewpoint {\n"); + fprintf(wrl," position 0 0 340 # Position we view from\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + if (doaxes != 0) { + fprintf(wrl,"# Lab axes as boxes:\n"); + for (i = 0; i < 5; i++) { + fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z); + fprintf(wrl,"\tchildren [\n"); + fprintf(wrl,"\t\tShape{\n"); + fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n", + axes[i].wx, axes[i].wy, axes[i].wz); + fprintf(wrl,"\t\t\tappearance Appearance { material Material "); + fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b); + fprintf(wrl,"\t\t}\n"); + fprintf(wrl,"\t]\n"); + fprintf(wrl,"}\n"); + } + fprintf(wrl,"\n"); + } + fprintf(wrl," Transform {\n"); + fprintf(wrl," translation 0 0 0\n"); + fprintf(wrl," children [\n"); + fprintf(wrl," Shape { \n"); + fprintf(wrl," geometry IndexedFaceSet {\n"); + fprintf(wrl," solid FALSE\n"); /* Don't back face cull */ + fprintf(wrl," convex TRUE\n"); + fprintf(wrl,"\n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [ # Verticy coordinates\n"); + + + /* Itterate over all the faces in the device space */ + /* generating the vertx positions. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MXDI]; + double inl[MXDI]; + double out[3]; + double sum; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->inputChan; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->inputChan; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (e = 0; e < p->inputChan; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + in[m1] = x/(res - 1.0); + for (y = 0; y < res; y++) { + in[m2] = y/(res - 1.0); + + for (sum = 0.0, e = 0; e < p->inputChan; e++) { + sum += inl[e] = in[e]; + } + if (sum >= tlimit) { + for (e = 0; e < p->inputChan; e++) + inl[e] *= tlimit/sum; + } + if (p->inputChan >= 3 && inl[3] >= klimit) + inl[3] = klimit; + p->lookup(p, out, inl); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-50.0); + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n"); + + /* Itterate over all the faces in the device space */ + /* generating the quadrilateral indexes. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MXDI]; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->inputChan; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->inputChan; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (e = 0; e < p->inputChan; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + /* Only output quads under the total ink limit */ + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + for (y = 0; y < res; y++) { + if (x < (res-1) && y < (res-1)) { + fprintf(wrl,"%d, %d, %d, %d, -1\n", + vix, vix + 1, vix + 1 + res, vix + res); + } + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ]\n"); + fprintf(wrl,"\n"); + fprintf(wrl," colorPerVertex TRUE\n"); + fprintf(wrl," color Color {\n"); + fprintf(wrl," color [ # RGB colors of each vertex\n"); + + /* Itterate over all the faces in the device space */ + /* generating the vertx colors. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MXDI]; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->inputChan; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->inputChan; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (e = 0; e < p->inputChan; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + double xb = x/(res - 1.0); + for (y = 0; y < res; y++) { + int v0, v1, v2, v3; + double yb = y/(res - 1.0); + double rgb[3]; + + for (v0 = 0, e = 0; e < p->inputChan; e++) + v0 |= coa[e] ? (1 << e) : 0; /* Binary index */ + + v1 = v0 | (1 << m2); /* Y offset */ + v2 = v0 | (1 << m2) | (1 << m1); /* X+Y offset */ + v3 = v0 | (1 << m1); /* Y offset */ + + /* Linear interp between the main verticies */ + for (j = 0; j < 3; j++) { + rgb[j] = (1.0 - yb) * (1.0 - xb) * col[v0][j] + + yb * (1.0 - xb) * col[v1][j] + + (1.0 - yb) * xb * col[v3][j] + + yb * xb * col[v2][j]; + } + fprintf(wrl,"%f %f %f,\n",rgb[1], rgb[2], rgb[0]); + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ] \n"); + fprintf(wrl," }\n"); + fprintf(wrl," }\n"); + fprintf(wrl," appearance Appearance { \n"); + fprintf(wrl," material Material {\n"); + fprintf(wrl," transparency 0.0\n"); + fprintf(wrl," ambientIntensity 0.3\n"); + fprintf(wrl," shininess 0.5\n"); + fprintf(wrl," }\n"); + fprintf(wrl," }\n"); + fprintf(wrl," } # end Shape\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + + fprintf(wrl,"\n"); + fprintf(wrl," ] # end of children for world\n"); + fprintf(wrl,"}\n"); + + if (fclose(wrl) != 0) + error("Error closing output file '%s'",outname); +} + diff --git a/xicc/iccjpeg.c b/xicc/iccjpeg.c new file mode 100644 index 0000000..13d4e25 --- /dev/null +++ b/xicc/iccjpeg.c @@ -0,0 +1,271 @@ + +/* + +Little CMS +Copyright (c) 1998-2010 Marti Maria Saguer + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and as +sociated documentation files (the "Software"), to deal in the Software without restriction, includin +g 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, subj +ect 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 NO +T 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 LIABI +LITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WIT +H THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +/* + * iccjpeg.c + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include <stdlib.h> /* define malloc() */ + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +void +write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/xicc/iccjpeg.h b/xicc/iccjpeg.h new file mode 100644 index 0000000..5e1888d --- /dev/null +++ b/xicc/iccjpeg.h @@ -0,0 +1,73 @@ +/* + * iccprofile.h + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ + +#include <stdio.h> /* needed to define "FILE", "NULL" */ +#include "jpeglib.h" + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +extern void write_icc_profile JPP((j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len)); + + +/* + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/* + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len)); diff --git a/xicc/icheck.c b/xicc/icheck.c new file mode 100644 index 0000000..ed3c6e5 --- /dev/null +++ b/xicc/icheck.c @@ -0,0 +1,532 @@ + +/* + * Argyll. + * + * Check for B2A table PCS->Device interpolation faults + * + * Author: Graeme W. Gill + * Date: 2000/12/11 + * Version: 1.00 + * + * Copyright 2000 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "numlib.h" +#include "copyright.h" +#include "aconfig.h" +#include "icc.h" + +void usage(void) { + fprintf(stderr,"Check PCS->Device Interpolation faults of ICC file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: icheck [-v] [-w] infile\n"); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -w create VRML visualisation\n"); + fprintf(stderr," -x Use VRML axies\n"); + exit(1); +} + +FILE *start_vrml(char *name, int doaxes); +void start_line_set(FILE *wrl); +void add_vertex(FILE *wrl, double pp[3]); +void make_lines(FILE *wrl, int ppset); +void end_vrml(FILE *wrl); + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int dovrml = 0; + int doaxes = 0; + char in_name[100]; + char out_name[100], *xl; + icmFile *rd_fp; + icc *rd_icco; + int rv = 0; + + /* Check variables */ + icmLuBase *luof, *luob; /* A2B and B2A table lookups */ + icmLuLut *lluof, *lluob; /* Lookup Lut type object */ + int gres; /* Grid resolution of B2A */ + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + int inn; /* Number of input chanels */ + icmLuAlgType alg; + FILE *wrl = NULL; + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + /* VRML */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + dovrml = 1; + } + /* Axes */ + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + doaxes = 1; + } + else if (argv[fa][1] == '?') + usage(); + else + usage(); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(in_name,argv[fa]); + + strcpy(out_name, in_name); + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + strcpy(xl,".wrl"); + + /* Open up the file for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((rd_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + /* Read the header and tag list */ + if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) + error ("Read: %d, %s",rv,rd_icco->err); + + /* Get a Device to PCS conversion object */ + if ((luof = rd_icco->get_luobj(rd_icco, icmFwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((luof = rd_icco->get_luobj(rd_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + + /* Get a PCS to Device conversion object */ + if ((luob = rd_icco->get_luobj(rd_icco, icmBwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((luob = rd_icco->get_luobj(rd_icco, icmBwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + + /* Get details of conversion (for B2A direction) */ + luob->spaces(luob, &outs, NULL, &ins, &inn, &alg, NULL, NULL, NULL, NULL); + + if (alg != icmLutType) { + error("Expecting Lut based profile"); + } + + if (outs != icSigLabData) { + error("Expecting Lab PCS"); + } + + lluof = (icmLuLut *)luof; /* Lookup Lut type object */ + lluob = (icmLuLut *)luob; /* Lookup Lut type object */ + + gres = lluob->lut->clutPoints; + + if (dovrml) { + wrl = start_vrml(out_name, doaxes); + start_line_set(wrl); + } + + { + double aerr = 0.0; + double ccount = 0.0; + double merr = 0.0; + double tcount = 0.0; + int co[3]; /* PCS grid counter */ + + /* Itterate throught the PCS clut grid cells */ + for (co[2] = 0; co[2] < (gres-1); co[2]++) { + for (co[1] = 0; co[1] < (gres-1); co[1]++) { + for (co[0] = 0; co[0] < (gres-1); co[0]++) { + int j, k, m; + int cc[3]; /* Cube corner offsets */ + double pcs[8][3], wpcsd; + double apcs[3]; + double adev[MAX_CHAN]; + double check[3]; /* Check PCS */ + double ier; /* Interpolation error */ + + apcs[0] = apcs[1] = apcs[2] = 0.0; + for (k = 0; k < inn; k++) + adev[k] = 0.0; + + /* For each corner of the PCS grid based at the current point, */ + /* average the PCS and Device values */ + m = 0; + for (cc[2] = 0; cc[2] < 2; cc[2]++, m++) { + for (cc[1] = 0; cc[1] < 2; cc[1]++) { + for (cc[0] = 0; cc[0] < 2; cc[0]++) { + double dev[MAX_CHAN]; + + pcs[m][0] = (co[0] + cc[0])/(gres - 1.0); + pcs[m][1] = (co[1] + cc[1])/(gres - 1.0); + pcs[m][2] = (co[2] + cc[2])/(gres - 1.0); + + /* Match icclib settable() range */ + pcs[m][0] = pcs[m][0] * 100.0; + pcs[m][1] = (pcs[m][1] * 254.0) - 127.0; + pcs[m][2] = (pcs[m][2] * 254.0) - 127.0; + +//printf("Input PCS %f %f %f\n", pcs[m][0], pcs[m][1], pcs[m][2]); + + /* PCS to (cliped) Device */ + if ((rv = lluob->clut(lluob, dev, pcs[m])) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* (clipped) Device to (clipped) PCS */ + if ((rv = lluof->clut(lluof, pcs[m], dev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + apcs[0] += pcs[m][0]; + apcs[1] += pcs[m][1]; + apcs[2] += pcs[m][2]; + +//printf("Corner PCS %f %f %f -> %f %f %f %f\n", +//pcs[m][0], pcs[m][1], pcs[m][2], dev[0], dev[1], dev[2], dev[3]); + + for (k = 0; k < inn; k++) + adev[k] += dev[k]; + } + } + } + + for (j = 0; j < 3; j++) + apcs[j] /= 8.0; + + for (k = 0; k < inn; k++) + adev[k] /= 8.0; + + /* Compute worst case distance of PCS corners to average PCS */ + wpcsd = 0.0; + for (m = 0; m < 8; m++) { + double ss; + for (ss = 0.0, j = 0; j < 3; j++) { + double tt = pcs[m][j] - apcs[j]; + ss += tt * tt; + } + ss = sqrt(ss); + if (ss > wpcsd) + wpcsd = ss; + } + wpcsd *= 0.75; /* Set threshold at 75% of most distant corner */ + /* Set a worst case */ + if (wpcsd < 1.0) + wpcsd = 1.0; + +// else if (wpcsd > 3.0) +// wpcsd = 3.0; + + +//printf("Average PCS %f %f %f, Average Device %f %f %f %f\n", +//apcs[0], apcs[1], apcs[2], adev[0], adev[1], adev[2], adev[3]); + + /* Average Device to PCS */ + if ((rv = lluof->clut(lluof, check, adev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + +//printf("Check PCS %f %f %f\n", +//check[0], check[1], check[2]); + + /* Compute error in PCS vs. Device interpolation */ + for (ier = 0.0, j = 0; j < 3; j++) { + double tt = apcs[j] - check[j]; + ier += tt * tt; + } + ier = sqrt(ier); + +//printf("Average PCS %f %f %f, Check PCS %f %f %f, error %f\n", +//apcs[0], apcs[1], apcs[2], check[0], check[1], check[2], ier); + + aerr += ier; + ccount++; + if (ier > merr) + merr = ier; + + if (ier > wpcsd) { + tcount++; + + printf("ier = %f, Dev = %f %f %f %f\n", + ier, adev[0], adev[1], adev[2], adev[3]); + if (dovrml) { + add_vertex(wrl, apcs); + add_vertex(wrl, check); + } + } + +//printf("~1 ier = %f\n",ier); +//printf("\n"); + + + if (verb) + printf("."), fflush(stdout); + } + } + } + + if (dovrml) { + make_lines(wrl, 2); + end_vrml(wrl); + } + + aerr /= ccount; + + printf("Average interpolation error %f, maximum %f\n",aerr, merr); + printf("Number outside corner radius = %f%%\n",tcount * 100.0/ccount); + } + + /* Done with lookup objects */ + luof->del(luof); + luob->del(luob); + + rd_icco->del(rd_icco); + rd_fp->del(rd_fp); + + return 0; +} + +/* ------------------------------------------------ */ +/* Some simple functions to do basix VRML work */ + +#define GAMUT_LCENT 50.0 +static int npoints = 0; +static int paloc = 0; +static struct { double pp[3]; } *pary; + +static void Lab2RGB(double *out, double *in); + +FILE *start_vrml(char *name, int doaxes) { + FILE *wrl; + struct { + double x, y, z; + double wx, wy, wz; + double r, g, b; + } axes[5] = { + { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */ + { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */ + { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */ + { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */ + { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */ + }; + int i; + + if ((wrl = fopen(name,"w")) == NULL) + error("Error opening VRML file '%s'\n",name); + + npoints = 0; + + fprintf(wrl,"#VRML V2.0 utf8\n"); + fprintf(wrl,"\n"); + fprintf(wrl,"# Created by the Argyll CMS\n"); + fprintf(wrl,"Transform {\n"); + fprintf(wrl,"children [\n"); + fprintf(wrl," NavigationInfo {\n"); + fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n"); + fprintf(wrl," } # We'll add our own light\n"); + fprintf(wrl,"\n"); + fprintf(wrl," DirectionalLight {\n"); + fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n"); + fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," Viewpoint {\n"); + fprintf(wrl," position 0 0 340 # Position we view from\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + if (doaxes != 0) { + fprintf(wrl,"# Lab axes as boxes:\n"); + for (i = 0; i < 5; i++) { + fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z); + fprintf(wrl,"\tchildren [\n"); + fprintf(wrl,"\t\tShape{\n"); + fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n", + axes[i].wx, axes[i].wy, axes[i].wz); + fprintf(wrl,"\t\t\tappearance Appearance { material Material "); + fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b); + fprintf(wrl,"\t\t}\n"); + fprintf(wrl,"\t]\n"); + fprintf(wrl,"}\n"); + } + fprintf(wrl,"\n"); + } + + return wrl; +} + +void +start_line_set(FILE *wrl) { + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); +} + +void add_vertex(FILE *wrl, double pp[3]) { + + fprintf(wrl,"%f %f %f,\n",pp[1], pp[2], pp[0]-GAMUT_LCENT); + + if (paloc < (npoints+1)) { + paloc = (paloc + 10) * 2; + if (pary == NULL) + pary = malloc(paloc * 3 * sizeof(double)); + else + pary = realloc(pary, paloc * 3 * sizeof(double)); + + if (pary == NULL) + error ("Malloc failed"); + } + pary[npoints].pp[0] = pp[0]; + pary[npoints].pp[1] = pp[1]; + pary[npoints].pp[2] = pp[2]; + npoints++; +} + + +void make_lines(FILE *wrl, int ppset) { + int i, j; + + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (i = 0; i < npoints;) { + for (j = 0; j < ppset; j++, i++) { + fprintf(wrl,"%d, ", i); + } + fprintf(wrl,"-1,\n"); + } + fprintf(wrl," ]\n"); + + /* Color */ + fprintf(wrl," colorPerVertex TRUE\n"); + fprintf(wrl," color Color {\n"); + fprintf(wrl," color [ # RGB colors of each vertex\n"); + + for (i = 0; i < npoints; i++) { + double rgb[3], Lab[3]; + Lab[0] = pary[i].pp[0]; + Lab[1] = pary[i].pp[1]; + Lab[2] = pary[i].pp[2]; + Lab2RGB(rgb, Lab); + fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]); + } + fprintf(wrl," ] \n"); + fprintf(wrl," }\n"); + /* End color */ + + fprintf(wrl," }\n"); + fprintf(wrl,"} # end shape\n"); + +} + +void end_vrml(FILE *wrl) { + + fprintf(wrl,"\n"); + fprintf(wrl," ] # end of children for world\n"); + fprintf(wrl,"}\n"); + + if (fclose(wrl) != 0) + error("Error closing VRML file\n"); +} + + +/* Convert a gamut Lab value to an RGB value for display purposes */ +static void +Lab2RGB(double *out, double *in) { + double L = in[0], a = in[1], b = in[2]; + double x,y,z,fx,fy,fz; + double R, G, B; + + /* Scale so that black is visible */ + L = L * (100 - 40.0)/100.0 + 40.0; + + /* First convert to XYZ using D50 white point */ + if (L > 8.0) { + fy = (L + 16.0)/116.0; + y = pow(fy,3.0); + } else { + y = L/903.2963058; + fy = 7.787036979 * y + 16.0/116.0; + } + + fx = a/500.0 + fy; + if (fx > 24.0/116.0) + x = pow(fx,3.0); + else + x = (fx - 16.0/116.0)/7.787036979; + + fz = fy - b/200.0; + if (fz > 24.0/116.0) + z = pow(fz,3.0); + else + z = (fz - 16.0/116.0)/7.787036979; + + x *= 0.9642; /* Multiply by white point, D50 */ + y *= 1.0; + z *= 0.8249; + + /* Now convert to sRGB values */ + R = x * 3.2410 + y * -1.5374 + z * -0.4986; + G = x * -0.9692 + y * 1.8760 + z * 0.0416; + B = x * 0.0556 + y * -0.2040 + z * 1.0570; + + if (R < 0.0) + R = 0.0; + else if (R > 1.0) + R = 1.0; + + if (G < 0.0) + G = 0.0; + else if (G > 1.0) + G = 1.0; + + if (B < 0.0) + B = 0.0; + else if (B > 1.0) + B = 1.0; + + R = pow(R, 1.0/2.2); + G = pow(G, 1.0/2.2); + B = pow(B, 1.0/2.2); + + out[0] = R; + out[1] = G; + out[2] = B; +} + diff --git a/xicc/monctest.c b/xicc/monctest.c new file mode 100644 index 0000000..29c9298 --- /dev/null +++ b/xicc/monctest.c @@ -0,0 +1,278 @@ + +/* + * Author: Graeme Gill + * Date: 30/10/2005 + * + * Copyright 2005 Graeme W. Gill + * Parts derived from rspl/c1.c, cv.c etc. + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Test monocurve class. + * + */ + +#undef DIAG +#undef TEST_SYM + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "plot.h" +#include "moncurve.h" + +double lin(double x, double xa[], double ya[], int n); +void usage(void); + +#define TRIALS 30 /* Number of random trials */ +#define SKIP 0 /* Number of random trials to skip */ +#undef NORMONLY /* Defined to use 0.0 - 1.0 limited curve */ + +#undef ORDER_STEP /* Step orders from 2 to SHAPE_ORDERS */ +#define SHAPE_ORDS 30 /* Number of order to use */ + +#define ABS_MAX_PNTS 100 + +#define MIN_PNTS 2 +#define MAX_PNTS 20 + +#define MIN_RES 20 +#define MAX_RES 500 + +double xa[ABS_MAX_PNTS]; +double ya[ABS_MAX_PNTS]; + +#define XRES 100 + +#define TSETS 3 +#define PNTS 11 +#define GRES 100 +int t1p[TSETS] = { + 4, + 11, + 11 +}; + +double t1xa[TSETS][PNTS] = { + { 0.0, 0.2, 0.8, 1.0 }, + { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }, + { 0.0, 0.25, 0.30, 0.35, 0.40, 0.44, 0.48, 0.51, 0.64, 0.75, 1.0 } +}; + +double t1ya[TSETS][PNTS] = { + { 0.0, 0.5, 0.6, 1.0 }, + { 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.8, 1.0, 1.0, 1.0 }, + { 0.0, 0.35, 0.4, 0.41, 0.42, 0.46, 0.5, 0.575, 0.48, 0.75, 1.0 } +}; + + +mcvco test_points[ABS_MAX_PNTS]; + +double lin(double x, double xa[], double ya[], int n); + +void +usage(void) { + puts("usage: monctest"); + exit(1); +} + +int main() { + mcv *p; + int i, n; + double x, y; + double xx[XRES]; + double y1[XRES]; + double y2[XRES]; + int np = SHAPE_ORDS; /* Number of harmonics */ + + error_program = "monctest"; + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + +#ifdef NORMONLY + if ((p = new_mcv_noos()) == NULL) +#else + if ((p = new_mcv()) == NULL) +#endif + error("new_mcv failed"); + + for (n = 0; n < TRIALS; n++) { + double lrand; /* Amount of level randomness */ + int pnts; + +#ifdef NEVER + if (n < TSETS) /* Standard versions */ { + pnts = t1p[n]; + for (i = 0; i < pnts; i++) { + xa[i] = t1xa[n][i]; + ya[i] = t1ya[n][i]; + } + } else if (n == TSETS) { /* Exponential function aproximation */ + double ex = 2.4; + pnts = MAX_PNTS; + + printf("Trial %d, no points = %d, exponential %f\n",n,pnts,ex); + + /* Create X values */ + for (i = 0; i < pnts; i++) + xa[i] = i/(pnts-1.0); + + for (i = 0; i < pnts; i++) + ya[i] = pow(xa[i], ex); + +#else /* Put exponenial first */ + if (n == 0) { /* Exponential function aproximation */ + double ex = 2.4; + pnts = MAX_PNTS; + + printf("Trial %d, no points = %d, exponential %f\n",n,pnts,ex); + + /* Create X values */ + for (i = 0; i < pnts; i++) + xa[i] = i/(pnts-1.0); + + for (i = 0; i < pnts; i++) + ya[i] = pow(xa[i], ex); + + } else if (n < (TSETS+1)) /* Standard versions */ { + pnts = t1p[n-1]; + for (i = 0; i < pnts; i++) { + xa[i] = t1xa[n-1][i]; + ya[i] = t1ya[n-1][i]; + } +#endif + + } else { /* Random versions */ + double ymax; + lrand = d_rand(0.0,0.2); /* Amount of level randomness */ + lrand *= lrand; + pnts = i_rand(MIN_PNTS,MAX_PNTS); + + printf("Trial %d, no points = %d, level randomness = %f\n",n,pnts,lrand); + + /* Create X values */ + xa[0] = 0.0; + for (i = 1; i < pnts; i++) + xa[i] = xa[i-1] + d_rand(0.5,1.0); + for (i = 0; i < pnts; i++) /* Divide out */ + xa[i] = (xa[i]/xa[pnts-1]); + + /* Create y values */ + ya[0] = xa[0] + d_rand(-0.1, 0.5); + for (i = 1; i < pnts; i++) + ya[i] = ya[i-1] + d_rand(0.1,1.0) + d_rand(-0.1,0.4) + d_rand(-0.4,0.5); + + ymax = d_rand(0.6, 10.2); /* Scale target */ + for (i = 0; i < pnts; i++) { + ya[i] = ymax * (ya[i]/ya[pnts-1]); +// if (ya[i] < 0.0) +// ya[i] = 0.0; +// else if (ya[i] > 1.0) +// ya[i] = 1.0; + } + } + + if (n < SKIP) + continue; + + for (i = 0; i < pnts; i++) { + test_points[i].p = xa[i]; + test_points[i].v = ya[i]; + test_points[i].w = 1.0; + } + /* Test weighting */ + test_points[pnts-1].w = 1.0; + +#ifdef ORDER_STEP + for (np = 2; np <= SHAPE_ORDS; np++) { +#else /* Full number of orders */ + for (np = SHAPE_ORDS; np <= SHAPE_ORDS; np++) { +#endif + + /* Fit to scattered data */ + p->fit(p, + 1, /* Vebose */ + np, /* Number of parameters */ + test_points, /* Test points */ + pnts, /* Number of test points */ + 1.0 /* Smoothing */ + ); + + printf("Residual = %f\n",p->resid); + printf("Number params = %d\n",np); + for (i = 0; i < p->luord; i++) { + printf("Param %d = %f\n",i,p->pms[i]); + } + + /* Display the result */ + for (i = 0; i < XRES; i++) { + x = i/(double)(XRES-1); + xx[i] = x; + y1[i] = lin(x,xa,ya,pnts); + y2[i] = p->interp(p, x); + y = p->inv_interp(p, y2[i]); + if (fabs(x - y) > 0.00001) + printf("Inverse mismatch: %f -> %f -> %f\n",x,y2[i],y); +// if (y2[i] < -0.2) +// y2[i] = -0.2; +// else if (y2[i] > 1.2) +// y2[i] = 1.2; + } + do_plot(xx,y1,y2,NULL,XRES); + } + + } /* next trial */ + + p->del(p); + + return 0; +} + +double lin( +double x, +double xa[], +double ya[], +int n) { + int i; + double y; + + if (x < xa[0]) + return ya[0]; + else if (x > xa[n-1]) + return ya[n-1]; + + for (i = 0; i < (n-1); i++) + if (x >=xa[i] && x <= xa[i+1]) + break; + + x = (x - xa[i])/(xa[i+1] - xa[i]); + + y = ya[i] + (ya[i+1] - ya[i]) * x; + + return y; + } + + + + + + + + + + + + + + diff --git a/xicc/moncurve.c b/xicc/moncurve.c new file mode 100644 index 0000000..b904420 --- /dev/null +++ b/xicc/moncurve.c @@ -0,0 +1,668 @@ + +/* + * Argyll Color Correction System + * Monotonic curve class for display calibration. + * + * Author: Graeme W. Gill + * Date: 30/10/2005 + * + * Copyright 2005 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * This is based on the monotonic curve equations used elsewhere, + * but currently intended to support the display calibration process. + * moncurve is not currently general, missing: + * + * input scaling + * output scaling + */ + +#undef DEBUG /* Input points */ +#undef DEBUG2 /* Detailed progress */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "moncurve.h" + +#define POWTOL 1e-5 /* Powell optimiser tollerance (was 1e-5 ?) */ +#define MAXITS 10000 + +#undef TEST_PDE /* Ckeck partial derivative calcs */ + +/* Normalization factors for an average data point error squared, scale 100 */ +#define HW01 0.002 /* 0 & 1 harmonic weights */ +#define HBREAK 4 /* Harmonic that has HWBR */ +#define HWBR 0.8 /* Base weight of harmonics HBREAK up */ +#define HWINC 0.5 /* Increase in weight for each harmonic above HWBR */ + +static void mcv_del(mcv *p); +static void mcv_fit(mcv *p, int verb, int order, mcvco *d, int ndp, double smooth); +static void mcv_force_0(mcv *p, double target); +static void mcv_force_1(mcv *p, double target); +static void mcv_force_scale(mcv *p, double target); +static int mcv_get_params(mcv *p, double **rp); +static double mcv_interp(struct _mcv *p, double in); +static double mcv_inv_interp(struct _mcv *p, double in); + +static double mcv_interp_p(struct _mcv *p, double *pms, double in); +static double mcv_shweight_p(mcv *p, double *v, double smooth); +double mcv_dinterp_p(mcv *p, double *pms, double *dv, double vv); +static double mcv_dshweight_p(mcv *p, double *v, double *dv, double smooth); + +/* Create a new, uninitialised mcv that will fit with offset and scale */ +/* (Note thate black and white points aren't allocated) */ +mcv *new_mcv(void) { + mcv *p; + + if ((p = (mcv *)calloc(1, sizeof(mcv))) == NULL) + return NULL; + + /* Init method pointers */ + p->del = mcv_del; + p->fit = mcv_fit; + p->force_0 = mcv_force_0; + p->force_1 = mcv_force_1; + p->force_scale = mcv_force_scale; + p->get_params = mcv_get_params; + p->interp = mcv_interp; + p->inv_interp = mcv_inv_interp; + p->interp_p = mcv_interp_p; + p->shweight_p = mcv_shweight_p; + p->dinterp_p = mcv_dinterp_p; + p->dshweight_p = mcv_dshweight_p; + + p->luord = 0; + p->pms = NULL; + return p; +} + +/* Create a new, uninitialised mcv without offset and scale parameters. */ +/* Note thate black and white points aren't allocated */ +mcv *new_mcv_noos(void) { + mcv *p; + + if ((p = new_mcv()) == NULL) + return p; + + p->noos = 2; + return p; +} + +/* Create a new mcv initiated with the given curve parameters */ +/* (Assuming parameters always includes offset and scale) */ +mcv *new_mcv_p(double *pp, int np) { + int i; + mcv *p; + + if ((p = new_mcv()) == NULL) + return p; + + p->luord = np; + if ((p->pms = (double *)calloc(p->luord, sizeof(double))) == NULL) + error ("Malloc failed"); + + for (i = 0; i < np; i++) + p->pms[i] = *pp++; + + return p; +} + +/* Delete an mcv */ +static void mcv_del(mcv *p) { + if (p->pms != NULL) + free(p->pms); + free(p); +} + +#ifdef TEST_PDE +#define mcv_opt_func mcv_opt_func_ +#endif + +/* Shaper+Matrix optimisation function handed to powell() */ +static double mcv_opt_func(void *edata, double *v) { + mcv *p = (mcv *)edata; + double totw = 0.0; + double ev = 0.0, rv, smv; + double out; + int i; + +#ifdef DEBUG2 + printf("params ="); + for (i = 0; i < p->luord-p->noos; i++) + printf(" %f",v[i]); + printf("\n"); + printf("ndp = %d\n",p->ndp); +#endif + + /* For all our data points */ + for (i = 0; i < p->ndp; i++) { + double del; + + /* Apply our function */ + out = p->interp_p(p, v, p->d[i].p); + + del = out - p->d[i].v; + + ev += p->d[i].w * del * del; + totw += p->d[i].w; + } + + /* Normalise error to be an average delta E squared */ + totw = (100.0 * 100.0)/(p->dra * p->dra * totw); + ev *= totw; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + smv = mcv_shweight_p(p, v, p->smooth); + rv = ev + smv; + +#ifdef DEBUG2 + printf("rv = %f (er %f + sm %f)\n",rv,ev,smv); +#endif + return rv; +} + +/* Shaper+Matrix optimisation function handed to conjgrad() */ +static double mcv_dopt_func(void *edata, double *dv, double *v) { + mcv *p = (mcv *)edata; + double totw = 0.0; + double ev = 0.0, rv, smv; + double out; + int i, j; + +#ifdef DEBUG2 + printf("params ="); + for (i = 0; i < (p->luord-p->noos); i++) + printf(" %f",v[i]); + printf("\n"); +#endif + + /* Zero the dv's */ + for (j = 0; j < (p->luord-p->noos); j++) + dv[j] = 0.0; + + /* For all our data points */ + for (i = 0; i < p->ndp; i++) { + double del; + + /* Apply our function with dv's */ + out = p->dinterp_p(p, v, p->dv, p->d[i].p); +//printf("~1 point %d: p %f, v %f, func %f\n",i,p->d[i].p,p->d[i].v,out); + + del = out - p->d[i].v; + ev += p->d[i].w * del * del; +//printf("~1 del %f, ev %f\n",del,ev); + + /* Sum the dv's */ + for (j = 0; j < (p->luord-p->noos); j++) { + dv[j] += p->d[i].w * 2.0 * del * p->dv[j]; +//printf("~1 dv[%d] = %f\n",j,dv[j]); + } + + totw += p->d[i].w; + } + +//printf("~1 totw = %f, dra = %f\n",totw, p->dra); + /* Normalise error to be an average delta E squared */ + totw = (100.0 * 100.0)/(p->dra * p->dra * totw); + ev *= totw; + for (j = 0; j < (p->luord-p->noos); j++) { + dv[j] *= totw; +//printf("~1 norm dv[%d] = %f\n",j,dv[j]); + } + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles", */ + /* with partial derivatives */ + smv = mcv_dshweight_p(p, v, dv, p->smooth); + rv = ev + smv; + +#ifdef DEBUG2 + printf("drv = %f (er %f + sm %f)\n",rv,ev,smv); +#endif + return rv; +} + +#ifdef TEST_PDE +/* Check partial derivative function */ + +#undef mcv_opt_func + +static double mcv_opt_func(void *edata, double *v) { + mcv *p = (mcv *)edata; + int i; + double dv[500]; + double rv, drv; + double trv; + + rv = mcv_opt_func_(edata, v); + drv = mcv_dopt_func(edata, dv, v); + + if (fabs(rv - drv) > 1e-6) + printf("######## RV MISMATCH is %f should be %f ########\n",rv,drv); + + /* Check each parameter delta */ + for (i = 0; i < (p->luord-p->noos); i++) { + double del; + + v[i] += 1e-7; + trv = mcv_opt_func_(edata, v); + v[i] -= 1e-7; + + /* Check that del is correct */ + del = (trv - rv)/1e-7; + if (fabs(dv[i] - del) > 0.04) { +//printf("~1 del = %f from (trv %f - rv %f)/0.1\n",del,trv,rv); + printf("######## EXCESSIVE at v[%d] is %f should be %f ########\n",i,dv[i],del); + } + } + return rv; +} +#endif /* TEST_PDE */ + +/* Fit the curve to the given points */ +static void mcv_fit(mcv *p, + int verb, /* Vebosity level, 0 = none */ + int order, /* Number of curve orders, 1..MCV_MAXORDER */ + mcvco *d, /* Array holding scattered initialisation data */ + int ndp, /* Number of data points */ + double smooth /* Degree of smoothing, 1.0 = normal */ +) { + int i; + double *sa; /* Search area */ + double *pms; /* Parameters to optimise */ + double min, max; + + p->verb = verb; + p->smooth = smooth; + p->luord = order+2; /* Add two for offset and scale */ + + if (p->pms != NULL) + free(p->pms); + if ((p->pms = (double *)calloc(p->luord, sizeof(double))) == NULL) + error ("Malloc failed"); + if ((pms = (double *)calloc(p->luord, sizeof(double))) == NULL) + error ("Malloc failed"); + if ((sa = (double *)calloc(p->luord, sizeof(double))) == NULL) + error ("Malloc failed"); + if ((p->dv = (double *)calloc(p->luord, sizeof(double))) == NULL) + error ("Malloc failed"); + +#ifdef DEBUG + printf("mcv_fit with %d points (noos = %d)\n",ndp,p->noos); +#endif + /* Establish the range of data values */ + min = 1e38; /* Locate min, and make that offset */ + max = -1e38; /* Locate max */ + for (i = 0; i < ndp; i++) { + if (d[i].v < min) + min = d[i].v; + if (d[i].v > max) + max = d[i].v; +#ifdef DEBUG + printf("point %d is %f %f\n",i,d[i].p,d[i].v); +#endif + } + + if (p->noos) { + p->pms[0] = min = 0.0; + p->pms[1] = max = 1.0; + } else { + /* Set offset and scale to reasonable values */ + p->pms[0] = min; + p->pms[1] = max - min; + } + p->dra = max - min; + if (p->dra <= 1e-12) + error("Mcv max - min %e too small",p->dra); + + /* Use powell to minimise the sum of the squares of the */ + /* input points to the curvem, plus a parameter damping factor. */ + p->d = d; + p->ndp = ndp; + + for (i = 0; i < p->luord; i++) + sa[i] = 0.2; + +#ifdef NEVER + if (powell(&p->resid, p->luord-p->noos, p->pms+p->noos, sa+p->noos, POWTOL, MAXITS, + mcv_opt_func, (void *)p, NULL, NULL) != 0) + error ("Mcv fit powell failed"); +#else + if (conjgrad(&p->resid, p->luord-p->noos, p->pms+p->noos, sa+p->noos, POWTOL, MAXITS, + mcv_opt_func, mcv_dopt_func, (void *)p, NULL, NULL) != 0) { +#ifndef NEVER + fprintf(stderr,"Mcv fit conjgrad failed with %d points:\n",ndp); + for (i = 0; i < ndp; i++) { + fprintf(stderr," %d: %f -> %f\n",i,d->p, d->v); + } +#endif + error ("Mcv fit conjgrad failed"); + } +#endif + + free(p->dv); + p->dv = NULL; + free(sa); + free(pms); +} + +/* The native values from the curve parameters are 0 - 1.0, */ +/* then the scale is applied, then the offset added, so the */ +/* output always ranges from (offset) to (offset + scale). */ + +/* Offset the the output so that the value for input 0.0, */ +/* is the given value. Don't change the output for 1.0 */ +void mcv_force_0( + mcv *p, + double target /* Target output value */ +) { + if (p->luord > 0) { + target -= p->pms[0]; /* Change */ + if (p->luord > 1) + p->pms[1] -= target; /* Adjust scale to leave 1.0 output untouched */ + p->pms[0] += target; /* Adjust offset */ + } +} + +/* Scale the the output so that the value for input 1.0, */ +/* is the given target value. Don't change the output for 0.0 */ +static void mcv_force_1( + mcv *p, + double target /* Target output value */ +) { + if (p->luord > 1) { + target -= p->pms[0]; /* Offset */ + p->pms[1] = target; /* Scale */ + } +} + +/* Scale the the output so that the value for input 1.0, */ +/* is the given target value. Scale the value for 0 in proportion. */ +static void mcv_force_scale( + mcv *p, + double target /* Target output value */ +) { + if (p->luord > 1) { + p->pms[0] *= target/(p->pms[0] + p->pms[1]); /* Offset */ + p->pms[1] = target - p->pms[0]; /* Scale */ + } +} + +/* Return the number of parameters and the parameters in */ +/* an allocated array. free() when done. */ +/* The parameters are the offset, scale, then all the other parameters */ +static int mcv_get_params(mcv *p, double **rp) { + double *pp; + int np, i; + + np = p->luord; + + if ((pp = (double *)malloc(np * sizeof(double))) == NULL) + error("mcb_get_params malloc failed"); + + *rp = pp; + + for (i = 0; i < np; i++) + *pp++ = p->pms[i]; + + return np; +} + +/* Translate a value through the curve */ +/* using the currently set pms */ +static double mcv_interp(struct _mcv *p, + double vv /* Input value */ +) { + return mcv_interp_p(p, p->pms + p->noos, vv); +} + +/* Translate a value through backwards the curve */ +static double mcv_inv_interp(struct _mcv *p, + double vv /* Input value */ +) { + double g; + int ord; + + /* Process everything in reverse order to mcv_interp */ + + if (p->noos == 0) { + /* Do order 0 & 1, the offset and scale */ + if (p->luord > 0) + vv -= p->pms[0]; + + if (p->luord > 1) + vv /= p->pms[1]; + } + + for (ord = p->luord-1; ord > 1; ord--) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = -p->pms[ord]; /* Inverse parameter */ + + nsec = ord-1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} + +/* Translate a value through the curve */ +/* using the given parameters */ +static double mcv_interp_p( + mcv *p, + double *pms, /* Parameters to use - may exclude offset and scale */ + double vv /* Input value */ +) { + double g; + int ord; + + /* Process all the shaper orders from low to high. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* are monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. */ + for (ord = (2 - p->noos); ord < (p->luord - p->noos); ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = pms[ord]; /* Parameter */ + + nsec = ord-1+p->noos; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + if (p->noos == 0) { + /* Do order 0 & 1 */ + if (p->luord > 1) + vv *= pms[1]; /* Scale */ + + if (p->luord > 0) + vv += pms[0]; /* Offset */ + } + + return vv; +} + +/* Return the shaper parameters regularizing weight */ +static double mcv_shweight_p( +mcv *p, +double *pms, /* Parameters to use - may exclude offset and scale */ +double smooth) { + + double smv; + int i; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + /* Note:- we start at 2, to skip offset and scale. */ + /* ?? Should these have a weight too ?? */ + smv = 0.0; + for (i = (2-p->noos); i < (p->luord-p->noos); i++) { + double w, tt; + int cx; /* Curve index (skips offset & scale) */ + + cx = i - 2 + p->noos; + tt = pms[i]; + + /* Weigh to suppress ripples */ + if (cx <= 1) { + w = HW01; + } else if (cx <= HBREAK) { + double bl = (cx - 1.0)/(HBREAK - 1.0); + w = (1.0 - bl) * HW01 + bl * HWBR; + } else { + w = HWBR + (cx-HBREAK) * HWINC * smooth; + } + tt *= tt; + smv += w * tt; + } + return smv; +} + +/* Transfer function with partial derivative */ +/* with respect to the given parameters. */ +double mcv_dinterp_p(mcv *p, +double *pms, /* Parameters to use - may exclude offset and scale */ +double *dv, /* Return derivative wrt each parameter - may exclude offset and scale */ +double vv /* Source of value */ +) { + double g; + int i, ord; + + /* Process all the shaper orders from low to high. */ + for (ord = (2-p->noos); ord < (p->luord-p->noos); ord++) { + double dsv; /* del for del in g */ + double ddv; /* del for del in vv */ + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = pms[ord]; /* Parameter */ + + nsec = ord-1+p->noos; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) { + g = -g; /* Alternate action in each section */ + } + vv -= sec; + if (g >= 0.0) { + double tt = g - g * vv + 1.0; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (g + 1.0)/(tt * tt); + vv = vv/tt; + } else { + double tt = 1.0 - g * vv; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (1.0 - g)/(tt * tt); + vv = (vv - g * vv)/tt; + } + + vv += sec; + vv /= (double)nsec; + dsv /= (double)nsec; + if (((int)sec) & 1) + dsv = -dsv; + + dv[ord] = dsv; + for (i = ord - 1; i >= (2-p->noos); i--) + dv[i] *= ddv; + } + + if (p->noos == 0) { + /* Do order 0, the scale */ + if (p->luord > 1) { + dv[1] = vv; + vv *= pms[1]; + } + if (p->luord > 0) { + dv[0] = 1.0; + vv += pms[0]; /* Offset */ + } + } + + return vv; +} + +/* Return the shaper parameters regularizing weight, */ +/* and add in partial derivatives. */ +/* Weight error and derivatrive by wht */ +static double mcv_dshweight_p( +mcv *p, +double *pms, /* Parameters to use - may exclude offset and scale */ +double *dpms, +double smooth) { + double smv; + int i; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles", */ + /* with partial derivatives */ + smv = 0.0; + for (i = (2-p->noos); i < (p->luord-p->noos); i++) { + double w, tt; + int cx; + + cx = i - 2 + p->noos; + tt = pms[i]; + + /* Weigh to suppress ripples */ + if (cx <= 1) { /* First or second curves */ + w = HW01; + } else if (cx <= HBREAK) { /* First or second curves */ + double bl = (cx - 1.0)/(HBREAK - 1.0); + w = (1.0 - bl) * HW01 + bl * HWBR; + } else { + w = HWBR + (cx-HBREAK) * HWINC * smooth; + } + dpms[i] += w * 2.0 * tt; + tt *= tt; + smv += w * tt; + } + + return smv; +} + + diff --git a/xicc/moncurve.h b/xicc/moncurve.h new file mode 100644 index 0000000..5510c7d --- /dev/null +++ b/xicc/moncurve.h @@ -0,0 +1,121 @@ + +#ifndef MCV_H +#define MCV_H + +/* + * Argyll Color Correction System + * Monotonic curve class for display calibration. + * + * Author: Graeme W. Gill + * Date: 30/10/2005 + * + * Copyright 2005 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * This is based on the monotonic curve equations used elsewhere, + * but currently intended to support the display calibration process. + * moncurve is not currently general, missing: + * + * input scaling + * output scaling + * + * The nominal input and output ranges are 0.0 to 1.0, + */ + +/* A test patch value */ +typedef struct { + double p; /* Position */ + double v; /* Value */ + double w; /* Weighting, nominally 1.0 */ +} mcvco; + +struct _mcv { + + /* Public: */ + void (*del)(struct _mcv *p); + + /* Fit the curve to the given points */ + void (*fit) (struct _mcv *p, + int verb, /* Vebosity level, 0 = none */ + int order, /* Number of curve orders, 1..MCV_MAXORDER */ + mcvco *d, /* Array holding scattered initialisation data */ + int ndp, /* Number of data points */ + double smooth /* Degree of smoothing, 1.0 = normal */ + ); + + /* Offset the the output so that the value for input 0.0, */ + /* is the given value. Don't change the 1.0 output */ + void (*force_0) (struct _mcv *p, + double target /* Target output value */ + ); + + /* Scale the the output so that the value for input 1.0, */ + /* is the given value. Don't change the 0.0 output */ + void (*force_1) (struct _mcv *p, + double target /* Target output value */ + ); + + /* Scale the the output so that the value for input 1.0, */ + /* is the given target value. Scale the value for 0.0 too. */ + void (*force_scale) (struct _mcv *p, + double target /* Target output value */ + ); + + /* Return the number of parameters and the parameters in */ + /* an allocated array. free() when done. */ + /* The parameters are the offset, scale, then all the other parameters */ + int (*get_params)(struct _mcv *p, double **rp); + + /* Translate a value through the current curve */ + double (*interp) (struct _mcv *p, + double in); /* Input value */ + + /* Translate a value backwards through the current curve */ + double (*inv_interp) (struct _mcv *p, + double in); /* Input value */ + + + /* Translate a value given the parametrs */ + double (*interp_p) (struct _mcv *p, + double *pms, double in); /* Input value */ + + /* return the shaper parameters normalising weight */ + double (*shweight_p)(struct _mcv *p, double *v, double smooth); + + /* Translate a value given the parametrs, with partial derivatives */ + double (*dinterp_p) (struct _mcv *p, double *pms, double *dv, double vv); + + /* return the shaper parameters normalising weight, with partial derivatives */ + double (*dshweight_p)(struct _mcv *p, double *v, double *dv, double smooth); + + + /* Private: */ + int verb; /* Verbose */ + int noos; /* flag, 2 = offset and scale not fitted */ + int luord; /* Lookup order including offset and scale */ + double *pms; /* Allocated curve parameters */ + double *dv; /* Work space for dv's during optimisation */ + double resid; /* Residual fit error */ + + mcvco *d; /* Array holding scattered initialisation data */ + int ndp; /* Number of data points */ + double dra; /* Data range */ + + double smooth; /* Smoothing factor */ + +}; typedef struct _mcv mcv; + +/* Create a new, uninitialised mcv that will fit with offset and scale */ +mcv *new_mcv(void); + +/* Create a new mcv initiated with the given curve parameters */ +mcv *new_mcv_p(double *pp, int np); + +/* Create a new, uninitialised mcv with offset and scale not to be fitted, */ +/* and defaulting to 0.0 and 1.0 */ +mcv *new_mcv_noos(void); + +#endif /* MCV */ + diff --git a/xicc/mpp.c b/xicc/mpp.c new file mode 100644 index 0000000..02b3019 --- /dev/null +++ b/xicc/mpp.c @@ -0,0 +1,4446 @@ + +/* + * Argyll Color Correction System + * Model Printer Profile object. + * + * Author: Graeme W. Gill + * Date: 24/2/2002 + * + * Copyright 2003 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* This version (based on mpp_x1) has n * 2^(n-1) shape params, */ +/* used for linear interpolation of the shaping correction. */ + +/* + * This program takes in the scattered test chart + * points, and creates a model based forward printer profile + * (Device -> CIE + spectral), based on heuristic extensions to + * the Neugenbauer model. + * It is designed to handle an arbitrary number of colorants, + * and in the future, (optionaly) create an aproximate ink overlap/mixing model + * to allow synthesis of a forward model for a hyperthetical + * similar printing process with additional inks. + * + * The model is as follows: + * + * A per input channel transfer curve, that models + * per channel dot gain, transfer curve processing etc. + * + * A per input channel shape modification model. + * Each transfer curve adjusted value is further + * adjusted in a way that depends on the combination + * of value of all the other input channels. + * This allows for ink interaction effects in a way + * that should allow good conformance to all combinations + * of input values at 50% values. + * + * An n-linear interpolation between all combination of + * the 0 and 100% colorant primary combinations (Neugenbauer). + * + * The model making can be rather slow, particularly if high + * quality, large number of colorants, large number of sample + * points. + * + * This code is based on profile.c, sprof.c and xlut.c + * + */ + +/* + * TTBD: + * + * !!!! Should change device transfer model to include offset & scale, + * to better match display & other devices !!!! + * + * Should add Jab pcs mode, so that Jab gamuts can be + * written. + * + * Remove #ifndef DEBUG & replace with verbose progress bars. + * + * Add support for extra profile details to create() to support + * profxinf like stuff. + * + * Add ink order and overlay modeling stuff back in, with + * new ink overlay model (see mpprof0.c). + * + * Rather than computing XYZ based versios of the print model + * and ink mixing models, should compute spectrally sharpened + * equivalents to XYZ ?? (The spectral CIE base values don't + * seem much more accurate than XYZ, so perhaps remove SHARPEN code ?). + * + * Need to cleanup error handling. + */ + +#define VERSION "1.0" + +#undef DEBUG +#undef TESTDFUNC /* Check delta functions */ +#undef NODDV /* Use (very slow) non d/dv powell */ +#undef DOPLOT /* Plot the device curves */ + +#undef NOPROCESS /* Define to skip all fitting */ +#define MULTIPASS /* Fit passes by parts (normal mode) */ +#undef BIGBANG /* Define to fit all parameters at once */ +#undef ISHAPE /* define to try SVD init of shape parameters */ +#undef SHARPEN /* use sharpened XYZ values for modeling */ + /* (Wrecks derivative lookup at the moment, and makes */ + /* accuracy worse!) */ + + /* Transfer curve parameter (wiggle) minimisation weight */ +#define TRANS_BASE 0.2 /* 0 & 1 harmonic parameter weight */ +#define TRANS_HBASE 0.8 /* 2nd harmonic and above base parameter weight */ +#define SHAPE_PMW 0.2 /* Shape parameter (wiggle) minimisation weight */ +#define COMB_PMW 0.008 /* Primary combination anchor point distance weight */ + +#define verbo stdout + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <sys/types.h> +#include <time.h> +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "numlib.h" +#include "cgats.h" +#include "icc.h" +#include "xicc.h" /* Spectral support */ +#include "xspect.h" /* Spectral support */ +#include "xcolorants.h" /* Known colorants support */ +#include "insttypes.h" +#include "gamut.h" +#include "mpp.h" +#ifdef DOPLOT +#include "plot.h" +#endif /* DOPLOT */ + +/* Forward declarations */ +static double bandval(mpp *p, int band, double *dev); +static double dbandval(mpp *p, double *dv, int band, double *dev); +static void forward(mpp *p, double *spec, double *Lab, double *XYZ, double *dev); +static int create(mpp *p, int verb, int quality, int display, double limit, inkmask devmask, + int spec_n, double spec_wl_short, double spec_wl_long, + double norm, instType itype, int nodp, mppcol *points); +static void compute_wb(mpp *p); +static void init_shape(mpp *p); + +/* Utilities */ + +#ifdef SHARPEN +/* Convert from XYZ to spectrally sharpened response */ +/* (Can this generate -ve values for real colors ??) */ +static void XYZ2sharp(double *a, double *b, double *c) { + double xyz[3], rgb[3]; + + xyz[0] = *a; + xyz[1] = *b; + xyz[2] = *c; + + rgb[0] = 0.8562 * xyz[0] + 0.3372 * xyz[1] - 0.1934 * xyz[2]; + rgb[1] = -0.8360 * xyz[0] + 1.8327 * xyz[1] + 0.0033 * xyz[2]; + rgb[2] = 0.0357 * xyz[0] - 0.0469 * xyz[1] + 1.0112 * xyz[2]; + + *a = rgb[0]; + *b = rgb[1]; + *c = rgb[2]; +} + +/* Convert from spectrally sharpened response to XYZ */ +static void sharp2XYZ(double *a, double *b, double *c) { + double xyz[3], rgb[3]; + + rgb[0] = *a; + rgb[1] = *b; + rgb[2] = *c; + + xyz[0] = 0.9873999149199270 * rgb[0] + - 0.1768250198556842 * rgb[1] + + 0.1894251049357572 * rgb[2]; + xyz[1] = 0.4504351090445316 * rgb[0] + + 0.4649328977527109 * rgb[1] + + 0.0846319932027575 * rgb[2]; + xyz[2] = -0.0139683251072516 * rgb[0] + + 0.0278065725014340 * rgb[1] + + 0.9861617526058175 * rgb[2]; + + *a = xyz[0]; + *b = xyz[1]; + *c = xyz[2]; +} +#endif /* SHARPEN */ + + +/* Method implimentations */ + +/* Write out the mpp to a CGATS format .mpp file */ +/* Return nz on error */ +static int write_mpp( +mpp *p, /* This */ +char *outname, /* Filename to write to */ +int dolab /* If NZ, write Lab values rather than XYZ */ +) { + int i, j, n; + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *ocg; /* CGATS structure */ + char *ident = icx_inkmask2char(p->imask, 1); + int nsetel = 0; + cgats_set_elem *setel; /* Array of set value elements */ + char buf[100]; + + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "MPP"); /* our special type is Model Printer Profile */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Model Printer Profile, Colorant linearisation",NULL); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll mpp", NULL); + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + if (p->display) + ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */ + else { + ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */ + + /* Note what instrument the chart was read with, in case we */ + /* want to apply FWA when converting spectral data to CIE */ + ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(p->itype) , NULL); + + sprintf(buf,"%5.1f",p->limit * 100.0); + ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT", buf, NULL); + } + + ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL); + + /* Record how many factors are used in device channel transfer curve */ + sprintf(buf,"%d",p->cord); + ocg->add_kword(ocg, 0, "TRANSFER_ORDERS", buf, NULL); + + /* Record if shaper parameters are being used */ + if (p->useshape) { + ocg->add_kword(ocg, 0, "USE_SHAPER", "YES", NULL); + } else { + ocg->add_kword(ocg, 0, "USE_SHAPER", "NO", NULL); + } + + /* Setup the table, which holds all the model parameters. */ + /* There is always a parameter per X Y Z or spectral band */ + ocg->add_field(ocg, 0, "PARAMETER", nqcs_t); + if (dolab) { + ocg->add_field(ocg, 0, "LAB_L", r_t); + ocg->add_field(ocg, 0, "LAB_A", r_t); + ocg->add_field(ocg, 0, "LAB_B", r_t); + } else { + ocg->add_field(ocg, 0, "XYZ_X", r_t); + ocg->add_field(ocg, 0, "XYZ_Y", r_t); + ocg->add_field(ocg, 0, "XYZ_Z", r_t); + } + nsetel = 1 + 3; + + /* Add fields for spectral values */ + if (p->spec_n > 0) { + + nsetel += p->spec_n; /* Spectral values */ + sprintf(buf,"%d", p->spec_n); + ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL); + sprintf(buf,"%f", p->spec_wl_short); + ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL); + sprintf(buf,"%f", p->spec_wl_long); + ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL); + sprintf(buf,"%f", p->norm * 100.0); + ocg->add_kword(ocg, 0, "SPECTRAL_NORM",buf, NULL); + + /* Generate fields for spectral values */ + for (i = 0; i < p->spec_n; i++) { + int nm; + + /* Compute nearest integer wavelength */ + nm = (int)(p->spec_wl_short + ((double)i/(p->spec_n-1.0)) + * (p->spec_wl_long - p->spec_wl_short) + 0.5); + + sprintf(buf,"SPEC_%03d",nm); + ocg->add_field(ocg, 0, buf, r_t); + } + } + + if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) { + free(ident); + sprintf(p->err,"write_mpp: malloc of setel failed"); + return 1; + } + + /* Write out the transfer curve values */ + for (i = 0; i < p->n; i++) { /* each colorant */ + for (j = 0; j < p->cord; j++) { /* curve order values */ + + sprintf(buf,"t_%d_%d",i,j); + setel[0].c = buf; /* Parameter identifier */ + + for (n = 0; n < (3+p->spec_n); n++) + setel[1+n].d = p->tc[i][n][j]; + ocg->add_setarr(ocg, 0, setel); + } + } + + if (p->useshape) { + + /* Write out the shaper values */ + for (i = 0; i < p->nnn2; i++) { /* For all shaper values */ + int m = p->c2f[i].ink; + int k = p->c2f[i].comb; + + sprintf(buf,"s_%d_%d",m, k); + setel[0].c = buf; /* Parameter identifier */ + + for (n = 0; n < (3+p->spec_n); n++) + setel[1+n].d = p->shape[m][k][n]; + + ocg->add_setarr(ocg, 0, setel); + } + } + + /* Write out the colorant combination values */ + for (i = 0; i < p->nn; i++) { + + sprintf(buf,"c_%d",i); + setel[0].c = buf; /* Parameter identifier */ + + for (n = 0; n < (3+p->spec_n); n++) + setel[1+n].d = p->pc[i][n]; + +#ifdef SHARPEN + sharp2XYZ(&setel[1+0].d, &setel[1+1].d, &setel[1+2].d); +#endif + if (dolab) { + double ttt[3]; + ttt[0] = setel[1+0].d, ttt[1] = setel[1+1].d, ttt[2] = setel[1+2].d; + icmXYZ2Lab(&icmD50, ttt, ttt); + setel[1+0].d = ttt[0], setel[1+1].d = ttt[1], setel[1+2].d = ttt[2]; + } + ocg->add_setarr(ocg, 0, setel); + } + free(setel); + free(ident); + + /* Write it */ + if (ocg->write_name(ocg, outname)) { + strcpy(p->err, ocg->err); + return 1; + } + + ocg->del(ocg); /* Clean up */ + + return 0; +} + +/* Read in the mpp CGATS .mpp file */ +/* Return nz on error */ +static int read_mpp( +mpp *p, /* This */ +char *inname /* Filename to read from */ +) { + int i, j, n, ix; + cgats *icg; /* input cgats structure */ + int ti; /* Temporary CGATs index */ + int islab = 0; /* nz if Lab parameters */ + + /* Open and look at the .mpp model printer profile */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + sprintf(p->err, "read_mpp: new_cgats() failed"); + return 2; + } + icg->add_other(icg, "MPP"); /* our special type is Model Printer Profile */ + + if (icg->read_name(icg, inname)) { + strcpy(p->err, icg->err); + icg->del(icg); + return 1; + } + + if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0) { + sprintf(p->err, "read_mpp: Input file '%s' isn't a MPP format file",inname); + icg->del(icg); + return 1; + } + if (icg->ntables != 1) { + sprintf(p->err, "Input file '%s' doesn't contain exactly one table",inname); + icg->del(icg); + return 1; + } + if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain keyword COLOR_REP",inname); + icg->del(icg); + return 1; + } + + p->imask = icx_char2inkmask(icg->t[0].kdata[ti]); + p->n = icx_noofinks(p->imask); + p->nn = 1 << p->n; + p->nnn2 = p->n * p->nn/2; + + if (p->n == 0) { + sprintf(p->err, "read_mpp: COLOR_REP '%s' invalid from file '%s' (No matching devmask)", + icg->t[0].kdata[ti], inname); + icg->del(icg); + return 1; + } + + /* See if it is the expected device class */ + if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain keyword DEVICE_CLASS",inname); + icg->del(icg); + return 1; + } + if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) { + + if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) { + double imax; + imax = atof(icg->t[0].kdata[ti]); + p->limit = imax/100.0; + } else { + p->limit = 0.0; /* Don't use ink limit */ + } + + if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0) { + sprintf(p->err, "read_mpp: Can't find keyword TARGET_INSTRUMENT in file '%s'", inname); + icg->del(icg); + return 1; + } + + if ((p->itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown + && icg->find_kword(icg, 0, "SPECTRAL_BANDS") >= 0) { + sprintf(p->err, "read_mpp: Unrecognised target instrument '%s' in file '%s'", + icg->t[0].kdata[ti], inname); + icg->del(icg); + return 1; + } + + p->display = 0; + + } else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) { + + p->display = 1; + p->itype = instUnknown; + p->limit = p->n; + + } else { + /* Don't know anything else at the moment */ + sprintf(p->err, "read_mpp: Input file '%s' has unknown DEVICE_CLASS '%s'", + inname, icg->t[0].kdata[ti]); + icg->del(icg); + return 1; + } + + /* Read the number of device linearisation orders */ + if ((ti = icg->find_kword(icg, 0, "TRANSFER_ORDERS")) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain keyword TRANSFER_ORDERS", + inname); + icg->del(icg); + return 1; + } + p->cord = atoi(icg->t[0].kdata[ti]); + if (p->cord < 1 || p->cord > MPP_MXTCORD) { + sprintf(p->err, "read_mpp: Input file '%s' has out of range TRANSFER_ORDERS %d", + inname, p->cord); + icg->del(icg); + return 1; + } + + /* See if shaper parameters are used */ + p->useshape = 0; + if ((ti = icg->find_kword(icg, 0, "USE_SHAPER")) >= 0) { + if(strcmp(icg->t[0].kdata[ti], "YES") == 0) + p->useshape = 1; + } + + /* Read the model parameters */ + { + int ci; /* Parameter dentified index */ + int spi[3+MPP_MXBANDS]; /* CGATS indexes for each band */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" }; + char buf[100]; + + /* See if we have spectral information available */ + if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0) { + p->spec_n = 0; /* None */ + } else { + if ((ti = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0) + error ("Input file doesn't contain keyword SPECTRAL_BANDS"); + p->spec_n = atoi(icg->t[0].kdata[ti]); + if ((ti = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0) + error ("Input file doesn't contain keyword SPECTRAL_START_NM"); + p->spec_wl_short = atof(icg->t[0].kdata[ti]); + if ((ti = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0) + error ("Input file doesn't contain keyword SPECTRAL_END_NM"); + p->spec_wl_long = atof(icg->t[0].kdata[ti]); + if ((ti = icg->find_kword(icg, 0, "SPECTRAL_NORM")) < 0) + error ("Input file doesn't contain keyword SPECTRAL_NORM"); + p->norm = atof(icg->t[0].kdata[ti])/100.0; + } + + if ((new_mppcol(&p->white, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + if ((new_mppcol(&p->black, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + if ((new_mppcol(&p->kblack, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + init_shape(p); /* Allocate and init shape related parameter space */ + + /* Get the field indexes */ + if ((ci = icg->find_field(icg, 0, "PARAMETER")) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain field PARAMETER", + inname); + icg->del(icg); + return 1; + } + if (icg->t[0].ftype[ci] != nqcs_t) { + sprintf(p->err, "read_mpp: Input file '%s' field PARAMETER is wrong type", + inname); + icg->del(icg); + return 1; + } + + for (i = 0; i < 3; i++) { /* XYZ fields */ + if ((spi[i] = icg->find_field(icg, 0, xyzfname[i])) < 0) { + break; + } + if (icg->t[0].ftype[spi[i]] != r_t) { + sprintf(p->err, "read_mpp: Input file '%s' field %s is wrong type", + inname, buf); + icg->del(icg); + return 1; + } + } + + if (i < 3) { + islab = 1; + for (i = 0; i < 3; i++) { /* XYZ fields */ + if ((spi[i] = icg->find_field(icg, 0, labfname[i])) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain field %s or %s", + inname, xyzfname[i], labfname[i]); + icg->del(icg); + return 1; + } + if (icg->t[0].ftype[spi[i]] != r_t) { + sprintf(p->err, "read_mpp: Input file '%s' field %s is wrong type", + inname, buf); + icg->del(icg); + return 1; + } + } + } + + /* Find the fields for spectral values */ + if (p->spec_n > 0) { + for (j = 0; j < p->spec_n; j++) { + int nm; + + /* Compute nearest integer wavelength */ + nm = (int)(p->spec_wl_short + ((double)j/(p->spec_n-1.0)) + * (p->spec_wl_long - p->spec_wl_short) + 0.5); + + sprintf(buf,"SPEC_%03d",nm); + + if ((spi[3+j] = icg->find_field(icg, 0, buf)) < 0) { + sprintf(p->err, "read_mpp: Input file '%s' doesn't contain field %s", + buf,inname); + icg->del(icg); + return 1; + } + if (icg->t[0].ftype[spi[3+j]] != r_t) { + sprintf(p->err, "read_mpp: Input file '%s' field %s is wrong type", + inname, buf); + icg->del(icg); + return 1; + } + } + } + + /* Read the transfer curve values */ + for (i = 0; i < p->n; i++) { /* each colorant */ + for (j = 0; j < p->cord; j++) { /* curve order values */ + + sprintf(buf,"t_%d_%d",i,j); + + /* Find the right parameter */ + for (ix = 0; ix < icg->t[0].nsets; ix++) { + + if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) { + for (n = 0; n < (3+p->spec_n); n++) + p->tc[i][n][j] = *((double *)icg->t[0].fdata[ix][spi[n]]); + break; + } + } + } + } + + if (p->useshape) { + + /* Read the shaper values */ + for (i = 0; i < p->nnn2; i++) { /* For all shaper values */ + int m = p->c2f[i].ink; + int k = p->c2f[i].comb; + + sprintf(buf,"s_%d_%d",m, k); + + /* Find the right parameter */ + for (ix = 0; ix < icg->t[0].nsets; ix++) { + + if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) { + for (n = 0; n < (3+p->spec_n); n++) + p->shape[m][k][n] = *((double *)icg->t[0].fdata[ix][spi[n]]); + + break; + } + } + } + } + + /* Read the combination values */ + for (i = 0; i < p->nn; i++) { + + sprintf(buf,"c_%d",i); + + + /* Find the right parameter */ + for (ix = 0; ix < icg->t[0].nsets; ix++) { + + if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) { + for (n = 0; n < (3+p->spec_n); n++) + p->pc[i][n] = *((double *)icg->t[0].fdata[ix][spi[n]]); + if (islab) { + double tt[3]; + tt[0] = p->pc[i][0], tt[1] = p->pc[i][1], tt[2] = p->pc[i][2]; + icmLab2XYZ(&icmD50, tt, tt); + p->pc[i][0] = tt[0], p->pc[i][1] = tt[1], p->pc[i][2] = tt[2]; + } +#ifdef SHARPEN + XYZ2sharp(&p->pc[i][0], &p->pc[i][1], &p->pc[i][2]); +#endif + break; + } + } + } + } + + icg->del(icg); /* Clean up */ + + /* Figure out the white and black points */ + compute_wb(p); + + return 0; +} + +/* Get various types of information about the mpp */ +static void get_info( +mpp *p, /* This */ +inkmask *imask, /* Inkmask, describing device colorspace */ +int *nodchan, /* Number of device channels */ +double *limit, /* Total ink limit (0.0 .. devchan) */ +int *spec_n, /* Number of spectral bands, 0 if none */ +double *spec_wl_short, /* First reading wavelength in nm (shortest) */ +double *spec_wl_long, /* Last reading wavelength in nm (longest) */ +instType *itype, /* Instrument type */ +int *display /* Return nz if display type */ +) { + if (imask != NULL) + *imask = p->imask; + if (nodchan != NULL) + *nodchan = p->n; + if (limit != NULL) + *limit = p->limit; + if (spec_n != NULL) + *spec_n = p->spec_n; + if (spec_wl_short != NULL) + *spec_wl_short = p->spec_wl_short; + if (spec_wl_long != NULL) + *spec_wl_long = p->spec_wl_long; + if (itype != NULL) + *itype = p->itype; + if (display != NULL) + *display = p->display; +} + +/* Set an illuminant and observer to use spectral model */ +/* for CIE lookup with optional FWA. Set both to default for XYZ mpp model. */ +/* return 0 on OK, 1 on spectral not supported, 2 on other error */ +/* If the model is for a display, the illuminant will be ignored. */ +static int set_ilob( +mpp *p, +icxIllumeType ilType, /* Illuminant type (icxIT_default for none) */ +xspect *custIllum, /* Custom illuminant (NULL for none) */ +icxObserverType obType, /* Observer type (icxOT_default for none) */ +xspect custObserver[3], /* Custom observer (NULL for none) */ +icColorSpaceSignature rcs, /* Return color space, icSigXYZData or icSigLabData */ +int use_fwa /* NZ to involke FWA. */ +) { + + /* Get rid of any existing conversion object */ + if (p->spc != NULL) { + p->spc->del(p->spc); + p->spc = NULL; + } + + p->pcs = rcs; + + if (ilType == icxIT_default && obType == icxOT_default && use_fwa == 0) + return 0; + + if (p->spec_n == 0) { + p->errc = 1; + sprintf(p->err,"No Spectral Data in MPP"); + return 1; + } + + if (p->display) { + ilType = icxIT_none; + custIllum = NULL; + } + + if ((p->spc = new_xsp2cie(ilType, custIllum, obType, custObserver, rcs, 1)) == NULL) + error("mpp->set_ilob, new_xsp2cie failed"); + + if (use_fwa) { + int j; + xspect white, inst; + + white.norm = p->norm; + white.spec_n = p->spec_n; + white.spec_wl_short = p->spec_wl_short; + white.spec_wl_long = p->spec_wl_long; + for (j = 0; j < p->spec_n; j++) + white.spec[j] = p->white.band[3+j]; + + if (inst_illuminant(&inst, p->itype) != 0) + error ("mpp->set_ilob, instrument doesn't have an FWA illuminent"); + + if (p->spc->set_fwa(p->spc, &inst, NULL, &white)) + error ("mpp->set_ilob, set_fwa faild"); + } + + return 0; +} + +/* Lookup an XYZ or Lab color */ +static void lookup( +mpp *p, /* This */ +double *out, /* Returned XYZ or Lab */ +double *in /* Input device values */ +) { + if (p->spc == NULL) { + double *Lab; + double *XYZ; + + if (p->pcs == icSigLabData) { + Lab = out; + XYZ = NULL; + } else { + Lab = NULL; + XYZ = out; + } + + forward(p, NULL, Lab, XYZ, in); + + return; + } else { + xspect tspec; + + p->lookup_spec(p, &tspec, in); + p->spc->convert(p->spc, out, &tspec); + } +} + +/* Lookup an XYZ or Lab color, plus the partial derivative. */ +/* This is useful if the lookup is being used within */ +/* a minimisation routine */ +static void dlookup( +mpp *p, +double *out, /* Return the XYZ or Lab */ +double **dout, /* Return the partial derivative dout[3][n] */ +double *dev) { + int i, j, k; + +#ifdef SHARPEN + /* Can't handle this without converting from sharpened derivative */ + /* to XYZ derivative. ~~~~~~ */ +#endif + /* We can't handle derivative using FWA, so */ + /* always compute from the XYZ model values. */ + for (j = 0; j < 3; j++) { /* Compute each bands value */ + out[j] = dbandval(p, dout[j], j, dev); + } + if (p->pcs == icSigLabData) { + double dlab[3][3]; + + icxdXYZ2Lab(&icmD50, out, dlab, out); + + /* Apply Lab deriv to dout */ + for (i = 0; i < p->n; i++) { + double tt[3]; + + for (k = 0; k < 3; k++) + tt[k] = dout[k][i]; + + for (j = 0; j < 3; j++) { + dout[j][i] = 0.0; + + for (k = 0; k < 3; k++) { + dout[j][i] += dlab[j][k] * tt[k]; + } + } + } + } +} + + +/* Get the white and black points */ +static void get_wb( +mpp *p, /* This */ +double *white, +double *black, +double *kblack /* K only black */ +) { + if (white != NULL) { + if (p->spc == NULL) { + white[0] = p->white.band[0]; + white[1] = p->white.band[1]; + white[2] = p->white.band[2]; +#ifdef SHARPEN + sharp2XYZ(&white[0], &white[1], &white[2]); +#endif + if (p->pcs == icSigLabData) + icmXYZ2Lab(&icmD50, white, white); + } else { + int j; + xspect ispect; + + ispect.norm = p->norm; + ispect.spec_n = p->spec_n; + ispect.spec_wl_short = p->spec_wl_short; + ispect.spec_wl_long = p->spec_wl_long; + for (j = 0; j < p->spec_n; j++) + ispect.spec[j] = p->white.band[3+j]; + + p->spc->convert(p->spc, white, &ispect); + } + } + if (black != NULL) { + if (p->spc == NULL) { + black[0] = p->black.band[0]; + black[1] = p->black.band[1]; + black[2] = p->black.band[2]; +#ifdef SHARPEN + sharp2XYZ(&black[0], &black[1], &black[2]); +#endif + if (p->pcs == icSigLabData) + icmXYZ2Lab(&icmD50, black, black); + } else { + int j; + xspect ispect; + + ispect.norm = p->norm; + ispect.spec_n = p->spec_n; + ispect.spec_wl_short = p->spec_wl_short; + ispect.spec_wl_long = p->spec_wl_long; + for (j = 0; j < p->spec_n; j++) + ispect.spec[j] = p->black.band[3+j]; + + p->spc->convert(p->spc, black, &ispect); + } + } + + if (kblack != NULL) { + if (p->spc == NULL) { + kblack[0] = p->kblack.band[0]; + kblack[1] = p->kblack.band[1]; + kblack[2] = p->kblack.band[2]; +#ifdef SHARPEN + sharp2XYZ(&kblack[0], &kblack[1], &kblack[2]); +#endif + if (p->pcs == icSigLabData) + icmXYZ2Lab(&icmD50, kblack, kblack); + } else { + int j; + xspect ispect; + + ispect.norm = p->norm; + ispect.spec_n = p->spec_n; + ispect.spec_wl_short = p->spec_wl_short; + ispect.spec_wl_long = p->spec_wl_long; + for (j = 0; j < p->spec_n; j++) + ispect.spec[j] = p->kblack.band[3+j]; + + p->spc->convert(p->spc, kblack, &ispect); + } + } +} + +/* Lookup an XYZ value. */ +/* (Note that this is never FWA compensated) */ +static void lookup_xyz( +mpp *p, /* This */ +double *out, /* Returned XYZ value */ +double *in /* Input device values */ +) { + forward(p, NULL, NULL, out, in); +} + +/* Lookup a spectral value. */ +/* (Note that this is never FWA compensated) */ +static void lookup_spec( +mpp *p, /* This */ +xspect *out, /* Returned spectral value */ +double *in /* Input device values */ +) { + int j; + + out->norm = p->norm; + out->spec_n = p->spec_n; + out->spec_wl_short = p->spec_wl_short; + out->spec_wl_long = p->spec_wl_long; + + forward(p, out->spec, NULL, NULL, in); + + for (j = 0; j < p->spec_n; j++) + out->spec[j] *= out->norm; +} + +/* Macros for an arbitrary dimensional counter */ +/* Declare the counter name nn, dimensions di, & count */ + +#define DCOUNT(nn, di, start, reset, count) \ + int nn[MAX_CHAN]; /* counter value */ \ + int nn##_di = (di); /* Number of dimensions */ \ + int nn##_stt = (start); /* start count value */ \ + int nn##_rst = (reset); /* reset on carry value */ \ + int nn##_res = (count); /* last count +1 */ \ + int nn##_e /* dimension index */ + +/* Set the counter value to 0 */ +#define DC_INIT(nn) \ +{ \ + for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) \ + nn[nn##_e] = nn##_stt; \ + nn##_e = 0; \ +} + +/* Increment the counter value */ +#define DC_INC(nn) \ +{ \ + for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) { \ + nn[nn##_e]++; \ + if (nn[nn##_e] < nn##_res) \ + break; /* No carry */ \ + nn[nn##_e] = nn##_rst; \ + } \ +} + +/* After increment, expression is TRUE if counter is done */ +#define DC_DONE(nn) \ + (nn##_e >= nn##_di) + +/* Create a gamut */ +/* (This will be in the current PCS) */ +static gamut *mpp_gamut( +mpp *p, /* This */ +double detail /* gamut detail level, 0.0 = def */ +) { + gamut *gam; + int res; /* Sample point resolution */ + double white[3], black[3], kblack[3]; + DCOUNT(co, p->n, 0, 0, 2); + + if (detail == 0.0) + detail = 10.0; + + gam = new_gamut(detail, 0, 0); /* Lab only at the moment */ + + /* Explore the gamut by itterating through */ + /* it with sample points in device space. */ + + res = (int)(100.0/detail); /* Establish an appropriate sampling density */ + + if (res < 3) + res = 3; + + DC_INIT(co); + + /* Itterate over all the 2 faces in the device space. */ + /* We're assuming in practice that only colors lying */ + /* on such faces contribute to the gamut volume. */ + /* The total number of faces explored will be */ + /* (dim * (dim-1))/2 * 2 ^ (dim-2) */ + /* It seems possible that a number of these faces could */ + /* be cheaply culled by examining the PCS corner values */ + /* for each 2^(dim-2) combinations, and skiping those. */ + /* that cannot possibly expand the gamut ? */ + /* ie. that they are clearly contained by other face combinations. */ + while(!DC_DONE(co)) { + int e, m1, m2; + double in[MAX_CHAN]; + double out[3]; + double sum; + + /* Check the ink limit */ + for (sum = 0.0, e = 0; e < p->n; e++) + sum += (double)co[e]; + + if (p->limit > 1e-4 && (sum - 1.0) > p->limit) { + DC_INC(co); + continue; /* Skip points really over limit */ + } + + /* Scan only device surface */ + for (m1 = 0; m1 < p->n; m1++) { + if (co[m1] != 0) + continue; + for (m2 = m1 + 1; m2 < p->n; m2++) { + int x, y; + + if (co[m2] != 0) + continue; + + for (e = 0; e < p->n; e++) + in[e] = (double)co[e]; /* Base value */ + + for (x = 0; x < res; x++) { /* step over surface */ + in[m1] = x/(res - 1.0); + for (y = 0; y < res; y++) { + double ssum, iin[MAX_CHAN]; + in[m2] = y/(res - 1.0); + ssum = sum + in[m1] + in[m2]; + if (p->limit > 1e-4 && (ssum - 1.0) > p->limit) { + continue; + } + + for (e = 0; e < p->n; e++) + iin[e] = in[e]; /* Scalable copy */ + + /* Apply ink limit by simple scaling */ + if (p->limit > 1e-4 && ssum > p->limit) { + for (e = 0; e < p->n; e++) + iin[e] *= p->limit/ssum; + } + + forward(p, NULL, out, NULL, iin); + gam->expand(gam, out); + } + } + } + } + + /* Increment index within block */ + DC_INC(co); + } + + /* set the white and black points */ + p->get_wb(p, white, black, kblack); + gam->setwb(gam, white, black, kblack); + + /* Set the cusp points */ + /* Do this by scanning just 0 & 100% colorant combinations */ + + res = 2; + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + + /* Itterate over all the faces in the device space */ + while(!DC_DONE(co)) { + int e, m1, m2; + double in[MAX_CHAN]; + double out[3]; + double sum; + + /* Check the ink limit */ + for (sum = 0.0, e = 0; e < p->n; e++) + sum += (double)co[e]; + + if (p->limit > 1e-4 && (sum - 1.0) > p->limit) { + DC_INC(co); + continue; /* Skip points really over limit */ + } + + /* Scan only device surface */ + for (m1 = 0; m1 < p->n; m1++) { + if (co[m1] != 0) + continue; + for (m2 = m1 + 1; m2 < p->n; m2++) { + int x, y; + + if (co[m2] != 0) + continue; + + for (e = 0; e < p->n; e++) + in[e] = (double)co[e]; /* Base value */ + + for (x = 0; x < res; x++) { /* step over surface */ + in[m1] = x/(res - 1.0); + for (y = 0; y < res; y++) { + double ssum, iin[MAX_CHAN]; + in[m2] = y/(res - 1.0); + ssum = sum + in[m1] + in[m2]; + if (p->limit > 1e-4 && (ssum - 1.0) > p->limit) { + continue; + } + + for (e = 0; e < p->n; e++) + iin[e] = in[e]; /* Scalable copy */ + + /* Apply ink limit by simple scaling */ + if (p->limit > 1e-4 && ssum > p->limit) { + for (e = 0; e < p->n; e++) + iin[e] *= p->limit/ssum; + } + + forward(p, NULL, out, NULL, iin); + gam->setcusps(gam, 1, out); + } + } + } + } + + /* Increment index within block */ + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + + return gam; +} + +/* Delete it */ +static void del_mpp(mpp *p) { + if (p != NULL) { + del_mppcol(&p->white, p->n, p->spec_n); + del_mppcol(&p->black, p->n, p->spec_n); + del_mppcol(&p->kblack, p->n, p->spec_n); + del_mppcols(p->cols, p->nodp, p->n, p->spec_n); /* Delete array of target points */ + if (p->spc != NULL) + p->spc->del(p->spc); + + /* Delete shape parameters */ + if (p->shape != NULL) { + int i, j; + for (j = 0; j < p->n; j++) { + if (p->shape[j] != NULL) { + for (i = 0; i < p->nn; i++) { + if (p->shape[j][i] != NULL) + free(p->shape[j][i]); + } + free(p->shape[j]); + } + } + free(p->shape); + } + free(p); + } +} + +/* Allocate a new, uninitialised mpp */ +/* Note thate black and white points aren't allocated */ +mpp *new_mpp(void) { + mpp *p; + + if ((p = (mpp *)calloc(1, sizeof(mpp))) == NULL) + return NULL; + + p->pcs = icSigXYZData; + + /* Init method pointers */ + p->del = del_mpp; + p->create = create; + p->get_gamut = mpp_gamut; + p->write_mpp = write_mpp; + p->read_mpp = read_mpp; + p->get_info = get_info; + p->set_ilob = set_ilob; + p->get_wb = get_wb; + p->lookup = lookup; + p->dlookup = dlookup; + p->lookup_xyz = lookup_xyz; + p->lookup_spec = lookup_spec; + + return p; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Utility functions */ + +/* Allocate the data portion of an mppcol */ +/* Return NZ if malloc error */ +int new_mppcol( +mppcol *p, /* mppcol to allocate */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +) { + int nn = (1 << n); /* Number of ink combinations */ + + if ((p->nv = (double *)malloc(sizeof(double) * n)) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->band = (double *)malloc(sizeof(double) * (3 + nb))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->lband = (double *)malloc(sizeof(double) * (3 + nb))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->tcnv = (double *)calloc(n, sizeof(double))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->scnv = (double *)calloc(n, sizeof(double))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->pcnv = (double *)malloc(nn * sizeof(double))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + if ((p->fcnv = (double *)malloc(n * nn/2 * sizeof(double))) == NULL) { + del_mppcol(p, n, nb); + return 1; + } + return 0; +} + +/* Copy the contents of one mppcol to another */ +void copy_mppcol( +mppcol *d, /* Destination */ +mppcol *s, /* Source */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +) { + mppcol al; + int nn = (1 << n); /* Number of ink combinations */ + int i, j; + + al = *d; /* Copy destinations allocations */ + *d = *s; /* Copy source contents & pointers */ + + /* Now fixup allocation addresses */ + d->nv = al.nv; + d->band = al.band; + d->lband = al.lband; + d->tcnv = al.tcnv; + d->scnv = al.scnv; + d->pcnv = al.pcnv; + d->fcnv = al.fcnv; + + /* Copy contents of allocated data */ + for (j = 0; j < n; j++) + d->nv[j] = s->nv[j]; + + for (j = 0; j < (3+nb); j++) + d->band[j] = s->band[j]; + + for (j = 0; j < (3+nb); j++) + d->lband[j] = s->lband[j]; + + for (i = 0; i < n; i++) + d->tcnv[i] = s->tcnv[i]; + + for (i = 0; i < n; i++) + d->scnv[i] = s->scnv[i]; + + for (i = 0; i < nn; i++) + d->pcnv[i] = s->pcnv[i]; + + for (i = 0; i < (n * nn/2); i++) + d->fcnv[i] = s->fcnv[i]; +} + + +/* Free the data allocation of an mppcol */ +void del_mppcol( +mppcol *p, +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +) { + if (p != NULL) { + if (p->nv != NULL) + free (p->nv); + + if (p->band != NULL) + free (p->band); + + if (p->lband != NULL) + free (p->lband); + + if (p->tcnv != NULL) + free (p->tcnv); + + if (p->scnv != NULL) + free (p->scnv); + + if (p->pcnv != NULL) + free (p->pcnv); + + if (p->fcnv != NULL) + free (p->fcnv); + } +} + +/* Allocate an array of mppcol */ +/* Return NULL if malloc error */ +mppcol *new_mppcols( +int no, /* Number in array */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +) { + mppcol *p; + int i; + + if ((p = (mppcol *)calloc(no, sizeof(mppcol))) == NULL) { + return NULL; + } + + for (i = 0; i < no; i++) { + if (new_mppcol(&p[i], n, nb) != 0) { + del_mppcols(p, no, n, nb); + return NULL; + } + } + + return p; +} + +/* Free an array of mppcol */ +void del_mppcols( +mppcol *p, +int no, /* Number in array */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +) { + if (p != NULL) { + int i; + + for (i = 0; i < no; i++) + del_mppcol(&p[i], n, nb); + free (p); + } +} + +/* Allocate and setup the extra shape parameters combinations */ +static void init_shape(mpp *p) { + int i, j, k; + int ix[MPP_MXINKS]; + + /* First allocate the shape parameter array */ + + if ((p->shape = (double ***)malloc(p->n * sizeof(double **))) == NULL) + error("Malloc failed (mpp shape)!"); + for (j = 0; j < p->n; j++) { + if ((p->shape[j] = (double **)malloc(p->nn * sizeof(double *))) == NULL) + error("Malloc failed (mpp shape)!"); + for (i = 0; i < p->nn; i++) { + if ((i & (1 << j)) == 0) { /* Valid combo for this ink */ + if ((p->shape[j][i] = (double *)malloc((3+p->spec_n) * sizeof(double))) == NULL) + error("Malloc failed (mpp shape)!"); + for (k = 0; k < (3+p->spec_n); k++) + p->shape[j][i][k] = 0.0; /* Initial shape value */ + } else { + p->shape[j][i] = NULL; + } + } + } + + /* Setup sparse to full and back indexing lookup */ + for (j = 0; j < p->n; j++) + ix[j] = 0; + + for (i = 0; i < p->nn; i++) { + for (j = 0; j < p->n; j++) { + p->f2c[j][i] = j * p->nn/2 + ix[j]; + if ((i & (1 << j)) == 0) { + p->c2f[j * p->nn/2 + ix[j]].ink = j; + p->c2f[j * p->nn/2 + ix[j]].comb = i; + ix[j]++; + } + } + } + +#ifdef NEVER + /* Print result */ + for (j = 0; j < p->n; j++) { + for (i = 0; i < p->nn; i++) { + printf("f2c[%d][%d] = %d\n", j,i,p->f2c[j][i]); + } + for (i = 0; i < p->nn/2; i++) { + printf("c2f[%d].ink,comb = %d, %d\n", + j * p->nn/2 + i,p->c2f[j * p->nn/2 + i].ink, + p->c2f[j * p->nn/2 + i].comb); + } + } +#endif /* NEVER */ +} + + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Creation helper functions */ + +/* Per band error metric */ + +/* Convert argument to L* like scale */ +#define lDE(aa) ((aa) > 0.008856451586 ? 116.0 * pow((aa),1.0/3.0) - 16.0 : (aa) * 903.2962896) + +/* Function to approximate visible difference in an XYZ or spectral difference */ +static double DEsq(double aa, double bb) { + double dd; + + /* Convert input to L* like value */ + aa = lDE(aa); + bb = lDE(bb); + + dd = aa - bb; + + return dd * dd; +} + +/* Convert argument to the derivative of the DEsq function at that value */ +#define dDE(aa) ((aa) > 0.008856451586 ? 38.666667 * pow((aa), -2.0/3.0) : 903.2962896) + + +/* Given a device value, return the given bands value */ +/* according to the current model. */ +static double bandval(mpp *p, int band, double *dev) { + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int m, k; + double ov; + int j = band; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, dev[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + if (p->useshape) { + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + return ov; +} + +/* Given a device value, return the given bands value */ +/* according to the current model, as well as the partial */ +/* derivative wrt the device values. */ +static double dbandval(mpp *p, double *dv, int band, double *dev) { + + double dtcnv_ddev[MPP_MXINKS]; /* Del in tcnv[m] due to del dev[m] */ + double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */ + double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */ + double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */ + double dov[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */ + + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int m, k; + double ov; + int j = band; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxdiTransFunc(p->tc[m][j], &dtcnv_ddev[m], p->cord, dev[m]); + tcnv1[m] = 1.0 - tcnv[m]; + ww[m] = 0.0; + } + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Compute del ww[m][m] for del tcnv[m] */ + for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */ + int mo; + for (mo = 0; mo < p->n; mo++) + dww_dtcnv[mo][m] = 0.0; + for (k = 0; k < p->nn; k++) { + int mm; + double vv = 1.0; + for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */ + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + for (mo = 0; mo < p->n; mo++) { /* For each output channel */ + double vvv = vv * p->shape[mo][k & ~(1<<mo)][j]; + if (k & (1 << m)) + dww_dtcnv[mo][m] += vvv; + else + dww_dtcnv[mo][m] -= vvv; + } + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double tt; + double gg = ww[m]; /* Curve adjustment */ + double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + tt = gg - gg * vv + 1.0; + sv = vv/tt; + dsv = (gg + 1.0)/(tt * tt); + } else { + tt = 1.0 - gg * vv; + sv = (vv - gg * vv)/tt; + dsv = (1.0 - gg)/(tt * tt); + } + tcnv[m] = sv; + tcnv1[m] = 1.0 - sv; + dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */ + dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */ + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute del ov for del tcnv[m] */ + for (m = 0; m < p->n; m++) { + for (dov[m] = 0.0, k = 0; k < p->nn; k++) { + int mm; + double vv = p->pc[k][j]; + for (mm = 0; mm < p->n; mm++) { + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + if (k & (1 << m)) + dov[m] += vv; + else + dov[m] -= vv; + } + } + + /* Accumulate delta from input value to output */ + for (m = 0; m < p->n; m++) { + double ttt; + int mm; + + /* delta via dww */ + for (ttt = 0.0, mm = 0; mm < p->n; mm++) + ttt += dov[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_ddev[m]; + + /* delta direct */ + dv[m] = ttt + dov[m] * dtcnv_tc[m] * dtcnv_ddev[m]; + } + + return ov; +} + +/* Given a device value, return the XYZ, Lab and spectral */ +/* values according to the current model. */ +/* Pointer may be NULL if result is not needed */ +static void forward(mpp *p, double *spec, double *Lab, double *XYZ, double *dev) { + double tXYZ[3]; + int sb = 3, eb = 3; /* Start and end bands to compute */ + int j; + + if (XYZ != NULL || Lab != NULL) + sb = 0; + if (spec != NULL) + eb = 3 + p->spec_n; + + for (j = sb; j < eb; j++) { /* Compute each bands value */ + double ov; + + ov = bandval(p, j, dev); + + if (j < 3) + tXYZ[j] = ov; + else + spec[j-3] = ov; + } + +#ifdef SHARPEN + if (sb == 0) + sharp2XYZ(&tXYZ[0], &tXYZ[1], &tXYZ[2]); +#endif + if (XYZ != NULL) { + XYZ[0] = tXYZ[0]; + XYZ[1] = tXYZ[1]; + XYZ[2] = tXYZ[2]; + } + if (Lab != NULL) { + icmXYZ2Lab(&icmD50, Lab, tXYZ); /* Convert D50 Lab */ + } +} + +/* Return the specified bands current average error */ +static void banderr( +mpp *p, +double *avese, /* Return average Error from spectral bands */ +double *maxse, /* Return maximum Error from spectral bands */ +int band +) { + double serr = 0.0, smax = 0.0; + int i; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + double spv; + mppcol *c = &p->cols[i]; + + /* Compute models output */ + spv = bandval(p, band, c->nv); + spv = sqrt(DEsq(c->band[band], spv)); + + if (spv > smax) + smax = spv; + serr += spv; + } + + if (avese != NULL) + *avese = serr/((double)p->nodp); + if (maxse != NULL) + *maxse = smax; +} + +/* Figure out what the current model errors are and return them */ +static void deltae( +mpp *p, +double *avede, /* Return average Delta E from XYZ bands */ +double *maxde, /* Return maximum Delta E from XYZ bands */ +double *avese, /* Return average Error from spectral bands */ +double *maxse /* Return maximum Error from spectral bands */ +) { + double spec[MPP_MXBANDS]; /* spectral value for each point */ + double Lab[3]; + double err = 0.0, max = 0.0; /* Delta E */ + double serr = 0.0, smax = 0.0; /* Delta S */ + int i, j; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + double ee; + mppcol *c = &p->cols[i]; + + /* Compute models output */ + forward(p, p->spec_n > 0 ? spec : NULL, Lab, c->cXYZ, c->nv); + + /* Track the Delta E */ + c->err = icmLabDEsq(Lab, c->Lab); + ee = sqrt(c->err); + if (ee > max) + max = ee; + err += ee; + + /* Track the spectral error */ + for (j = 0; j < p->spec_n; j++) { + ee = DEsq(c->band[3+j], spec[j]); + ee = sqrt(ee); + if (ee > smax) + smax = ee; + serr += ee; + } + } + + if (avede != NULL) + *avede = err/((double)p->nodp); + if (maxde != NULL) + *maxde = max; + + if (p->spec_n > 0 && avese != NULL) + *avese = serr/(((double)p->nodp) * ((double)p->spec_n)); + if (maxse != NULL) + *maxse = smax; +} + +/* - - - - - - - - - - - - - - - */ +/* Powell optimisation callbacks */ + +/* Progress reporter for mpp powell/conjgrad */ +static void mppprog(void *pdata, int perc) { + mpp *p = (mpp *)pdata; + + if (p->verb) { + printf("%c% 3d%%",cr_char,perc); + if (perc == 100) + printf("\n"); + fflush(stdout); + } +} + +#ifdef NEVER // Skip efunc1 passes for now. + +/* Setup test point data ready for efunc1 on the given och and oba */ +static void sfunc1(mpp *p) { + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + int k = p->och; /* Channel being optimised */ + int j = p->oba; /* Band being optimised */ + int i, m; + + /* Pre-compute combination weighting and colorant combination values */ + /* for this band, without this device channels contribution. */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + int kk; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + /* Compute the test point primary combination values */ + c->tpcnv = c->tpcnv1 = 0.0; + for (kk = 0; kk < p->nn; kk++) { /* Combination value */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { + if (m == k) + continue; /* Multiply this in efunc1 */ + if (kk & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + if (kk & (1 << k)) + c->tpcnv += vv * p->pc[kk][j]; + else + c->tpcnv1 += vv * p->pc[kk][j]; + } + } +} + +#endif /* NEVER */ + +#ifdef NEVER // Skip efunc1 passes for now. +/* Optimise a device transfer curve to minimise a particular bands error */ +/* (Assume no shape parameters) */ +static double efunc1(void *adata, double pv[]) { + mpp *p = (mpp *)adata; + double smv, rv = 0.0; + double tcnv; /* Transfer curve corrected device values */ + int k = p->och; /* Channel being optimised */ + int j = p->oba; /* Band being optimised */ + int pc, m, i; + + /* For each test point */ + for (pc = m = 0; m < p->nodp; m++) { + mppcol *c = &p->cols[m]; + double vv; + + if (c->w < 1e-6) + continue; /* Skip zero weighted points */ + + /* Compute the tranfer corrected device values */ + tcnv = icxTransFunc(pv, p->cord, c->nv[k]); + + /* Compute band value */ + vv = tcnv * c->tpcnv + (1.0 - tcnv) * c->tpcnv1; + + /* Compute the band value error */ + vv = lDE(vv) - c->lband[j]; + rv += c->w * vv * vv; + pc++; + } + rv /= (double)pc; + + /* Compute average magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + for (smv = 0.0, m = 0; m < p->cord; m++) { + smv += pv[m] * pv[m]; + } + smv /= (double)(p->cord); + rv += 0.01 * smv; + +#ifdef DEBUG + printf("efunc1 itt %d/%d chan %d band %d k0 %f returning %f\n",p->oit,p->ott,k,j,pv[0],rv); +#endif + return rv; +} +#endif /* NEVER */ + +/* Optimise all transfer curves simultaniously to minimise a particular bands error */ +static double efunc2(void *adata, double pv[]) { + mpp *p = (mpp *)adata; + double smv, rv = 0.0; + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int i, m, k; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxTransFunc(&pv[m * p->cord], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + if (p->useshape) { + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute the band value error */ + ov = lDE(ov) - c->lband[j]; + rv += ov * ov; + } + + rv /= (double)p->nodp; + + /* Compute weighted magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + for (smv = 0.0, m = 0; m < p->n; m++) { + double w; + for (k = 0; k < p->cord; k++) { + i = m * p->cord + k; + w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */ + smv += w * pv[i] * pv[i]; + } + } + smv /= (double)(p->n); + rv += smv; + +#ifdef DEBUG + printf("efunc2 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +/* Return the gradient of the minimisation function at the given location, */ +/* as well as the function value at this location. */ +static double dfunc2(void *adata, double dv[], double pv[]) { + mpp *p = (mpp *)adata; + double smv, tt, rv = 0.0; + double dtcnv_dpv[MPP_MXINKS][MPP_MXTCORD]; /* Del in tcnv[m] due to del in parameter */ + double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */ + double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */ + double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */ + double dov[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */ + double ddov; /* Del in final ov due to del in raw ov */ + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int i, m, k; + + for (k = 0; k < (p->n * p->cord); k++) + dv[k] = 0.0; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + int mo; + mppcol *c = &p->cols[i]; + double ov; + + /* Compute the tranfer corrected device values */ + /* and del in these values due to del in input parameters */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxdpTransFunc(&pv[m * p->cord], dtcnv_dpv[m], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + +#ifdef NEVER +{ +for (m = 0; m < p->n; m++) { + int ii; + double ttt; + + for (ii = 0; ii < p->cord; ii++) { + pv[m * p->cord + ii] += 1e-6; + ttt = (icxTransFunc(&pv[m * p->cord], p->cord, c->nv[m]) - tcnv[m])/1e-6; + pv[m * p->cord + ii] -= 1e-6; + printf("~1 chan %d cord %d is %f ref %f\n",m,ii,dtcnv_dpv[m][ii],ttt); + } +} +} +#endif + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Compute del ww[m][m] for del tcnv[m] */ + for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */ + for (mo = 0; mo < p->n; mo++) + dww_dtcnv[mo][m] = 0.0; + for (k = 0; k < p->nn; k++) { + int mm; + double vv = 1.0; + for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */ + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + for (mo = 0; mo < p->n; mo++) { /* For each output channel */ + double vvv = vv * p->shape[mo][k & ~(1<<mo)][j]; + if (k & (1 << m)) + dww_dtcnv[mo][m] += vvv; + else + dww_dtcnv[mo][m] -= vvv; + } + } + } + +#ifdef NEVER +{ + int mm; + double tww[MPP_MXINKS][MPP_MXINKS]; + + for (mm = 0; mm < p->n; mm++) { /* For del in each input value */ + tcnv[mm] += 1e-6; + + for (m = 0; m < p->n; m++) + tww[m][mm] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= (1.0 - tcnv[m]); + } + for (m = 0; m < p->n; m++) { + tww[m][mm] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + tcnv[mm] -= 1e-6; + for (m = 0; m < p->n; m++) { + tww[m][mm] = (tww[m][mm] - ww[m])/1e-6; + } + } + + for (mm = 0; mm < p->n; mm++) { + for (m = 0; m < p->n; m++) { + printf("~1 ww[%d][%d] %f should be %f\n",mm, m, dww_dtcnv[mm][m],tww[mm][m]); + } + } +} +#endif /* NEVER */ + +#ifdef NEVER +{ + double itc[MPP_MXINKS]; + double ttc[MPP_MXINKS]; + double tww[MPP_MXINKS]; + + for (m = 0; m < p->n; m++) { + itc[m] = tcnv[m]; + } +#endif /* NEVER */ + + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + tt = gg - gg * vv + 1.0; + sv = vv/tt; + dsv = (gg + 1.0)/(tt * tt); + } else { + tt = 1.0 - gg * vv; + sv = (vv - gg * vv)/tt; + dsv = (1.0 - gg)/(tt * tt); + } + tcnv[m] = sv; + tcnv1[m] = 1.0 - sv; + dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */ + dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */ + } + + +#ifdef NEVER + /* Check derivatives */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = itc[m]; /* Input value to be tweaked */ + + vv += 1e-6; + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + ttc[m] = (vv - tcnv[m])/1e-6; + } + + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = itc[m]; /* Input value to be tweaked */ + + gg += 1e-6; + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tww[m] = (vv - tcnv[m])/1e-6; + } + + + for (m = 0; m < p->n; m++) { + printf("~1 dtcnv_tc[%d] is %f should be %f\n",m, dtcnv_tc[m], ttc[m]); + } + for (m = 0; m < p->n; m++) { + printf("~1 dtcnv_ww[%d] is %f should be %f\n",m, dtcnv_ww[m], tww[m]); + } +} +#endif /* NEVER */ + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute del ov for del tcnv[m] */ + for (m = 0; m < p->n; m++) { + for (dov[m] = 0.0, k = 0; k < p->nn; k++) { + int mm; + double vv = p->pc[k][j]; + for (mm = 0; mm < p->n; mm++) { + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + if (k & (1 << m)) + dov[m] += vv; + else + dov[m] -= vv; + } + } + +#ifdef NEVER +{ + int mm; + double tdov[MPP_MXINKS]; + + for (mm = 0; mm < p->n; mm++) { + tcnv[mm] += 1e-6; + + for (tdov[mm] = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= (1.0 - tcnv[m]); + } + tdov[mm] += vv; + } + tcnv[mm] -= 1e-6; + tdov[mm] = (tdov[mm] - ov)/1e-6; + } + + for (m = 0; m < p->n; m++) { + printf("~1 dov[%d] is %f should be %f\n",m, tdov[m], dov[m]); + } +} +#endif /* NEVER */ + + /* Compute the band value error */ + ddov = dDE(ov); + ov = lDE(ov) - c->lband[j]; + + ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */ + rv += ov * ov; + + /* Accumulate delta from input params to output */ + for (m = 0; m < p->n; m++) { + for (k = 0; k < p->cord; k++) { + double ttt; + int mm; + + /* delta via dww */ + for (ttt = 0.0, mm = 0; mm < p->n; mm++) + ttt += dov[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_dpv[m][k]; + + /* delta direct */ + dv[m * p->cord + k] += ddov * (ttt + dov[m] * dtcnv_tc[m] * dtcnv_dpv[m][k]); + } + } + } + + rv /= (double)p->nodp; + for (k = 0; k < (p->n * p->cord); k++) + dv[k] /= (double)p->nodp; + + /* Compute weighted magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + tt = 2.0/(double)(p->n); /* Common factor */ + for (smv = 0.0, m = 0; m < p->n; m++) { + double w; + for (k = 0; k < p->cord; k++) { + i = m * p->cord + k; + w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */ + dv[i] += w * tt * pv[i]; /* Del in rv due to del in pv */ + smv += w * pv[i] * pv[i]; + } + } + smv /= (double)(p->n); + rv += smv; + +#ifdef DEBUG + printf("dfunc2 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +#ifdef TESTDFUNC +/* Check that dfunc2 returns the right values */ +static void test_dfunc2( +mpp *p, +int nparms, +double *pv +) { + int i, j, k; + double refov; + double refdv[MPP_MXINKS * MPP_MXTCORD]; + double ov; + double dv[MPP_MXINKS * MPP_MXTCORD]; + + /* Create reference dvs */ + refov = efunc2((void *)p, pv); + for (i = 0; i < nparms; i++) { + pv[i] += 1e-6; + refdv[i] = (efunc2((void *)p, pv) - refov)/1e-6; + pv[i] -= 1e-6; + } + + /* Check dfunc2 */ + ov = dfunc2((void *)p, dv, pv); + + printf("~#############################################\n"); + printf("~! Check dfunc2, ov %f, refov %f\n",ov, refov); + for (i = 0; i < nparms; i++) { + printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]); + } +} +#endif /* TESTDFUNC */ + + +/* Setup test point data ready for efunc3 on the given oba */ +static void sfunc3(mpp *p) { + double pcnv[MPP_MXCCOMB]; /* Interpolation combination values */ + int j = p->oba; /* Band being optimised */ + int i, k, m; + + /* Setup the correct per patch value fcnv values */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) + c->tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]); + + /* Compute combination values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= c->tcnv[m]; + else + vv *= (1.0 - c->tcnv[m]); + } + pcnv[k] = vv; + } + + /* Compute shape weighting values */ + for (k = 0; k < p->nnn2; k++) { + int m = p->c2f[k].ink; + int n = p->c2f[k].comb; + /* Compress full interpolation one dimension to */ + /* exclude ink value being tweaked by shape */ + c->fcnv[k] = pcnv[n & ~(1<<m)] + pcnv[n | (1<<m)]; + } + } +} + + +/* Optimise all shape parameters simultaniously to minimise a particular bands error */ +/* Assume test point tcnv and pcnv are setup for pre-shape values */ +static double efunc3(void *adata, double pv[]) { + mpp *p = (mpp *)adata; + double smv, rv = 0.0; + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int n1 = p->n - 1; + int i, m, k; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov; + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Interpolate the per ink shape values for this set of input values */ + for (k = 0; k < p->nnn2; k++) { /* For each ink & interp vertex */ + m = k >> n1; /* Corresponding ink channel */ + ww[m] += pv[k] * c->fcnv[k]; /* Apply weighting to shape vertex value */ + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = c->tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute the band value error */ + ov = lDE(ov) - c->lband[j]; + rv += ov * ov; + } + + rv /= (double)p->nodp; + + /* Compute average magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + for (smv = 0.0, m = 0; m < p->nnn2; m++) { + smv += pv[m] * pv[m]; + } + smv /= (double)(p->nnn2); + rv += SHAPE_PMW * smv; + +#ifdef DEBUG + printf("efunc3 itt %d/%d band %d (smv %f) returning %f\n",p->oit,p->ott,j,smv,rv); +#endif + return rv; +} + +/* Return the gradient of the minimisation function at the given location, */ +/* as well as the function value at this location. */ +static double dfunc3(void *adata, double dv[], double pv[]) { + mpp *p = (mpp *)adata; + double smv, tt, rv = 0.0; + double dtcnv[MPP_MXINKS]; /* Derivative of transfer curve corrected device values */ + double dov[MPP_MXINKS]; /* Derivative of output interpolation device values */ + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int n1 = p->n - 1; + int i, m, k; + + for (k = 0; k < p->nnn2; k++) + dv[k] = 0.0; /* Start with 0 delta */ + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov, ddov; + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Interpolate the per ink shape values for this set of input values */ + for (k = 0; k < p->nnn2; k++) { + m = k >> n1; /* Corresponding ink channel */ + ww[m] += pv[k] * c->fcnv[k]; /* Apply weighting to shape vertex value */ + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double sv, vv = c->tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + tt = gg - gg * vv + 1.0; + sv = vv/tt; + } else { + tt = 1.0 - gg * vv; + sv = (vv - gg * vv)/tt; + } + tcnv[m] = sv; + tcnv1[m] = 1.0 - sv; + dtcnv[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */ + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute del ov[m] for del tcnv[m] */ + for (m = 0; m < p->n; m++) { + for (dov[m] = 0.0, k = 0; k < p->nn; k++) { + int mm; + double vv = p->pc[k][j]; + for (mm = 0; mm < p->n; mm++) { + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + if (k & (1 << m)) + dov[m] += vv; + else + dov[m] -= vv; + } + dov[m] *= dtcnv[m]; /* del ov[m] due to del in ww[m] */ + } + + /* Compute the band value error */ + ddov = dDE(ov); + ov = lDE(ov) - c->lband[j]; + + ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */ + rv += ov * ov; + + for (k = 0; k < p->nnn2; k++) { + m = k >> n1; /* Corresponding ink channel */ + dv[k] += ddov * dov[m] * c->fcnv[k]; + } + } + + rv /= ((double)p->nodp); + + for (k = 0; k < p->nnn2; k++) + dv[k] /= ((double)p->nodp); + + /* Compute average magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + tt = SHAPE_PMW * 2.0/(double)(p->nnn2); /* Common factor */ + for (smv = 0.0, k = 0; k < p->nnn2; k++) { + dv[k] += tt * pv[k]; /* del rv due to del pv[k] */ + smv += pv[k] * pv[k]; + } + smv /= (double)(p->nnn2); + rv += SHAPE_PMW * smv; + +#ifdef DEBUG + printf("dfunc3 itt %d/%d band %d (smv %f) returning %f\n",p->oit,p->ott,j,smv,rv); +#endif + return rv; +} + +#ifdef TESTDFUNC +/* Check that dfunc3 returns the right values */ +static void test_dfunc3( +mpp *p, +int nparms, +double *pv +) { + int i, j, k; + double refov; + double refdv[MPP_MXINKS * MPP_MXCCOMB/2]; + double ov; + double dv[MPP_MXINKS * MPP_MXCCOMB/2]; + + /* Create reference dvs */ + refov = efunc3((void *)p, pv); + for (k = 0; k < nparms; k++) { + pv[k] += 1e-6; + dv[k] = (efunc3((void *)p, pv) - refov)/1e-6; + pv[k] -= 1e-6; + } + + /* Check dfunc3 */ + ov = dfunc3((void *)p, dv, pv); + + printf("~#############################################\n"); + printf("~! Check dfunc3, ov %f, refov %f\n",ov, refov); + for (i = 0; i < nparms; i++) { + printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]); + } +} +#endif /* TESTDFUNC */ + + +/* Setup test point data ready for efunc4 on the given oba */ +static void sfunc4(mpp *p) { + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int i, k, m; + + /* Setup the correct per patch value fcnv values */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Lookup the interpolated shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += p->shape[m][k & ~(1<<m)][j] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + + /* Compute the shape adjusted primary combination values */ + for (k = 0; k < p->nn; k++) { + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + c->pcnv[k] = vv; + } + } +} + +/* Optimise all vertex values simultaniously to minimise a particular bands error */ +/* Assume test point tcnv and pcnv are setup for post-shape values */ +static double efunc4(void *adata, double pv[]) { + mpp *p = (mpp *)adata; + double smv = 0.0, rv = 0.0; + int j = p->oba; /* Band being optimised */ + int i, k; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov = 0.0; + + for (k = 0; k < p->nn; k++) { + double pp = pv[k]; + + if (pp < 0.0) /* Stop non-real values */ + rv += -5000.0 * pp; + ov += c->pcnv[k] * pp; + } + + /* Compute the band value error */ + ov = lDE(ov) - c->lband[j]; + rv += ov * ov; + } + + rv /= p->nodp; + + /* Compute anchor point error */ + for (smv = 0.0, i = 0; i < p->nn; i++) { + double tt = lDE(pv[i]) - p->lpca[i][j]; + smv += tt * tt; + } + smv /= (double)p->nn; + +//printf("~1 anchor error = %f\n",smv); + rv += COMB_PMW * smv; + +#ifdef DEBUG + printf("efunc4 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +/* Return the gradient of the minimisation function at the given location, */ +/* as well as the function value at this location. */ +static double dfunc4(void *adata, double dv[], double pv[]) { + mpp *p = (mpp *)adata; + double smv = 0.0, rv = 0.0; + double drv[MPP_MXCCOMB]; /* Delta in rv */ + int j = p->oba; /* Band being optimised */ + int i, k; + + for (k = 0; k < p->nn; k++) { + dv[k] = 0.0; + drv[k] = 0.0; + } + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov = 0.0, ddov; + + for (k = 0; k < p->nn; k++) { + double pp = pv[k]; + + if (pp < 0.0) { /* Stop non-real values */ + rv += -5000.0 * pp; + drv[k] += -5000.0; + } + ov += c->pcnv[k] * pp; + } + + /* Compute the band value error */ + ddov = dDE(ov); + ov = lDE(ov) - c->lband[j]; + + ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */ + rv += ov * ov; + + for (k = 0; k < p->nn; k++) { + dv[k] += ddov * c->pcnv[k]; + } + } + + rv /= p->nodp; + for (k = 0; k < p->nn; k++) + dv[k] /= ((double)p->nodp); + + /* Compute anchor point error */ + for (smv = 0.0, k = 0; k < p->nn; k++) { + double tt, ddov; + ddov = dDE(pv[k]); + tt = lDE(pv[k]) - p->lpca[k][j]; + ddov *= COMB_PMW * 2.0 * tt/(double)p->nn; + dv[k] += ddov; + smv += tt * tt; + } + smv /= (double)p->nn; + +//printf("~1 anchor error = %f\n",smv); + rv += COMB_PMW * smv; + + for (k = 0; k < p->nn; k++) /* Add any < 0 contribution */ + dv[k] += drv[k]; + +#ifdef DEBUG + printf("dfunc4 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +#ifdef TESTDFUNC +/* Check that dfunc4 returns the right values */ +static void test_dfunc4( +mpp *p, +int nparms, +double *pv +) { + int i, j, k; + double refov; + double refdv[MPP_MXCCOMB]; + double ov; + double dv[MPP_MXCCOMB]; + + /* Create reference dvs */ + refov = efunc4((void *)p, pv); + for (i = 0; i < nparms; i++) { + pv[i] += 1e-9; + refdv[i] = (efunc4((void *)p, pv) - refov)/1e-9; + pv[i] -= 1e-9; + } + + /* Check dfunc4 */ + ov = dfunc4((void *)p, dv, pv); + + printf("~#############################################\n"); + printf("~! Check dfunc4, ov %f, refov %f\n",ov, refov); + for (i = 0; i < nparms; i++) { + printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]); + } +} +#endif /* TESTDFUNC */ + +/* ---------------------------------------------------- */ +/* Optimise whole model in one go */ + +#ifdef BIGBANG + +/* Optimise all parameters simultaniously to minimise a particular bands error */ +static double efunc0(void *adata, double pv[]) { + mpp *p = (mpp *)adata; + double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */ + double smv, rv = 0.0; + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int i, m, k; + + pv2 = pv; + pv3 = pv + (p->n * p->cord); + pv4 = pv + (p->n * p->cord) + p->nnn2; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ov; + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxTransFunc(&pv2[m * p->cord], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + for (m = 0; m < p->n; m++) + ww[m] = 0.0; + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + ww[m] += pv3[p->f2c[m][k & ~(1<<m)]] * vv; + /* Apply weighting to shape vertex value */ + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[m] = vv; + tcnv1[m] = 1.0 - vv; + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = pv4[k]; + if (vv < 0.0) /* Stop non-real values */ + rv += -5000.0 * vv; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute the band value error */ + ov = lDE(ov) - c->lband[j]; + rv += ov * ov; + } + + rv /= (double)p->nodp; + + /* Compute weighted magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + for (smv = 0.0, m = 0; m < p->n; m++) { + double w; + for (k = 0; k < p->cord; k++) { + i = m * p->cord + k; + w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */ + smv += w * pv2[i] * pv2[i]; + } + } + smv /= (double)(p->n); + rv += smv; + + /* Compute average magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + for (smv = 0.0, m = 0; m < p->nnn2; m++) { + smv += pv3[m] * pv3[m]; + } + smv /= (double)(p->nnn2); + rv += SHAPE_PMW * smv; + + /* Compute anchor point error */ + for (smv = 0.0, i = 0; i < p->nn; i++) { + double tt = lDE(pv4[i]) - p->lpca[i][j]; + smv += tt * tt; + } + smv /= (double)p->nn; + rv += COMB_PMW * smv; + +#ifdef DEBUG + printf("efunc0 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +/* Return the gradient of the minimisation function at the given location, */ +/* as well as the function value at this location. */ +static double dfunc0(void *adata, double dv[], double pv[]) { + mpp *p = (mpp *)adata; + double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */ + double *dv2, *dv3, *dv4; /* Pointers to each group of derivatives */ + double smv, tt, rv = 0.0; + + double dtcnv_dpv2[MPP_MXINKS][MPP_MXTCORD]; /* Del in tcnv[m] due to del in pv2 */ + double dww_dpv3[MPP_MXINKS][MPP_MXINKS * MPP_MXCCOMB/2]; /* Del in ww[m] due to del in pv3 */ + double dov_dpv4[MPP_MXCCOMB]; /* Delta in ov due to delta in pv4 */ + double drv4[MPP_MXCCOMB]; /* Delta in rv due to error in pv4 */ + + double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */ + double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */ + double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */ + double dov_dcnv[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */ + double ddov; /* Del in final ov due to del in raw ov */ + + double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */ + double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */ + double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */ + int j = p->oba; /* Band being optimised */ + int i, m, k; + + pv2 = pv; + pv3 = pv + (p->n * p->cord); + pv4 = pv + (p->n * p->cord) + p->nnn2; + dv2 = dv; + dv3 = dv + (p->n * p->cord); + dv4 = dv + (p->n * p->cord) + p->nnn2; + + for (k = 0; k < (p->n * p->cord); k++) + dv2[k] = 0.0; + for (k = 0; k < p->nnn2; k++) + dv3[k] = 0.0; + for (k = 0; k < p->nn; k++) + dv4[k] += 0.0; + + /* For each test point */ + for (i = 0; i < p->nodp; i++) { + int mo; + mppcol *c = &p->cols[i]; + double ov; + + + /* Compute the tranfer corrected device values */ + /* and del in these values due to del in input parameters */ + for (m = 0; m < p->n; m++) { + tcnv[m] = icxdpTransFunc(&pv2[m * p->cord], dtcnv_dpv2[m], p->cord, c->nv[m]); + tcnv1[m] = 1.0 - tcnv[m]; + } + + for (m = 0; m < p->n; m++) { + ww[m] = 0.0; + for (k = 0; k < p->nnn2; k++) + dww_dpv3[m][k] = 0.0; + } + + /* Lookup the shape values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + for (m = 0; m < p->n; m++) { + int pix = p->f2c[m][k & ~(1<<m)]; + ww[m] += pv3[pix] * vv; /* Apply weighting to shape vertex value */ + dww_dpv3[m][pix] += vv; /* Delta to ww[m] from del in pv3[pix] */ + } + } + + /* Compute del ww[m][m] for del tcnv[m] */ + for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */ + for (mo = 0; mo < p->n; mo++) + dww_dtcnv[mo][m] = 0.0; + for (k = 0; k < p->nn; k++) { + int mm; + double vv = 1.0; + for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */ + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + for (mo = 0; mo < p->n; mo++) { /* For each output channel */ + double vvv = vv * pv3[p->f2c[mo][k & ~(1<<mo)]]; + if (k & (1 << m)) + dww_dtcnv[mo][m] += vvv; + else + dww_dtcnv[mo][m] -= vvv; + } + } + } + + /* Apply the shape values to adjust the primaries */ + for (m = 0; m < p->n; m++) { + double gg = ww[m]; /* Curve adjustment */ + double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */ + if (gg >= 0.0) { + tt = gg - gg * vv + 1.0; + sv = vv/tt; + dsv = (gg + 1.0)/(tt * tt); + } else { + tt = 1.0 - gg * vv; + sv = (vv - gg * vv)/tt; + dsv = (1.0 - gg)/(tt * tt); + } + tcnv[m] = sv; + tcnv1[m] = 1.0 - sv; + dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */ + dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */ + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double pp, vv; + for (vv = 1.0, m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + dov_dpv4[k] = vv; + + pp = pv4[k]; + if (pp < 0.0) { /* Stop non-real values */ + rv += -5000.0 * pp; + drv4[k] += -5000.0; + } + ov += vv * pp; + } + + /* Compute del ov for del tcnv[m] */ + for (m = 0; m < p->n; m++) { + for (dov_dcnv[m] = 0.0, k = 0; k < p->nn; k++) { + int mm; + double vv = p->pc[k][j]; + for (mm = 0; mm < p->n; mm++) { + if (m == mm) + continue; + if (k & (1 << mm)) + vv *= tcnv[mm]; + else + vv *= tcnv1[mm]; + } + if (k & (1 << m)) + dov_dcnv[m] += vv; + else + dov_dcnv[m] -= vv; + } + } + + /* Compute the band value error */ + ddov = dDE(ov); + ov = lDE(ov) - c->lband[j]; + + ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */ + rv += ov * ov; + + /* Accumulate delta from input params to output */ + for (m = 0; m < p->n; m++) { + for (k = 0; k < p->cord; k++) { + double ttt; + int mm; + + /* delta via dww */ + for (ttt = 0.0, mm = 0; mm < p->n; mm++) + ttt += dov_dcnv[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_dpv2[m][k]; + + /* delta direct */ + dv2[m * p->cord + k] += ddov * (ttt + dov_dcnv[m] * dtcnv_tc[m] * dtcnv_dpv2[m][k]); + } + } + for (k = 0; k < p->nnn2; k++) { + double ttt; + + /* delta via dww */ + for (ttt = 0.0, m = 0; m < p->n; m++) + ttt += dov_dcnv[m] * dtcnv_ww[m] * dww_dpv3[m][k]; + dv3[k] += ddov * ttt; + } + for (k = 0; k < p->nn; k++) { + dv4[k] += ddov * dov_dpv4[k]; + } + } + + rv /= (double)p->nodp; + for (k = 0; k < (p->n * p->cord); k++) + dv2[k] /= (double)p->nodp; + for (k = 0; k < p->nnn2; k++) + dv3[k] /= ((double)p->nodp); + for (k = 0; k < p->nn; k++) + dv4[k] /= ((double)p->nodp); + + /* Compute weighted magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + tt = 2.0/(double)(p->n); /* Common factor */ + for (smv = 0.0, m = 0; m < p->n; m++) { + double w; + for (k = 0; k < p->cord; k++) { + i = m * p->cord + k; + w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */ + dv2[i] += w * tt * pv2[i]; /* Del in rv due to del in pv */ + smv += w * pv2[i] * pv2[i]; + } + } + smv /= (double)(p->n); + rv += smv; + + /* Compute average magnitude of shaper parameters squared */ + /* to minimise unconstrained "wiggles" */ + tt = SHAPE_PMW * 2.0/(double)(p->nnn2); /* Common factor */ + for (smv = 0.0, k = 0; k < p->nnn2; k++) { + dv3[k] += tt * pv3[k]; /* del rv due to del pv[k] */ + smv += pv3[k] * pv3[k]; + } + smv /= (double)(p->nnn2); + rv += SHAPE_PMW * smv; + + /* Compute anchor point error */ + for (smv = 0.0, k = 0; k < p->nn; k++) { + double tt, ddov; + ddov = dDE(pv4[k]); + tt = lDE(pv4[k]) - p->lpca[k][j]; + ddov *= COMB_PMW * 2.0 * tt/(double)p->nn; + dv4[k] += ddov; + smv += tt * tt; + } + smv /= (double)p->nn; + rv += COMB_PMW * smv; + + for (k = 0; k < p->nn; k++) /* Add any < 0 contribution */ + dv4[k] += drv4[k]; + +#ifdef DEBUG + printf("dfunc0 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv); +#endif + return rv; +} + +#ifdef TESTDFUNC +/* Check that dfunc0 returns the right values */ +static void test_dfunc0( +mpp *p, +int nparms, +double *pv +) { + int i, j, k; + double refov; + double refdv[MPP_MXPARMS]; + double ov; + double dv[MPP_MXPARMS]; + + /* Create reference dvs */ + refov = efunc0((void *)p, pv); + for (i = 0; i < nparms; i++) { + pv[i] += 1e-9; + refdv[i] = (efunc0((void *)p, pv) - refov)/1e-9; + pv[i] -= 1e-9; + } + + /* Check dfunc0 */ + ov = dfunc0((void *)p, dv, pv); + + printf("~#############################################\n"); + printf("~! Check dfunc0, ov %f, refov %f\n",ov, refov); + for (i = 0; i < nparms; i++) { + printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]); + } +} +#endif /* TESTDFUNC */ +#endif /* BIGBANG */ + +#ifdef ISHAPE +/* ----------------------------------------------------- */ +/* Create an initial solution for shape parameters */ +/* by solving least squares using SVD */ + +/* Optimise device values to match test points Lab */ +/* while minimising the difference between the device */ +/* values and the current model device values in tcnv */ +static double efuncS(void *adata, double pv[]) { + double tcnv[MPP_MXINKS]; /* Shape tweaked input values */ + double tcnv1[MPP_MXINKS]; + mpp *p = (mpp *)adata; + double smv, rv = 0.0; + mppcol *c = p->otp; + int j = p->oba; + int k, m; + + double ov; + + for (k = 0; k < p->n; k++) { + double gg = pv[k]; /* Curve adjustment */ + double vv = c->tcnv[k]; /* Input value to be tweaked */ + if (gg >= 0.0) { + vv = vv/(gg - gg * vv + 1.0); + } else { + vv = (vv - gg * vv)/(1.0 - gg * vv); + } + tcnv[k] = vv; + tcnv1[k] = 1.0 - vv; + } + + /* Compute the primary combination values */ + for (ov = 0.0, k = 0; k < p->nn; k++) { + double vv = p->pc[k][j]; + for (m = 0; m < p->n; m++) { + if (k & (1 << m)) + vv *= tcnv[m]; + else + vv *= tcnv1[m]; + } + ov += vv; + } + + /* Compute the band value error */ + ov = lDE(ov) - c->lband[j]; + rv += ov * ov; + + /* Compute magnitude of shape params */ + for (smv = 0.0, k = 0; k < p->n; k++) { + smv += pv[k] * pv[k]; + } + rv += SHAPE_PMW * 0.1 * smv/(double)p->n; /* Don't worry about this here - SVD will cope */ + +//printf("~1 efuncS %f %f %f %f returning %f\n",pv[0],pv[1],pv[2],pv[3],rv); + return rv; +} + + +static void ishape( +mpp *p, +int j /* Band being initialised */ +) { + int i, k, m; + double **a; /* A[0..m-1][0..n-1] input A[][], will return U[][] */ + double *b; /* B[0..m-1] Right hand side of equation, return solution */ + + p->oba = j; + + if (p->nodp < (p->nn/2)) /* Nicer to return error ?? */ + error ("Not enough data points to solve shaper parameters"); + +//printf("~1 ishape called for band %d\n",j); + + /* First step is to compute the ideal shape values */ + /* for each test point. */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double pv[MPP_MXINKS]; /* Parameter values */ + double sr[MPP_MXINKS]; /* search radius */ + + p->otp = c; + +//printf("~1 setting up point %d\n",i); + + /* Compute the tranfer corrected device values */ + for (m = 0; m < p->n; m++) + c->tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]); + + /* Compute pre-shape combination values */ + for (k = 0; k < p->nn; k++) { /* For each interp vertex */ + double vv; + for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */ + if (k & (1 << m)) + vv *= c->tcnv[m]; + else + vv *= (1.0 - c->tcnv[m]); + } + c->pcnv[k] = vv; + } + + /* Do reverse lookup to find ideal post transfer device */ + /* shape values that are minimal, but will give the desired */ + /* band value */ + for (k = 0; k < p->n; k++) { + pv[k] = 0.0; + sr[k] = 0.5; + } + + if (powell(NULL, p->n, pv, sr, 0.001, 300, efuncS, (void *)p, NULL, NULL) != 0) { +//printf("~1 ishape Powell failed at point %d\n",i); + for (k = 0; k < p->n; k++) + pv[k] = 0.0; + } + +//printf("~1 got uncorrected device vals %f %f %f %f\n",c->nv[0],c->nv[1],c->nv[2],c->nv[3]); +//printf("~1 got shape target values %f %f %f %f\n",pv[0],pv[1],pv[2],pv[3]); + + /* put them leave them in scnv */ + for (k = 0; k < p->n; k++) { + c->scnv[k] = pv[k]; + } + } + + /* The second step is to use SVD to compute the per colorant */ + /* interpolation corner values that are the best least squares */ + /* solution to providing those shape parameters at each point. */ + + /* Allocate our SVD matricies */ + a = dmatrix(0, p->nodp-1 + p->nn/2, 0, p->nn/2-1); + b = dvector(0, p->nodp-1 + p->nn/2); + + for (m = 0; m < p->n; m++) { /* For each channel */ + int kk; + + /* Setup our matricies for the test point */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ss; /* Weighting factor */ + + /* The theoretically correct weighting to get */ + /* better alighment with DEsq() measure is */ + /* ss = dDE(c->band[j]);, but this doesn't seem */ + /* to work well in practice. */ + + /* I don't quite understand why this weighting */ + /* seems to give a good result. */ + ss = c->tcnv[m] * (1.0 - c->tcnv[m]); /* Don't understand why this is so good */ + + for (kk = k = 0; k < p->nn; k++) { + if (k & (1<<m)) + continue; /* Invalid comb for this channel */ + + a[i][kk++] = ss * (c->pcnv[k] + c->pcnv[k | (1<<m)]); + } + b[i] = ss * c->scnv[m]; + } + + /* Add extra fake "data points" to weight the minimisation of */ + /* the shape parameter values */ + { + int ii; + double ss; + + /* We want the relative weight to be similar to that */ + /* aimed for in efunc3 */ + + // ~~~ 0.1 seems about right for 1150 ??? + // 1.0 for roland8 ?? +// ss = (double)p->nodp/((double)(p->nn/2)) * 1.0 * SHAPE_PMW; + ss = (double)p->nodp * 0.05 * SHAPE_PMW; + + for (ii = 0; ii < p->nn/2; ii++) { + for (kk = k = 0; k < p->nn; k++) { + if (k & (1<<m)) + continue; /* Invalid comb for this channel */ + + if (kk == ii) /* Weights only on diagonals */ + a[i+ii][kk] = ss; + else + a[i+ii][kk] = 0.0; + kk++; + } + b[i+ii] = 0.0; + } + } + + /* Solve the equation A.x = b using SVD */ + if (svdsolve(a, b, p->nodp + p->nn/2, p->nn/2) != 0) + error("ishape: SVD failed"); + + /* Copy the solution back */ + for (kk = k = 0; k < p->nn; k++) { + if (k & (1<<m)) + continue; /* Invalid comb for this channel */ + +//printf("~1 Calculated shape[%d][%d] = %f\n",m,k,b[kk]); + p->shape[m][k][j] = b[kk++]; + } + } + + free_dvector(b, 0, p->nodp-1 + p->nn/2); + free_dmatrix(a, 0, p->nodp-1 + p->nn/2, 0, p->nn/2-1); +//printf("~1 ishape is done for band %d\n",j); + +} +#endif /* ISHAPE */ + + +/* ----------------------------------------------------- */ +/* Routine to figure out a suitable black point */ + +/* Structure to hold optimisation information */ +typedef struct { + mpp *p; /* Lookup object */ + int n; /* Number of device channels */ + double ilimit; /* Ink limit */ + double p1[3]; /* white pivot point */ + double p2[3]; /* Point on vector towards black */ +} bfinds; + +/* Optimise device values to minimise L, while remaining */ +/* within the ink limit, and staying in line between p1 and p2 */ +static double efunc6(void *adata, double pv[]) { + bfinds *b = (bfinds *)adata; + double rv = 0.0; + double dv[MPP_MXINKS]; + double Lab[3]; + double lr, ta, tb, terr; /* L ratio, target a, target b, target error */ + double sum, ovr; + int j; + + /* See if over ink limit or outside device range */ + for (ovr = sum = 0.0, j = 0; j < b->n; j++) { + dv[j] = pv[j]; + if (dv[j] < 0.0) { + if (-dv[j] > ovr) + ovr = -dv[j]; + dv[j] = 0.0; + } else if (dv[j] > 1.0) { + if ((dv[j] - 1.0) > ovr) + ovr = dv[j] - 1.0; + dv[j] = 1.0; + } + sum += dv[j]; + } + + if (b->ilimit > 1e-4) { + sum -= b->ilimit; + if (sum < 0.0) + sum = 0.0; + } else { + sum = 0.0; + } + + /* Compute Lab value */ + forward(b->p, NULL, Lab, NULL, dv); + +#ifdef DEBUG + printf("p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]); + printf("device value %f %f %f %f, Lab = %f %f %f\n",dv[0],dv[1],dv[2],dv[3],Lab[0],Lab[1],Lab[2]); +#endif + + /* Primary is to minimise L value */ + rv = Lab[0]; + + /* See how out of line from p1 to p2 we are */ + lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */ + ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */ + tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */ + + terr = (ta - Lab[1]) * (ta - Lab[1]) + + (tb - Lab[2]) * (tb - Lab[2]); + +#ifdef DEBUG + printf("target error %f\n",terr); +#endif + rv += 100.0 * terr; + +#ifdef DEBUG + printf("out of range error %f\n",ovr); + printf("over limit error %f\n",sum); +#endif + rv += 200 * (ovr + sum); + +#ifdef DEBUG + printf("black find tc ret %f\n",rv); +#endif + return rv; +} + +static void compute_wb( +mpp *p +) { + int j; + + /* Figure out the white and black points */ + if (p->imask & ICX_ADDITIVE) { /* Assume additive doesn't have an ink limit */ + + /* Simply lookup values from min and max device. */ + /* If we have ICX_WHITE, should this be treated the same as */ + /* subtractive black ???? */ + for (j = 0; j < p->n; j++) + p->white.nv[j] = 1.0; + forward(p, &p->white.band[3], NULL, p->white.band, p->white.nv); + + for (j = 0; j < p->n; j++) + p->black.nv[j] = 0.0; + forward(p, &p->black.band[3], NULL, p->black.band, p->black.nv); + + for (j = 0; j < p->n; j++) + p->kblack.nv[j] = 0.0; + forward(p, &p->kblack.band[3], NULL, p->kblack.band, p->kblack.nv); + + } else { /* Subtractive */ + bfinds bfs; + double sr[MPP_MXINKS]; /* search radius */ + double tt[MPP_MXINKS]; /* temporary */ + int trial; + double rv, brv; + int kbset = 0; + + /* Lookup white directly */ + for (j = 0; j < p->n; j++) + p->white.nv[j] = 0.0; + forward(p, &p->white.band[3], bfs.p1, p->white.band, p->white.nv); + + /* Choose a black direction */ + if (p->imask & ICX_BLACK) { + int bix; + bix = icx_ink2index(p->imask, ICX_BLACK); + + for (j = 0; j < p->n; j++) { + if (j == bix) + p->kblack.nv[j] = 1.0; + else + p->kblack.nv[j] = 0.0; + } + forward(p, &p->kblack.band[3], bfs.p2, p->kblack.band, p->kblack.nv); + kbset = 1; + + } else if ((p->imask & (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW)) + == (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW)) { + int cix, mix, yix; + cix = icx_ink2index(p->imask, ICX_CYAN); + mix = icx_ink2index(p->imask, ICX_MAGENTA); + yix = icx_ink2index(p->imask, ICX_YELLOW); + + for (j = 0; j < p->n; j++) { + if (j == cix) + p->kblack.nv[j] = 1.0; + else if (j == mix) + p->kblack.nv[j] = 1.0; + else if (j == yix) + p->kblack.nv[j] = 1.0; + else + p->kblack.nv[j] = 0.0; + } + forward(p, NULL, bfs.p2, NULL, p->kblack.nv); + + } else { /* Make direction parallel to L axis */ + bfs.p2[0] = 0.0; + bfs.p2[1] = bfs.p1[1]; + bfs.p2[2] = bfs.p1[2]; + } + bfs.p = p; + bfs.n = p->n; + bfs.ilimit = p->limit; + + /* Find the black point */ + /* Do several trials to avoid local minima */ + for (j = 0; j < p->n; j++) { + tt[j] = p->black.nv[j] = 0.5; /* Starting point */ + sr[j] = 0.1; + } + brv = 1e38; + for (trial = 0; trial < 20; trial++) { + + if (powell(&rv, p->n, tt, sr, 0.00001, 500, efunc6, (void *)&bfs, NULL, NULL) == 0) { + if (rv < brv) { + brv = rv; + for (j = 0; j < p->n; j++) + p->black.nv[j] = tt[j]; + } + } + for (j = 0; j < p->n; j++) { + tt[j] = p->black.nv[j] + d_rand(-0.3, 0.3); + if (tt[j] < 0.0) + tt[j] = 0.0; + else if (tt[j] > 1.0) + tt[j] = 1.0; + } + } + if (brv > 1000.0) + error ("mpp: Black point powell failed"); + + for (j = 0; j < p->n; j++) { /* Make sure device values are in range */ + if (p->black.nv[j] < 0.0) + p->black.nv[j] = 0.0; + else if (p->black.nv[j] > 1.0) + p->black.nv[j] = 1.0; + } + + /* Set black value */ + forward(p, &p->black.band[3], NULL, p->black.band, p->black.nv); + + /* Set K only if device doesn't have a K channel */ + if (kbset == 0) { + for (j = 0; j < p->n; j++) + p->kblack.nv[j] = p->black.nv[j]; + forward(p, &p->kblack.band[3], NULL, p->kblack.band, p->kblack.nv); + } + + if (p->verb) { + double Lab[3]; + + icmXYZ2Lab(&icmD50, Lab, p->white.band); + printf("White point %f %f %f [Lab %f %f %f]\n", + p->white.band[0],p->white.band[1],p->white.band[2], + Lab[0], Lab[1], Lab[2]); + + icmXYZ2Lab(&icmD50, Lab, p->black.band); + printf("Black point %f %f %f [Lab %f %f %f]\n", + p->black.band[0],p->black.band[1],p->black.band[2], + Lab[0], Lab[1], Lab[2]); + + icmXYZ2Lab(&icmD50, Lab, p->kblack.band); + printf("K only Black point %f %f %f [Lab %f %f %f]\n", + p->kblack.band[0],p->kblack.band[1],p->kblack.band[2], + Lab[0], Lab[1], Lab[2]); + } + } +} + +/* ===================================== */ + +/* Create the mpp from scattered data points */ +/* Returns nz on error */ +static int create( + mpp *p, /* This */ + int verb, /* Vebosity level, 0 = none */ + int quality, /* Profile quality, 0..3 */ + int display, /* non-zero if display device */ + double limit, /* Total ink limit (if not display) */ + inkmask devmask, /* Inkmask describing device colorspace */ + int spec_n, /* Number of spectral bands, 0 if not valid */ + double spec_wl_short, /* First reading wavelength in nm (shortest) */ + double spec_wl_long, /* Last reading wavelength in nm (longest) */ + double norm, /* Normalising scale value for spectral values */ + instType itype, /* Spectral instrument type (if not display) */ + int nodp, /* Number of points */ + mppcol *points /* Array of input points */ +) { + int it, i, j, k; + double de, mxde; /* Average Delta E and maximum Delta E */ + double sde, mxsde; /* Average Spectral error and maximum spectral error */ + int mxtcord; /* maximum transfer curve order */ + int maxit; /* Maximum number of tuning itterations */ + double thr; /* Powell threshold multiplier at each tuning pass */ + int useshape; /* Make use of shaping parameters */ + int mode; /* Band scanning mode */ + + /* Convert quality into operation counts */ + switch (quality) { + case 0: /* Low */ + useshape = 0; + mxtcord = 3; + maxit = 2; + break; + case 1: + default: /* Medium */ + useshape = 1; + mxtcord = 4; + maxit = 2; + break; + case 2: /* High */ + useshape = 1; + mxtcord = 5; + maxit = 3; + break; + case 3: /* Ultra high */ + useshape = 1; + mxtcord = 10; /* Is more actually better ? */ + maxit = 8; + break; + case 99: /* Special, simple model */ + useshape = 0; + mxtcord = 1; + maxit = 4; + break; + } + + /* Setup the basic mpp information */ + p->verb = verb; + p->imask = devmask; + p->n = icx_noofinks(devmask); + p->nn = 1 << p->n; + p->nnn2 = p->n * p->nn/2; + + if (display) { + p->display = 1; + p->limit = p->n; + p->itype = instUnknown; + } else { + p->display = 0; + p->limit = limit; /* Record it here */ + p->itype = itype; + } + p->spec_n = spec_n; + p->spec_wl_short = spec_wl_short; + p->spec_wl_long = spec_wl_long; + p->nodp = nodp; + + /* MPP limit is less than XICC */ + if (p->n > MPP_MXINKS) { + p->errc = 1; + sprintf(p->err,"MPP Can't handle %d colorants",p->n); + return 1; + } + + /* MPP limit is less than XSPECT */ + if (spec_n > MPP_MXBANDS) { + p->errc = 1; + sprintf(p->err,"MPP Can't handle %d spectral bands",spec_n); + return 1; + } + + /* Take a copy of the data points */ + if ((p->cols = new_mppcols(p->nodp, p->n, p->spec_n)) == NULL) { + error("Malloc failed!"); + } + if ((new_mppcol(&p->white, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + if ((new_mppcol(&p->black, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + if ((new_mppcol(&p->kblack, p->n, p->spec_n)) != 0) { + error("Malloc failed!"); + } + + p->spmax = -1e6; + for (i = 0; i < p->nodp; i++) { + copy_mppcol(&p->cols[i], &points[i], p->n, p->spec_n); /* Copy structure */ + + /* Create Lab version */ + icmXYZ2Lab(&icmD50, p->cols[i].Lab, p->cols[i].band); + +#ifdef SHARPEN + XYZ2sharp(&p->cols[i].band[0], &p->cols[i].band[1], &p->cols[i].band[2]); +#endif + /* Normalise spectral values */ + for (j = 0; j < p->spec_n; j++) { + p->cols[i].band[3+j] /= norm; /* Normalise spectral value to range 0..1 */ + + if (p->cols[i].band[3+j] > p->spmax) /* Track maximum value */ + p->spmax = p->cols[i].band[3+j]; + } + + /* Compute L* type band target values */ + for (j = 0; j < (3+p->spec_n); j++) { + p->cols[i].lband[j] = lDE(p->cols[i].band[j]); + } + } + p->norm = 1.0; /* Internal norm is 1.0 */ + + /* Compute L* type band target values */ + for (i = 0; i < p->nodp; i++) { + for (j = 0; j < (3+p->spec_n); j++) { + p->cols[i].lband[j] = lDE(p->cols[i].band[j]); + } + } + /* Init transfer curve parameters of model */ + for (k = 0; k < p->n; k++) { /* For each ink */ + for (j = 0; j < (p->spec_n+3); j++) { /* For each band */ + for (i = 0; i < mxtcord; i++) { /* For each curve order */ + if (i == 0) + p->tc[k][j][i] = -1.6; /* Typical starting value */ + else + p->tc[k][j][i] = 0.0; /* Straight transfer curve */ + } + } + } + + + /* Initialise the primary colorant values */ + for (i = -1; i < p->n; i++) { + int ii, bk = 0; + double bdif = 1e6; + + if (i < 0) + ii = 0; + else + ii = 1 << i; + + /* Search the patch list to find the one closest to this colorant combination */ + for (k = 0; k < p->nodp; k++) { + double dif = 0.0; + for (j = 0; j < p->n; j++) { + double tt; + if (i == j) + tt = 1.0 - p->cols[k].nv[j]; + else + tt = p->cols[k].nv[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bk = k; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + + /* Put that sample patch in place as initial value */ + for (j = 0; j < (3+p->spec_n); j++) + p->pc[ii][j] = p->cols[bk].band[j]; +#ifdef DEBUG + printf("comb 0x%x XYZ is %f %f %f\n", ii, p->pc[ii][0], p->pc[ii][1], p->pc[ii][2]); +#endif + } + + /* Estimate primary combination values from primary values */ + { + double sm[3+MPP_MXBANDS]; /* Smallest reflection sample in this band */ + double ink[3+MPP_MXBANDS]; + + /* Search the patch list to find the smallest values for each band */ + /* we'll use this as a guide to the freznell reflection from the surface */ + for (j = 0; j < (3+p->spec_n); j++) { + + sm[j] = 1e6; + for (k = 0; k < p->nodp; k++) { + if (sm[j] > p->cols[k].band[j]) { + int m; + sm[j] = p->cols[k].band[j]; + ink[j] = 0.0; + for (m = 0; m < p->n; m++) + ink[j] += p->cols[k].nv[m]; + } + } + /* Adjust for error in freznell was estimated from */ + /* a low ink coverage */ + if (ink[j] < 4.0) { + sm[j] *= pow(0.775, 4.0 - ink[j]); + } +#ifdef DEBUG + printf("smallest value in band %d = %f from total ink %f\n",j,sm[j],ink[j]); +#endif + } + + for (i = 3; i < p->nn; i++) { + + if ((i & (i-1)) == 0) + continue; /* Skip primaries */ + for (j = 0; j < (3+p->spec_n); j++) { + int k; + double wh = p->pc[0][j]; /* white */ + double tr = 1.0; /* Trapping coefficient */ + if (wh < 0.01) + wh = 0.01; /* Guard against silliness */ + p->pc[i][j] = wh; /* Start with white */ + for (k = 0; k < p->n; k++) { + if (i & (1<<k)) { + double co = p->pc[1 << k][j]; /* colorant + paper reflectance */ + co = (co - sm[j])/wh; /* Colorant reflectance */ + co /= tr; /* Trapping reduction */ + if (co > 1.0) + co = 1.0; + else if (co < 0.0) + co = 0.0; + p->pc[i][j] *= co; /* Estimated combined reflectivity */ + + tr *= 0.90; /* Next inks effectiveness due to trapping */ + } + } + p->pc[i][j] = p->pc[i][j]; + p->pc[i][j] += sm[j]; /* Add freznell back in */ + } +#ifdef DEBUG + printf("comb 0x%x estimated XYZ = %f %f %f\n",i, p->pc[i][0], p->pc[i][1], p->pc[i][2]); +#endif + } + } + + /* Override the estimated primary combination values, if actual readings are available */ + for (i = 3; i < p->nn; i++) { + int k, bk = 0; + double bdif = 1e6; + + if ((i & (i-1)) == 0) + continue; /* Skip primaries */ + + /* Search the patch list to find the one closest to this colorant combination */ + for (k = 0; k < p->nodp; k++) { + double dif = 0.0; + for (j = 0; j < p->n; j++) { + double tt; + if (i & (1<<j)) + tt = 1.0 - p->cols[k].nv[j]; + else + tt = p->cols[k].nv[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bk = k; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + + if (bdif < 0.02) { + + /* Override the estimated combination values with the real values */ + for (j = 0; j < (3+p->spec_n); j++) { +#ifdef DEBUG + printf("comb 0x%x band %d was %f ",i, j, p->pc[i][j]); +#endif + p->pc[i][j] = p->cols[bk].band[j]; +#ifdef DEBUG + printf("best %d now %f\n",bk, p->pc[i][j]); +#endif + } + } + } + + /* These initial primary combination values become the anchors during optimisation */ + for (i = 0; i < p->nn; i++) { + for (j = 0; j < (3+p->spec_n); j++) + p->lpca[i][j] = lDE(p->pc[i][j]); + } + + /* Allocate and init shape related parameter space */ + init_shape(p); + + p->cord = 1; /* Start with only 1 order */ + +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + deltae(p, &de, &mxde, &sde, &mxsde); + printf("Before optimising model have average dE of %f, max %f\n", de, mxde); + if (p->spec_n > 0) printf("and average spectral E of %f, max %f\n",sde, mxsde); + } + + /* - - - - - - - - - - - - - - - - - */ + +#ifndef NOPROCESS +#ifdef NEVER // Skip efunc1 passes for now. + /* Do initial fast pass of optimisations */ + /* using only first transfer curve order */ + for (it = 0, p->ott = 3; it < p->ott; it++) { + double resid; + + p->oit = it+1; + + /* First optimise each input channels transfer curve */ + for (k = 0; k < p->n; k++) { /* For each input channel */ + double tw = 0.0; /* Total weight */ + + p->och = k; /* device channel being optimised */ + + /* Setup the appropriate weights */ + for (i = 0; i < p->nodp; i++) { + mppcol *c = &p->cols[i]; + double ww; + int kk; + + for (ww = 0.0, kk = 0; kk < p->n; kk++) { + if (kk == k) + continue; /* Channel of interest can have any value */ + ww += c->nv[kk] * c->nv[kk]; + } + c->w = 1.0 - ww; + if (c->w < 0.0) + c->w = 0.0; + tw += c->w; + +//printf("~1 chan %d point %d weight %f\n",k,i,c->w); + } + + if (tw < 3.0) + error ("MPP - not enough weighted point"); + + for (j = 0; j < (3+p->spec_n); j++) { /* For all bands */ + double pv[MPP_MXTCORD]; /* Parameter values */ + double sr[MPP_MXTCORD]; /* search radius */ + + p->oba = j; /* Band being optimised */ + + if (p->verb) { + printf("Optimising device transfer curves channel %d band %d:\n",k,j); + } + + /* Get the current values */ + for (i = 0; i < p->cord; i++) { + pv[i] = p->tc[k][j][i]; + sr[i] = 0.05; + } + + sfunc1(p); /* Setup test point values for this chan and band */ + + if (powell(&resid, p->cord, pv, sr, 0.001, 100, efunc1, (void *)p, mppprog, (void *)p) != 0) + error ("Powell failed"); + + /* Put results back into place */ + for (i = 0; i < p->cord; i++) { + p->tc[k][j][i] = pv[i]; + } + } +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + deltae(p, &de, &mxde, &sde, &mxsde); + printf("\nNow got avg dE of %f, max %f\n",de, mxde); + if (p->spec_n > 0) + printf("and avgerage spectral E of %f, max %f\n",sde, mxsde); + } + } + } +#endif /* NEVER */ + + p->cord = mxtcord; + + /* - - - - - - - - - - - - - - - - - */ + /* Fine tune all parameters in the model */ + for (mode = 0;;) { + double pv[MPP_MXPARMS]; /* Parameter values */ + double sr[MPP_MXPARMS]; /* search radius */ + int lj, yj = 0; /* Last band, peak Y band */ + + /* Decide which band to do next */ + if (mode == 0) { /* Start at the beginning */ + lj = -1; + if (p->spec_n == 0) { + mode = 3; /* Switch to doing Y values */ + j = 1; + } else { + /* Start at the peak Y bandwidth */ + j = (int)(p->spec_n * (555.0 - p->spec_wl_short) + /(p->spec_wl_long - p->spec_wl_short) + 0.5); + if (j < 0) + j = 0; + else if (j >= p->spec_n) + j = p->spec_n-1; + j += 3; + yj = j; + mode = 1; /* Switch to incrementing j */ + } + } else if (mode == 1) { /* Increment j */ + lj = j; + j++; + if (j >= (3+p->spec_n)) { /* We're finished moving up */ + lj = yj; + j = yj-1; + mode = 2; + if (j < 3) { /* Just in case */ + lj = yj; + j = 1; + mode = 3; + } + } + } else if (mode == 2) { /* Decrement j */ + lj = j; + j--; + if (j < 3) { + lj = yj; /* We know that spect is valid */ + j = 1; + mode = 3; /* Switch to Y mode */ + } + } else if (mode == 3) { /* Doing Y mode */ + if (spec_n > 0) { + /* Use spec at the peak X bandwidth */ + lj = (int)(p->spec_n * (600.0 - p->spec_wl_short) + /(p->spec_wl_long - p->spec_wl_short) + 0.5); + if (lj < 0) + lj = 0; + else if (lj >= p->spec_n) + lj = p->spec_n-1; + lj += 3; + } else { + lj = 1; + } + j = 0; /* Doing X mode */ + mode = 4; + } else if (mode == 4) { /* Doing X mode */ + if (spec_n > 0) { + /* Use spec at the peak Z bandwidth */ + lj = (int)(p->spec_n * (445.0 - p->spec_wl_short) + /(p->spec_wl_long - p->spec_wl_short) + 0.5); + if (lj < 0) + lj = 0; + else if (lj >= p->spec_n) + lj = p->spec_n-1; + lj += 3; + } else { + lj = 1; + } + j = 2; /* Doing Z mode */ + mode = 5; + } else { + break; /* we're now done */ + } + + p->oba = j; /* Band being optimised */ + + if (p->verb) + printf("Doing band %d, last band %d\n",j,lj); + + /* See if the last bands values are a good place to start */ + if (lj >= 0) { + double cval, pval, p0val; + + banderr(p, &cval, NULL, j); /* Current error */ + + /* Copy previous band transfer and shape into current band */ + for (k = 0; k < p->n; k++) + for (i = 0; i < p->cord; i++) { + pv[k * p->cord + i] = p->tc[k][j][i]; /* Save current for restore */ + p->tc[k][j][i] = p->tc[k][lj][i]; + } + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + + sr[i] = p->shape[m][n][j]; + p->shape[m][n][j] = p->shape[m][n][lj]; + } + banderr(p, &pval, NULL, j); + + /* Try out the urrent order 0 transfer values with rest of transfer and shape */ + for (k = 0; k < p->n; k++) + p->tc[k][j][0] = pv[k * p->cord]; + banderr(p, &p0val, NULL, j); + + /* See which was best out of the three */ + if (pval >= cval && p0val >= pval) { /* Original was the best */ + for (k = 0; k < p->n; k++) + for (i = 0; i < p->cord; i++) + p->tc[k][j][i] = pv[k * p->cord + i]; /* Restore previous values */ + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + p->shape[m][n][j] = sr[i]; /* Restore previous value */ + } +//printf("~1 Starting values were best (%f && %f > %f)\n",pval,p0val,cval); + } else if (p0val >= pval) { /* Copying all was best */ + + for (k = 0; k < p->n; k++) + p->tc[k][j][0] = p->tc[k][lj][0]; /* Back to order 0 values */ + +//printf("~1 copying all previous bands values was best (%f < %f && %f)\n",pval,cval,p0val); + } else { +//printf("~1 copying except order 0 values was best (%f < %f && %f)\n",p0val,cval,pval); + } + } + +#ifdef MULTIPASS /* Multipass in parts */ + for (it = 0, p->ott = maxit, thr = 1.0; it < maxit; it++, thr *= 0.2) { + double sde, mxsde; + double resid; + + p->oit = it+1; + + /* Optimise main transfer curve to minimise each bands error */ + /* Initially using only first transfer curve order */ + + if (p->verb) + printf("Fine tuning device transfer curves itteration %d\n",it); + + /* Get the current values */ + for (k = 0; k < p->n; k++) { + for (i = 0; i < p->cord; i++) { + pv[k * p->cord + i] = p->tc[k][j][i]; + sr[k * p->cord + i] = 0.05; + } + } +#ifdef TESTDFUNC + test_dfunc2(p, p->n * p->cord, pv); +#endif /* TESTDFUNC */ +#ifdef NODDV + if (powell(&resid, p->n * p->cord, pv, sr, thr * 0.01, 200, + efunc2, (void *)p, mppprog, (void *)p) != 0) + error ("Powell failed"); +#else /* !NODDV */ + if (conjgrad(&resid, p->n * p->cord, pv, sr, thr * 0.01, 200, + efunc2, dfunc2, (void *)p, mppprog, (void *)p)!= 0) + error ("ConjGrad failed"); +#endif /* !NODDV */ + + /* Put results back into place */ + for (k = 0; k < p->n; k++) { + for (i = 0; i < p->cord; i++) + p->tc[k][j][i] = pv[k * p->cord + i]; + } + +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + banderr(p, &sde, &mxsde, j); /* Current error */ + printf("\nNow got avg E of %f, max %f for this band\n",sde, mxsde); + } + + p->cord = mxtcord; /* maximum transfer curve order after very first run */ + + /* Tune the shaping parameters */ + if (useshape) { + + if (p->verb) + printf("Tuning detailed shaping parameters itteration %d\n",it); + + sfunc3(p); /* Setup test point values for this band */ + + /* Get the current values */ + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + + pv[i] = p->shape[m][n][j]; + sr[i] = 0.01; + } + +#ifdef TESTDFUNC + test_dfunc3(p, p->nnn2, pv); +#endif /* TESTDFUNC */ +#ifdef NODDV + if (powell(&resid, p->nnn2, pv, sr, thr * 0.05, 2000, + efunc3, (void *)p, mppprog, (void *)p) != 0) + error ("Powell failed"); + +#else /* !NODDV */ + if (conjgrad(&resid, p->nnn2, pv, sr, thr * 0.05, 2000, + efunc3, dfunc3, (void *)p, mppprog, (void *)p) != 0.0) + error ("ConjGrad failed"); +#endif /* !NODDV */ + + /* Put results back into place */ + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + + p->shape[m][n][j] = pv[i]; +//printf("~1 shape[%d][%d] = %f\n",m,n,pv[i]); + } + +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + banderr(p, &sde, &mxsde, j); /* Current error */ + printf("\nNow got avg E of %f, max %f for this band\n",sde, mxsde); + } +#ifdef DEBUG + dump_shape(p, 0, "After efunc3:"); +#endif /* DEBUG */ + + p->useshape = 1; /* Would be nice to flag this on a per band basis */ + } + + /* Tune the vertex parameters */ + + if (p->verb) + printf("Optimising device combination values itteration %d\n",it); + + sfunc4(p); /* Setup test point values for this band */ + + /* Get the current values */ + for (k = 0; k < p->nn; k++) { + pv[k] = p->pc[k][j]; + sr[k] = 0.01; + } + +#ifdef TESTDFUNC + test_dfunc4(p, p->nn, pv); +#endif /* TESTDFUNC */ +#ifdef NODDV + if (powell(&resid, p->nn, pv, sr, thr * 0.01, 500, + efunc4, (void *)p, mppprog, (void *)p) != 0) + error ("Powell failed"); +#else /* !NODDV */ + if (conjgrad(&resid, p->nn, pv, sr, thr * 0.01, 500, + efunc4, dfunc4, (void *)p, mppprog, (void *)p) != 0) + error ("ConjGrad failed"); +#endif /* !NODDV */ + + /* Put results back into place */ + for (k = 0; k < p->nn; k++) { + double pp = pv[k]; + if (pp < 0.0) + pp = 0.0; + p->pc[k][j] = pp; + } + +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + banderr(p, &sde, &mxsde, j); /* Current error */ + printf("\nNow got avg E of %f, max %f for this band\n",sde, mxsde); + } + } +#endif /* MULTIPASS */ + +#ifdef BIGBANG + /* Do optimisation with one big bang */ + { + double resid; + double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */ + double *sr2, *sr3, *sr4; /* Pointers to each group of search radius */ + int tparms = p->n * p->cord + p->nnn2 + p->nn; + + pv2 = pv; + pv3 = pv + (p->n * p->cord); + pv4 = pv + (p->n * p->cord) + p->nnn2; + sr2 = sr; + sr3 = sr + (p->n * p->cord); + sr4 = sr + (p->n * p->cord) + p->nnn2; + + /* Get the current transfer values */ + for (k = 0; k < p->n; k++) { + for (i = 0; i < p->cord; i++) { + pv2[k * p->cord + i] = p->tc[k][j][i]; + sr2[k * p->cord + i] = 0.005; + } + } + /* Get the current shaper values */ + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + + pv3[i] = p->shape[m][n][j]; + sr3[i] = 0.005; + } + /* Get the current device combination values */ + for (k = 0; k < p->nn; k++) { + pv4[k] = p->pc[k][j]; + sr4[k] = 0.005; + } + +#ifdef TESTDFUNC + test_dfunc0(p, tparms, pv); +#endif /* TESTDFUNC */ + + if (conjgrad(&resid, tparms, pv, sr, 0.001, 4000, efunc0, dfunc0, (void *)p, + mppprog, (void *)p) != 0) + error ("ConjGrad failed"); + + /* Put results back into place */ + for (k = 0; k < p->n; k++) { + for (i = 0; i < p->cord; i++) + p->tc[k][j][i] = pv2[k * p->cord + i]; + } + for (i = 0; i < p->nnn2; i++) { + int m = p->c2f[i].ink; + int n = p->c2f[i].comb; + + p->shape[m][n][j] = pv3[i]; + } + for (k = 0; k < p->nn; k++) { + double pp = pv4[k]; + if (pp < 0.0) + pp = 0.0; + p->pc[k][j] = pp; + } + +#ifndef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + banderr(p, &sde, &mxsde, j); /* Current error */ + printf("\nNow got avg E of %f, max %f for this band\n",sde, mxsde); + } + } +#endif /* BIGBANG */ + } +#endif /* NOPROCESS */ + +#ifdef DEBUG + if (p->verb) +#endif /* !DEBUG */ + { + double de, mxde; + deltae(p, &de, &mxde, &sde, &mxsde); + printf("\nNow got avg dE of %f, max %f\n",de, mxde); + if (p->spec_n > 0) + printf("and avgerage spectral E of %f, max %f\n",sde, mxsde); + } + +#ifdef DOPLOT /* Plot the device curves */ + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + + for (i = 0; i < (3+p->spec_n); i++) { + printf("Band %d:\n",i); + for (j = 0; j < p->n; j++) { + printf("Ink %d:\n",j); + + for (k = 0; k < XRES; k++) { + double x; + x = k/(double)(XRES-1); + xx[k] = x; + y1[k] = icxTransFunc(p->tc[j][i], p->cord, x); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + } + } +#endif /* DOPLOT */ + + /* Figure out the white and black points */ + compute_wb(p); + + /* Done with our copy of the input points */ + free (p->cols); + p->nodp = 0; + p->cols = NULL; + + return 0; +} + + +#ifdef DEBUG + +/* Dump current shape params to a file */ +static void dump_shape(mpp *p, int first, char *title) { + int i,j,k; + FILE *df; + /* Some debug code */ + + if (first) { + if ((df = fopen("debug.txt","w")) == NULL) + error ("Failed to open debug.txt"); + } else { + if ((df = fopen("debug.txt","a")) == NULL) + error ("Failed to open debug.txt"); + } + + fprintf(df,"%s\n",title); + + for (i = 0; i < (3+p->spec_n); i++) { + fprintf(df,"Band %d:\n",i); + for (j = 0; j < p->n; j++) { + fprintf(df,"Ink %d:\n",j); + + for (k = 0; k < p->nn; k++) { + if ((k & (1<<j)) == 0) { + int m, n; + double val = 0.0; + fprintf(df,"Comb %d = %f\n", k, p->shape[j][k][i]); + } + } + fprintf(df,"\n"); + } + fprintf(df,"\n"); + } + fclose(df); +} + +/* Combine two first order shapers coefficients together */ +static double comb(double n1, double n2) { + double nn; + if (n1 > 0.0) + n1 = (n1+1.0); + else + n1 = 1.0/(1.0-n1); + + if (n2 > 0.0) + n2 = (n2+1.0); + else + n2 = 1.0/(1.0-n2); + + nn = n1 * n2; + + if (nn >= 1.0) + nn -= 1.0; + else + nn = 1.0-(1.0/nn); + + return nn; +} + +#endif /* DEBUG */ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/mpp.h b/xicc/mpp.h new file mode 100644 index 0000000..0becaad --- /dev/null +++ b/xicc/mpp.h @@ -0,0 +1,268 @@ +#ifndef MPP_H +#define MPP_H + +/* + * Argyll Color Correction System + * Model Printer Profile object. + * + * Author: Graeme W. Gill + * Date: 24/2/2002 + * + * Copyright 2002 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* This version (based on mpp_x1) has n * 2^(n-1) shape params, */ +/* used for linear interpolation of the shaping correction. */ + +/* + * This object provides a model based printer forward profile + * functionality, to support forward profiling and optimised + * separation of printing devices with more than 4 inks. + */ + +/* ------------------------------------------------------------------------------ */ + +#define MPP_MXINKS 8 /* Would like to be ICX_MXINKS but need more dynamic allocation */ +#define MPP_MXTCORD 10 /* Maxkimum shaper harmonic orders to use */ +#define MPP_MXCCOMB (1 << MPP_MXINKS) /* Maximum number of primary combinations */ +#define MPP_MXPARMS (MPP_MXINKS * MPP_MXTCORD + (MPP_MXINKS * MPP_MXCCOMB/2) + MPP_MXCCOMB) + /* Maximum total parameters for a band */ +#define MPP_MXBANDS 61 /* Maximum number of spectral bands (enought for 10nm) */ + +/* A test patch value */ +typedef struct { + /* public: */ + double *nv; /* [MPP_MXINKS] Device values */ + double *band; /* [3+MPP_MXBANDS]; Target XYZ & Spectral reflectance values */ + + /* private: */ + double w; /* Weight for this pass */ + double *lband; /* [3+MPP_MXBANDS]; L* scale Target XYZ & Spectral reflectance values */ + double Lab[3]; /* Target Lab value */ + double tpcnv, tpcnv1; /* Intermediate values for band oba without channel och */ + double *tcnv; /* [MPP_MXINKS] Transfer curve corrected device values */ + double *scnv; /* [MPP_MXINKS] Ideal shape correction values for device input */ + double *pcnv; /* [MPP_MXCCOMB] Primary combination values (pre or post shape) */ + double *fcnv; /* [MPP_MXINKS * MPP_MXCCOMB/2] shape interpolation weights for och */ + double cXYZ[3]; /* Current model XYZ value */ + double err; /* Delta E squared */ +} mppcol; + +struct _mpp { + + /* Public: */ + void (*del)(struct _mpp *p); + + + /* Create the mpp from scattered data points */ + /* Returns nz on error */ + int (*create) (struct _mpp *p, + int verb, /* Vebosity level, 0 = none */ + int quality, /* Profile quality, 0..3 */ + int display, /* non-zero if display device */ + double limit, /* Total ink limit (not %) (if not display) */ + inkmask imask, /* Inkmask describing device colorspace */ + int spec_n, /* Number of spectral bands, 0 if not valid */ + double spec_wl_short, /* First reading wavelength in nm (shortest) */ + double spec_wl_long, /* Last reading wavelength in nm (longest) */ + double norm, /* Normalising scale value */ + instType itype, /* Spectral instrument type (if not display) */ + int no, /* Number of points */ + mppcol *points); /* Array of input points */ + + int (*write_mpp)(struct _mpp *p, char *filename, int lab); /* write to a CGATS .mpp file */ + int (*read_mpp)(struct _mpp *p, char *filename); /* read from a CGATS .mpp file */ + + /* Get various types of information about the mpp */ + void (*get_info) (struct _mpp *p, + inkmask *imask, /* Inkmask, describing device colorspace */ + int *nodchan, /* Number of device channels */ + double *limit, /* Total ink limit (0.0 .. devchan) */ + int *spec_n, /* Number of spectral bands, 0 if none */ + double *spec_wl_short,/* First reading wavelength in nm (shortest) */ + double *spec_wl_long, /* Last reading wavelength in nm (longest) */ + instType *itype, /* Instrument type */ + int *display); /* NZ if display type */ + + /* Set an illuminant and observer to use spectral model */ + /* for CIE lookup with optional FWA. Set both to default for XYZ mpp model. */ + /* Return 0 on OK, 1 on spectral not supported */ + /* If the model is for a display, the illuminant will be ignored. */ + int (*set_ilob) (struct _mpp *p, + icxIllumeType ilType, /* Illuminant type (icxIT_default for none) */ + xspect *custIllum, /* Custom illuminant (NULL for none) */ + icxObserverType obType, /* Observer type (icxOT_default for none) */ + xspect custObserver[3], /* Custom observer (NULL for none) */ + icColorSpaceSignature rcs, /* Return color space, icSigXYZData or icSigLabData */ + int use_fwa /* NZ to involke FWA. */ + ); + + /* Get the white and black points for this profile (default XYZ) */ + void (*get_wb) (struct _mpp *p, + double *white, + double *black, + double *kblack); + + + /* Lookup an XYZ or Lab color (default XYZ) */ + /* [will use spectral and FWA if configured] */ + void (*lookup) (struct _mpp *p, + double *out, /* Returned XYZ or Lab */ + double *in); /* Input device values */ + + /* Lookup an XYZ or Lab color with its partial derivative. */ + /* [will ignore spectral and FWA even if configured] */ + void (*dlookup)(struct _mpp *p, + double *out, /* Return the XYZ or Lab */ + double **dout, /* Return the partial derivative dout[3][n] */ + double *dev); + + /* Lookup an XYZ value. (never FWA corrected) */ + void (*lookup_xyz) (struct _mpp *p, + double *out, /* Returned XYZ value */ + double *in); /* Input device values */ + + /* Lookup a spectral value. (never FWA corrected) */ + void (*lookup_spec) (struct _mpp *p, + xspect *out, /* Returned spectral value */ + double *in); /* Input device values */ + + /* Return a gamut object, return NULL on error */ + gamut *(*get_gamut)(struct _mpp *p, double detail); /* detail level 0.0 = default */ + + /* Private: */ + int verb; /* Verbose */ + + /* General model information */ + int display; /* Non-zero if display profile rather than output */ + inkmask imask; /* Inkmask describing device space */ + double limit; /* Total ink limit (If output device) */ + int spec_n; /* Number of spectral bands, 0 if not valid */ + double spec_wl_short; /* First reading wavelength in nm (shortest) */ + double spec_wl_long; /* Last reading wavelength in nm (longest) */ + double norm; /* Normalising scale value (will be 1.0) */ + instType itype; /* Spectral instrument type (If output device) */ + mppcol white, black; /* White and black points */ + mppcol kblack; /* K only black point */ + + /* Foward model parameters */ + int n; /* Number of chanels */ + int nn; /* Number of primary combinations = 1 << n */ + int nnn2; /* Total shape combinations = n * nn/2 */ + + int cord; /* Device transfer curve order (must be 1 <= cord <= MPP_MXTCORD) */ + double tc[MPP_MXINKS][3+MPP_MXBANDS][MPP_MXTCORD]; /* Device transfer curve parameters */ + int useshape; /* Flag, NZ if shape parameters are being used */ + double ***shape; /* [MPP_MXINKS][MPP_MXCCOMB][3+MPP_MXBANDS] Extra shaping parameters */ + double pc[MPP_MXCCOMB][3+MPP_MXBANDS]; /* Primary overlay combinations color values */ + + /* Model housekeeping */ + /* Translate sparse shape color combo to compacted parameters and back */ + int f2c[MPP_MXINKS][MPP_MXCCOMB]; /* Full to Compact */ + struct { int ink; int comb; } c2f[MPP_MXINKS * MPP_MXCCOMB/2]; /* Compact to Full */ + + /* Optimisation state */ + mppcol *otp; /* Optimisation test point */ + int oit, ott; /* Optimisation itteration and total itterations */ + int och; /* Optimisation device channel 0..n-1 */ + int oba; /* Optimisation band, 0..2 are XYZ, 3..spec_n+3 are spectral */ + double lpca[MPP_MXCCOMB][MPP_MXBANDS]; /* Primary combinations anchor L* band values */ + int nodp; /* Number of device data points */ + mppcol *cols; /* List of test points */ + double spmax; /* Maximum spectral value of any sample and band */ + + /* Lookup */ + icColorSpaceSignature pcs; /* PCS to return, XYZ, Lab */ + xsp2cie *spc; /* Spectral to CIE converter (NULL if using XYZ model) */ + + /* Houskeeping */ + int errc; /* Error code */ + char err[200]; /* Error message */ +}; typedef struct _mpp mpp; + +/* Create a new, uninitialised mpp */ +mpp *new_mpp(void); + + +/* - - - - - - - - - - - - - */ +/* mppcol utility functions */ +/* Allocate the data portion of an mppcol */ +/* Return NZ if malloc error */ +int new_mppcol( +mppcol *p, /* mppcol to allocate */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +); + +/* Free the data allocation of an mppcol */ +void del_mppcol( +mppcol *p, /* mppcol to free */ +int n, /* Number of inks */ +int nb /* Number of spectral bands */ +); + +/* Copy the contents of one mppcol to another */ +void copy_mppcol( + mppcol *d, /* Destination */ + mppcol *s, /* Source */ + int n, /* Number of inks */ + int nb /* Number of spectral bands */ +); + +/* Allocate an array of mppcol, */ +/* Return NULL if malloc error */ +mppcol *new_mppcols( + int no, /* Number in array */ + int n, /* Number of inks */ + int nb /* Number of spectral bands */ +); + +/* Free an array of mppcol */ +void del_mppcols( + mppcol *p, /* mppcol array to be free'd */ + int no, /* Number in array */ + int n, /* Number of inks */ + int nb /* Number of spectral bands */ +); + +#endif /* MPP_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/mpplu.c b/xicc/mpplu.c new file mode 100644 index 0000000..dcbd1c3 --- /dev/null +++ b/xicc/mpplu.c @@ -0,0 +1,1355 @@ + +/* + * Model Printer Profile Lookup test utility. + * + * Author: Graeme W. Gill + * Date: 2002/12/30 + * Version: 1.00 + * + * Copyright 2002, 2003 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "numlib.h" +#include "xicc.h" +#include "counters.h" + +void usage(void) { + fprintf(stderr,"Translate colors through an MPP profile, V1.00\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: mpplu [-v] [-f func] [-i intent] [-o order] profile\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -f function f = forward, b = backwards\n"); + fprintf(stderr," -p oride x = XYZ_PCS, l = Lab_PCS, y = Yxy, s = spectral,\n"); + fprintf(stderr," -l limit override default ink limit, 1 - N00%%\n"); + fprintf(stderr," -i illum Choose illuminant for print/transparency spectral data:\n"); + fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n"); + fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n"); + fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n"); + fprintf(stderr," -u Use Fluorescent Whitening Agent compensation\n"); + fprintf(stderr," -g Create gamut output\n"); + fprintf(stderr," -w Create gamut VRML as well\n"); + fprintf(stderr," -n Don't add VRML axes\n"); + fprintf(stderr," -a n Gamut transparency level\n"); + fprintf(stderr," -d n Gamut surface detail level\n"); + fprintf(stderr," -t num Invoke debugging test code \"num\" 1..n\n"); + fprintf(stderr," 1 - check partial derivative for device input\n"); + fprintf(stderr," 2 - create overlap diagnostic VRML gamut surface\n"); + fprintf(stderr,"\n"); + fprintf(stderr," The colors to be translated should be fed into stdin,\n"); + fprintf(stderr," one input color per line, white space separated.\n"); + fprintf(stderr," A line starting with a # will be ignored.\n"); + fprintf(stderr," A line not starting with a number will terminate the program.\n"); + exit(1); +} + +static void diag_gamut(mpp *p, double gamres, int doaxes, double trans, char *outname); +static void mpp_rev(mpp *mppo, double limit, double *out, double *in); + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char prof_name[100]; + mpp *mppo; + int verb = 0; + int test = 0; /* special test code */ + int dogam = 0; /* Create gamut */ + int dowrl = 0; /* Create VRML gamut */ + int doaxes = 1; /* Create VRML axes */ + double trans = 0.0; /* Transparency */ + double gamres = 0.0; /* Gamut resolution */ + int repYxy = 0; /* Report Yxy */ + int repSpec = 0; /* Report Spectral */ + int bwd = 0; /* Do reverse lookup */ + double dlimit; /* Device ink limit */ + double limit = -1.0; /* Used ink limit */ + int display = 0; /* NZ if display type */ + int spec = 0; /* Use spectral data flag */ + int spec_n; /* Number of spectral bands, 0 if not valid */ + double spec_wl_short; /* First reading wavelength in nm (shortest) */ + double spec_wl_long; /* Last reading wavelength in nm (longest) */ + int fwacomp = 0; /* FWA compensation */ + icxIllumeType illum = icxIT_default; /* Spectral defaults */ + xspect cust_illum; /* Custom illumination spectrum */ + icxObserverType observ = icxOT_default; + char buf[200]; + double in[MAX_CHAN], out[MAX_CHAN]; + int rv = 0; + + inkmask imask; /* Device Ink mask */ + char *ident = NULL; /* Device colorspec description */ + icColorSpaceSignature pcss; /* Type of PCS space */ + int devn, pcsn; /* Number of components */ + + icColorSpaceSignature pcsor = icSigLabData; /* Default */ + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + /* function */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'f': + case 'F': + bwd = 0; + break; + case 'b': + case 'B': + bwd = 1; + break; + default: + usage(); + } + } + + /* PCS override */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'x': + case 'X': + pcsor = icSigXYZData; + repYxy = repSpec = 0; + break; + case 'l': + case 'L': + pcsor = icSigLabData; + repYxy = repSpec = 0; + break; + case 'y': + case 'Y': + pcsor = icSigXYZData; + repYxy = 1; + repSpec = 0; + break; + case 's': + case 'S': + pcsor = icSigXYZData; + repYxy = 0; + repSpec = 1; + spec = 1; + break; + default: + usage(); + } + } + + /* Ink Limit */ + else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage(); + limit = atof(na); + } + + /* Spectral Illuminant type */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage(); + if (strcmp(na, "A") == 0) { + spec = 1; + illum = icxIT_A; + } else if (strcmp(na, "C") == 0) { + spec = 1; + illum = icxIT_C; + } else if (strcmp(na, "D50") == 0) { + spec = 1; + illum = icxIT_D50; + } else if (strcmp(na, "D50M2") == 0) { + spec = 1; + illum = icxIT_D50M2; + } else if (strcmp(na, "D65") == 0) { + spec = 1; + illum = icxIT_D65; + } else if (strcmp(na, "F5") == 0) { + spec = 1; + illum = icxIT_F5; + } else if (strcmp(na, "F8") == 0) { + spec = 1; + illum = icxIT_F8; + } else if (strcmp(na, "F10") == 0) { + spec = 1; + illum = icxIT_F10; + } else { /* Assume it's a filename */ + spec = 1; + illum = icxIT_custom; + if (read_xspect(&cust_illum, na) != 0) + usage(); + } + } + + /* Spectral Observer type */ + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage(); + if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */ + spec = 1; + observ = icxOT_CIE_1931_2; + } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */ + spec = 1; + observ = icxOT_CIE_1964_10; + } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */ + spec = 1; + observ = icxOT_Stiles_Burch_2; + } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */ + spec = 1; + observ = icxOT_Judd_Voss_2; + } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */ + spec = 1; + observ = icxOT_Shaw_Fairchild_2; + } else + usage(); + } + + /* Fluerescent Whitner compensation */ + else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') + fwacomp = 1; + + /* Gamut plot */ + else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') + dogam = 1; + + /* VRML plot */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + dogam = 1; + dowrl = 1; + } + + /* No VRML axes */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + doaxes = 0; + } + + /* Transparency */ + else if (argv[fa][1] == 'a' || argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage(); + trans = atof(na); + } + + /* Surface Detail */ + else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage(); + gamres = atof(na); + dogam = 1; + } + + /* Test code */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + fa = nfa; + if (na == NULL) usage(); + test = atoi(na); + } + + else + usage(); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(prof_name,argv[fa]); + + if ((mppo = new_mpp()) == NULL) + error ("Creation of MPP object failed"); + + if ((rv = mppo->read_mpp(mppo,prof_name)) != 0) + error ("%d, %s",rv,mppo->err); + + mppo->get_info(mppo, &imask, &devn, &dlimit, &spec_n, &spec_wl_short, &spec_wl_long, NULL, &display); + ident = icx_inkmask2char(imask, 1); + + if (verb) { + printf("MPP profile with %d colorants, type %s, TAC %f\n",devn,ident, dlimit); + if (display) + printf("MPP profile is for a display type device\n"); + } + + if (limit <= 0.0 || dlimit < limit) + limit = dlimit; + + pcss = pcsor; + pcsn = 3; + + if (spec && spec_n == 0) { + error("Spectral profile needed for spectral result, custom illuminant, observer or FWA"); + } + + /* Select CIE return value details */ + if ((rv = mppo->set_ilob(mppo, illum, &cust_illum, observ, NULL, pcss, fwacomp)) != 0) { + if (rv == 1) + error("Spectral profile needed for custom illuminant, observer or FWA"); + error("Error setting illuminant, observer, or FWA"); + } + + if (test != 0) { + printf("!!!!! Running special test code no %d !!!!!\n",test); + + if (test == 1) { + double **dv, **rdv; + + dv = dmatrix(0, pcsn-1, 0, devn-1); + rdv = dmatrix(0, pcsn-1, 0, devn-1); + + printf("Checking partial derivative at each input value\n"); + /* Process colors to translate */ + for (;;) { + int i,j; + char *bp, *nbp; + double tout[MAX_CHAN]; + + /* Read in the next line */ + if (fgets(buf, 200, stdin) == NULL) + break; + if (buf[0] == '#') { + fprintf(stdout,"%s\n",buf); + continue; + } + /* For each input number */ + for (bp = buf-1, nbp = buf, i = 0; i < MAX_CHAN; i++) { + bp = nbp; + in[i] = strtod(bp, &nbp); + if (nbp == bp) + break; /* Failed */ + } + if (i == 0) + break; + + /* Do conversion */ + mppo->lookup(mppo, out, in); + mppo->dlookup(mppo, out, dv, in); + + for (j = 0; j < devn; j++) { + double del = 1e-9; + + if (in[j] > 0.5) + del = -del; + + in[j] += del; + mppo->lookup(mppo, tout, in); + in[j] -= del; + + for (i = 0; i < pcsn; i++) { + rdv[i][j] = (tout[i] - out[i])/del; + } + } + + /* Output the results */ + for (j = 0; j < devn; j++) { + if (j > 0) + fprintf(stdout," %f",in[j]); + else + fprintf(stdout,"%f",in[j]); + } + printf(" [%s] -> ", ident); + + for (j = 0; j < pcsn; j++) { + if (j > 0) + fprintf(stdout," %f",out[j]); + else + fprintf(stdout,"%f",out[j]); + } + printf(" [%s]\n", icm2str(icmColorSpaceSignature, pcss)); + + /* Print the derivatives */ + for (i = 0; i < pcsn; i++) { + + printf("Output chan %d: ",i); + for (j = 0; j < devn; j++) { + if (j < (devn-1)) + fprintf(stdout,"%f ref %f, ",dv[i][j], rdv[i][j]); + else + fprintf(stdout,"%f ref %f\n",dv[i][j], rdv[i][j]); + } + } + } + + free_dmatrix(dv, 0, pcsn-1, 0, devn-1); + free_dmatrix(rdv, 0, pcsn-1, 0, devn-1); + + } else if (test == 2) { + char *xl, gam_name[100]; + + strcpy(gam_name, prof_name); + if ((xl = strrchr(gam_name, '.')) == NULL) /* Figure where extention is */ + xl = gam_name + strlen(gam_name); + + strcpy(xl,".wrl"); + diag_gamut(mppo, gamres, doaxes, trans, gam_name); + + } else { + printf("Unknown test!\n"); + } + + } else if (dogam) { + gamut *gam; + char *xl, gam_name[100]; + int docusps = 1; + + if ((gam = mppo->get_gamut(mppo, gamres)) == NULL) + error("get_gamut failed\n"); + + strcpy(gam_name, prof_name); + if ((xl = strrchr(gam_name, '.')) == NULL) /* Figure where extention is */ + xl = gam_name + strlen(gam_name); + + strcpy(xl,".gam"); + if (gam->write_gam(gam,gam_name)) + error ("write gamut failed on '%s'",gam_name); + + if (dowrl) { + strcpy(xl,".wrl"); + if (gam->write_vrml(gam,gam_name, doaxes, docusps)) + error ("write vrml failed on '%s'",gam_name); + } + + gam->del(gam); + + } else { + /* Normal color lookup */ + + if (repYxy) { /* report Yxy rather than XYZ */ + if (pcss == icSigXYZData) + pcss = icSigYxyData; + } + + /* Process colors to translate */ + for (;;) { + int i,j; + char *bp, *nbp; + + /* Read in the next line */ + if (fgets(buf, 200, stdin) == NULL) + break; + if (buf[0] == '#') { + fprintf(stdout,"%s\n",buf); + continue; + } + /* For each input number */ + for (bp = buf-1, nbp = buf, i = 0; i < MAX_CHAN; i++) { + bp = nbp; + in[i] = strtod(bp, &nbp); + if (nbp == bp) + break; /* Failed */ + } + if (i == 0) + break; + + if (!bwd) { + + if (repSpec) { + xspect ospec; + + /* Do lookup of spectrum */ + mppo->lookup_spec(mppo, &ospec, in); + + /* Output the results */ + for (j = 0; j < devn; j++) { + if (j > 0) + fprintf(stdout," %f",in[j]); + else + fprintf(stdout,"%f",in[j]); + } + printf(" [%s] -> ", ident); + + for (j = 0; j < spec_n; j++) { + if (j > 0) + fprintf(stdout," %f",ospec.spec[j]); + else + fprintf(stdout,"%f",ospec.spec[j]); + } + + printf(" [%3.0f .. %3.0f nm]\n", spec_wl_short, spec_wl_long); + + } else { + + /* Do conversion */ + mppo->lookup(mppo, out, in); + + /* Output the results */ + for (j = 0; j < devn; j++) { + if (j > 0) + fprintf(stdout," %f",in[j]); + else + fprintf(stdout,"%f",in[j]); + } + printf(" [%s] -> ", ident); + + if (repYxy && pcss == icSigYxyData) { + double X = out[0]; + double Y = out[1]; + double Z = out[2]; + double sum = X + Y + Z; + if (sum < 1e-6) { + out[0] = out[1] = out[2] = 0.0; + } else { + out[0] = Y; + out[1] = X/sum; + out[2] = Y/sum; + } + } + for (j = 0; j < pcsn; j++) { + if (j > 0) + fprintf(stdout," %f",out[j]); + else + fprintf(stdout,"%f",out[j]); + } + + printf(" [%s]\n", icm2str(icmColorSpaceSignature, pcss)); + } + + } else { /* Do a reverse lookup */ + + if (repYxy && pcss == icSigYxyData) { + double Y = in[0]; + double x = in[1]; + double y = in[2]; + double z = 1.0 - x - y; + double sum; + if (y < 1e-6) { + in[0] = in[1] = in[2] = 0.0; + } else { + sum = Y/y; + in[0] = x * sum; + in[1] = Y; + in[2] = z * sum; + } + } + + /* Do conversion */ + mpp_rev(mppo, limit, out, in); + + /* Output the results */ + for (j = 0; j < pcsn; j++) { + if (j > 0) + fprintf(stdout," %f",in[j]); + else + fprintf(stdout,"%f",in[j]); + } + printf(" [%s] -> ", icm2str(icmColorSpaceSignature, pcss)); + + for (j = 0; j < devn; j++) { + if (j > 0) + fprintf(stdout," %f",out[j]); + else + fprintf(stdout,"%f",out[j]); + } + + printf(" [%s]\n", ident); + } + } + } + + free(ident); + mppo->del(mppo); + + return 0; +} + + +/* -------------------------------------------- */ +/* Code for special gamut surface plot */ + +#define GAMUT_LCENT 50 + +/* Create a diagnostic gamut, illustrating */ +/* device space "fold-over" */ +/* (This will be in the current PCS, but assumed to be Lab) */ +static void diag_gamut( +mpp *p, /* This */ +double detail, /* Gamut resolution detail */ +int doaxes, +double trans, /* Transparency */ +char *outname /* Output VRML file */ +) { + int i, j; + FILE *wrl; + struct { + double x, y, z; + double wx, wy, wz; + double r, g, b; + } axes[5] = { + { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */ + { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */ + { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */ + { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */ + { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */ + }; + int vix; /* Vertex index */ + DCOUNT(coa, MAX_CHAN, p->n, 0, 0, 2); + double col[MPP_MXCCOMB][3]; /* Color asigned to each major vertex */ + int res; + + /* Asign some colors to the combination nodes */ + for (i = 0; i < p->nn; i++) { + int a, b, c, j; + double h; + + j = (i ^ 0x5a5a5a5a) % p->nn; + h = (double)j/(p->nn-1); + + /* Make fully saturated with chosen hue */ + if (h < 1.0/3.0) { + a = 0; + b = 1; + c = 2; + } else if (h < 2.0/3.0) { + a = 1; + b = 2; + c = 0; + h -= 1.0/3.0; + } else { + a = 2; + b = 0; + c = 1; + h -= 2.0/3.0; + } + h *= 3.0; + + col[i][a] = (1.0 - h); + col[i][b] = h; + col[i][c] = d_rand(0.0, 1.0); + } + + if (detail > 0.0) + res = (int)(100.0/detail); /* Establish an appropriate sampling density */ + else + res = 4; + + if (res < 2) + res = 2; + + if ((wrl = fopen(outname,"w")) == NULL) + error("Error opening wrl output file '%s'",outname); + + /* Spit out a VRML 2 Object surface of gamut */ + fprintf(wrl,"#VRML V2.0 utf8\n"); + fprintf(wrl,"\n"); + fprintf(wrl,"# Created by the Argyll CMS\n"); + fprintf(wrl,"Transform {\n"); + fprintf(wrl,"children [\n"); + fprintf(wrl," NavigationInfo {\n"); + fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n"); + fprintf(wrl," } # We'll add our own light\n"); + fprintf(wrl,"\n"); + fprintf(wrl," DirectionalLight {\n"); + fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n"); + fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," Viewpoint {\n"); + fprintf(wrl," position 0 0 340 # Position we view from\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + if (doaxes != 0) { + fprintf(wrl,"# Lab axes as boxes:\n"); + for (i = 0; i < 5; i++) { + fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z); + fprintf(wrl,"\tchildren [\n"); + fprintf(wrl,"\t\tShape{\n"); + fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n", + axes[i].wx, axes[i].wy, axes[i].wz); + fprintf(wrl,"\t\t\tappearance Appearance { material Material "); + fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b); + fprintf(wrl,"\t\t}\n"); + fprintf(wrl,"\t]\n"); + fprintf(wrl,"}\n"); + } + fprintf(wrl,"\n"); + } + fprintf(wrl," Transform {\n"); + fprintf(wrl," translation 0 0 0\n"); + fprintf(wrl," children [\n"); + fprintf(wrl," Shape { \n"); + fprintf(wrl," geometry IndexedFaceSet {\n"); + fprintf(wrl," solid FALSE\n"); /* Don't back face cull */ + fprintf(wrl," convex TRUE\n"); + fprintf(wrl,"\n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [ # Verticy coordinates\n"); + + + /* Itterate over all the faces in the device space */ + /* generating the vertx positions. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MAX_CHAN]; + double out[3]; + double sum; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->n; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->n; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (sum = 0.0, e = 0; e < p->n; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + in[m1] = x/(res - 1.0); + for (y = 0; y < res; y++) { + in[m2] = y/(res - 1.0); + + p->lookup(p, out, in); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-50.0); + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n"); + + /* Itterate over all the faces in the device space */ + /* generating the quadrilateral indexes. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MAX_CHAN]; + double sum; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->n; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->n; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (sum = 0.0, e = 0; e < p->n; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + for (y = 0; y < res; y++) { + + if (x < (res-1) && y < (res-1)) { + fprintf(wrl,"%d, %d, %d, %d, -1\n", + vix, vix + 1, vix + 1 + res, vix + res); + } + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ]\n"); + fprintf(wrl,"\n"); + fprintf(wrl," colorPerVertex TRUE\n"); + fprintf(wrl," color Color {\n"); + fprintf(wrl," color [ # RGB colors of each vertex\n"); + + /* Itterate over all the faces in the device space */ + /* generating the vertx colors. */ + DC_INIT(coa); + vix = 0; + while(!DC_DONE(coa)) { + int e, m1, m2; + double in[MAX_CHAN]; + double sum; + + /* Scan only device surface */ + for (m1 = 0; m1 < p->n; m1++) { + if (coa[m1] != 0) + continue; + + for (m2 = m1 + 1; m2 < p->n; m2++) { + int x, y; + + if (coa[m2] != 0) + continue; + + for (sum = 0.0, e = 0; e < p->n; e++) + in[e] = (double)coa[e]; /* Base value */ + + /* Scan over 2D device space face */ + for (x = 0; x < res; x++) { /* step over surface */ + double xb = x/(res - 1.0); + for (y = 0; y < res; y++) { + int v0, v1, v2, v3; + double yb = y/(res - 1.0); + double rgb[3]; + + for (v0 = 0, e = 0; e < p->n; e++) + v0 |= coa[e] ? (1 << e) : 0; /* Binary index */ + + v1 = v0 | (1 << m2); /* Y offset */ + v2 = v0 | (1 << m2) | (1 << m1); /* X+Y offset */ + v3 = v0 | (1 << m1); /* Y offset */ + + /* Linear interp between the main verticies */ + for (j = 0; j < 3; j++) { + rgb[j] = (1.0 - yb) * (1.0 - xb) * col[v0][j] + + yb * (1.0 - xb) * col[v1][j] + + (1.0 - yb) * xb * col[v3][j] + + yb * xb * col[v2][j]; + } + fprintf(wrl,"%f %f %f,\n",rgb[1], rgb[2], rgb[0]); + vix++; + } + } + } + } + /* Increment index within block */ + DC_INC(coa); + } + + fprintf(wrl," ] \n"); + fprintf(wrl," }\n"); + fprintf(wrl," }\n"); + fprintf(wrl," appearance Appearance { \n"); + fprintf(wrl," material Material {\n"); + fprintf(wrl," transparency %f\n",trans); + fprintf(wrl," ambientIntensity 0.3\n"); + fprintf(wrl," shininess 0.5\n"); + fprintf(wrl," }\n"); + fprintf(wrl," }\n"); + fprintf(wrl," } # end Shape\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + + fprintf(wrl,"\n"); + fprintf(wrl," ] # end of children for world\n"); + fprintf(wrl,"}\n"); + + if (fclose(wrl) != 0) + error("Error closing output file '%s'",outname); +} + +/* -------------------------------------------- */ +/* Reverse lookup support */ +/* This is for developing the appropriate reverse lookup */ +/* code for xsep.c */ + +/* + * TTBD: + * Not handlink rule or separate black + * Not handling linearisation callback for ink limit. + * Not allowing for other possible secondary limits/goals/tunings. + */ + +/* + * Descriptionr: + * + * The primary reverse lookup optimisation goals are to remain within + * the device gamut, and to match the target PCS. + * + * The secondary optimisation goals for solving this under constrained + * problem can be chosen to a achieve a wide variety of possible aims. + * + * 1) One that is applicable to screened devices might be to use the extra + * inks to minimise the visiblility of screening. For screening resolutions + * above 50 lpi (equivalent to 100 dpi stocastic screens) only luminance + * contrast will be relavant, so priority to the inks closest to paper white + * measured purely by L distance is appropriate. (In practice I've used + * a measure that adds a small color distance component.) + * + * 2) Another possible goal would be to optimise fit to a desired spectral + * profile, if spectral information is avaiable as an aim. The goal would + * be best fit weighted to the visual sensitivity curve. + * + * 3) Another possible secondary goal might be to minimise the total + * amount of ink used, to minimise cost, drying time, media ink loading. + * + * 4) A fourth possible secondary goal might be to choose ink combinations + * that are the most robust given an error in device values. + * + * In practice these secondary goals need not be entirely exclusive, + * but the overall secondary goal could be a weighted blending between + * these goals. Overall the ink limit (TAC) is extremely important, + * since this will be the primary thing that stops large amounds of + * light ink being used at all times. + * + * Numerous other tweaks, limits or goals (such as secondary combination + * ink limits, exclusions such as Cyan/Oraange, Magenta/Green) + * could also be applied in the reverse optimisation routine. + * + */ + + +#ifdef NEVER +/* These weights are the "theoreticaly correct" weightings, since */ +/* at 50 lpi or higher, the color contrast sensitivity should be close to 0 */ +# define L_WEIGHT 1.0 +# define a_WEIGHT 0.0 +# define b_WEIGHT 0.0 +#else +/* These weights give us our "expected" ink ordering of */ +/* Yellow, light Cyan/Magenta, Orange/Green, Cyan/Magenta, Black. */ +# define L_WEIGHT 1.0 +# define a_WEIGHT 0.4 +# define b_WEIGHT 0.2 +#endif + +/* Start array entry */ +typedef struct { + double dev[MAX_CHAN]; /* Device value */ + double Lab[3]; /* Lab value */ + double oerr; /* Order error */ +} saent; + +/* Context for reverse lookup */ +typedef struct { + int pass; + int di; /* Number of device channels */ + double Lab[3]; /* Lab target value */ + void (*dev2lab) (mpp *d2lcntx, double *out, double *in); /* Device to Lab callback */ + mpp *d2lcntx; /* dev2lab callback context */ + double ilimit; /* Total limit */ + int sord[MAX_CHAN]; /* Sorted order index */ + double oweight[MAX_CHAN]; /* Order weighting (not used ?) */ +} revlus; + +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +dist2gamut(revlus *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + + for (e = 0; e < di; e++) { + ss += d[e]; + + tt = 0.0 - d[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = d[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} + +/* Snap a point to the device gamut boundary. */ +/* Return nz if it has been snapped. */ +static int snap2gamut(revlus *s, double *d) { + int e; + int di = s->di; + double dd; /* Smallest distance */ + double ss; /* Sum */ + int rv = 0; + + /* Snap to ink limit first */ + for (ss = 0.0, e = 0; e < di; e++) + ss += d[e]; + dd = fabs(ss - s->ilimit); + + if (dd < 0.0) { + int j; + for (j = 0; j < di; j++) + d[j] *= s->ilimit/ss; /* Snap to ink limit */ + rv = 1; + } + + /* Now snap to any other dimension */ + for (e = 0; e < di; e++) { + + dd = fabs(d[e] - 0.0); + if (dd < 0.0) { + d[e] = 0.0; /* Snap to orthogonal boundary */ + rv = 1; + } + dd = fabs(1.0 - d[e]); + if (dd < 0.0) { + d[e] = 1.0; /* Snap to orthogonal boundary */ + rv = 1; + } + } + + return rv; +} + +/* Reverse optimisation function handed to powell() */ +static double revoptfunc(void *edata, double *v) { + revlus *s = (revlus *)edata; + double rv; + +printf("~1 target %f %f %f\n",s->Lab[0],s->Lab[1],s->Lab[2]); + + if ((rv = (dist2gamut(s, v))) > 0.0) { +// rv = rv * 1000.0 + 45000.0; /* Discourage being out of gamut */ + rv = rv * 5e6; /* Discourage being out of gamut */ + + } +printf("~1 out of gamut error = %f\n",rv); + { + int j; + double oerr; + double Lab[3]; + double tot; + + /* Convert device to Lab */ + s->dev2lab(s->d2lcntx, Lab, v); + + /* Accumulate total delta E squared */ + for (j = 0; j < 3; j++) { + double tt = s->Lab[j] - Lab[j]; + rv += tt * tt; + } + +printf("~1 Delta E squared = %f\n",rv); + + /* Skip first 3 colorants */ + oerr = tot = 0.0; +printf("oerr = %f\n",oerr); + for (j = 3; j < s->di; j++) { + int ix = s->sord[j]; /* Sorted order index */ + double vv = v[ix]; + double we = (double)j - 2.0; /* Increasing weight */ + +printf("Comp %d value %f\n",ix,vv); + if (vv > 0.0001) { + oerr += tot + we * vv; +printf("Added %f + %f to give oerr %f\n",tot,we * vv,oerr); + } + tot += we; + } + oerr /= tot; + if (s->pass == 0) + oerr *= 2000.0; + else + oerr *= 1.0; +printf("Final after div by %f oerr = %f\n",tot,oerr); + +printf("~1 Order error %f\n",oerr); + rv += oerr; + } + +printf("~1 Returning total error %f\n",rv); + return rv; +} + + +/* Do a reverse lookup on the mpp */ +static void mpp_rev( +mpp *mppo, +double limit, /* Ink limit */ +double *out, /* Device value */ +double *in /* Lab target */ +) { + int i, j; + inkmask imask; /* Device Ink mask */ + int inn; + revlus rs; /* Reverse lookup structure */ + double sr[MAX_CHAN]; /* Search radius */ + double tt; + /* !!! This needs to be cached elsewhere !!!! */ + static saent *start = NULL; /* Start array */ + static int nisay = 0; /* Number in start array */ + + mppo->get_info(mppo, &imask, &inn, NULL, NULL, NULL, NULL, NULL, NULL); + + rs.di = inn; /* Number of device channels */ + + rs.Lab[0] = in[0]; /* Target PCS value */ + rs.Lab[1] = in[1]; + rs.Lab[2] = in[2]; + + rs.dev2lab = mppo->lookup; /* Dev->PCS Lookup function and context */ + rs.d2lcntx = (void *)mppo; + + rs.ilimit = limit; /* Total ink limit */ + + { + double Labw[3]; /* Lab value of white */ + double Lab[MAX_CHAN][3]; /* Lab value of device primaries */ + double min, max; + + /* Lookup the L value of all the device primaries */ + for (j = 0; j < inn; j++) + out[j] = 0.0; + + mppo->lookup(mppo, Labw, out); + + for (i = 0; i < inn; i++) { + double tt; + double de; + + out[i] = 1.0; + mppo->lookup(mppo, Lab[i], out); + + /* Use DE measure heavily weighted towards L only */ + tt = L_WEIGHT * (Labw[0] - Lab[i][0]); + de = tt * tt; + tt = 0.4 * (Labw[1] - Lab[i][1]); + de += tt * tt; + tt = 0.2 * (Labw[2] - Lab[i][2]); + de += tt * tt; + rs.oweight[i] = sqrt(de); + out[i] = 0.0; + } + + /* Normalise weights from 0 .. 1.0 */ + min = 1e6, max = 0.0; + for (j = 0; j < inn; j++) { + if (rs.oweight[j] < min) + min = rs.oweight[j]; + if (rs.oweight[j] > max) + max = rs.oweight[j]; + } + for (j = 0; j < inn; j++) + rs.oweight[j] = (rs.oweight[j] - min)/(max - min); + + { + for (j = 0; j < inn; j++) + rs.sord[j] = j; + + for (i = 0; i < (inn-1); i++) { + for (j = i+1; j < inn; j++) { + if (rs.oweight[rs.sord[i]] > rs.oweight[rs.sord[j]]) { + int xx; + xx = rs.sord[i]; + rs.sord[i] = rs.sord[j]; + rs.sord[j] = xx; + } + } + } + } + +for (j = 0; j < inn; j++) +printf("~1 oweight[%d] = %f\n",j,rs.oweight[j]); +for (j = 0; j < inn; j++) +printf("~1 sorted oweight[%d] = %f\n",j,rs.oweight[rs.sord[j]]); + } + + /* Initialise the start point array */ + if (start == NULL) { + int mxstart; + int steps = 4; + + DCOUNT(dix, MAX_CHAN, inn, 0, 0, steps); + +printf("~1 initing start point array\n"); + for (mxstart = 1, j = 0; j < inn; j++) /* Compute maximum entries */ + mxstart *= steps; + +printf("~1 mxstart = %d\n",mxstart); + if ((start = malloc(sizeof(saent) * mxstart)) == NULL) + error("mpp_rev malloc of start array failed\n"); + + nisay = 0; + DC_INIT(dix); + + while(!DC_DONE(dix)) { + double sum = 0.0; + + /* Figure device values */ + for (j = 0; j < inn; j++) { + sum += start[nisay].dev[j] = dix[j]/(steps-1.0); + } + + if (sum <= limit) { /* Within ink limit */ + double oerr; + double tot; + + /* Compute Lab */ + mppo->lookup(mppo, start[nisay].Lab, start[nisay].dev); + + /* Compute order error */ + /* Skip first 3 colorants */ + oerr = tot = 0.0; + for (j = 3; j < inn; j++) { + int ix = rs.sord[j]; /* Sorted order index */ + double vv = start[nisay].dev[ix]; + double we = (double)j - 2.0; /* Increasing weight */ + + if (vv > 0.0001) { + oerr += tot + we * vv; + } + tot += we; + } + oerr /= tot; + start[nisay].oerr = oerr; + + nisay++; + } + + DC_INC(dix); + } +printf("~1 start point array done, %d out of %d valid\n",nisay,mxstart); + } + + /* Search the start array for closest matching point */ + { + double bde = 1e38; + int bix = 0; + + for (i = 0; i < nisay; i++) { + double de; + + /* Compute delta E */ + for (de = 0.0, j = 0; j < 3; j++) { + double tt = rs.Lab[j] - start[i].Lab[j]; + de += tt * tt; + } + de += 0.0 * start[i].oerr; + if (de < bde) { + bde = de; + bix = i; + } + } + +printf("Start point at index %d, bde = %f, dev = ",bix,bde); +for (j = 0; j < inn; j++) { +printf("%f",start[bix].dev[j]); +if (j < (inn-1)) +printf(" "); +} +printf("\n"); + + for (j = 0; j < inn; j++) { + out[j] = start[bix].dev[j]; + sr[j] = 0.1; + } + } + +#ifdef NEVER + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.45; + out[3] = 0.0; + out[4] = 0.0; + out[5] = 0.0; + out[6] = 0.6; + out[7] = 1.0; +#endif + +#ifdef NEVER + out[0] = 1.0; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = 0.0; + out[6] = 0.0; + out[7] = 0.0; +#endif + +#ifdef NEVER + rs.pass = 0; + if (powell(&tt, inn, out, sr, 0.001, 5000, revoptfunc, (void *)&rs) != 0) { + error("Powell failed inside mpp_rev()"); + } +printf("\n\n\n\n\n\n#############################################\n"); +printf("~1 after first pass got "); +for (j = 0; j < inn; j++) { +printf("%f",out[j]); +if (j < (inn-1)) +printf(" "); +} +printf("\n"); +printf("#############################################\n\n\n\n\n\n\n\n"); +#endif + +#ifndef NEVER + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = 1.0; + out[6] = 0.0; + out[7] = 0.0; +#endif +#ifndef NEVER + rs.pass = 1; + if (powell(&tt, inn, out, sr, 0.00001, 5000, revoptfunc, (void *)&rs, NULL, NULL) != 0) { + error("Powell failed inside mpp_rev()"); + } +#endif + + snap2gamut(&rs, out); +} + + + + + + + + + + + + + + + + + + diff --git a/xicc/revfix.c b/xicc/revfix.c new file mode 100644 index 0000000..59fdd0e --- /dev/null +++ b/xicc/revfix.c @@ -0,0 +1,796 @@ + +/* + * ICC reprocess to give true BtoA1 by inverting + * the AtoB1 table, and also correct the neutral + * axis of the BtoA0 and BtoA2 tables. + * + * + * Author: Graeme W. Gill + * Date: 9/7/00 + * Version: 1.00 + * + * Copyright 2000 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + * Add support for proper gamut mapping, just like profile, + * or deprecate revfix by modifying profile to work from + * an existing profile ? + * + * Remove the auxiliary fixup stuff when we have implemented + * optimised separations. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "xicc.h" + +#define USE_CAM_CLIP_OPT /* Clip in CAM Jab space rather than Lab */ +#undef DEBUG /* Print each value changed */ + +void usage(void) { + fprintf(stderr,"Invert AtoB1 to make BtoA1 for CMYK profiles, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: revfix [-options] iccin iccout\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -0 Process perceptual\n"); + fprintf(stderr," -1 Process absolute/relative colorimetric\n"); + fprintf(stderr," -2 Process saturation\n"); + fprintf(stderr," -r res Override BtoA1 Clut res\n"); + fprintf(stderr," -k [ezhxr] e = same K as existing BtoA table (def)\n"); + fprintf(stderr," z = zero, h = 0.5 K, x = max K, r = ramp K\n"); + fprintf(stderr," -k p stle stpo enle enpo shape\n"); + fprintf(stderr," p = curve parameters\n"); + fprintf(stderr," stle: K level at White 0.0 - 1.0\n"); + fprintf(stderr," stpo: start point of transition Wh 0.0 - Bk 1.0\n"); + fprintf(stderr," enpo: End point of transition Wh 0.0 - Bk 1.0\n"); + fprintf(stderr," enle: K level at Black 0.0 - 1.0\n"); + fprintf(stderr," shape: 1.0 = straight, 0.0-1.0 concave, 1.0-2.0 convex\n"); + fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n"); + fprintf(stderr," -l tlimit set total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," -p absprof Include abstract profile in output tables\n"); +// fprintf(stderr," -s Use internal optimized separation for CMYK\n"); + exit(1); +} + +/* ------------------------------------------- */ +/* structure to support icc Lut initialisation calbacks */ + +/* ~~~Note that we're not coping with a matrix or XYZ PCS properly here. */ + +struct _callback { + int verb; /* Verbosity */ + int total, count, last; /* Progress count information */ + icColorSpaceSignature pcsspace; + int inking; /* k inking algorithm */ + icxLuLut *BtoA; /* BtoA of table being processed */ + icxLuLut *AtoB; /* AtoB of table being processed */ + icxLuLut *AtoB1; /* AtoB of colorimetric table */ + + icRenderingIntent abs_intent; /* Desired abstract profile rendering intent */ + icxLuBase *abs_luo; /* abstract profile tranform in PCS, NULL if none */ + +}; typedef struct _callback callback; + + +/* Utility to handle abstract profile application to PCS */ +/* PCS in creating output table is always XYZ or Lab relative colorimetric, */ +/* and abstract profile is absolute or relative, and will be */ +/* XYZ if absolute, and PCS if relative. */ +static void do_abstract(callback *p, double out[3], double in[3]) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + +//printf("~1 do_abstract got %f %f %f\n",in[0],in[1],in[2]); + + if (p->abs_intent == icAbsoluteColorimetric) { + if (p->pcsspace == icSigLabData) { + icmLab2XYZ(&icmD50, out, out); +//printf("~1 after Lab 2 XYZ got %f %f %f\n",out[0],out[1],out[2]); + } + p->AtoB1->plu->XYZ_Rel2Abs(p->AtoB1->plu, out, out); +//printf("~1 after rel to abs got %f %f %f\n",out[0],out[1],out[2]); + } + + p->abs_luo->lookup(p->abs_luo, out, out); +//printf("~1 after abs_luo got %f %f %f\n",out[0],out[1],out[2]); + + if (p->abs_intent == icAbsoluteColorimetric) { + p->AtoB1->plu->XYZ_Abs2Rel(p->AtoB1->plu, out, out); +//printf("~1 after abs2rel got %f %f %f\n",out[0],out[1],out[2]); + if (p->pcsspace == icSigLabData) { + icmXYZ2Lab(&icmD50, out, out); +//printf("~1 after XYZ to Lab got %f %f %f\n",out[0],out[1],out[2]); + } + } +//printf("~1 returning %f %f %f\n\n",out[0],out[1],out[2]); +} + +/* - - - - - - - - - */ +/* New input table */ +void Lab_Labp(void *cntx, double out[3], double in[3]) { + callback *p = (callback *)cntx; + +#ifdef DEBUG + printf("Got Lab %f %f %f\n",in[0],in[1],in[2]); +#endif + if (p->AtoB != p->AtoB1) { + /* Non-colorimetric, use existing input table */ + if (p->BtoA->input(p->BtoA, out, in) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + } else { + /* Colorimetric, use inverse AtoB output */ + if (p->AtoB1->inv_output(p->AtoB1, out, in) > 1) + error ("%d, %s",p->AtoB1->pp->errc,p->AtoB1->pp->err); + } +#ifdef DEBUG + printf("New Lab' %f %f %f\n",out[0],out[1],out[2]); +#endif +} + +/* - - - - */ +/* clut */ + +/* Normal CLUT routine */ +void Labp_CMYKp(void *cntx, double out[4], double in[3]) { + double temp[4], targetk = 0.0; + int rv; + callback *p = (callback *)cntx; + +#ifdef DEBUG + printf("Got Lab' %f %f %f\n",in[0],in[1],in[2]); +#endif + + if (p->inking == 0) { /* If we are copying existing K value */ + + /* Figure out what K value was previously here */ + if (p->AtoB != p->AtoB1) { + /* Simple because BtoA input & output tables don't change */ + /* Figure out what DEV' K value the BtoA table currently has for this PCS' */ + if (p->BtoA->clut(p->BtoA, temp, in) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + + /* Convert DEV' to DEV */ + if (p->BtoA->output(p->BtoA, temp, temp) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->AtoB->pp->err); + } else { + /* More complicated because old BtoA in/out tables are different */ + /* from the new ones. */ + /* We know that new BtoA in/out tables are inverse of AtoB in/out, */ + /* so we don't have to use BtoA1->inv_input, & BtoA1->inv_output */ + /* Convert PCS' to PCS */ + if (p->AtoB->output(p->AtoB, temp, in) > 1) + error ("%d, %s",p->AtoB->pp->errc,p->AtoB->pp->err); + + /* Figure out what DEV K value the BtoA table currently has for this PCS */ + if (((icxLuBase *)p->BtoA)->lookup((icxLuBase *)p->BtoA, temp, temp) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + } + targetk = temp[3]; +#ifdef DEBUG + printf("Got existing CMYK %f %f %f %f\n",temp[0],temp[1],temp[2],temp[3]); +#endif + } + + /* Copy the Lab in */ + temp[0] = in[0]; + temp[1] = in[1]; + temp[2] = in[2]; + + if (p->AtoB != p->AtoB1) { + double tt[4]; + + /* Can't assume B2A in/out tables are inverses of AtoB */ + /* Convert PCS' -> PCS for this table */ + if (p->BtoA->inv_input(p->BtoA, temp, temp) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + /* Convert PCS -> PCS' for colorimetric */ + if (p->AtoB1->inv_output(p->AtoB1, temp, temp) > 1) + error ("%d, %s",p->AtoB->pp->errc,p->AtoB->pp->err); + } + + /* Abstract profile applied before inversion */ + if (p->abs_luo != NULL) { + do_abstract(p, temp, temp); + } + + /* Invert AtoB1 clut, using set inking policy */ + out[3] = targetk; + + /* PCS' -> DEV' colorimetric (aux target is DEV space) */ + if ((rv = p->AtoB1->inv_clut(p->AtoB1, out, temp)) > 1) + error ("%d, %s",p->AtoB1->pp->errc,p->AtoB1->pp->err); + + /* ~~~ Note that the ink limit will be wrong for non-colorimetric, */ + /* since AtoB1->inv_clut will be assuming A->toB1->inv_input as the output table, */ + /* while we will actually be using BtoA->output ~~~~ */ + /* Need to override ink limit computation function in icx for non-colorimetric. */ + +//#ifdef DEBUG +// if (rv != 0) { +// printf("Inversion clipped\n"); +// } +//#endif + if (p->AtoB != p->AtoB1) { + /* Can't assume B2A in/out tables are inverses of AtoB */ + /* Converts DEV' -> DEV colorimetric */ + if (p->AtoB1->inv_input(p->AtoB1, out, out) > 1) + error ("%d, %s",p->AtoB->pp->errc,p->AtoB->pp->err); + /* Convert DEV -> DEV' for this table */ + if (p->BtoA->inv_output(p->BtoA, out, out) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + } +#ifdef DEBUG + printf("New CMYK' %f %f %f %f\n",out[0],out[1],out[2],out[3]); + printf("\n"); +#endif + + if (p->verb) { /* Output percent intervals */ + int pc; + p->count++; + pc = (int)(p->count * 100.0/p->total + 0.5); + if (pc != p->last) { + printf("%c%2d%%",cr_char,pc), fflush(stdout); + p->last = pc; + } + } +} + +/* - - - - - - - - - */ +/* New output table */ +void CMYKp_CMYK(void *cntx, double out[4], double in[4]) { + callback *p = (callback *)cntx; + +#ifdef DEBUG + printf("Got CMYK' %f %f %f %f\n",in[0],in[1],in[2],in[3]); +#endif + if (p->AtoB != p->AtoB1) { + /* Non-colorimetric, use existing output table */ + if (p->BtoA->output(p->BtoA, out, in) > 1) + error ("%d, %s",p->BtoA->pp->errc,p->BtoA->pp->err); + } else { + /* Colorimetric, use inverse AtoB input */ + if (p->AtoB1->inv_input(p->AtoB1, out, in) > 1) + error ("%d, %s",p->AtoB1->pp->errc,p->AtoB1->pp->err); + } +#ifdef DEBUG + printf("New CMYK %f %f %f %f\n",out[0],out[1],out[2],out[3]); +#endif +} + + +/* ------------------------------------------- */ + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char in_name[MAXNAMEL+1]; + char out_name[MAXNAMEL+1]; + char abs_name[MAXNAMEL+1] = "\000"; /* Abstract profile name */ + icmFile *rd_fp, *wr_fp; + icc *icco; + int verb = 0; + int clutres = 0; + int do0 = 0; + int do1 = 0; + int do2 = 0; + int inking = 0; /* Default copy from existing */ + int locus = 0; /* Default K value target */ + double Kstle = 0.0, Kstpo = 0.0, Kenle = 0.0, Kenpo = 0.0, Kshap = 0.0; + double tlimit = -1.0; /* Total ink limit */ + double klimit = -1.0; /* Black ink limit */ + int intsep = 0; /* Not implimented in xicc yet ??? */ + int rv = 0; + + error_program = argv[0]; + check_if_not_interactive(); + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + else if (argv[fa][1] == '0') { + do0 = 1; + } + else if (argv[fa][1] == '1') { + do1 = 1; + } + else if (argv[fa][1] == '2') { + do2 = 1; + } + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + clutres = atoi(na); + } + + /* Inking rule */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + fa = nfa; + if (na == NULL) usage(); + if (argv[fa][1] == 'k') + locus = 0; /* K value target */ + else + locus = 1; /* K locus target */ + switch (na[0]) { + case 'e': + case 'E': + inking = 0; /* Use existing table as guide */ + break; + case 'z': + case 'Z': + inking = 1; /* Use minimum k */ + break; + case 'h': + case 'H': + inking = 2; /* Use 0.5 k */ + break; + case 'x': + case 'X': + inking = 3; /* Use maximum k */ + break; + case 'r': + case 'R': + inking = 4; /* Use ramp */ + break; + case 'p': + case 'P': + inking = 5; /* Use curve parameter */ + ++fa; + if (fa >= argc) usage(); + Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage(); + Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage(); + Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage(); + Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage(); + Kshap = atof(argv[fa]); + break; + default: + usage(); + } + } + else if (argv[fa][1] == 'l') { + fa = nfa; + if (na == NULL) usage(); + tlimit = atoi(na)/100.0; + } + else if (argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage(); + klimit = atoi(na)/100.0; + } + + /* Use internal separation */ + else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + intsep = 1; + } + + /* Abstract profile */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + if (na == NULL) usage(); + fa = nfa; + strncpy(abs_name,na,MAXNAMEL); abs_name[MAXNAMEL] = '\000'; + } + + else + usage(); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage(); + strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + + /* Open up the profile for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Can't open file '%s'",in_name); + + if ((icco = new_icc()) == NULL) + error ("Creation of ICC object failed"); + + /* Read header etc. */ + if ((rv = icco->read(icco,rd_fp,0)) != 0) + error ("%d, %s",rv,icco->err); + + /* Read every tag */ + if (icco->read_all_tags(icco) != 0) { + error("Unable to read all tags: %d, %s",icco->errc,icco->err); + } + + rd_fp->del(rd_fp); + + /* ======================= */ + /* Check that it is a suitable icc */ + { + icmHeader *rh = icco->header; + + if (rh->deviceClass != icSigOutputClass) + error("Profile isn't an output device profile"); + + if (rh->colorSpace != icSigCmykData) + error("Profile isn't for a CMYK device"); + + if (rh->pcs != icSigLabData) + error("Profile is not using a PCS of Lab - can't cope with this yet"); + } + + if (verb && inking == 5) { + double tL; + printf("K parameters are are %f %f %f %f %f\n",Kstle, Kstpo, Kenpo, Kenle, Kshap); + for (tL = 100.0; tL >= 0.0; tL -= 10.0) { + double rv, L; + + L = 0.01 * tL; + + /* Code from xlut.c: */ + /* Invert sense of L, so that 0.0 = white, 1.0 = black */ + L = 1.0 - L; + + if (L <= Kstpo && L <= Kenpo) { + /* We are at white level */ + rv = Kstle; + } else if (L >= Kstpo && L >= Kenpo) { + /* We are at black level */ + rv = Kenle; + } else { + double g; + /* We must be on the curve from start to end levels */ + + if (Kstpo > Kenpo) { + rv = (L - Kenpo)/(Kstpo - Kenpo); + } else { + rv = (L - Kstpo)/(Kenpo - Kstpo); + } + + g = Kshap/2.0; + + /* A value of 0.5 will be tranlated to g */ + rv = rv/((1.0/g - 2.0) * (1.0 - rv) + 1.0); + + /* Transition between start end end levels */ + rv = rv * (Kenle - Kstle) + Kstle; + } + + /* To be safe */ + if (rv < 0.0) + rv = 0.0; + else if (rv > 1.0) + rv = 1.0; + + printf("L = %f, K %s = %f\n",tL, locus ? "locus" : "value", rv); + } + } + + /* ======================= */ + { + int ii; /* Current intent */ + icmLut *done[3]; /* record pointers to done Luts here */ + icRenderingIntent intent = 0; + icTagSignature sig = 0; + xicc *xicco; + callback cb; /* Callback support stucture */ + icxInk ink; /* Ink parameters */ + icmLuAlgType alg; /* Type of lookup algorithm */ + icmFile *abs_fp = NULL; /* Abstract profile transform: */ + icc *abs_icc = NULL; + xicc *abs_xicc = NULL; + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(icco)) == NULL) + error ("Creation of xicc failed"); + + /* Setup CMYK -> Lab conversion (Fwd) object */ + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(xicco, &ink.tlimit, tlimit, &ink.klimit, klimit); + + if (verb) { + if (!do0 && !do1 && !do2) + printf("WARNING: nothing to do!\n"); + if (ink.tlimit >= 0.0) + printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); + if (ink.klimit >= 0.0) + printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); + } + + ink.KonlyLmin = 0; /* Use normal black Lmin for locus */ + + ink.c.Ksmth = ICXINKDEFSMTH; /* Default curve smoothing */ + ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + ink.x.Ksmth = ICXINKDEFSMTH; + ink.x.Kskew = ICXINKDEFSKEW; + + if (inking == 0) { + ink.k_rule = icxKvalue; /* K is auxiliary target */ + + } else if (inking == 1) { /* Use minimum */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; + ink.c.Kstle = 0.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 0.0; + ink.c.Kshap = 1.0; + } else if (inking == 2) { /* Use 0.5 */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; + ink.c.Kstle = 0.5; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 0.5; + ink.c.Kshap = 1.0; + } else if (inking == 3) { /* Use maximum */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; + ink.c.Kstle = 1.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 1.0; + ink.c.Kshap = 1.0; + } else if (inking == 4) { /* Use ramp */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; + ink.c.Kstle = 0.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 1.0; + ink.c.Kshap = 1.0; + } else { /* Use specified curve */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; + ink.c.Kstle = Kstle; + ink.c.Kstpo = Kstpo; + ink.c.Kenpo = Kenpo; + ink.c.Kenle = Kenle; + ink.c.Kshap = Kshap; + } + + cb.verb = verb; + cb.count = 0; + cb.last = -1; + cb.inking = inking; + + /* Setup our access to the device characteristic */ + if ((cb.AtoB1 = (icxLuLut *)xicco->get_luobj(xicco, + ICX_CLIP_NEAREST +#ifdef USE_CAM_CLIP_OPT + | ICX_CAM_CLIP +#endif + | (intsep ? ICX_INT_SEPARATE : 0), + icmFwd, icRelativeColorimetric, + icmSigDefaultData, icmLuOrdNorm, NULL, &ink)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + cb.AtoB1->spaces((icxLuBase *)cb.AtoB1, NULL, NULL, NULL, NULL, &alg, + NULL, NULL, &cb.pcsspace); + if (alg != icmLutType) + error("Forward conversion is not a Lut"); + + /* Open up the abstract profile if supplied, and setup luo */ + if (abs_name[0] != '\000') { + if ((abs_fp = new_icmFileStd_name(abs_name,"r")) == NULL) + error ("Can't open abstract profile file '%s'",abs_name); + + if ((abs_icc = new_icc()) == NULL) + error ("Creation of Abstract profile ICC object failed"); + + /* Read header etc. */ + if ((rv = abs_icc->read(abs_icc,abs_fp,0)) != 0) + error ("%d, %s",rv,abs_icc->err); + + if (abs_icc->header->deviceClass != icSigAbstractClass) + error("Abstract profile isn't an abstract profile"); + + /* Take intended abstract intent from profile itself */ + if ((cb.abs_intent = abs_icc->header->renderingIntent) != icAbsoluteColorimetric) + cb.abs_intent = icRelativeColorimetric; + + /* Wrap with an expanded icc */ + if ((abs_xicc = new_xicc(abs_icc)) == NULL) + error ("Creation of abstract profile xicc failed"); + + /* The abstract profile intent is assumed to determine how it gets applied. */ + /* Make abstract PCS XYZ if icAbsoluteColorimetric is needed. */ + if ((cb.abs_luo = abs_xicc->get_luobj(abs_xicc, ICX_CLIP_NEAREST, icmFwd, cb.abs_intent, + (cb.pcsspace == icSigLabData && cb.abs_intent == icRelativeColorimetric) + ? icSigLabData : icSigXYZData, + icmLuOrdNorm, NULL, NULL)) == NULL) + error ("%d, %s",abs_icc->errc, abs_icc->err); + } else { + cb.abs_luo = NULL; + } + + /* for all intents, and not already processed */ + for (ii = 0; ii <= 2; ii++) { + int i; + icmLut *wo; + + switch(ii) { + case 0: + intent = icRelativeColorimetric; + sig = icSigBToA1Tag; + if (do1 == 0) + continue; + break; + case 1: + intent = icPerceptual; + sig = icSigBToA0Tag; + if (do0 == 0) + continue; + break; + case 2: + intent = icSaturation; + sig = icSigBToA2Tag; + if (do2 == 0) + continue; + break; + } + + if (intent == icRelativeColorimetric) { + cb.AtoB = cb.AtoB1; + } else { + /* Setup CMYK -> Lab conversion (Fwd) object */ + if ((cb.AtoB = (icxLuLut *)xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, intent, + icmSigDefaultData, icmLuOrdNorm, NULL, NULL)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + cb.AtoB->spaces((icxLuBase *)cb.AtoB, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL); + if (alg != icmLutType) + error("Forwards conversion is not a Lut"); + } + + /* Setup Lab -> CMYK conversion (Bwd) object */ + if ((cb.BtoA = (icxLuLut *)xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, intent, + icmSigDefaultData, icmLuOrdNorm, NULL, NULL)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + cb.BtoA->spaces((icxLuBase *)cb.BtoA, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL); + if (alg != icmLutType) + error("Backwards conversion is not a Lut"); + + /* Try and read the tag from the file */ + wo = (icmLut *)icco->read_tag(icco, sig); + if (wo == NULL) + error("Can't find %s", icm2str(icmRenderingIntent, intent)); + + /* Need to check that the cast is appropriate. */ + if (wo->ttype != icSigLut16Type && wo->ttype != icSigLut8Type) + error("Lut table isn't Lut8 or Lut16 Type"); + + /* Set reverse input table resolution to same as fwd output */ + wo->inputEnt = cb.AtoB->lut->outputEnt; + + /* Let user override for BtoA1 */ + if (sig == icSigBToA1Tag && clutres > 0) { + if (verb) + printf("Overriding existing clut resolution %d with %d\n",wo->clutPoints,clutres); + wo->clutPoints = clutres; + } + /* If Lut8, make sure the input and output tables have 256 enries. */ + if (wo->ttype == icSigLut8Type) { + wo->inputEnt = 256; + wo->outputEnt = 256; + } + wo->allocate((icmBase *)wo);/* Allocate space */ + + /* Make sure that we don't process a table twice */ + done[ii] = wo; + for (i = ii-1; i >= 0; i--) { + if (done[i] == wo) { + break; + } + } + if (i >= 0) { + if (verb) + printf("Skipping %s table - already done\n", icm2str(icmRenderingIntent, intent)); + + } else { + + if (verb) + printf("About to start processing %s\n", icm2str(icmRenderingIntent, intent)); + + if (cb.verb) { + unsigned int ui; + int extra; + for (cb.total = 1, ui = 0; ui < wo->inputChan; ui++, cb.total *= wo->clutPoints) + ; + /* Add in cell center points */ + for (extra = 1, ui = 0; ui < wo->inputChan; ui++, extra *= (wo->clutPoints-1)) + ; + cb.total += extra; + printf(" 0%%"), fflush(stdout); + } + /* Use helper function to do the hard work. */ + if (wo->set_tables(wo, + ICM_CLUT_SET_APXLS, + &cb, /* Context */ + icSigLabData, /* Input color space */ + icSigCmykData, /* Output color space */ + Lab_Labp, /* Linear input transform Lab->Lab' */ + NULL, NULL, /* Use default Lab' range */ + Labp_CMYKp, /* Lab' -> CMYK' transfer function */ + NULL, NULL, /* Use default CMYK' range */ + CMYKp_CMYK) != 0) /* Output transfer function, CMYK'->CMYK (NULL = deflt) */ + error("Setting 16 bit Lab->CMYK Lut failed: %d, %s",icco->errc,icco->err); + + if (verb) + printf("\nDone processing %s\n", icm2str(icmRenderingIntent, intent)); + + } + + /* Done with this intents lookup object */ + cb.BtoA->del((icxLuBase *)cb.BtoA); + if (cb.AtoB != cb.AtoB1) + cb.AtoB->del((icxLuBase *)cb.AtoB); + } + /* Done with colorimetric intents AtoB1 lookup objects, and xicc */ + cb.AtoB1->del((icxLuBase *)cb.AtoB1); + xicco->del(xicco); + + if (cb.abs_luo != NULL) { /* Free up abstract transform */ + cb.abs_luo->del(cb.abs_luo); + abs_xicc->del(abs_xicc); + abs_icc->del(abs_icc); + abs_fp->del(abs_fp); + } + + } + /* ======================================= */ + + /* Open up the other profile for writing */ + if ((wr_fp = new_icmFileStd_name(out_name,"w")) == NULL) + error ("Can't open file '%s'",out_name); + + if ((rv = icco->write(icco,wr_fp,0)) != 0) + error ("Write file: %d, %s",rv,icco->err); + + wr_fp->del(wr_fp); + icco->del(icco); + + return 0; +} + diff --git a/xicc/specplot.c b/xicc/specplot.c new file mode 100644 index 0000000..b2baf05 --- /dev/null +++ b/xicc/specplot.c @@ -0,0 +1,370 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2006/5/9 + * Version: 1.00 + * + * Copyright 2006 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the Daylight and Plankian spectra, + * Correlated and Visual Color Temperatures, and CRI. + * and plot a spectrum, CMF or CCSS. + */ + +#include <stdio.h> +#include <math.h> +#include "xspect.h" +#include "numlib.h" +#include "plot.h" + +#define PLANKIAN +#define XRES 500 + +#define MAXGRAPHS 10 + +#ifdef PLANKIAN +#define BBTYPE icxIT_Ptemp +#else +#define BBTYPE icxIT_Dtemp +#endif + +/* Display a spectrum etc. */ +/* We are guaranteed that the x range/increments are identical, */ +/* and that there is only one spectrum if douv */ +static int do_spec( + char name[MAXGRAPHS][200], + xspect *sp, + int nsp, /* Number of sp */ + int dozero, /* Include zero in the range */ + int douv, /* Do variation of added UV test */ + double uvmin, + double uvmax +) { + int n, i, j, k, m; + double wl_short, wl_long; /* Common range */ + double xyz[3]; /* Color temperature */ + double Yxy[3]; + double Lab[3]; /* D50 Lab value */ + double xx[XRES]; + double yy[10][XRES]; + double *yp[10]; + double cct, vct; + double cct_xyz[3], vct_xyz[3]; + double cct_lab[3], vct_lab[3]; + icmXYZNumber wp; + double de; + double uv = uvmin; + double step = 0.1; + xspect tsp; /* Spectrum with possible UV added */ + char *color[] = { + "Black", "Red", "Green", "Blue", "Yellow", "Purple", "Brown", "Orange", "Grey", "Magenta" + }; + + printf("\n"); + + for (j = 0; j < 10; j++) + yp[j] = NULL; + + if (nsp > 10) + nsp = 10; + + m = 0; /* offset in output array */ + n = 1; + + wl_short = 1e6; + wl_long = -1e6; + for (k = 0; k < nsp; k++) { + if (sp[k].spec_wl_long > wl_long) + wl_long = sp[k].spec_wl_long; + if (sp[k].spec_wl_short < wl_short) + wl_short = sp[k].spec_wl_short; + } + + if (douv) { + n = 1 + (int)(0.5 + (uvmax-uvmin)/0.1); + if (n > 9) + n = 9; /* Don't use white */ + if (n > 1) + step = (uvmax-uvmin)/(n-1.0); + } + + for (k = 0; k < nsp; k++) { + tsp = sp[k]; + for (uv = uvmax, j = 0; j < n; j++, uv -= step) { + + if (douv) { + printf("UV level = %f\n",uv); + xsp_setUV(&tsp, &sp[k], uv); + } + + /* Compute XYZ of illuminant */ + if (icx_ill_sp2XYZ(xyz, icxOT_CIE_1931_2, NULL, icxIT_custom, 0, &tsp) != 0) + error ("icx_sp_temp2XYZ returned error"); + + icmXYZ2Yxy(Yxy, xyz); + icmXYZ2Lab(&icmD50, Lab, xyz); + + printf("Type = %s [%s]\n",name[k], color[k]); + printf("XYZ = %f %f %f, x,y = %f %f\n", xyz[0], xyz[1], xyz[2], Yxy[1], Yxy[2]); + printf("D50 L*a*b* = %f %f %f\n", Lab[0], Lab[1], Lab[2]); + +#ifndef NEVER + /* Test density */ + { + double dens[4]; + + xsp_Tdensity(dens, &tsp); + + printf("CMYV density = %f %f %f %f\n", dens[0], dens[1], dens[2], dens[3]); + } +#endif + + /* Compute CCT */ + if ((cct = icx_XYZ2ill_ct(cct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 0)) < 0) + error ("Got bad cct\n"); + + /* Compute VCT */ + if ((vct = icx_XYZ2ill_ct(vct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 1)) < 0) + error ("Got bad vct\n"); + +#ifdef PLANKIAN + printf("CCT = %f, VCT = %f\n",cct, vct); +#else + printf("CDT = %f, VDT = %f\n",cct, vct); +#endif + + { + int invalid = 0; + double cri; + cri = icx_CIE1995_CRI(&invalid, &tsp); + printf("CRI = %.1f%s\n",cri,invalid ? " (Invalid)" : ""); + } + + /* Use modern color difference - gives a better visual match */ + icmAry2XYZ(wp, vct_xyz); + icmXYZ2Lab(&wp, cct_lab, cct_xyz); + icmXYZ2Lab(&wp, vct_lab, vct_xyz); + de = icmCIE2K(cct_lab, vct_lab); + printf("CIEDE2000 Delta E = %f\n",de); + + /* Plot spectrum out */ + for (i = 0; i < XRES; i++) { + double ww; + + ww = (wl_long - wl_short) + * ((double)i/(XRES-1.0)) + wl_short; + + xx[i] = ww; + yy[(m + k + j) % 10][i] = value_xspect(&tsp, ww); + } + yp[(m + k + j) % 10] = &yy[(m + k + j) % 10][0]; + } + } + do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], XRES, dozero); + + + return 0; +} + + +void usage(void) { + fprintf(stderr,"Plot spectrum and calculate CCT and VCT\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: specplot [infile.sp]\n"); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -c combine multiple files into one plot\n"); + fprintf(stderr," -z don't make range cover zero\n"); + fprintf(stderr," -u level plot effect of adding estimated UV level\n"); + fprintf(stderr," -U plot effect of adding range of estimated UV level\n"); + fprintf(stderr," [infile.sp ...] spectrum files to plot\n"); + fprintf(stderr," default is all built in illuminants\n"); + exit(1); +} + +int +main( + int argc, + char *argv[] +) { + int fa, nfa; /* argument we're looking at */ + int k; + int verb = 0; + int comb = 0; + int zero = 1; + double temp; + xspect sp[MAXGRAPHS]; + icxIllumeType ilType; + int douv = 0; + double uvmin = -1.0, uvmax = 1.0; + char buf[MAXGRAPHS][200]; + + error_program = argv[0]; + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Show added UV */ + if (argv[fa][1] == 'u') { + douv = 1; + + fa = nfa; + if (na == NULL) + usage(); + + uvmin = uvmax = atof(na); + if (uvmin < -10.0 || uvmax > 10.0) + usage(); + } + + else if (argv[fa][1] == 'U') { + douv = 1; + } + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + + } else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + comb = 1; + + } else if (argv[fa][1] == 'z' || argv[fa][1] == 'Z') { + zero = 0; + + } else { + usage(); + } + } + else + break; + } + + if (fa < argc && argv[fa][0] != '-') { /* Got file arguments */ + int nsp = 0; /* Current number in sp[] */ + int soff = 0; /* Offset within file */ + int maxgraphs = MAXGRAPHS; + int eof; + + if (douv) + maxgraphs = 1; + + nsp = 0; + + /* Until we run out */ + for (;;) { + int i, nret, nreq; + + /* If we've got to the limit of each plot, */ + /* or at least one and there are no more files, */ + /* or at least one and we're not combining files and at start of a new file */ + if (nsp >= MAXGRAPHS || (nsp > 0 && ((!comb && soff == 0) || fa >= argc))) { + /* Plot what we've got */ + do_spec(buf, sp, nsp, zero, douv, uvmin, uvmax); + nsp = 0; + } + + if (fa >= argc) /* No more files */ + break; + + /* Read as many spectra from the file as possible */ + nreq = MAXGRAPHS - nsp; + if (read_nxspect(&sp[nsp], argv[fa], &nret, soff, nreq, 0) != 0) { + error ("Unable to read custom spectrum, CMF or CCSS '%s'",argv[fa]); + } + for (i = 0; i < nret; i++) { + xspect_denorm(&sp[nsp + i]); + sprintf(buf[nsp + i],"File '%s' spect %d",argv[fa], soff + i); + } + nsp += nret; + soff += nret; + if (nret < nreq) { /* We're done with this file */ + fa++; + soff = 0; + } + } + + } else { + + /* For each standard illuminant */ + for (ilType = icxIT_A; ilType <= icxIT_F10; ilType++) { + char *inm = NULL; + + switch (ilType) { + case icxIT_A: + inm = "A"; break; + case icxIT_C: + inm = "C"; break; + case icxIT_D50: + inm = "D50"; break; + case icxIT_D50M2: + inm = "D50M2"; break; + case icxIT_D65: + inm = "D65"; break; + case icxIT_E: + inm = "E"; break; + case icxIT_F5: + inm = "F5"; break; + case icxIT_F8: + inm = "F8"; break; + case icxIT_F10: + inm = "F10"; break; + default: + inm = "Unknown"; break; + break; + } + + if (standardIlluminant(&sp[0], ilType, 0) != 0) + error ("standardIlluminant returned error"); + + strcpy(buf[0],inm); + do_spec(buf, sp, 1, zero, douv, uvmin, uvmax); + } + + /* For each material and illuminant */ + for (temp = 2500; temp <= 9000; temp += 500) { + + for (k = 0; k < 2; k++) { + + ilType = k == 0 ? icxIT_Dtemp : icxIT_Ptemp; + + if (standardIlluminant(&sp[0], ilType, temp) != 0) + error ("standardIlluminant returned error"); + + sprintf(buf[0], "%s at %f", k == 0 ? "Daylight" : "Black body", temp); + + do_spec(buf, sp, 1, zero, douv, uvmin, uvmax); + } + } + + } + return 0; +} + + + + + + + + diff --git a/xicc/specsubsamp.c b/xicc/specsubsamp.c new file mode 100644 index 0000000..b13ad94 --- /dev/null +++ b/xicc/specsubsamp.c @@ -0,0 +1,232 @@ + +/* + * Author: Graeme W. Gill + * Date: 2007/3/21 + * Version: 1.00 + * + * Copyright 2007 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some utility code to subsample the 1 and 5nm spectral + * data in xspect to wider spacings using ANSI-CGATS the recommended + * triangular filter. + */ + +#include <stdio.h> +#include <math.h> +#include "xspect.h" +#include "numlib.h" + + +void usage(void) { + fprintf(stderr,"Downsample spectral data\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: specsubsamp -options\n"); + fprintf(stderr," -i illum Choose illuminant for print/transparency spectral data:\n"); + fprintf(stderr," A, C, D50, D50M2, D65, F5, F8, F10 or file.sp\n"); + fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n"); + fprintf(stderr," 1931_2, 1964_10, S&B 1955_2, shaw, J&V 1978_2\n"); + fprintf(stderr," -w st,en,sp Output start, end and spacing nm\n"); + fprintf(stderr," -5 Commenf output wavelegths every 5\n"); + exit(1); +} + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + icxIllumeType illum = icxIT_D50; /* Spectral defaults */ + xspect cust_illum; /* Custom illumination spectrum */ + icxObserverType observ = icxOT_CIE_1931_2; + int obs = 0; /* If nz output observer */ + double wl_short = 380.0, wl_long = 730.0, wl_width = 10.0; + int wl_n = 0; + int evy5 = 0; + + error_program = argv[0]; + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Spectral Illuminant type */ + if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage(); + if (strcmp(na, "A") == 0) { + illum = icxIT_A; + } else if (strcmp(na, "C") == 0) { + illum = icxIT_C; + } else if (strcmp(na, "D50") == 0) { + illum = icxIT_D50; + } else if (strcmp(na, "D50M2") == 0) { + illum = icxIT_D50M2; + } else if (strcmp(na, "D65") == 0) { + illum = icxIT_D65; + } else if (strcmp(na, "F5") == 0) { + illum = icxIT_F5; + } else if (strcmp(na, "F8") == 0) { + illum = icxIT_F8; + } else if (strcmp(na, "F10") == 0) { + illum = icxIT_F10; + } else { /* Assume it's a filename */ + illum = icxIT_custom; + if (read_xspect(&cust_illum, na) != 0) + usage(); + } + } + + /* Spectral Observer type */ + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage(); + if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */ + obs = 1; + observ = icxOT_CIE_1931_2; + } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */ + obs = 1; + observ = icxOT_CIE_1964_10; + } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */ + obs = 1; + observ = icxOT_Stiles_Burch_2; + } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */ + obs = 1; + observ = icxOT_Judd_Voss_2; + } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */ + obs = 1; + observ = icxOT_Shaw_Fairchild_2; + } else + usage(); + } + + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage(); + if (sscanf(na, " %lf,%lf,%lf ",&wl_short,&wl_long,&wl_width) != 3) + + if (wl_short > wl_long) + usage(); + } + + else if (argv[fa][1] == '5') { + evy5 = 1; + } + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } else { + usage(); + } + } + else + break; + } + + wl_n = (int)((wl_long - wl_short)/wl_width + 1.5); + + if ((fabs(wl_short + (wl_n - 1.0) * wl_width) - wl_long) > 0.001) + error("Not an integer number of output samples"); + + + if (obs) { + int i, j, k; + xspect *sp[3]; + + if (standardObserver(sp, observ) != 0) + error ("standardObserver returned error"); + printf("/* %f - %f, %f spacing, %d samples */\n",wl_short,wl_long,wl_width,wl_n); + printf("{\n"); + for (k = 0; k < 3; k++) { + + printf(" {\n"); + for (i = 0; i < wl_n; i++) { + double cwl, swl, sw, tw, outv; + int ns; + + cwl = XSPECT_WL(wl_short, wl_long, wl_n, i); + ns = (int)(wl_width/0.1 + 0.5); + tw = outv = 0.0; + for (j = -ns; j <= ns; j++) { + swl = cwl + (j * wl_width)/ns; + sw = (ns - abs(j))/(double)ns; + tw += sw; + outv += sw * value_xspect(sp[k], swl); + } + outv /= tw; + if (!evy5 || i % 5 == 0) + printf(" /* %3.1f */ %1.10f%s\n",cwl,outv, i < (wl_n-1) ? "," : ""); + else + printf(" %1.10f%s\n",outv, i < (wl_n-1) ? "," : ""); + } + printf(" }%s\n",k < (3-1) ? "," : ""); + } + printf("}\n"); + + } else { + int i, j; + xspect sp; + + if (illum == icxIT_custom) + sp = cust_illum; + else { + if (standardIlluminant(&sp, illum, 0) != 0) + error ("standardIlluminant returned error"); + } + + printf("/* %f - %f, %f spacing, %d samples */\n",wl_short,wl_long,wl_width,wl_n); + printf("{\n"); + for (i = 0; i < wl_n; i++) { + double cwl, swl, sw, tw, outv; + int ns; + + cwl = XSPECT_WL(wl_short, wl_long, wl_n, i); +//printf("~1 output %f\n",cwl); + ns = (int)(wl_width/0.1 + 0.5); + tw = outv = 0.0; + for (j = -ns; j <= ns; j++) { + swl = cwl + (j * wl_width)/ns; + sw = (ns - abs(j))/(double)ns; + tw += sw; +//printf("~1 sample %f weight %f\n",swl,sw); + outv += sw * value_xspect(&sp, swl); + } + outv /= tw; + if (!evy5 || i % 5 == 0) + printf(" /* %3.1f */ %1.10f%s\n",cwl,outv, i < (wl_n-1) ? "," : ""); + else + printf(" %1.10f%s\n",outv, i < (wl_n-1) ? "," : ""); + } + printf("}\n"); + } + return 0; +} + + + + + + + + diff --git a/xicc/spectest.c b/xicc/spectest.c new file mode 100644 index 0000000..1c92df4 --- /dev/null +++ b/xicc/spectest.c @@ -0,0 +1,750 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 8/5/2002 + * Version: 1.00 + * + * Copyright 2002 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some test code to test the FWA compensation + * feature of the spectal to CIE conversion. + * + */ + +#define FILTERED_D65 /* Use Spectrolino filtered D65 instead of ideal D65 */ + +#define DOPLOT /* Graphs: Black = target curve */ + /* Red = uncorrected curve */ + /* Green = corrected curve */ + +#define DATAFILE /* Write the plot data to a data file */ + +#include <stdio.h> +#include <math.h> +#include "xspect.h" +#include "numlib.h" +#ifdef DOPLOT +#include "plot.h" +#endif + + +/* Spectrolino filter "D65" illuminant */ +static xspect il_sod65 = { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 17.42, 29.51, 36.54, 38.39, 38.15, + 39.68, 44.7, 53.6, 64.26, 74.01, + 82.39, 89.9, 93.45, 94.05, 91.59, + 89.4, 93.69, 100.85, 99.66, 87.12, + 78.34, 82.95, 91.31, 98.18, 99.99, + 104.38, 120.06, 145.35, 172.8, 190.38, + 192.46, 181.28, 163.7, 143.14, 123.57, + 104.67 + } +}; + +/* Material/illuminant record */ +struct { + char *media; /* Media/process identifier */ + char *illum; /* Primary illuminant identifier */ + icxIllumeType ill; /* Illuminant used to get reflectance spectrum */ + xspect white; /* Media on its own spectrum */ + xspect black; /* Media with maximum colorant */ + struct { + char *pdesc; /* Description of sample */ + xspect s; /* Spectrum of it */ + } patches[6]; + +} matilum[2][2] = { /* [Cromalin, Epson 10K] [A, D65] */ + { + { + "Cromalin", /* Paper measured */ + "Illuminant A", /* Illuminant measured under */ + icxIT_A, + { /* White */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0376, 0.0217, 0.0325, 0.1779, 0.5891, + 0.9366, 1.0307, 1.0241, 0.9996, 0.9738, + 0.9521, 0.9355, 0.9245, 0.9174, 0.9147, + 0.9113, 0.9112, 0.9118, 0.9111, 0.9148, + 0.9147, 0.9162, 0.9172, 0.9185, 0.9197, + 0.9194, 0.9213, 0.9239, 0.9259, 0.9257, + 0.9259, 0.9250, 0.9271, 0.9293, 0.9296, 0.9310 + } + }, + { /* Black */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 + } + }, + { + { + "30% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0274, 0.0161, 0.0218, 0.1072, 0.3077, + 0.4280, 0.4532, 0.4530, 0.4508, 0.4498, + 0.4585, 0.5019, 0.6137, 0.7716, 0.8680, + 0.8939, 0.9000, 0.8999, 0.9015, 0.9098, + 0.9153, 0.9193, 0.9212, 0.9230, 0.9245, + 0.9247, 0.9267, 0.9294, 0.9317, 0.9315, + 0.9317, 0.9317, 0.9331, 0.9354, 0.9357, 0.9366 + } + } + }, + { + "60% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0225, 0.0130, 0.0169, 0.0613, 0.1512, + 0.1951, 0.2039, 0.2059, 0.2095, 0.2149, + 0.2301, 0.2850, 0.4283, 0.6585, 0.8249, + 0.8767, 0.8878, 0.8860, 0.8887, 0.9017, + 0.9115, 0.9181, 0.9202, 0.9219, 0.9232, + 0.9229, 0.9247, 0.9269, 0.9289, 0.9283, + 0.9280, 0.9277, 0.9292, 0.9307, 0.9305, 0.9315 + } + } + }, + { + "90% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0195, 0.0114, 0.0140, 0.0314, 0.0568, + 0.0664, 0.0691, 0.0708, 0.0760, 0.0836, + 0.1003, 0.1571, 0.3087, 0.5728, 0.7861, + 0.8602, 0.8759, 0.8737, 0.8781, 0.8952, + 0.9094, 0.9185, 0.9215, 0.9234, 0.9250, + 0.9248, 0.9267, 0.9292, 0.9312, 0.9307, + 0.9305, 0.9300, 0.9313, 0.9328, 0.9321, 0.9339 + } + } + }, + { + "30% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0163, 0.0117, 0.0181, 0.1018, 0.3029, + 0.4324, 0.4585, 0.4539, 0.4437, 0.4330, + 0.4247, 0.4180, 0.4140, 0.4113, 0.4108, + 0.4095, 0.4097, 0.4101, 0.4100, 0.4117, + 0.4119, 0.4126, 0.4133, 0.4141, 0.4152, + 0.4156, 0.4169, 0.4187, 0.4200, 0.4206, + 0.4211, 0.4214, 0.4224, 0.4240, 0.4243, 0.4256 + } + } + }, + { + "60% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0087, 0.0070, 0.0138, 0.0539, 0.1378, + 0.1802, 0.1872, 0.1840, 0.1794, 0.1748, + 0.1711, 0.1686, 0.1669, 0.1661, 0.1659, + 0.1656, 0.1657, 0.1661, 0.1660, 0.1670, + 0.1670, 0.1675, 0.1680, 0.1686, 0.1693, + 0.1700, 0.1707, 0.1720, 0.1728, 0.1735, + 0.1740, 0.1745, 0.1749, 0.1761, 0.1759, 0.1779 + } + } + }, + { + "90% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0044, 0.0040, 0.0090, 0.0243, 0.0430, + 0.0503, 0.0508, 0.0485, 0.0460, 0.0440, + 0.0423, 0.0412, 0.0406, 0.0404, 0.0404, + 0.0404, 0.0407, 0.0409, 0.0411, 0.0415, + 0.0418, 0.0422, 0.0424, 0.0429, 0.0434, + 0.0440, 0.0444, 0.0452, 0.0459, 0.0465, + 0.0469, 0.0473, 0.0477, 0.0483, 0.0489, 0.0496 + } + } + } + } + }, + { + "Cromalin", + "Illuminant filtered D65", + icxIT_D65, + { /* White */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0096, 0.0082, 0.0242, 0.1791, 0.6547, + 1.0944, 1.2012, 1.1545, 1.0891, 1.0388, + 1.0009, 0.9725, 0.9552, 0.9442, 0.9384, + 0.9313, 0.9266, 0.9235, 0.9213, 0.9250, + 0.9249, 0.9247, 0.9239, 0.9248, 0.9253, + 0.9249, 0.9267, 0.9293, 0.9310, 0.9308, + 0.9307, 0.9311, 0.9309, 0.9335, 0.9263, 0.8774 + } + }, + { /* Black */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 + } + }, + { + { + "30% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0054, 0.0050, 0.0157, 0.1084, 0.3443, + 0.4998, 0.5278, 0.5108, 0.4915, 0.4802, + 0.4823, 0.5221, 0.6331, 0.7929, 0.8894, + 0.9116, 0.9134, 0.9095, 0.9097, 0.9176, + 0.9222, 0.9245, 0.9251, 0.9267, 0.9278, + 0.9275, 0.9293, 0.9323, 0.9344, 0.9346, + 0.9348, 0.9346, 0.9344, 0.9354, 0.9252, 0.8748 + } + } + }, + { + "60% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0039, 0.0042, 0.0117, 0.0632, 0.1733, + 0.2333, 0.2430, 0.2364, 0.2315, 0.2315, + 0.2440, 0.2976, 0.4421, 0.6777, 0.8461, + 0.8950, 0.9014, 0.8967, 0.8971, 0.9095, + 0.9187, 0.9234, 0.9242, 0.9263, 0.9270, + 0.9257, 0.9280, 0.9303, 0.9326, 0.9322, + 0.9315, 0.9317, 0.9314, 0.9306, 0.9169, 0.8664 + } + } + }, + { + "90% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0032, 0.0038, 0.0099, 0.0342, 0.0716, + 0.0889, 0.0918, 0.0884, 0.0887, 0.0931, + 0.1086, 0.1652, 0.3190, 0.5892, 0.8060, + 0.8777, 0.8891, 0.8839, 0.8863, 0.9022, + 0.9158, 0.9230, 0.9259, 0.9269, 0.9283, + 0.9266, 0.9304, 0.9327, 0.9350, 0.9344, + 0.9342, 0.9336, 0.9328, 0.9323, 0.9175, 0.8589 + } + } + }, + { + "30% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0047, 0.0049, 0.0153, 0.1046, 0.3404, + 0.5036, 0.5317, 0.5102, 0.4828, 0.4620, + 0.4468, 0.4358, 0.4293, 0.4249, 0.4232, + 0.4207, 0.4191, 0.4181, 0.4172, 0.4188, + 0.4190, 0.4194, 0.4191, 0.4196, 0.4203, + 0.4204, 0.4218, 0.4236, 0.4251, 0.4255, + 0.4258, 0.4259, 0.4267, 0.4276, 0.4249, 0.4031 + } + } + }, + { + "60% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0042, 0.0047, 0.0126, 0.0586, 0.1584, + 0.2146, 0.2206, 0.2101, 0.1978, 0.1885, + 0.1820, 0.1774, 0.1748, 0.1730, 0.1726, + 0.1714, 0.1710, 0.1703, 0.1704, 0.1712, + 0.1709, 0.1717, 0.1713, 0.1724, 0.1730, + 0.1732, 0.1738, 0.1753, 0.1759, 0.1768, + 0.1766, 0.1778, 0.1774, 0.1775, 0.1774, 0.1707 + } + } + }, + { + "90% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0018, 0.0039, 0.0103, 0.0293, 0.0560, + 0.0677, 0.0676, 0.0611, 0.0551, 0.0505, + 0.0475, 0.0452, 0.0441, 0.0434, 0.0430, + 0.0430, 0.0428, 0.0428, 0.0427, 0.0433, + 0.0433, 0.0434, 0.0441, 0.0438, 0.0448, + 0.0457, 0.0453, 0.0468, 0.0471, 0.0477, + 0.0483, 0.0483, 0.0487, 0.0486, 0.0492, 0.0500 + } + } + } + } + } + }, + { + { + "Epson10K", + "Illuminant A", + icxIT_A, + { /* White */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.4561, 0.4677, 0.5052, 0.6477, 0.8785, + 0.9929, 1.0127, 0.9909, 0.9676, 0.9539, + 0.9420, 0.9330, 0.9277, 0.9246, 0.9236, + 0.9205, 0.9200, 0.9191, 0.9172, 0.9198, + 0.9184, 0.9185, 0.9181, 0.9189, 0.9204, + 0.9207, 0.9228, 0.9256, 0.9277, 0.9276, + 0.9284, 0.9286, 0.9304, 0.9332, 0.9321, 0.9349 + } + }, + { /* Black */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 + } + }, + { + { + "30% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.3650, 0.3689, 0.3908, 0.4912, 0.6549, + 0.7395, 0.7667, 0.7745, 0.7868, 0.8103, + 0.8385, 0.8663, 0.8869, 0.8983, 0.9037, + 0.9028, 0.9031, 0.9024, 0.9006, 0.9030, + 0.9018, 0.9016, 0.9007, 0.9011, 0.9013, + 0.9002, 0.9015, 0.9032, 0.9048, 0.9043, + 0.9040, 0.9038, 0.9050, 0.9073, 0.9062, 0.9082 + } + } + }, + { + "60% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.2376, 0.2286, 0.2349, 0.2856, 0.3740, + 0.4290, 0.4642, 0.5005, 0.5513, 0.6172, + 0.6959, 0.7750, 0.8340, 0.8683, 0.8848, + 0.8884, 0.8901, 0.8900, 0.8884, 0.8906, + 0.8892, 0.8893, 0.8884, 0.8885, 0.8886, + 0.8875, 0.8885, 0.8903, 0.8917, 0.8910, + 0.8911, 0.8908, 0.8924, 0.8951, 0.8940, 0.8962 + } + } + }, + { + "90% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0482, 0.0362, 0.0314, 0.0323, 0.0373, + 0.0448, 0.0570, 0.0829, 0.1310, 0.2130, + 0.3465, 0.5227, 0.6838, 0.7871, 0.8409, + 0.8622, 0.8713, 0.8749, 0.8752, 0.8787, + 0.8783, 0.8787, 0.8781, 0.8784, 0.8784, + 0.8773, 0.8780, 0.8794, 0.8802, 0.8789, + 0.8783, 0.8773, 0.8778, 0.8794, 0.8775, 0.8779 + } + } + }, + { + "30% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.3304, 0.3398, 0.3632, 0.4562, 0.6018, + 0.6702, 0.6808, 0.6663, 0.6520, 0.6434, + 0.6362, 0.6311, 0.6285, 0.6272, 0.6269, + 0.6253, 0.6253, 0.6253, 0.6247, 0.6274, + 0.6280, 0.6301, 0.6320, 0.6351, 0.6385, + 0.6414, 0.6464, 0.6530, 0.6593, 0.6643, + 0.6692, 0.6727, 0.6770, 0.6811, 0.6826, 0.6856 + } + } + }, + { + "60% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.1735, 0.1784, 0.1886, 0.2295, 0.2936, + 0.3232, 0.3279, 0.3227, 0.3177, 0.3152, + 0.3133, 0.3126, 0.3126, 0.3131, 0.3141, + 0.3142, 0.3151, 0.3160, 0.3169, 0.3195, + 0.3217, 0.3251, 0.3287, 0.3335, 0.3386, + 0.3439, 0.3511, 0.3599, 0.3692, 0.3775, + 0.3853, 0.3919, 0.3982, 0.4036, 0.4068, 0.4109 + } + } + }, + { + "90% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0197, 0.0198, 0.0201, 0.0201, 0.0208, + 0.0207, 0.0204, 0.0202, 0.0200, 0.0197, + 0.0194, 0.0194, 0.0193, 0.0192, 0.0191, + 0.0191, 0.0190, 0.0190, 0.0190, 0.0192, + 0.0194, 0.0198, 0.0205, 0.0212, 0.0221, + 0.0230, 0.0244, 0.0261, 0.0282, 0.0304, + 0.0326, 0.0346, 0.0366, 0.0383, 0.0396, 0.0412 + } + } + } + } + }, + { + "Epson10K", + "Illuminant filtered D65", + icxIT_D65, + { /* White */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.4452, 0.4631, 0.5041, 0.6692, 0.9582, + 1.1083, 1.1289, 1.0717, 1.0191, 0.9929, + 0.9721, 0.9560, 0.9483, 0.9439, 0.9419, + 0.9374, 0.9357, 0.9337, 0.9313, 0.9340, + 0.9330, 0.9329, 0.9320, 0.9328, 0.9341, + 0.9339, 0.9366, 0.9387, 0.9406, 0.9405, + 0.9404, 0.9405, 0.9414, 0.9455, 0.9369, 0.9289 + } + }, + { /* Black */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000 + } + }, + { + { + "30% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.3538, 0.3632, 0.3888, 0.5058, 0.7111, + 0.8221, 0.8512, 0.8334, 0.8251, 0.8392, + 0.8609, 0.8832, 0.9013, 0.9113, 0.9157, + 0.9137, 0.9128, 0.9113, 0.9086, 0.9111, + 0.9103, 0.9094, 0.9091, 0.9083, 0.9085, + 0.9080, 0.9091, 0.9104, 0.9118, 0.9104, + 0.9099, 0.9099, 0.9094, 0.9111, 0.9054, 0.8793 + } + } + }, + { + "60% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.2220, 0.2209, 0.2299, 0.2901, 0.4010, + 0.4716, 0.5092, 0.5337, 0.5732, 0.6337, + 0.7095, 0.7848, 0.8422, 0.8755, 0.8907, + 0.8933, 0.8938, 0.8927, 0.8909, 0.8929, + 0.8922, 0.8912, 0.8897, 0.8902, 0.8900, + 0.8893, 0.8896, 0.8915, 0.8932, 0.8918, + 0.8921, 0.8915, 0.8921, 0.8939, 0.8825, 0.8575 + } + } + }, + { + "90% Y", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0326, 0.0287, 0.0269, 0.0295, 0.0376, + 0.0469, 0.0605, 0.0862, 0.1343, 0.2162, + 0.3503, 0.5257, 0.6862, 0.7889, 0.8420, + 0.8632, 0.8719, 0.8753, 0.8753, 0.8784, + 0.8776, 0.8778, 0.8778, 0.8783, 0.8775, + 0.8772, 0.8777, 0.8791, 0.8808, 0.8793, + 0.8784, 0.8770, 0.8758, 0.8757, 0.8626, 0.8106 + } + } + }, + { + "30% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.3243, 0.3369, 0.3635, 0.4708, 0.6532, + 0.7439, 0.7542, 0.7167, 0.6839, 0.6673, + 0.6546, 0.6454, 0.6412, 0.6387, 0.6380, + 0.6357, 0.6351, 0.6341, 0.6334, 0.6363, + 0.6367, 0.6388, 0.6400, 0.6431, 0.6465, + 0.6491, 0.6532, 0.6591, 0.6656, 0.6699, + 0.6743, 0.6778, 0.6816, 0.6859, 0.6815, 0.6758 + } + } + }, + { + "60% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.1707, 0.1772, 0.1895, 0.2374, 0.3174, + 0.3566, 0.3616, 0.3464, 0.3329, 0.3272, + 0.3228, 0.3201, 0.3196, 0.3198, 0.3208, + 0.3203, 0.3207, 0.3217, 0.3221, 0.3249, + 0.3269, 0.3295, 0.3339, 0.3375, 0.3427, + 0.3483, 0.3545, 0.3635, 0.3726, 0.3808, + 0.3881, 0.3949, 0.4003, 0.4053, 0.4052, 0.3962 + } + } + }, + { + "90% K", + { + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 1.0, /* Scale factor */ + { + 0.0206, 0.0205, 0.0202, 0.0205, 0.0213, + 0.0217, 0.0210, 0.0207, 0.0202, 0.0199, + 0.0198, 0.0197, 0.0197, 0.0195, 0.0196, + 0.0193, 0.0192, 0.0194, 0.0194, 0.0194, + 0.0197, 0.0198, 0.0204, 0.0214, 0.0223, + 0.0236, 0.0242, 0.0265, 0.0283, 0.0306, + 0.0330, 0.0347, 0.0370, 0.0389, 0.0394, 0.0383 + } + } + } + } + } + } +}; + +/* ---------------------------------------------------------- */ + +int +main(void) { + int m, ps, ss, pn; +#ifdef DATAFILE + FILE *df; +#endif + + printf("Hi there\n"); + +#ifdef DATAFILE + if ((df = fopen("spectest.dat", "w")) == NULL) + error ("Unable to open data file '%s'","spectest.dat"); +#endif + /* For each material and illuminant */ + for (m = 0; m < 2; m++) { + + printf("Material '%s'\n", matilum[m][0].media); + + /* For each light source as primary (target/check), other as secondary (instrument) */ + for (ps = 0; ps < 2; ps++) { + xsp2cie *pcon, *scon; /* Conversions */ + xspect pill, sill; /* Illuminants */ + + ss = 1 - ps; /* Opposite */ + +#ifdef FILTERED_D65 + if (matilum[m][ps].ill == icxIT_D65) + pill = il_sod65; + else +#endif + standardIlluminant(&pill, matilum[m][ps].ill, 0); /* Target/check */ + +#ifdef FILTERED_D65 + if (matilum[m][ss].ill == icxIT_D65) + sill = il_sod65; + else +#endif + standardIlluminant(&sill, matilum[m][ss].ill, 0); /* Instrument */ + + /* Create two conversions for the target/check illuminant */ + if ((pcon = new_xsp2cie(icxIT_custom, &pill, icxOT_Shaw_Fairchild_2, + NULL, icSigLabData, 1)) == NULL) + error ("Creating conversion failed"); + + if ((scon = new_xsp2cie(icxIT_custom, &pill, icxOT_Shaw_Fairchild_2, + NULL, icSigLabData, 1)) == NULL) + error ("Creating conversion failed"); + + /* Tell the secondary conversion to allow for instrument illuminant */ + if (scon->set_fwa(scon, &sill, NULL, &matilum[m][ss].white)) + error ("Setting FWA compensation failed"); + + printf("Primary (Target/Check) '%s', Secondary (Instrument)'%s'\n", + matilum[m][ps].illum, matilum[m][ss].illum); + + /* For each test patch */ + for (pn = 0; pn < 6; pn++) { + int i, j; + double plab[3]; + double slab[3]; + double sclab[3]; + xspect psp; /* Reference spectrum */ + xspect ssp; /* un-compensated spectrum */ + xspect scsp; /* Compensated spectrum */ + double de, cde; +#ifdef DOPLOT +#define XRES 400 + double xx[XRES]; + double y1[XRES]; + double y2[XRES]; + double y3[XRES]; +#endif /* DOPLOT */ + + + /* Compute reference value for target illuminant */ + pcon->sconvert(pcon, &psp, plab, &matilum[m][ps].patches[pn].s); + + /* Compute uncompensated value for target illuminant */ + pcon->sconvert(pcon, &ssp, slab, &matilum[m][ss].patches[pn].s); + + /* Compute compensated value for target illuminant */ + scon->sconvert(scon, &scsp, sclab, &matilum[m][ss].patches[pn].s); + + de = 0.0; + for (j = 0; j < 3; j++) { + double tt = plab[j] - slab[j]; + de += tt * tt; + } + de = sqrt(de); + + printf("Patch '%s', Ref %f %f %f, Other %f %f %f DE %f\n", + matilum[m][ps].patches[pn].pdesc, plab[0], plab[1], plab[2], + slab[0], slab[1], slab[2], de); + + cde = 0.0; + for (j = 0; j < 3; j++) { + double tt = plab[j] - sclab[j]; + cde += tt * tt; + } + cde = sqrt(cde); + + printf("Patch '%s', Ref %f %f %f, COther %f %f %f DE %f\n", + matilum[m][ps].patches[pn].pdesc, plab[0], plab[1], plab[2], + sclab[0], sclab[1], sclab[2], cde); + printf("DE change %f\n",cde - de); + printf("\n"); + +#ifdef DOPLOT + for (i = 0; i < psp.spec_n; i++) { + double ww; + + ww = (psp.spec_wl_long - psp.spec_wl_short) + * ((double)i/(psp.spec_n-1.0)) + psp.spec_wl_short; + + xx[i] = ww; + y1[i] = ssp.spec[i]; /* Black - Input */ + y2[i] = scsp.spec[i]; /* Red - Estimate */ + y3[i] = psp.spec[i]; /* Green - Target */ + } + do_plot(xx,y1,y2,y3,i); +#endif /* DOPLOT */ +#ifdef DATAFILE + fprintf(df,"\nPrimary (Target/Check) '%s', Secondary (Instrument)'%s'\n", + matilum[m][ps].illum, matilum[m][ss].illum); + fprintf(df,"Patch '%s', Ref %f %f %f, Other %f %f %f DE %f\n", + matilum[m][ps].patches[pn].pdesc, plab[0], plab[1], plab[2], + slab[0], slab[1], slab[2], de); + fprintf(df,"Patch '%s', Ref %f %f %f, COther %f %f %f DE %f\n", + matilum[m][ps].patches[pn].pdesc, plab[0], plab[1], plab[2], + sclab[0], sclab[1], sclab[2], cde); + fprintf(df,"NM Ref. Un-comp. Comp.\n"); + for (i = 0; i < psp.spec_n; i++) { + double ww; + + ww = (psp.spec_wl_long - psp.spec_wl_short) + * ((double)i/(psp.spec_n-1.0)) + psp.spec_wl_short; + + fprintf(df,"%d %f %f %f\n",((int)ww), psp.spec[i], ssp.spec[i], scsp.spec[i]); + } +#endif /* DATAFILE */ + } + pcon->del(pcon); + scon->del(scon); + } + } +#ifdef DATAFILE + fclose(df); +#endif + + return 0; +} + + + + + + + + diff --git a/xicc/spectest2.c b/xicc/spectest2.c new file mode 100644 index 0000000..e9c1754 --- /dev/null +++ b/xicc/spectest2.c @@ -0,0 +1,270 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 30/3/2007 + * Version: 1.00 + * + * Copyright 2002 - 2007 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This is some more test code to test the FWA compensation + * feature of the spectal to CIE conversion. + * Check how well the FWA is estimated from some paper samples. + */ + +#define DOPLOT /* Graphs: Black = target curve */ + /* Red = uncorrected curve */ + /* Green = corrected curve */ + +#include <stdio.h> +#include <math.h> +#include "xspect.h" +#include "insttypes.h" +//#include "inst.h" +#include "numlib.h" +#ifdef DOPLOT +#include "plot.h" +#endif + + +/* Normal 'A' spectra, then UV filtered version */ + +xspect mat[2][20] = { + /* 'A' illuminant */ + { + { /* 1 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 14.267, 15.584, 20.723, 41.993, 84.338, 104.728, 108.065, 104.009, 98.717, 95.746, 93.079, 90.568, 88.689, 87.157, 85.795, 84.303, 83.109, 81.981, 80.966, 80.685, 80.361, 80.369, 80.530, 81.249, 82.823, 85.006, 87.685, 90.146, 91.902, 92.799, 93.246, 93.458, 93.767, 94.212, 94.372, 94.651 + } + }, + { /* 2 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 14.983, 16.831, 22.426, 43.716, 84.430, 105.538, 109.685, 105.582, 100.060, 96.944, 94.119, 91.251, 88.909, 86.894, 84.962, 83.264, 82.636, 82.203, 81.208, 80.950, 81.420, 82.457, 83.033, 83.040, 83.033, 83.700, 85.422, 87.339, 88.728, 89.234, 89.152, 88.913, 89.203, 89.990, 90.613, 91.301 + } + }, + { /* 3 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 24.488, 29.581, 37.749, 69.706, 94.472, 100.591, 100.273, 98.291, 97.532, 96.351, 95.134, 94.261, 93.428, 92.569, 91.950, 91.181, 90.660, 90.115, 89.426, 89.326, 89.147, 89.154, 89.151, 89.321, 89.636, 89.984, 90.717, 91.607, 92.478, 93.171, 93.867, 94.470, 95.044, 95.638, 95.840, 96.071 + } + }, + { /* 4 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 44.123, 45.345, 46.333, 52.603, 77.805, 95.763, 102.401, 99.326, 95.610, 95.188, 94.009, 92.570, 92.002, 91.687, 91.396, 90.911, 90.662, 90.464, 90.104, 90.172, 89.881, 89.799, 89.690, 89.664, 89.708, 89.679, 89.894, 90.203, 90.473, 90.556, 90.720, 90.841, 91.078, 91.416, 91.445, 91.559 + } + }, + { /* 5 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 59.175, 67.893, 72.295, 79.833, 89.531, 92.976, 93.994, 93.660, 93.322, 93.028, 92.527, 91.913, 91.195, 90.494, 89.974, 89.258, 88.761, 88.208, 87.470, 87.222, 86.769, 86.608, 86.590, 86.842, 87.344, 87.956, 88.924, 90.036, 91.088, 91.870, 92.720, 93.474, 94.280, 95.064, 95.370, 95.685 + } + }, + { /* 6 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 37.749, 42.672, 50.439, 75.066, 95.496, 97.450, 98.295, 95.268, 93.331, 92.634, 91.847, 91.403, 91.410, 91.538, 91.791, 91.771, 91.955, 92.073, 91.974, 92.312, 92.335, 92.575, 92.698, 92.835, 93.066, 93.277, 93.761, 94.294, 94.661, 94.719, 94.749, 94.757, 95.001, 95.429, 95.619, 95.931 + } + }, + { /* 7 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 57.122, 62.955, 67.576, 72.801, 76.460, 78.437, 79.846, 80.856, 81.509, 82.098, 82.427, 82.523, 82.718, 82.593, 82.345, 82.122, 82.183, 82.198, 82.035, 82.353, 82.618, 83.188, 83.512, 83.549, 83.583, 84.044, 85.291, 86.585, 87.429, 87.476, 87.439, 87.590, 88.241, 89.077, 89.530, 90.008 + } + }, + { /* 8 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 58.637, 64.082, 68.830, 73.651, 76.911, 78.772, 80.309, 81.192, 82.121, 82.541, 82.893, 83.226, 83.147, 83.038, 83.058, 82.702, 82.727, 83.025, 82.982, 83.281, 83.705, 84.410, 84.663, 84.619, 84.804, 85.372, 86.410, 87.352, 88.013, 88.340, 88.616, 88.753, 89.031, 89.572, 89.914, 90.712 + } + }, + { /* 9 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 32.774, 38.428, 47.037, 72.668, 97.391, 102.330, 102.404, 99.655, 97.093, 95.368, 93.825, 92.567, 91.674, 90.957, 90.507, 89.992, 89.784, 89.565, 89.259, 89.610, 89.881, 90.199, 90.325, 90.465, 90.671, 90.878, 91.428, 92.051, 92.507, 92.607, 92.842, 93.236, 93.963, 94.682, 94.942, 95.146 + } + }, + { /* 10 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 66.797, 69.934, 72.229, 74.822, 76.630, 78.036, 78.706, 79.021, 79.371, 79.281, 79.175, 79.144, 79.095, 79.119, 79.272, 79.143, 79.064, 78.924, 78.588, 78.615, 78.296, 78.132, 77.969, 77.909, 77.943, 77.812, 77.925, 78.115, 78.213, 78.133, 78.181, 78.345, 78.757, 79.268, 79.782, 80.318 + } + }, + { /* 11 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 29.478, 33.585, 40.588, 60.658, 85.350, 92.218, 93.564, 91.366, 88.913, 87.742, 86.793, 86.094, 85.874, 85.915, 86.117, 86.130, 86.333, 86.510, 86.589, 87.051, 87.157, 87.444, 87.677, 88.083, 88.545, 88.907, 89.442, 90.002, 90.440, 90.591, 90.787, 91.029, 91.434, 91.878, 92.014, 92.373 + } + }, + { /* 12 */ + 0, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 0.0 + } + } + }, + + /* UV filtered */ + { + { /* 1 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 28.419, 33.018, 42.215, 51.413, 69.808, 85.626, 88.515, 88.544, 88.433, 88.268, 87.971, 87.553, 86.919, 86.138, 85.337, 84.207, 83.249, 82.288, 81.393, 81.182, 80.923, 80.961, 81.138, 81.828, 83.388, 85.583, 88.212, 90.650, 92.373, 93.250, 93.633, 93.778, 94.089, 94.424, 94.498, 94.910 + } + }, + { /* 2 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 28.104, 32.711, 41.924, 51.137, 69.563, 85.811, 89.046, 89.203, 89.062, 88.799, 88.455, 87.735, 86.713, 85.504, 84.103, 82.802, 82.413, 82.146, 81.252, 81.067, 81.568, 82.671, 83.263, 83.214, 83.248, 83.963, 85.592, 87.499, 88.976, 89.428, 89.257, 89.095, 89.302, 90.015, 90.578, 91.308 + } + }, + { /* 3 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 70.653, 72.514, 76.238, 79.961, 87.407, 92.047, 92.848, 93.040, 93.369, 93.389, 93.257, 93.088, 92.713, 92.239, 91.847, 91.231, 90.837, 90.378, 89.765, 89.702, 89.530, 89.587, 89.577, 89.724, 90.020, 90.395, 91.115, 91.948, 92.866, 93.526, 94.160, 94.800, 95.340, 95.785, 96.001, 96.205 + } + }, + { /* 4 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 39.580, 43.280, 50.681, 58.081, 72.882, 86.792, 90.031, 90.136, 90.086, 90.403, 90.619, 90.769, 90.883, 90.942, 91.074, 90.832, 90.758, 90.656, 90.367, 90.489, 90.233, 90.180, 90.049, 90.013, 90.100, 90.021, 90.238, 90.576, 90.811, 90.900, 91.031, 91.114, 91.314, 91.537, 91.438, 91.634 + } + }, + { /* 5 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 77.775, 78.760, 80.729, 82.698, 86.637, 89.962, 90.821, 91.214, 91.679, 91.804, 91.748, 91.501, 91.059, 90.523, 90.154, 89.541, 89.136, 88.644, 87.956, 87.743, 87.265, 87.155, 87.163, 87.349, 87.809, 88.407, 89.293, 90.340, 91.417, 92.181, 92.880, 93.690, 94.331, 94.912, 95.209, 95.549 + } + }, + { /* 6 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 70.130, 71.508, 74.263, 77.018, 82.527, 85.915, 86.959, 87.455, 88.211, 88.869, 89.502, 90.186, 90.813, 91.367, 91.951, 92.170, 92.518, 92.720, 92.703, 93.117, 93.192, 93.453, 93.621, 93.771, 94.057, 94.262, 94.695, 95.157, 95.508, 95.572, 95.574, 95.771, 96.080, 96.418, 96.573, 96.865 + } + }, + { /* 7 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 70.903, 71.374, 72.316, 73.259, 75.144, 77.016, 78.664, 79.797, 80.774, 81.501, 81.859, 82.236, 82.471, 82.309, 82.265, 82.050, 82.044, 82.123, 82.052, 82.288, 82.492, 83.143, 83.510, 83.466, 83.495, 84.034, 85.312, 86.528, 87.301, 87.403, 87.410, 87.697, 88.149, 88.800, 89.108, 89.670 + } + }, + { /* 8 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 71.356, 71.858, 72.862, 73.867, 75.875, 77.895, 79.391, 80.476, 81.601, 82.106, 82.559, 83.048, 83.056, 83.041, 83.107, 82.713, 82.781, 83.133, 83.088, 83.355, 83.790, 84.538, 84.821, 84.683, 84.892, 85.550, 86.553, 87.512, 88.192, 88.505, 88.733, 88.959, 89.162, 89.539, 89.868, 90.603 + } + }, + { /* 9 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 68.893, 70.690, 74.285, 77.880, 85.070, 89.766, 90.646, 90.753, 90.971, 90.955, 90.895, 90.798, 90.641, 90.406, 90.284, 89.991, 89.943, 89.840, 89.591, 89.991, 90.279, 90.608, 90.760, 90.860, 91.096, 91.360, 91.836, 92.435, 92.923, 93.002, 93.227, 93.684, 94.312, 94.851, 95.151, 95.382 + } + }, + { /* 10 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 71.725, 72.140, 72.969, 73.798, 75.457, 76.827, 77.511, 78.066, 78.560, 78.621, 78.659, 78.711, 78.736, 78.759, 78.905, 78.808, 78.824, 78.662, 78.369, 78.382, 78.062, 77.906, 77.778, 77.756, 77.796, 77.681, 77.606, 77.754, 77.733, 77.637, 77.658, 77.616, 77.867, 78.183, 78.446, 78.888 + } + }, + { /* 11 */ + 36, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 56.723, 58.809, 62.983, 67.157, 75.504, 81.580, 82.970, 83.489, 84.048, 84.454, 84.913, 85.415, 85.885, 86.365, 86.852, 87.052, 87.381, 87.634, 87.777, 88.262, 88.371, 88.665, 88.885, 89.250, 89.674, 89.930, 90.462, 90.913, 91.339, 91.367, 91.548, 91.681, 92.032, 92.466, 92.440, 92.765 + } + }, + { /* 12 */ + 0, 380.0, 730.0, /* 36 bands from 380 to 730 in 10nm steps */ + 100.0, /* Scale factor */ + { + 0.0 + } + } + } +}; + +int +main(void) { + int m, i; + xspect insp; + double xx[500],y1[500],y2[500],y3[500]; + + if (inst_illuminant(&insp, instSpectrolino) != 0) + error ("Instrument doesn't have an FWA illuminent"); + + printf("Hi there\n"); + + /* For each material */ + for (m = 0; ; m++) { + xsp2cie *con; /* Conversions */ + + if (mat[0][m].spec_n == 0) + break; + + printf("Material %d\n", m+1); + + /* Create two conversions for the target/check illuminant */ + if ((con = new_xsp2cie(icxIT_D50, NULL, icxOT_CIE_1931_2, + NULL, icSigLabData, 1)) == NULL) + error ("Creating conversion failed"); + + if (con->set_fwa(con, &insp, NULL, &mat[0][m])) + error ("Setting FWA compensation failed"); + + for (i = 0; i < mat[0][m].spec_n; i++) { + double ww; + + ww = (mat[0][m].spec_wl_long - mat[0][m].spec_wl_short) + * ((double)i/(mat[0][m].spec_n-1.0)) + mat[0][m].spec_wl_short; + + xx[i] = ww; + y1[i] = mat[0][m].spec[i]; /* 'A' Black = Input Measurement */ + y2[i] = con->media.spec[i] * 100.0; /* Red = Estimated media */ + y3[i] = mat[1][m].spec[i]; /* Green = Target Measurement with UV */ + } +// do_plot(xx,y1,y2,y3,i); + do_plot_x(xx,y1,y2,y3,i, + 1, 370.0, 740.0, 0.0, 120.0, 2.0); + con->del(con); + } + return 0; +} + + + + + + + + diff --git a/xicc/tiffgamut.c b/xicc/tiffgamut.c new file mode 100644 index 0000000..e3e34e8 --- /dev/null +++ b/xicc/tiffgamut.c @@ -0,0 +1,1300 @@ + +/* + * Create a visualisation of the gamut of a TIFF or JPEG file. + * + * Author: Graeme W. Gill + * Date: 00/9/20 + * Version: 1.00 + * + * Copyright 2000, 2002, 2012 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* + * TTBD: + * + * Should record abs/relative intent, PCS space used together + * with viewing conditions, so that a mismatch within + * icclink can be detected or allowed for. + * + * Need to cope with profile not having black point. + * + * How to cope with no profile, therefore no white or black point ? + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <setjmp.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "tiffio.h" +#include "jpeglib.h" +#include "iccjpeg.h" +#include "icc.h" +#include "gamut.h" +#include "xicc.h" +#include "sort.h" + +#undef NOCAMGAM_CLIP /* No clip to CAM gamut before CAM lookup */ +#undef DEBUG /* Dump filter cell contents */ + +#if !defined(O_CREAT) && !defined(_O_CREAT) +# error "Need to #include fcntl.h!" +#endif + +void set_fminmax(double min[3], double max[3]); +void reset_filter(); +void add_fpixel(double val[3]); +void flush_filter(int verb, gamut *gam, double filtperc); +void del_filter(); + +void usage(void) { + int i; + fprintf(stderr,"Create VRML image of the gamut surface of a TIFF or JPEG, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: tiffgamut [-v level] [profile.icm | embedded.tif/jpg] infile1.tif/jpg [infile2.tif/jpg ...] \n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -d sres Surface resolution details 1.0 - 50.0\n"); + fprintf(stderr," -w emit VRML .wrl file as well as CGATS .gam file\n"); + fprintf(stderr," -n Don't add VRML axes or white/black point\n"); + fprintf(stderr," -k Add markers for prim. & sec. \"cusp\" points\n"); + fprintf(stderr," -f perc Filter by popularity, perc = percent to use\n"); + fprintf(stderr," -i intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute (default), d = profile default\n"); +// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n"); + fprintf(stderr," -p oride l = Lab_PCS (default), j = %s Appearance Jab\n",icxcam_description(cam_default)); + fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n"); + fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n"); + fprintf(stderr," -c viewcond set appearance mode and viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or a parameter:value changes\n"); + for (i = 0; ; i++) { + icxViewCond vc; + if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999) + break; + + fprintf(stderr," %s\n",vc.desc); + } + fprintf(stderr," s:surround n = auto, a = average, m = dim, d = dark,\n"); + fprintf(stderr," c = transparency (default average)\n"); + fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white)\n"); + fprintf(stderr," w:x:y Adapted white point as x, y\n"); + fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n"); + fprintf(stderr," b:background Background %% of image luminance (default 20)\n"); + fprintf(stderr," l:scenewhite Scene white in cd.m^2 if surround = auto (default 250)\n"); + fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n"); + fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white)\n"); + fprintf(stderr," f:x:y Flare color as x, y\n"); + fprintf(stderr," -O outputfile Override the default output filename.\n"); + exit(1); +} + +#define GAMRES 10.0 /* Default surface resolution */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Conversion functions from direct binary 0..n^2-1 == 0.0 .. 1.0 range */ +/* to ICC luo input range. */ +/* It is assumed that the binary has been sign corrected to be */ +/* contiguous (ie CIELab). */ + +/* TIFF 8 bit CIELAB to standard L*a*b* */ +/* Assume that a & b have been converted from signed to offset */ +static void cvt_CIELAB8_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = in[1] * 255.0 - 128.0; + out[2] = in[2] * 255.0 - 128.0; +} + +/* TIFF 16 bit CIELAB to standard L*a*b* */ +/* Assume that a & b have been converted from signed to offset */ +static void cvt_CIELAB16_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = (in[1] - 32768.0/65535.0) * 256.0; + out[2] = (in[2] - 32768.0/65535.0) * 256.0; +} + +/* TIFF 8 bit ICCLAB to standard L*a*b* */ +static void cvt_ICCLAB8_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = (in[1] * 255.0) - 128.0; + out[2] = (in[2] * 255.0) - 128.0; +} + +/* TIFF 16 bit ICCLAB to standard L*a*b* */ +static void cvt_ICCLAB16_to_Lab(double *out, double *in) { + out[0] = in[0] * (100.0 * 65535.0)/65280.0; + out[1] = (in[1] * (255.0 * 65535.0)/65280) - 128.0; + out[2] = (in[2] * (255.0 * 65535.0)/65280) - 128.0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Convert a TIFF Photometric tag to an ICC colorspace. */ +/* return 0 if not possible or applicable. */ +icColorSpaceSignature +TiffPhotometric2ColorSpaceSignature( +void (**icvt)(double *out, double *in), /* Return read conversion function, NULL if none */ +int *smsk, /* Return signed handling mask, 0x0 if none */ +int pmtc, /* Input TIFF photometric */ +int bps, /* Input Bits per sample */ +int spp, /* Input Samples per pixel */ +int extra /* Extra Samples per pixel, if any */ +) { + if (icvt != NULL) + *icvt = NULL; /* Default return values */ + if (smsk != NULL) + *smsk = 0x0; + + switch (pmtc) { + case PHOTOMETRIC_MINISWHITE: /* Subtractive Gray */ + return icSigGrayData; + + case PHOTOMETRIC_MINISBLACK: /* Additive Gray */ + return icSigGrayData; + + case PHOTOMETRIC_RGB: + return icSigRgbData; + + case PHOTOMETRIC_PALETTE: + return 0x0; + + case PHOTOMETRIC_MASK: + return 0x0; + + case PHOTOMETRIC_SEPARATED: + /* Should look at the colorant names to figure out if this is CMY, CMYK */ + /* Should at least return both Cmy/3 or Cmyk/4 ! */ + switch(spp) { + case 2: + return icSig2colorData; + case 3: +// return icSig3colorData; + return icSigCmyData; + case 4: +// return icSig4colorData; + return icSigCmykData; + case 5: + return icSig5colorData; + case 6: + return icSig6colorData; + case 7: + return icSig7colorData; + case 8: + return icSig8colorData; + case 9: + return icSig9colorData; + case 10: + return icSig10colorData; + case 11: + return icSig11colorData; + case 12: + return icSig12colorData; + case 13: + return icSig13colorData; + case 14: + return icSig14colorData; + case 15: + return icSig15colorData; + } + + case PHOTOMETRIC_YCBCR: + return icSigYCbCrData; + + case PHOTOMETRIC_CIELAB: + if (bps == 8) { + if (icvt != NULL) + *icvt = cvt_CIELAB8_to_Lab; + } else { + if (icvt != NULL) + *icvt = cvt_CIELAB16_to_Lab; + } + *smsk = 0x6; /* Treat a & b as signed */ + return icSigLabData; + + case PHOTOMETRIC_ICCLAB: + if (bps == 8) { + if (icvt != NULL) + *icvt = cvt_ICCLAB8_to_Lab; + } else { + if (icvt != NULL) + *icvt = cvt_ICCLAB16_to_Lab; + } + return icSigLabData; + + case PHOTOMETRIC_ITULAB: + return 0x0; /* Could add this with a conversion function */ + /* but have to allow for variable ITU gamut */ + /* (Tag 433, "Decode") */ + + case PHOTOMETRIC_LOGL: + return 0x0; /* Could add this with a conversion function */ + + case PHOTOMETRIC_LOGLUV: + return 0x0; /* Could add this with a conversion function */ + } + return 0x0; +} + + +char * +Photometric2str( +int pmtc +) { + static char buf[80]; + switch (pmtc) { + case PHOTOMETRIC_MINISWHITE: + return "Subtractive Gray"; + case PHOTOMETRIC_MINISBLACK: + return "Additive Gray"; + case PHOTOMETRIC_RGB: + return "RGB"; + case PHOTOMETRIC_PALETTE: + return "Indexed"; + case PHOTOMETRIC_MASK: + return "Transparency Mask"; + case PHOTOMETRIC_SEPARATED: + return "Separated"; + case PHOTOMETRIC_YCBCR: + return "YCbCr"; + case PHOTOMETRIC_CIELAB: + return "CIELab"; + case PHOTOMETRIC_ICCLAB: + return "ICCLab"; + case PHOTOMETRIC_ITULAB: + return "ITULab"; + case PHOTOMETRIC_LOGL: + return "CIELog2L"; + case PHOTOMETRIC_LOGLUV: + return "CIELog2Luv"; + } + sprintf(buf,"Unknown Photometric Tag %d",pmtc); + return buf; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* JPEG error information */ +typedef struct { + jmp_buf env; /* setjmp/longjmp environment */ + char message[JMSG_LENGTH_MAX]; +} jpegerrorinfo; + +/* JPEG error handler */ +static void jpeg_error(j_common_ptr cinfo) { + jpegerrorinfo *p = (jpegerrorinfo *)cinfo->client_data; + (*cinfo->err->format_message) (cinfo, p->message); + longjmp(p->env, 1); +} + +static char * +JPEG_cspace2str( +J_COLOR_SPACE cspace +) { + static char buf[80]; + switch (cspace) { + case JCS_UNKNOWN: + return "Unknown"; + case JCS_GRAYSCALE: + return "Monochrome"; + case JCS_RGB: + return "RGB"; + case JCS_YCbCr: + return "YCbCr"; + case JCS_CMYK: + return "CMYK"; + case JCS_YCCK: + return "YCCK"; + } + sprintf(buf,"Unknown JPEG colorspace %d",cspace); + return buf; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int ffa, lfa; /* First, last input file argument */ + char prof_name[MAXNAMEL+1] = { '\000' }; /* ICC profile name, "" if none */ + char in_name[MAXNAMEL+1]; /* TIFF input file */ + char *xl = NULL, out_name[MAXNAMEL+4+1] = { '\000' }; /* VRML output file */ + int verb = 0; + int vrml = 0; + int doaxes = 1; + int docusps = 0; + int filter = 0; + double filtperc = 100.0; + int rv = 0; + + icc *icco = NULL; + xicc *xicco = NULL; + icxcam *cam = NULL; /* Separate CAM used for Lab TIFF files */ + icxViewCond vc; /* Viewing Condition for CIECAM */ + int vc_e = -1; /* Enumerated viewing condition */ + int vc_s = -1; /* Surround override */ + double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */ + double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */ + double vc_a = -1.0; /* Adapted luminance */ + double vc_b = -1.0; /* Background % overid */ + double vc_l = -1.0; /* Scene luminance override */ + double vc_f = -1.0; /* Flare % overid */ + double vc_fXYZ[3] = {-1.0, -1.0, -1.0}; /* Flare color override in XYZ */ + double vc_fxy[2] = {-1.0, -1.0}; /* Flare color override in x,y */ + icxLuBase *luo = NULL; /* Generic lookup object */ + icColorSpaceSignature ins = icSigLabData, outs; /* Type of input and output spaces */ + int inn, outn; /* Number of components */ + icmLuAlgType alg; /* Type of lookup algorithm */ + icmLookupFunc func = icmFwd; /* Must be */ + icRenderingIntent intent = -1; /* Default */ + icColorSpaceSignature pcsor = icSigLabData; /* Default */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + + /* TIFF */ + TIFFErrorHandler olderrh, oldwarnh; + TIFFErrorHandlerExt olderrhx, oldwarnhx; + TIFF *rh = NULL; + int x, y, width, height; /* Size of image */ + uint16 samplesperpixel, bitspersample; + uint16 pconfig, photometric, pmtc; + tdata_t *inbuf; + int inbpix; /* Number of pixels in jpeg in buf */ + void (*cvt)(double *out, double *in) = NULL; /* TIFF conversion function, NULL if none */ + icColorSpaceSignature tcs; /* TIFF colorspace */ + uint16 extrasamples; /* Extra "alpha" samples */ + uint16 *extrainfo; /* Info about extra samples */ + int sign_mask; /* Handling of encoding sign */ + + /* JPEG */ + jpegerrorinfo jpeg_rerr; + FILE *rf = NULL; + struct jpeg_decompress_struct rj; + struct jpeg_error_mgr jerr; + int iinv; /* Invert the input */ + + double gamres = GAMRES; /* Surface resolution */ + gamut *gam; + + double apcsmin[3], apcsmax[3]; /* Actual PCS range */ + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + /* Intent */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'd': + intent = icmDefaultIntent; + break; + case 'a': + intent = icAbsoluteColorimetric; + break; + case 'p': + intent = icPerceptual; + break; + case 'r': + intent = icRelativeColorimetric; + break; + case 's': + intent = icSaturation; + break; + /* Argyll special intents to check spaces underlying */ + /* icxPerceptualAppearance & icxSaturationAppearance */ + case 'P': + intent = icmAbsolutePerceptual; + break; + case 'S': + intent = icmAbsoluteSaturation; + break; + default: + usage(); + } + } + + /* Search order */ + else if (argv[fa][1] == 'o') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'n': + case 'N': + order = icmLuOrdNorm; + break; + case 'r': + case 'R': + order = icmLuOrdRev; + break; + default: + usage(); + } + } + + /* PCS override */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'l': + pcsor = icSigLabData; + break; + case 'j': + pcsor = icxSigJabData; + break; + default: + usage(); + } + } + + /* Viewing conditions */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage(); + + /* Switch to Jab automatically */ + pcsor = icxSigJabData; + + /* Set the viewing conditions */ + if (na[1] != ':') { + if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage(); + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage(); + if (na[2] == 'n' || na[2] == 'N') { + vc_s = vc_none; /* Automatic */ + } else if (na[2] == 'a' || na[2] == 'A') { + vc_s = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc_s = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc_s = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc_s = vc_cut_sheet; + } else + usage(); + } else if (na[0] == 'w' || na[0] == 'W') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_wxy[0] = x; vc_wxy[1] = y; + } else + usage(); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage(); + vc_a = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage(); + vc_b = atof(na+2); + } else if (na[0] == 'l' || na[0] == 'L') { + if (na[1] != ':') + usage(); + vc_l = atof(na+2); + } else if (na[0] == 'f' || na[0] == 'F') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_fXYZ[0] = x; vc_fXYZ[1] = y; vc_fXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_fxy[0] = x; vc_fxy[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc_f = x; + } else + usage(); + } else + usage(); + } + + /* VRML output */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + vrml = 1; + } + /* No axis output */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + doaxes = 0; + } + /* Do cusp markers */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + docusps = 1; + } + /* Surface Detail */ + else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage(); + gamres = atof(na); + } + /* Filtering */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage(); + filtperc = atof(na); + if (filtperc < 0.0 || filtperc> 100.0) + usage(); + filter = 1; + } + + /* Output file name */ + else if (argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage(); + strncpy(out_name,na,MAXNAMEL); out_name[MAXNAMEL] = '\000'; + } + + else + usage(); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + if (fa < (argc-1)) + strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000'; + + for (ffa = fa; fa < argc; fa++) + if (fa >= argc || argv[fa][0] == '-') usage(); + lfa = fa-1; + if (verb) + printf("There are %d raster files to process\n",lfa - ffa + 1); + + if (out_name[0] == '\000') { + strncpy(out_name,argv[lfa],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + strcpy(xl,".gam"); + } else { + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + } + + if (verb) + printf("Gamut output files is '%s'\n",out_name); + + if (intent == -1) { + if (pcsor == icxSigJabData) + intent = icRelativeColorimetric; /* Default to icxAppearance */ + else + intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */ + } + + /* - - - - - - - - - - - - - - - - */ + /* If we were provided an ICC profile to use */ + if (prof_name[0] != '\000') { + + /* Open up the profile or TIFF embedded profile for reading */ + if ((icco = read_embedded_icc(prof_name)) == NULL) + error ("Can't open profile in file '%s'",prof_name); + + if (verb) { + icmFile *op; + if ((op = new_icmFileStd_fp(stdout)) == NULL) + error ("Can't open stdout"); + icco->header->dump(icco->header, op, 1); + op->del(op); + } + + /* Check that the profile is appropriate */ + if (icco->header->deviceClass != icSigInputClass + && icco->header->deviceClass != icSigDisplayClass + && icco->header->deviceClass != icSigOutputClass + && icco->header->deviceClass != icSigColorSpaceClass) + error("Profile type isn't device or colorspace"); + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(icco)) == NULL) + error ("Creation of xicc failed"); + + /* Setup the default viewing conditions */ + if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + + if (vc_e != -1) + if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + if (vc_s >= 0) + vc.Ev = vc_s; + if (vc_wXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; + vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; + } + if (vc_wxy[0] >= 0.0) { + double x = vc_wxy[0]; + double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Wxyz[0] = x/y * vc.Wxyz[1]; + vc.Wxyz[2] = z/y * vc.Wxyz[1]; + } + if (vc_a >= 0.0) + vc.La = vc_a; + if (vc_b >= 0.0) + vc.Yb = vc_b/100.0; + if (vc_l >= 0.0) + vc.Lv = vc_l; + if (vc_f >= 0.0) + vc.Yf = vc_f/100.0; + if (vc_fXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; + vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; + } + if (vc_fxy[0] >= 0.0) { + double x = vc_fxy[0]; + double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Fxyz[0] = x/y * vc.Fxyz[1]; + vc.Fxyz[2] = z/y * vc.Fxyz[1]; + } + + /* Get a expanded color conversion object */ + if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST +#ifdef NOCAMGAM_CLIP + | ICX_CAM_NOGAMCLIP +#endif + , func, intent, pcsor, order, &vc, NULL)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL); + + /* Else if the image is in Lab space, and we want Jab */ + } else if (pcsor == icxSigJabData) { + double wp[3]; /* D50 white point */ + + wp[0] = icmD50.X; + wp[1] = icmD50.Y; + wp[2] = icmD50.Z; + + /* Setup the default viewing conditions */ + if (xicc_enum_viewcond(NULL, &vc, -1, NULL, 0, wp) == -999) + error("Failed to locate a default viewing condition"); + + if (vc_e != -1) + if (xicc_enum_viewcond(NULL, &vc, vc_e, NULL, 0, wp) == -999) + error("Failed to enumerate viewing condition %d",vc_e); + if (vc_s >= 0) + vc.Ev = vc_s; + if (vc_wXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; + vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; + } + if (vc_wxy[0] >= 0.0) { + double x = vc_wxy[0]; + double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Wxyz[0] = x/y * vc.Wxyz[1]; + vc.Wxyz[2] = z/y * vc.Wxyz[1]; + } + if (vc_a >= 0.0) + vc.La = vc_a; + if (vc_b >= 0.0) + vc.Yb = vc_b/100.0; + if (vc_f >= 0.0) + vc.Yf = vc_f/100.0; + if (vc_fXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; + vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; + } + if (vc_fxy[0] >= 0.0) { + double x = vc_fxy[0]; + double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Fxyz[0] = x/y * vc.Fxyz[1]; + vc.Fxyz[2] = z/y * vc.Fxyz[1]; + } + + if ((cam = new_icxcam(cam_default)) == NULL) + error("new_icxcam failed"); + + cam->set_view(cam, vc.Ev, vc.Wxyz, vc.La, vc.Yb, vc.Lv, vc.Yf, vc.Fxyz, + XICC_USE_HK); + } + + /* Establish the PCS range if we are filtering */ + if (filter) { + double pcsmin[3], pcsmax[3]; /* PCS range for filter stats array */ + + if (luo) { + gamut *csgam; + + if ((csgam = luo->get_gamut(luo, 20.0)) == NULL) + error("Getting the gamut of the source colorspace failed"); + + csgam->getrange(csgam, pcsmin, pcsmax); + csgam->del(csgam); + } else { + pcsmin[0] = -10.0; + pcsmax[0] = 110.0; + pcsmin[1] = -138.0; + pcsmax[1] = 138.0; + pcsmin[2] = -138.0; + pcsmax[2] = 138.0; + +#ifdef NEVER /* Does this make any sense ?? */ + if (cam) { + icmLab2XYZ(&icmD50, pcsmin, pcsmin); + cam->XYZ_to_cam(cam, pcsmin, pcsmin); + + icmLab2XYZ(&icmD50, pcsmax, pcsmax); + cam->XYZ_to_cam(cam, pcsmax, pcsmax); + } +#endif + } + + if (verb) + printf("PCS range = %f..%f, %f..%f. %f..%f\n\n", pcsmin[0], pcsmax[0], pcsmin[1], pcsmax[1], pcsmin[2], pcsmax[2]); + + /* Allocate and initialize the filter */ + set_fminmax(pcsmin, pcsmax); + } + + /* - - - - - - - - - - - - - - - */ + /* Creat a raster gamut surface */ + gam = new_gamut(gamres, pcsor == icxSigJabData, 1); + + apcsmin[0] = apcsmin[1] = apcsmin[2] = 1e6; + apcsmax[0] = apcsmax[1] = apcsmax[2] = -1e6; + + /* Process all the tiff files */ + for (fa = ffa; fa <= lfa; fa++) { + + /* - - - - - - - - - - - - - - - */ + /* Open up input tiff/jpeg file ready for reading */ + /* Got arguments, so setup to process the file */ + strncpy(in_name,argv[fa],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + olderrh = TIFFSetErrorHandler(NULL); + oldwarnh = TIFFSetWarningHandler(NULL); + olderrhx = TIFFSetErrorHandlerExt(NULL); + oldwarnhx = TIFFSetWarningHandlerExt(NULL); + + if ((rh = TIFFOpen(in_name, "r")) != NULL) { + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + + TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height); + + TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); + TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample); + if (bitspersample != 8 && bitspersample != 16) + error("TIFF Input file must be 8 or 16 bits/channel"); + + TIFFGetFieldDefaulted(rh, TIFFTAG_EXTRASAMPLES, &extrasamples, &extrainfo); + TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric); + + if (luo != NULL) { + if (inn != (samplesperpixel-extrasamples)) + error("TIFF Input file has %d input chanels and is mismatched to colorspace '%s'", + samplesperpixel, icm2str(icmColorSpaceSignature, ins)); + } + + if ((tcs = TiffPhotometric2ColorSpaceSignature(&cvt, &sign_mask, photometric, + bitspersample, samplesperpixel, extrasamples)) == 0) + error("Can't handle TIFF file photometric %s", Photometric2str(photometric)); + + if (tcs != ins) { + if (luo != NULL) + error("TIFF photometric '%s' doesn't match ICC input colorspace '%s' !", + Photometric2str(photometric), icm2str(icmColorSpaceSignature,ins)); + else + error("No profile provided and TIFF photometric '%s' isn't Lab !", + Photometric2str(photometric)); + } + + TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig); + if (pconfig != PLANARCONFIG_CONTIG) + error("TIFF Input file must be planar"); + + if (verb) { + printf("Input TIFF file '%s'\n",in_name); + printf("TIFF file photometric is %s\n",Photometric2str(photometric)); + printf("TIFF file colorspace is %s\n",icm2str(icmColorSpaceSignature,tcs)); + printf("File size %d x %d pixels\n",width,height); + printf("\n"); + } + + } else { + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + + /* We cope with the horrible ijg jpeg library error handling */ + /* by using a setjmp/longjmp. */ + jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error; + if (setjmp(jpeg_rerr.env)) { + jpeg_destroy_decompress(&rj); + fclose(rf); + error("Unable to read JPEG file '%s'\n",in_name); + } + + rj.err = &jerr; + rj.client_data = &jpeg_rerr; + jpeg_create_decompress(&rj); + +#if defined(O_BINARY) || defined(_O_BINARY) + if ((rf = fopen(in_name,"rb")) == NULL) +#else + if ((rf = fopen(in_name,"r")) == NULL) +#endif + { + jpeg_destroy_decompress(&rj); + error("Unable to open input file '%s'\n",in_name); + } + + jpeg_stdio_src(&rj, rf); + jpeg_read_header(&rj, TRUE); /* we'll longjmp on error */ + + bitspersample = rj.data_precision; + if (bitspersample != 8 && bitspersample != 16) { + error("JPEG Input file must be 8 or 16 bit/channel"); + } + + /* No extra samples */ + extrasamples = 0; + iinv = 0; + + switch (rj.jpeg_color_space) { + case JCS_GRAYSCALE: + rj.out_color_space = JCS_GRAYSCALE; + tcs = icSigGrayData; + samplesperpixel = 1; + break; + + case JCS_YCbCr: /* get libjpg to convert to RGB */ + rj.out_color_space = JCS_RGB; + tcs = icSigRgbData; + samplesperpixel = 3; + break; + + case JCS_RGB: + rj.out_color_space = JCS_RGB; + tcs = icSigRgbData; + samplesperpixel = 3; + break; + + case JCS_YCCK: /* libjpg to convert to CMYK */ + rj.out_color_space = JCS_CMYK; + tcs = icSigCmykData; + samplesperpixel = 4; + if (rj.saw_Adobe_marker) + iinv = 1; + break; + + case JCS_CMYK: + rj.out_color_space = JCS_CMYK; + tcs = icSigCmykData; + samplesperpixel = 4; + if (rj.saw_Adobe_marker) /* Adobe inverts CMYK */ + iinv = 1; + break; + + default: + error("Can't handle JPEG file '%s' colorspace 0x%x", in_name, rj.jpeg_color_space); + } + + if (luo != NULL) { + if (inn != samplesperpixel) + error ("JPEG Input file has %d input chanels and is mismatched to colorspace '%s'", + samplesperpixel, icm2str(icmColorSpaceSignature, ins)); + } + + if (tcs != ins) { + if (luo != NULL) + error("JPEG colorspace '%s' doesn't match ICC input colorspace '%s' !", + icm2str(icmColorSpaceSignature, tcs), icm2str(icmColorSpaceSignature,ins)); + else + error("No profile provided and JPEG colorspace '%s' isn't Lab !", + icm2str(icmColorSpaceSignature, tcs)); + } + jpeg_calc_output_dimensions(&rj); + width = rj.output_width; + height = rj.output_height; + + if (verb) { + printf("Input JPEG file '%s'\n",in_name); + printf("JPEG file original colorspace is %s\n",JPEG_cspace2str(rj.jpeg_color_space)); + printf("JPEG file colorspace is %s\n",icm2str(icmColorSpaceSignature,tcs)); + printf("File size %d x %d pixels\n",width,height); + printf("\n"); + } + jpeg_start_decompress(&rj); + } + + /* - - - - - - - - - - - - - - - */ + /* Process colors to translate */ + /* (Should fix this to process a group of lines at a time ?) */ + + if (rh != NULL) + inbuf = _TIFFmalloc(TIFFScanlineSize(rh)); + else { + inbpix = rj.output_width * rj.num_components; + if ((inbuf = (tdata_t *)malloc(inbpix)) == NULL) + error("Malloc failed on input line buffer"); + + if (setjmp(jpeg_rerr.env)) { + /* Something went wrong with reading the file */ + jpeg_destroy_decompress(&rj); + error("failed to read JPEG line of file '%s' [%s]",in_name, jpeg_rerr.message); + } + } + + for (y = 0; y < height; y++) { + + /* Read in the next line */ + if (rh) { + if (TIFFReadScanline(rh, inbuf, y, 0) < 0) + error ("Failed to read TIFF line %d",y); + } else { + jpeg_read_scanlines(&rj, (JSAMPARRAY)&inbuf, 1); + if (iinv) { + unsigned char *cp, *ep = (unsigned char *)inbuf + inbpix; + for (cp = (unsigned char *)inbuf; cp < ep; cp++) + *cp = ~*cp; + } + } + + /* Do floating point conversion */ + for (x = 0; x < width; x++) { + int i; + double in[MAX_CHAN], out[MAX_CHAN]; + + if (bitspersample == 8) { + for (i = 0; i < samplesperpixel; i++) { + int v = ((unsigned char *)inbuf)[x * samplesperpixel + i]; + if (sign_mask & (1 << i)) /* Treat input as signed */ + v = (v & 0x80) ? v - 0x80 : v + 0x80; + in[i] = v/255.0; + } + } else { + for (i = 0; i < samplesperpixel; i++) { + int v = ((unsigned short *)inbuf)[x * samplesperpixel + i]; + if (sign_mask & (1 << i)) /* Treat input as signed */ + v = (v & 0x8000) ? v - 0x8000 : v + 0x8000; + in[i] = v/65535.0; + } + } + if (cvt != NULL) { /* Undo TIFF encoding */ + cvt(in, in); + } + /* ICC profile to convert RGB to Lab or Jab */ + if (luo != NULL) { +//printf("~1 RGB in value = %f %f %f\n",in[0],in[1],in[2]); + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",icco->errc,icco->err); +//printf("~1 after luo = %f %f %f\n",out[0],out[1],out[2]); + + if (outs == icSigXYZData) { /* Convert to Lab */ + icmXYZ2Lab(&icco->header->illuminant, out, out); +//printf("~1 after XYZ2Lab = %f %f %f\n",out[0],out[1],out[2]); + } + /* Lab TIFF - may need to convert to Jab */ + } else if (cam != NULL) { +//printf("~1 Lab in value = %f %f %f\n",in[0],in[1],in[2]); + icmLab2XYZ(&icmD50, out, in); +//printf("~1 XYZ = %f %f %f\n",out[0],out[1],out[2]); + cam->XYZ_to_cam(cam, out, out); +//printf("~1 Jab = %f %f %f\n",out[0],out[1],out[2]); + + } else { +//printf("~1 Lab value = %f %f %f\n",in[0],in[1],in[2]); + for (i = 0; i < samplesperpixel; i++) + out[i] = in[i]; + } + + for (i = 0; i < 3; i++) { + if (out[i] < apcsmin[i]) + apcsmin[i] = out[i]; + if (out[i] > apcsmax[i]) + apcsmax[i] = out[i]; + } + if (filter) + add_fpixel(out); + else + gam->expand(gam, out); + } + } + + /* Release buffers and close files */ + if (rh != NULL) { + if (inbuf != NULL) + _TIFFfree(inbuf); + TIFFClose(rh); /* Close Input file */ + } else { + jpeg_finish_decompress(&rj); + jpeg_destroy_decompress(&rj); + if (inbuf != NULL) + free(inbuf); + if (fclose(rf)) + error("Error closing JPEG input file '%s'\n",in_name); + } + + /* If filtering, flush filtered points to the gamut */ + if (filter) { + flush_filter(verb, gam, filtperc); + } + } + + if (verb) + printf("Actual PCS range = %f..%f, %f..%f. %f..%f\n\n", apcsmin[0], apcsmax[0], apcsmin[1], apcsmax[1], apcsmin[2], apcsmax[2]); + + if (filter) + del_filter(); + + /* Get White and Black points from the profile, and set them in the gamut. */ + if (luo != NULL) { + double wp[3], bp[3], kp[3]; + + luo->efv_wh_bk_points(luo, wp, bp,kp); + gam->setwb(gam, wp, bp, kp); + } else { + double wp[3] = { 100.0, 0.0, 0.0 }; + double bp[3] = { 0.0, 0.0, 0.0 }; + gam->setwb(gam, wp, bp, bp); + } + + /* Done with lookup object */ + if (luo != NULL) { + luo->del(luo); + xicco->del(xicco); /* Expansion wrapper */ + icco->del(icco); /* Icc */ + } + if (cam != NULL) { + cam->del(cam); + } + + if (verb) + printf("Output Gamut file '%s'\n",out_name); + + /* Create the VRML file */ + if (gam->write_gam(gam,out_name)) + error ("write gamut failed on '%s'",out_name); + + if (vrml) { + + strcpy(xl,".wrl"); + printf("Output vrml file '%s'\n",out_name); + if (gam->write_vrml(gam,out_name, doaxes, docusps)) + error ("write vrml failed on '%s'",out_name); + } + + if (verb) { + printf("Total volume of gamut is %f cubic colorspace units\n",gam->volume(gam)); + } + gam->del(gam); + + return 0; +} + + +/* ============================================================================= */ +/* A pixel value filter module. We quantize the pixel values and keep statistics */ +/* on them, so as to filter out low frequency colors. */ + +#define FILTBITS 6 /* Total = 2 ^ (3 * FILTBITS) entries = 33Mbytes*/ +#define FILTSIZE (1 << FILTBITS) + +/* A filtered cell entry */ +typedef struct { + int count; /* Count of pixels that fall in this cell */ + float pcs[3]; /* Most extreme PCS value that fell in this cell */ +} fent; + +struct _ffilter { + double min[3], max[3]; /* PCS range */ + + fent cells[FILTSIZE][FILTSIZE][FILTSIZE]; /* Quantized pixels stats */ + fent *scells[FILTSIZE * FILTSIZE * FILTSIZE]; /* Sorted order */ + +}; typedef struct _ffilter ffilter; + + + +/* Use a global object */ +ffilter *ff = NULL; + +/* Set the min and max values and init the filter */ +void set_fminmax(double min[3], double max[3]) { + + if (ff == NULL) { + if ((ff = (ffilter *) calloc(1,sizeof(ffilter))) == NULL) + error("ffilter: calloc failed"); + } + + ff->min[0] = min[0]; + ff->min[1] = min[1]; + ff->min[2] = min[2]; + ff->max[0] = max[0]; + ff->max[1] = max[1]; + ff->max[2] = max[2]; +} + +/* Add another pixel to the filter */ +void add_fpixel(double val[3]) { + int j; + int qv[3]; + fent *fe; + double cent[3] = { 50.0, 0.0, 0.0 }; /* Assumed center */ + double tt, cdist, ndist; + + if (ff == NULL) + error("ffilter not initialized"); + + /* Quantize the values */ + for (j = 0; j < 3; j++) { + double vv; + + vv = (val[j] - ff->min[j])/(ff->max[j] - ff->min[j]); + qv[j] = (int)(vv * (FILTSIZE - 1) + 0.5); + if (qv[j] < 0) + qv[j] = 0; + else if (qv[j] >= FILTSIZE) + qv[j] = FILTSIZE-1; + } + +//printf("~1 color %f %f %f -> Cell %d %d %d\n", val[0], val[1], val[2], qv[0], qv[1], qv[2]); + + /* Find the appropriate cell */ + fe = &ff->cells[qv[0]][qv[1]][qv[2]]; + + /* See if this is the new most distant value in this cell */ + for (cdist = ndist = 0.0, j = 0; j < 3; j++) { + tt = fe->pcs[j] - cent[j]; + cdist += tt * tt; + tt = val[j] - cent[j]; + ndist += tt * tt; + } + if (fe->count == 0 || ndist > cdist) { + fe->pcs[0] = val[0]; + fe->pcs[1] = val[1]; + fe->pcs[2] = val[2]; +//printf("Updated pcs to %f %f %f\n", val[0],val[1],val[2]); + } + fe->count++; +//printf("Cell count = %d\n",fe->count); +} + +/* Flush the filter contents to the gamut, and then reset it */ +void flush_filter(int verb, gamut *gam, double filtperc) { + int i, j; + int totcells = FILTSIZE * FILTSIZE * FILTSIZE; + int used, hasone; + double cuml, avgcnt; + + if (ff == NULL) + error("ffilter not initialized"); + + /* Sort the cells by popularity from most to least */ + for (used = hasone = avgcnt = i = 0; i < totcells; i++) { + ff->scells[i] = (fent *)ff->cells + i; + if (ff->scells[i]->count > 0) { + used++; + if (ff->scells[i]->count == 1) + hasone++; + avgcnt += ff->scells[i]->count; + } + } + avgcnt /= used; + +#define HEAP_COMPARE(A,B) A->count > B->count + HEAPSORT(fent *,ff->scells, totcells) + +#ifdef DEBUG + for (i = 0; i < totcells; i++) { + if (ff->scells[i]->count > 0) { + printf("Cell %d at %f %f %f count %d\n", + i, + ff->scells[i]->pcs[0], + ff->scells[i]->pcs[1], + ff->scells[i]->pcs[2], + ff->scells[i]->count); + } + } +#endif + if (verb) { + printf("Total of %d cells out of %d were hit (%.1f%%)\n",used,totcells,used * 100.0/totcells); + printf("%.1f%% have a count of 1\n",hasone * 100.0/used); + printf("Average cell count = %f\n",avgcnt); + printf("\n"); + } + + /* Add the populated cells in order */ + filtperc /= 100.0; + for (cuml = 0.0, i = j = 0; cuml < filtperc && i < totcells; i++) { + + if (ff->scells[i]->count > 0) { + double val[3]; + val[0] = ff->scells[i]->pcs[0]; /* float -> double */ + val[1] = ff->scells[i]->pcs[1]; + val[2] = ff->scells[i]->pcs[2]; +#ifdef DEBUG + printf("Adding %d: %f %f %f count %d to gamut\n", i, val[0], val[1], val[2],ff->scells[i]->count); +#endif + gam->expand(gam, val); + j++; + cuml = j/(used-1.0); + } + + } + + /* Reset it to empty */ + { + double min[3], max[3]; + + for (j = 0; j < 3; j++) { + min[j] = ff->min[j]; + max[j] = ff->max[j]; + } + memset(ff, 0, sizeof(ffilter)); + for (j = 0; j < 3; j++) { + ff->min[j] = min[j]; + ff->max[j] = max[j]; + } + } +} + +/* Free up the filter structure */ +void del_filter() { + + free(ff); +} diff --git a/xicc/tiffgmts.c b/xicc/tiffgmts.c new file mode 100644 index 0000000..9d21d36 --- /dev/null +++ b/xicc/tiffgmts.c @@ -0,0 +1,1187 @@ + +/* + * Create a gamut mapping test set from a TIFF file. + * + * Author: Graeme W. Gill + * Date: 08/10/14 + * Version: 1.00 + * + * Copyright 2000, 2008 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Derived from tiffgamut.c + * + * This program creates a locus of test values that spans the + * volume of colors ocupied by the pixels of the TIFF file. + * The direction that spans the greatest distance is + * turned into a line of source test points, that can + * then be used by gammap to illustrate how the gamut mapping + * alters the locus. + */ + +/* + * TTBD: + * + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "tiffio.h" +#include "icc.h" +#include "gamut.h" +#include "xicc.h" +#include "vrml.h" +#include "sort.h" + +#define DE_SPACE 3 /* Delta E of spacing for output points */ +#undef DEBUG_PLOT + +void usage(void) { + int i; + fprintf(stderr,"Create locus of test points that spans the range of colors a TIFF, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: tiffgmts [-v level] [profile.icm | embedded.tif] infile.tif\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -w emit VRML .wrl file as well as CGATS .ts file\n"); + fprintf(stderr," -n Don't add VRML axes or white/black point\n"); + fprintf(stderr," -i intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute (default), d = profile default\n"); +// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n"); + fprintf(stderr," -p oride l = Lab_PCS (default), j = %s Appearance Jab\n",icxcam_description(cam_default),icxcam_description(cam_default)); + fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n"); + fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n"); + fprintf(stderr," -c viewcond set appearance mode and viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or a parameter:value changes\n"); + for (i = 0; ; i++) { + icxViewCond vc; + if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999) + break; + + fprintf(stderr," %s\n",vc.desc); + } + fprintf(stderr," s:surround a = average, m = dim, d = dark,\n"); + fprintf(stderr," c = transparency (default average)\n"); + fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white)\n"); + fprintf(stderr," w:x:y Adapted white point as x, y\n"); + fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n"); + fprintf(stderr," b:background Background %% of image luminance (default 20)\n"); + fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n"); + fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white)\n"); + fprintf(stderr," f:x:y Flare color as x, y\n"); + fprintf(stderr," -V L,a,b Overide normal vector direction for span\n"); + fprintf(stderr," -O outputfile Override the default output filename (locus.ts)\n"); + fprintf(stderr," infile.tif File to create test value from\n"); + exit(1); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Conversion functions from direct binary 0..n^2-1 == 0.0 .. 1.0 range */ +/* to ICC luo input range. */ +/* It is assumed that the binary has been sign corrected to be */ +/* contiguous (ie CIELab). */ + +/* TIFF 8 bit CIELAB to standard L*a*b* */ +/* Assume that a & b have been converted from signed to offset */ +static void cvt_CIELAB8_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = in[1] * 255.0 - 128.0; + out[2] = in[2] * 255.0 - 128.0; +} + +/* TIFF 16 bit CIELAB to standard L*a*b* */ +/* Assume that a & b have been converted from signed to offset */ +static void cvt_CIELAB16_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = (in[1] - 32768.0/65535.0) * 256.0; + out[2] = (in[2] - 32768.0/65535.0) * 256.0; +} + +/* TIFF 8 bit ICCLAB to standard L*a*b* */ +static void cvt_ICCLAB8_to_Lab(double *out, double *in) { + out[0] = in[0] * 100.0; + out[1] = (in[1] * 255.0) - 128.0; + out[2] = (in[2] * 255.0) - 128.0; +} + +/* TIFF 16 bit ICCLAB to standard L*a*b* */ +static void cvt_ICCLAB16_to_Lab(double *out, double *in) { + out[0] = in[0] * (100.0 * 65535.0)/65280.0; + out[1] = (in[1] * (255.0 * 65535.0)/65280) - 128.0; + out[2] = (in[2] * (255.0 * 65535.0)/65280) - 128.0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Convert a TIFF Photometric tag to an ICC colorspace. */ +/* return 0 if not possible or applicable. */ +icColorSpaceSignature +TiffPhotometric2ColorSpaceSignature( +void (**icvt)(double *out, double *in), /* Return read conversion function, NULL if none */ +int *smsk, /* Return signed handling mask, 0x0 if none */ +int pmtc, /* Input TIFF photometric */ +int bps, /* Input Bits per sample */ +int spp, /* Input Samples per pixel */ +int extra /* Extra Samples per pixel, if any */ +) { + if (icvt != NULL) + *icvt = NULL; /* Default return values */ + if (smsk != NULL) + *smsk = 0x0; + + switch (pmtc) { + case PHOTOMETRIC_MINISWHITE: /* Subtractive Gray */ + return icSigGrayData; + + case PHOTOMETRIC_MINISBLACK: /* Additive Gray */ + return icSigGrayData; + + case PHOTOMETRIC_RGB: + return icSigRgbData; + + case PHOTOMETRIC_PALETTE: + return 0x0; + + case PHOTOMETRIC_MASK: + return 0x0; + + case PHOTOMETRIC_SEPARATED: + /* Should look at the colorant names to figure out if this is CMY, CMYK */ + /* Should at least return both Cmy/3 or Cmyk/4 ! */ + switch(spp) { + case 2: + return icSig2colorData; + case 3: +// return icSig3colorData; + return icSigCmyData; + case 4: +// return icSig4colorData; + return icSigCmykData; + case 5: + return icSig5colorData; + case 6: + return icSig6colorData; + case 7: + return icSig7colorData; + case 8: + return icSig8colorData; + case 9: + return icSig9colorData; + case 10: + return icSig10colorData; + case 11: + return icSig11colorData; + case 12: + return icSig12colorData; + case 13: + return icSig13colorData; + case 14: + return icSig14colorData; + case 15: + return icSig15colorData; + } + + case PHOTOMETRIC_YCBCR: + return icSigYCbCrData; + + case PHOTOMETRIC_CIELAB: + if (bps == 8) { + if (icvt != NULL) + *icvt = cvt_CIELAB8_to_Lab; + } else { + if (icvt != NULL) + *icvt = cvt_CIELAB16_to_Lab; + } + *smsk = 0x6; /* Treat a & b as signed */ + return icSigLabData; + + case PHOTOMETRIC_ICCLAB: + if (bps == 8) { + if (icvt != NULL) + *icvt = cvt_ICCLAB8_to_Lab; + } else { + if (icvt != NULL) + *icvt = cvt_ICCLAB16_to_Lab; + } + return icSigLabData; + + case PHOTOMETRIC_ITULAB: + return 0x0; /* Could add this with a conversion function */ + /* but have to allow for variable ITU gamut */ + /* (Tag 433, "Decode") */ + + case PHOTOMETRIC_LOGL: + return 0x0; /* Could add this with a conversion function */ + + case PHOTOMETRIC_LOGLUV: + return 0x0; /* Could add this with a conversion function */ + } + return 0x0; +} + + +char * +Photometric2str( +int pmtc +) { + static char buf[80]; + switch (pmtc) { + case PHOTOMETRIC_MINISWHITE: + return "Subtractive Gray"; + case PHOTOMETRIC_MINISBLACK: + return "Additive Gray"; + case PHOTOMETRIC_RGB: + return "RGB"; + case PHOTOMETRIC_PALETTE: + return "Indexed"; + case PHOTOMETRIC_MASK: + return "Transparency Mask"; + case PHOTOMETRIC_SEPARATED: + return "Separated"; + case PHOTOMETRIC_YCBCR: + return "YCbCr"; + case PHOTOMETRIC_CIELAB: + return "CIELab"; + case PHOTOMETRIC_ICCLAB: + return "ICCLab"; + case PHOTOMETRIC_ITULAB: + return "ITULab"; + case PHOTOMETRIC_LOGL: + return "CIELog2L"; + case PHOTOMETRIC_LOGLUV: + return "CIELog2Luv"; + } + sprintf(buf,"Unknown Photometric Tag %d",pmtc); + return buf; +} + +void set_fminmax(double min[3], double max[3]); +void reset_filter(); +void add_fpixel(double val[3]); +int flush_filter(int verb, double filtperc); +void get_filter(co *inp); +void del_filter(); + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char prof_name[MAXNAMEL+1] = { '\000' }; /* ICC profile name, "" if none */ + char in_name[MAXNAMEL+1]; /* TIFF input file */ + char *xl = NULL, out_name[MAXNAMEL+4+1] = "locus.ts"; /* locus output file */ + int verb = 0; + int dovrml = 0; + int doaxes = 1; + int usevec = 0; + double vec[3]; + int rv = 0; + + icc *icco = NULL; + xicc *xicco = NULL; + icxViewCond vc; /* Viewing Condition for CIECAM */ + int vc_e = -1; /* Enumerated viewing condition */ + int vc_s = -1; /* Surround override */ + double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */ + double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */ + double vc_a = -1.0; /* Adapted luminance */ + double vc_b = -1.0; /* Background % overid */ + double vc_f = -1.0; /* Flare % overid */ + double vc_fXYZ[3] = {-1.0, -1.0, -1.0}; /* Flare color override in XYZ */ + double vc_fxy[2] = {-1.0, -1.0}; /* Flare color override in x,y */ + icxLuBase *luo = NULL; /* Generic lookup object */ + icColorSpaceSignature ins = icSigLabData, outs; /* Type of input and output spaces */ + int inn, outn; /* Number of components */ + icmLuAlgType alg; /* Type of lookup algorithm */ + icmLookupFunc func = icmFwd; /* Must be */ + icRenderingIntent intent = -1; /* Default */ + icColorSpaceSignature pcsor = icSigLabData; /* Default */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + + TIFF *rh = NULL; + int x, y, width, height; /* Size of image */ + uint16 samplesperpixel, bitspersample; + uint16 pconfig, photometric, pmtc; + uint16 resunits; + float resx, resy; + tdata_t *inbuf; + void (*cvt)(double *out, double *in); /* TIFF conversion function, NULL if none */ + icColorSpaceSignature tcs; /* TIFF colorspace */ + uint16 extrasamples; /* Extra "alpha" samples */ + uint16 *extrainfo; /* Info about extra samples */ + int sign_mask; /* Handling of encoding sign */ + + int i, j; + int nipoints = 0; /* Number of raster sample points */ + co *inp = NULL; /* Input point values */ + double tdel = 0.0; /* Total delta along locus */ + rspl *rr = NULL; + int nopoints = 0; /* Number of raster sample points */ + co *outp = NULL; + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(); + + /* Verbosity */ + else if (argv[fa][1] == 'v') { + verb = 1; + } + + /* Intent */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'd': + intent = icmDefaultIntent; + break; + case 'a': + intent = icAbsoluteColorimetric; + break; + case 'p': + intent = icPerceptual; + break; + case 'r': + intent = icRelativeColorimetric; + break; + case 's': + intent = icSaturation; + break; + /* Argyll special intents to check spaces underlying */ + /* icxPerceptualAppearance & icxSaturationAppearance */ + case 'P': + intent = icmAbsolutePerceptual; + break; + case 'S': + intent = icmAbsoluteSaturation; + break; + default: + usage(); + } + } + + /* Search order */ + else if (argv[fa][1] == 'o') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'n': + case 'N': + order = icmLuOrdNorm; + break; + case 'r': + case 'R': + order = icmLuOrdRev; + break; + default: + usage(); + } + } + + /* PCS override */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + fa = nfa; + if (na == NULL) usage(); + switch (na[0]) { + case 'l': + pcsor = icSigLabData; + break; + case 'j': + pcsor = icxSigJabData; + break; + default: + usage(); + } + } + + /* Viewing conditions */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage(); + + /* Switch to Jab automatically */ + pcsor = icxSigJabData; + + /* Set the viewing conditions */ + if (na[1] != ':') { + if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage(); + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage(); + if (na[2] == 'a' || na[2] == 'A') { + vc_s = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc_s = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc_s = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc_s = vc_cut_sheet; + } else + usage(); + } else if (na[0] == 'w' || na[0] == 'W') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_wxy[0] = x; vc_wxy[1] = y; + } else + usage(); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage(); + vc_a = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage(); + vc_b = atof(na+2); + } else if (na[0] == 'f' || na[0] == 'F') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_fXYZ[0] = x; vc_fXYZ[1] = y; vc_fXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_fxy[0] = x; vc_fxy[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc_f = x; + } else + usage(); + } else + usage(); + } + + /* VRML output */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + dovrml = 1; + } + /* No axis output */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + doaxes = 0; + } + /* Vector direction for span */ + else if (argv[fa][1] == 'V') { + usevec = 1; + if (na == NULL) usage(); + fa = nfa; + if (sscanf(na, " %lf , %lf , %lf ",&vec[0], &vec[1], &vec[2]) != 3) + usage(); + } + /* Output file name */ + else if (argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage(); + strncpy(out_name,na,MAXNAMEL); out_name[MAXNAMEL] = '\000'; + } + + else + usage(); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + if (fa < (argc-1)) + strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage(); + strncpy(in_name,argv[fa],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + + if (verb) { + printf("Profile = '%s'\n",prof_name); + printf("Input TIFF = '%s'\n",in_name); + printf("Output file = '%s'\n",out_name); + } + + if (intent == -1) { + if (pcsor == icxSigJabData) + intent = icRelativeColorimetric; /* Default to icxAppearance */ + else + intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */ + } + + /* - - - - - - - - - - - - - - - - */ + /* If we were provided an ICC profile to use */ + if (prof_name[0] != '\000') { + + /* Open up the profile or TIFF embedded profile for reading */ + if ((icco = read_embedded_icc(prof_name)) == NULL) + error ("Can't open profile in file '%s'",prof_name); + + if (verb) { + icmFile *op; + if ((op = new_icmFileStd_fp(stdout)) == NULL) + error ("Can't open stdout"); + icco->header->dump(icco->header, op, 1); + op->del(op); + } + + /* Check that the profile is appropriate */ + if (icco->header->deviceClass != icSigInputClass + && icco->header->deviceClass != icSigDisplayClass + && icco->header->deviceClass != icSigOutputClass + && icco->header->deviceClass != icSigColorSpaceClass) + error("Profile type isn't device or colorspace"); + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(icco)) == NULL) + error ("Creation of xicc failed"); + + /* Setup the default viewing conditions */ + if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + + if (vc_e != -1) + if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + if (vc_s >= 0) + vc.Ev = vc_s; + if (vc_wXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; + vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; + } + if (vc_wxy[0] >= 0.0) { + double x = vc_wxy[0]; + double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Wxyz[0] = x/y * vc.Wxyz[1]; + vc.Wxyz[2] = z/y * vc.Wxyz[1]; + } + if (vc_a >= 0.0) + vc.La = vc_a; + if (vc_b >= 0.0) + vc.Yb = vc_b/100.0; + if (vc_f >= 0.0) + vc.Yf = vc_f/100.0; + if (vc_fXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; + vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; + } + if (vc_fxy[0] >= 0.0) { + double x = vc_fxy[0]; + double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Fxyz[0] = x/y * vc.Fxyz[1]; + vc.Fxyz[2] = z/y * vc.Fxyz[1]; + } + + /* Get a expanded color conversion object */ + if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST + , func, intent, pcsor, order, &vc, NULL)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL); + + } + + /* Establish the PCS range if we are filtering */ + { + double pcsmin[3], pcsmax[3]; /* PCS range for filter stats array */ + + if (luo) { + gamut *csgam; + + if ((csgam = luo->get_gamut(luo, 20.0)) == NULL) + error("Getting the gamut of the source colorspace failed"); + + csgam->getrange(csgam, pcsmin, pcsmax); + csgam->del(csgam); + } else { + pcsmin[0] = 0.0; + pcsmax[0] = 100.0; + pcsmin[1] = -128.0; + pcsmax[1] = 128.0; + pcsmin[2] = -128.0; + pcsmax[2] = 128.0; + } + + if (verb) + printf("PCS range = %f..%f, %f..%f. %f..%f\n\n", pcsmin[0], pcsmax[0], pcsmin[1], pcsmax[1], pcsmin[2], pcsmax[2]); + + /* Allocate and initialize the filter */ + set_fminmax(pcsmin, pcsmax); + } + + /* - - - - - - - - - - - - - - - */ + /* Open up input tiff file ready for reading */ + /* Got arguments, so setup to process the file */ + + if ((rh = TIFFOpen(in_name, "r")) == NULL) + error("error opening read file '%s'",in_name); + + TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height); + + TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); + TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample); + if (bitspersample != 8 && bitspersample != 16) + error("TIFF Input file must be 8 bit/channel"); + + TIFFGetFieldDefaulted(rh, TIFFTAG_EXTRASAMPLES, &extrasamples, &extrainfo); + TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric); + + if (inn != (samplesperpixel-extrasamples)) + error ("TIFF Input file has %d input chanels mismatched to colorspace '%s'", + samplesperpixel, icm2str(icmColorSpaceSignature, ins)); + + if ((tcs = TiffPhotometric2ColorSpaceSignature(&cvt, &sign_mask, photometric, + bitspersample, samplesperpixel, extrasamples)) == 0) + error("Can't handle TIFF file photometric %s", Photometric2str(photometric)); + + if (tcs != ins) { + if (luo != NULL) + error("TIFF photometric '%s' doesn't match ICC input colorspace '%s' !", + Photometric2str(photometric), icm2str(icmColorSpaceSignature,ins)); + else + error("No profile provided and TIFF photometric '%s' isn't Lab !", + Photometric2str(photometric)); + } + + TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig); + if (pconfig != PLANARCONFIG_CONTIG) + error ("TIFF Input file must be planar"); + + TIFFGetField(rh, TIFFTAG_RESOLUTIONUNIT, &resunits); + TIFFGetField(rh, TIFFTAG_XRESOLUTION, &resx); + TIFFGetField(rh, TIFFTAG_YRESOLUTION, &resy); + + if (verb) { + printf("Input TIFF file '%s'\n",in_name); + printf("TIFF file colorspace is %s\n",icm2str(icmColorSpaceSignature,tcs)); + printf("TIFF file photometric is %s\n",Photometric2str(photometric)); + printf("\n"); + } + + /* - - - - - - - - - - - - - - - */ + /* Process colors to translate */ + /* (Should fix this to process a group of lines at a time ?) */ + + nipoints = width * height; + +// if ((inp = malloc(sizeof(co) * nipoints)) == NULL) +// error("Unable to allocate co array"); + + inbuf = _TIFFmalloc(TIFFScanlineSize(rh)); + + for (i = y = 0; y < height; y++) { + + /* Read in the next line */ + if (TIFFReadScanline(rh, inbuf, y, 0) < 0) + error ("Failed to read TIFF line %d",y); + + /* Do floating point conversion */ + for (x = 0; x < width; x++) { + int e; + double in[MAX_CHAN], out[MAX_CHAN]; + + if (bitspersample == 8) { + for (e = 0; e < samplesperpixel; e++) { + int v = ((unsigned char *)inbuf)[x * samplesperpixel + e]; + if (sign_mask & (1 << i)) /* Treat input as signed */ + v = (v & 0x80) ? v - 0x80 : v + 0x80; + in[e] = v/255.0; + } + } else { + for (e = 0; e < samplesperpixel; e++) { + int v = ((unsigned short *)inbuf)[x * samplesperpixel + e]; + if (sign_mask & (1 << i)) /* Treat input as signed */ + v = (v & 0x8000) ? v - 0x8000 : v + 0x8000; + in[e] = v/65535.0; + } + } + if (cvt != NULL) { /* Undo TIFF encoding */ + cvt(in, in); + } + if (luo != NULL) { + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",icco->errc,icco->err); + + if (outs == icSigXYZData) /* Convert to Lab */ + icmXYZ2Lab(&icco->header->illuminant, out, out); + } else { + for (e = 0; e < samplesperpixel; e++) + out[e] = in[e]; + } + +//printf("~1 %f %f %f -> %f %f %f\n", in[0], in[1], in[2], out[0], out[1], out[2]); + + add_fpixel(out); + +#ifdef NEVER + /* Store PCS value in array */ + inp[i].v[0] = out[0]; + inp[i].v[1] = out[1]; + inp[i].v[2] = out[2]; + i++; +#endif + } + } + + _TIFFfree(inbuf); + + TIFFClose(rh); /* Close Input file */ + + /* Done with lookup object */ + if (luo != NULL) { + luo->del(luo); + xicco->del(xicco); /* Expansion wrapper */ + icco->del(icco); /* Icc */ + } + + + nipoints = flush_filter(verb, 80.0); + + if ((inp = malloc(sizeof(co) * nipoints)) == NULL) + error("Unable to allocate co array"); + + get_filter(inp); + +printf("~1 There are %d points\n",nipoints); +//for (i = 0; i < nipoints; i++) +//printf("~1 point %d = %f %f %f\n", i, inp[i].v[0], inp[i].v[1], inp[i].v[2]); + + del_filter(); + + /* Create the locus */ + { + double s0[3], s1[3]; + double t0[3], t1[3]; + double mm[3][4]; + double im[3][4]; + int gres[MXDI] = { 256 } ; + + if (usevec) { + double max = -1e6; + double min = 1e6; + double dist; + + icmScale3(vec, vec, 1.0/icmNorm3(vec)); + + /* Locate the two furthest distant points measured along the vector */ + for (i = 0; i < nipoints; i++) { + double tt; + tt = icmDot3(vec, inp[i].v); + if (tt > max) { + max = tt; + icmAry2Ary(s1, inp[i].v); + } + if (tt < min) { + min = tt; + icmAry2Ary(s0, inp[i].v); + } + } + dist = icmNorm33sq(s0, s1); + +printf("~1 most distant in vector %f %f %f = %f %f %f -> %f %f %f dist %f\n", +vec[0], vec[1], vec[2], s0[0], s0[1], s0[2], s1[0], s1[1], s1[2], sqrt(dist)); + + t0[0] = 0.0; + t0[1] = 0.0; + t0[2] = 0.0; + t1[0] = sqrt(dist); + t1[1] = 0.0; + t1[2] = 0.0; + + } else { + double dist = 0.0; + + /* Locate the two furthest distant points (brute force) */ + for (i = 0; i < (nipoints-1); i++) { + for (j = i+1; j < nipoints; j++) { + double tt; + if ((tt = icmNorm33sq(inp[i].v, inp[j].v)) > dist) { + dist = tt; + icmAry2Ary(s0, inp[i].v); + icmAry2Ary(s1, inp[j].v); + } + } + } +printf("~1 most distant = %f %f %f -> %f %f %f dist %f\n", +s0[0], s0[1], s0[2], s1[0], s1[1], s1[2], sqrt(dist)); + + t0[0] = 0.0; + t0[1] = 0.0; + t0[2] = 0.0; + t1[0] = sqrt(dist); + t1[1] = 0.0; + t1[2] = 0.0; + } + + /* Transform our direction vector to the L* axis, and create inverse too */ + icmVecRotMat(mm, s1, s0, t1, t0); + icmVecRotMat(im, t1, t0, s1, s0); + + /* Setup for rspl to create smoothed locus */ + for (i = 0; i < nipoints; i++) { + icmMul3By3x4(inp[i].v, mm, inp[i].v); + inp[i].p[0] = inp[i].v[0]; + inp[i].v[0] = inp[i].v[1]; + inp[i].v[1] = inp[i].v[2]; +//printf("~1 point %d = %f -> %f %f\n", i, inp[i].p[0], inp[i].v[0], inp[i].v[1]); + } + + /* Create rspl */ + if ((rr = new_rspl(RSPL_NOFLAGS, 1, 2)) == NULL) + error("Creating rspl failed"); + + rr->fit_rspl(rr, RSPL_NOFLAGS,inp, nipoints, NULL, NULL, gres, NULL, NULL, 5.0, NULL, NULL); +#ifdef DEBUG_PLOT + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + double y2[XRES]; + + for (i = 0; i < XRES; i++) { + co pp; + double x; + x = i/(double)(XRES-1); + xx[i] = x * (t1[0] - t0[0]); + pp.p[0] = xx[i]; + rr->interp(rr, &pp); + y1[i] = pp.v[0]; + y2[i] = pp.v[1]; + } + do_plot(xx,y1,y2,NULL,XRES); + } +#endif /* DEBUG_PLOT */ + + free(inp); + + nopoints = t1[0] / DE_SPACE; + if (nopoints < 2) + nopoints = 2; + + /* Create the output points */ + if ((outp = malloc(sizeof(co) * nopoints)) == NULL) + error("Unable to allocate co array"); + + /* Setup initial division of locus */ + for (i = 0; i < nopoints; i++) { + double xx; + + xx = i/(double)(nopoints-1); + xx *= (t1[0] - t0[0]); + + outp[i].p[0] = xx; +//printf("~1 div %d = %f\n",i,outp[i].p[0]); + } + for (i = 0; i < (nopoints-1); i++) { + outp[i].p[1] = outp[i+1].p[0] - outp[i].p[0]; +//printf("~1 del div %d = %f\n",i,outp[i].p[1]); + } + + /* Itterate until the delta between samples is even */ + for (j = 0; j < 10; j++) { + double alen, minl, maxl; + double tdiv; + + alen = 0.0; + minl = 1e38; + maxl = -1.0; + for (i = 0; i < nopoints; i++) { + rr->interp(rr, &outp[i]); + outp[i].v[2] = outp[i].v[1]; + outp[i].v[1] = outp[i].v[0]; + outp[i].v[0] = outp[i].p[0]; + icmMul3By3x4(outp[i].v, im, outp[i].v); + +//printf("~1 locus pnt %d = %f %f %f\n", i,outp[i].v[0],outp[i].v[1],outp[i].v[1]); + + if (i > 0) { + double tt[3], len; + icmSub3(tt, outp[i].v, outp[i-1].v); + len = icmNorm3(tt); + outp[i-1].p[2] = len; + if (len > maxl) + maxl = len; + if (len < minl) + minl = len; + alen += len; + } + } + alen /= (nopoints-1.0); +printf("~1 itter %d, alen = %f, minl = %f, maxl = %f\n",j,alen,minl,maxl); + + /* Adjust spacing */ + tdiv = 0.0; + for (i = 0; i < (nopoints-1); i++) { + outp[i].p[1] *= pow(alen/outp[i].p[2], 1.0); + tdiv += outp[i].p[1]; + } +//printf("~1 tdiv = %f\n",tdiv); + for (i = 0; i < (nopoints-1); i++) { + outp[i].p[1] *= (t1[0] - t0[0])/tdiv; +//printf("~1 del div %d = %f\n",i,outp[i].p[1]); + } + tdiv = 0.0; + for (i = 0; i < (nopoints-1); i++) { + tdiv += outp[i].p[1]; + } +//printf("~1 tdiv now = %f\n",tdiv); + for (i = 1; i < nopoints; i++) { + outp[i].p[0] = outp[i-1].p[0] + outp[i-1].p[1]; +//printf("~1 div %d = %f\n",i,outp[i].p[0]); + } + } + + /* Write the CGATS file */ + { + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *pp; + + pp = new_cgats(); /* Create a CGATS structure */ + pp->add_other(pp, "TS"); /* Test Set */ + + pp->add_table(pp, tt_other, 0); /* Add the first table for target points */ + pp->add_kword(pp, 0, "DESCRIPTOR", "Argyll Test Point set",NULL); + pp->add_kword(pp, 0, "ORIGINATOR", "Argyll tiffgmts", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + pp->add_kword(pp, 0, "CREATED",atm, NULL); + + pp->add_field(pp, 0, "SAMPLE_ID", cs_t); + pp->add_field(pp, 0, "LAB_L", r_t); + pp->add_field(pp, 0, "LAB_A", r_t); + pp->add_field(pp, 0, "LAB_B", r_t); + + for (i = 0; i < nopoints; i++) { + char buf[100]; + cgats_set_elem ary[1 + 3]; + + sprintf(buf,"%d",i+1); + ary[0].c = buf; + ary[1 + 0].d = outp[i].v[0]; + ary[1 + 1].d = outp[i].v[1]; + ary[1 + 2].d = outp[i].v[2]; + + pp->add_setarr(pp, 0, ary); + } + + if (pp->write_name(pp, out_name)) + error("Write error : %s",pp->err); + } + + /* Create the VRML file */ + if (dovrml) { + vrml *vv; + + strcpy(xl,".wrl"); + printf("Output vrml file '%s'\n",out_name); + if ((vv = new_vrml(out_name, doaxes, 0)) == NULL) + error ("Creating VRML object failed"); + +#ifdef NEVER + vv->start_line_set(vv); + for (i = 0; i < nopoints; i++) { + vv->add_vertex(vv, outp[i].v); + } + vv->make_lines(vv, nopoints); +#else + for (i = 1; i < nopoints; i++) { + vv->add_cone(vv, outp[i-1].v, outp[i].v, NULL, 0.5); + } +#endif + + vv->del(vv); + } + free(outp); + } + + rr->del(rr); + + return 0; +} + +/* ------------------------------------------ */ +/* A pixel value filter module. We quantize the pixel values and keep statistics */ +/* on them, so as to filter out low frequency colors. */ + +#define FILTBITS 5 /* Total = 2 ^ (3 * FILTBITS) entries = 33Mbytes*/ +#define FILTSIZE (1 << FILTBITS) + +/* A filtered cell entry */ +typedef struct { + int count; /* Count of pixels that fall in this cell */ + float pcs[3]; /* Sum of PCS value that fall in this cell */ +} fent; + +struct _ffilter { + double min[3], max[3]; /* PCS range */ + double filtperc; + int used; + + fent cells[FILTSIZE][FILTSIZE][FILTSIZE]; /* Quantized pixels stats */ + fent *scells[FILTSIZE * FILTSIZE * FILTSIZE]; /* Sorted order */ + +}; typedef struct _ffilter ffilter; + + + +/* Use a global object */ +ffilter *ff = NULL; + +/* Set the min and max values and init the filter */ +void set_fminmax(double min[3], double max[3]) { + + if (ff == NULL) { + if ((ff = (ffilter *) calloc(1,sizeof(ffilter))) == NULL) + error("ffilter: calloc failed"); + } + + ff->min[0] = min[0]; + ff->min[1] = min[1]; + ff->min[2] = min[2]; + ff->max[0] = max[0]; + ff->max[1] = max[1]; + ff->max[2] = max[2]; +} + +/* Add another pixel to the filter */ +void add_fpixel(double val[3]) { + int j; + int qv[3]; + fent *fe; + double cent[3] = { 50.0, 0.0, 0.0 }; /* Assumed center */ + double tt, cdist, ndist; + + if (ff == NULL) + error("ffilter not initialized"); + + /* Quantize the values */ + for (j = 0; j < 3; j++) { + double vv; + + vv = (val[j] - ff->min[j])/(ff->max[j] - ff->min[j]); + qv[j] = (int)(vv * (FILTSIZE - 1) + 0.5); + } + +//printf("~1 color %f %f %f -> Cell %d %d %d\n", val[0], val[1], val[2], qv[0], qv[1], qv[2]); + + /* Find the appropriate cell */ + fe = &ff->cells[qv[0]][qv[1]][qv[2]]; + + fe->pcs[0] += val[0]; + fe->pcs[1] += val[1]; + fe->pcs[2] += val[2]; +//printf("Updated pcs to %f %f %f\n", val[0],val[1],val[2]); + fe->count++; +//printf("Cell count = %d\n",fe->count); +} + +/* Flush the filter contents, and return the number of filtered values */ +int flush_filter(int verb, double filtperc) { + int i, j; + int totcells = FILTSIZE * FILTSIZE * FILTSIZE; + int used, hasone; + double cuml, avgcnt; + + if (ff == NULL) + error("ffilter not initialized"); + + /* Sort the cells by popularity from most to least */ + for (used = hasone = avgcnt = i = 0; i < totcells; i++) { + ff->scells[i] = (fent *)ff->cells + i; + if (ff->scells[i]->count > 0) { + used++; + if (ff->scells[i]->count == 1) + hasone++; + avgcnt += ff->scells[i]->count; + } + } + avgcnt /= used; + +#define HEAP_COMPARE(A,B) A->count > B->count + HEAPSORT(fent *,ff->scells, totcells) + + if (verb) { + printf("Total of %d cells out of %d were hit (%.1f%%)\n",used,totcells,used * 100.0/totcells); + printf("%.1f%% have a count of 1\n",hasone * 100.0/used); + printf("Average cell count = %f\n",avgcnt); + printf("\n"); + } + + /* Add the populated cells in order */ + filtperc /= 100.0; + for (cuml = 0.0, i = j = 0; cuml < filtperc && i < totcells; i++) { + + if (ff->scells[i]->count > 0) { + double val[3]; + ff->scells[i]->pcs[0] /= ff->scells[i]->count; + ff->scells[i]->pcs[1] /= ff->scells[i]->count; + ff->scells[i]->pcs[2] /= ff->scells[i]->count; + j++; + cuml = j/(used-1.0); + } + } + ff->used = used; + ff->filtperc = filtperc; + + return j; +} + +/* Add the points to the array */ +void get_filter(co *inp) { + int i, j; + int totcells = FILTSIZE * FILTSIZE * FILTSIZE; + double cuml, filtperc; + int used; + + used = ff->used; + filtperc = ff->filtperc; + + for (cuml = 0.0, i = j = 0; cuml < filtperc && i < totcells; i++) { + + if (ff->scells[i]->count > 0) { + double val[3]; + inp[j].v[0] = ff->scells[i]->pcs[0]; /* float -> double */ + inp[j].v[1] = ff->scells[i]->pcs[1]; + inp[j].v[2] = ff->scells[i]->pcs[2]; +//printf("~1 adding %f %f %f to gamut\n", val[0], val[1], val[2]); + j++; + cuml = j/(used-1.0); + } + + } +} + +/* Free up the filter structure */ +void del_filter() { + + free(ff); +} + diff --git a/xicc/transplot.c b/xicc/transplot.c new file mode 100644 index 0000000..e196544 --- /dev/null +++ b/xicc/transplot.c @@ -0,0 +1,279 @@ + +/* + * International Color Consortium Format Library (icclib) + * Check various aspects of CMYK device link, + * and RGB/CMYK profile transfer characteristics. + * + * Author: Graeme W. Gill + * Date: 2003/9/7 + * Version: 1.0 + * + * Copyright 2000 - 2003 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + * Make general device input, not just CMYK + * + * Allow specifying a vector in the range space. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "icc.h" +#include "numlib.h" +#include "plot.h" + +void usage(void) { + fprintf(stderr,"Check CMYK/RGB/PCS->PCS/CMYK/RGB transfer response\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: transplot [-v] infile\n"); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -c -m -y -k Check Cyan and/or Magenta and/or Yellow and/or Black input\n"); + fprintf(stderr," -r -g -b Check Red and/or Green and/or Blue input\n"); + fprintf(stderr," -L -A -B Check L and/or a* and/or b* input\n"); + exit(1); +} + +#define XRES 256 +//#define XRES 11 + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int chans[4] = { 0, 0, 0, 0 }; /* Flags indicating which channels to plot against */ + char in_name[100]; + icmFile *rd_fp; + icc *rd_icco; /* Keep object separate */ + int rv = 0; + + /* Check variables */ + icmLuBase *luo; + icmLuLut *luluto; /* Lookup xLut type object */ + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + int inn; /* Number of input chanels */ + icmLuAlgType alg; + int labin = 0; /* Flag */ + int rgbin = 0; /* Flag */ + int labout = 0; /* Flag */ + int rgbout = 0; /* Flag */ + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + /* Cyan, Red */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C' + || argv[fa][1] == 'r' || argv[fa][1] == 'R') { + chans[0] = 1; + } + /* Magenta, Green, a* */ + else if (argv[fa][1] == 'm' || argv[fa][1] == 'M' + || argv[fa][1] == 'g' || argv[fa][1] == 'G') { + chans[1] = 1; + } + /* Yellow, Blue, b* */ + else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y' + || argv[fa][1] == 'b') { + chans[2] = 1; + } + /* L* */ + else if (argv[fa][1] == 'L') { + chans[0] = 1; + labin = 1; + } + /* a* */ + else if (argv[fa][1] == 'A') { + chans[1] = 1; + labin = 1; + } + /* Yellow, Blue, b* */ + else if (argv[fa][1] == 'B') { + chans[2] = 1; + labin = 1; + } + /* Black */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + chans[3] = 1; + } + else if (argv[fa][1] == '?') + usage(); + else + usage(); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(in_name,argv[fa]); + + /* Open up the file for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((rd_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + /* Read the header and tag list */ + if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) + error ("Read: %d, %s",rv,rd_icco->err); + + if (labin) { + /* Get a Device to PCS conversion object */ + if ((luo = rd_icco->get_luobj(rd_icco, icmBwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } else { + /* Get a PCS to Device conversion object */ + if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) { + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + } + } + + /* Get details of conversion */ + luo->spaces(luo, &ins, &inn, &outs, NULL, &alg, NULL, NULL, NULL, NULL); + + if (labin) { + chans[3] = 0; + + if (outs != icSigCmykData && outs != icSigRgbData) { + error("Expecting CMYK or RGB output space"); + } + + if (outs == icSigRgbData) { + rgbout = 1; + } + + } else { + if (ins != icSigCmykData && ins != icSigRgbData) { + error("Expecting CMYK or RGB input space"); + } + + if (ins == icSigRgbData) { + rgbin = 1; + chans[3] = 0; + } + + if (outs != icSigCmykData && outs != icSigLabData) { + error("Expecting Lab or CMYK output space"); + } + + if (outs == icSigLabData) + labout = 1; + } + + + luluto = (icmLuLut *)luo; /* Lookup xLut type object */ + + { + int i, j; + double xx[XRES]; + double y0[XRES]; + double y1[XRES]; + double y2[XRES]; + double y3[XRES]; + + for (i = 0; i < XRES; i++) { + double ival = (double)i/(XRES-1.0); + double in[4]; + double out[4]; + + for (j = 0; j < 4; j++) + in[j] = 0.0; + + if (labin) { + in[0] = 0.5; + in[1] = 0.5; + in[2] = 0.5; + } + + if (chans[0]) + in[0] = ival; + if (chans[1]) + in[1] = ival; + if (chans[2]) + in[2] = ival; + if (chans[3]) + in[3] = ival; + + if (labin) { + in[0] = in[0] * 100.0; + in[1] = (in[1] - 0.5) * 254.0; + in[2] = (in[2] - 0.5) * 254.0; + } + + /* Do the conversion */ + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (labout) { +//printf("~1 in %f %f %f %f, out %f %f %f\n",in[0],in[1],in[2],in[3],out[0],out[1],out[2]); + xx[i] = ival; + y0[i] = out[0]/1.0; + y1[i] = 50.0 + out[1]/2.0; + y2[i] = 50.0 + out[2]/2.0; + if (y1[i] < 0.0) + y1[i] = 0.0; + if (y1[i] > 100.0) + y1[i] = 100.0; + if (y2[i] < 0.0) + y2[i] = 0.0; + if (y2[i] > 100.0) + y2[i] = 100.0; + } else { + xx[i] = ival; + y0[i] = out[0]; + y1[i] = out[1]; + y2[i] = out[2]; + y3[i] = out[3]; + } + + } + if (labout) + do_plot6(xx,y0,y1,NULL,NULL,y2,NULL,XRES); + else + do_plot6(xx,y3,y1,NULL,y0,y2,NULL,XRES); + } + + /* Done with lookup object */ + luo->del(luo); + + rd_icco->del(rd_icco); + rd_fp->del(rd_fp); + + return 0; +} diff --git a/xicc/xcal.c b/xicc/xcal.c new file mode 100644 index 0000000..97a3086 --- /dev/null +++ b/xicc/xcal.c @@ -0,0 +1,496 @@ + +/* + * Argyll Color Correction System + * Calibration curve class. + * + * Author: Graeme W. Gill + * Date: 30/10/2005 + * + * Copyright 2005 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * This class allows reading and using a calibration file. + * Creation is currently left up to specialized programs (dispcal, printcal). + * This class doesn't handle the extra table that dispcal creates/uses. + * + */ + +#undef DEBUG /* Input points */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <math.h> +#include <sys/types.h> +#include <time.h> +#include <string.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "xicc.h" + +#ifdef NT /* You'd think there might be some standards.... */ +# ifndef __BORLANDC__ +# define stricmp _stricmp +# endif +#else +# define stricmp strcasecmp +#endif + +/* rspl setting functions */ +static void xcal_rsplset(void *cbntx, double *out, double *in) { + co *dpoints = (co *)cbntx; + int ix; + + ix = *((int*)&in[-0-1]); /* Get grid index being looked up */ + out[0] = dpoints[ix].v[0]; +} + +/* Read a calibration file from a cgats table */ +/* Return nz if this fails */ +static int xcal_read_cgats(xcal *p, cgats *tcg, int table, char *filename) { + int oi; + int i, j, ti; + char *ident; + char *bident; + int spi[1+MAX_CHAN]; /* CGATS indexes for each field */ + char buf[100]; + + if ((oi = tcg->get_oi(tcg, "CAL")) < 0) { + sprintf(p->err, "Input file '%s' can't be a CAL format file", filename); + return p->errc = 1; + } + + if (tcg->t[table].tt != tt_other || tcg->t[table].oi != oi) { + sprintf(p->err, "Input file '%s' isn't a CAL format file", filename); + return p->errc = 1; + } + + /* See what sort of device type this calibration is for */ + if ((ti = tcg->find_kword(tcg, table, "DEVICE_CLASS")) < 0) { + sprintf(p->err, "Calibration file '%s'doesn't contain keyword DEVICE_CLASS",filename); + return p->errc = 1; + } + if (strcmp(tcg->t[table].kdata[ti],"INPUT") == 0) { + p->devclass = icSigInputClass; + } else if (strcmp(tcg->t[table].kdata[ti],"OUTPUT") == 0) { + p->devclass = icSigOutputClass; + } else if (strcmp(tcg->t[table].kdata[ti],"DISPLAY") == 0) { + p->devclass = icSigDisplayClass; + } else { + sprintf(p->err,"Calibration file '%s' contain unknown DEVICE_CLASS '%s'", + filename,tcg->t[table].kdata[ti]); + return p->errc = 1; + } + + if ((ti = tcg->find_kword(tcg, table, "COLOR_REP")) < 0) { + /* Be backwards compatible with V1.0.4 display calibration files */ + if (p->devclass != icSigDisplayClass) { + sprintf(p->err, "Calibration file '%s'doesn't contain keyword COLOR_REP",filename); + return p->errc = 1; + } + warning("\n *** Calibration file '%s'doesn't contain keyword COLOR_REP, assuming RGB ***",filename); + if ((p->devmask = icx_char2inkmask("RGB") ) == 0) { + sprintf(p->err, "Calibration file '%s' has unrecognized COLOR_REP '%s'", + filename,tcg->t[table].kdata[ti]); + return p->errc = 1; + } + } else { + if ((p->devmask = icx_char2inkmask(tcg->t[table].kdata[ti]) ) == 0) { + sprintf(p->err, "Calibration file '%s' has unrecognized COLOR_REP '%s'", + filename,tcg->t[table].kdata[ti]); + return p->errc = 1; + } + } + + if ((ti = tcg->find_kword(tcg, table, "VIDEO_LUT_CALIBRATION_POSSIBLE")) >= 0) { + if (stricmp(tcg->t[table].kdata[ti], "NO") == 0) + p->noramdac = 1; + } + + p->colspace = icx_colorant_comb_to_icc(p->devmask); /* 0 if none */ + p->devchan = icx_noofinks(p->devmask); + ident = icx_inkmask2char(p->devmask, 1); + bident = icx_inkmask2char(p->devmask, 0); + + /* Grab any descriptive information */ + if ((ti = tcg->find_kword(tcg, table, "MANUFACTURER")) >= 0) + p->xpi.deviceMfgDesc = strdup(tcg->t[table].kdata[ti]); + if ((ti = tcg->find_kword(tcg, table, "MODEL")) >= 0) + p->xpi.modelDesc = strdup(tcg->t[table].kdata[ti]); + if ((ti = tcg->find_kword(tcg, table, "DESCRIPTION")) >= 0) + p->xpi.profDesc = strdup(tcg->t[table].kdata[ti]); + if ((ti = tcg->find_kword(tcg, table, "COPYRIGHT")) >= 0) + p->xpi.copyright = strdup(tcg->t[table].kdata[ti]); + + if (tcg->t[table].nsets <= 0) { + sprintf(p->err, "Calibration file '%s' has too few entries %d", + filename,tcg->t[table].nsets); + return p->errc = 1; + } + + /* Figure out the indexes of all the fields */ + sprintf(buf, "%s_I",bident); + if ((spi[0] = tcg->find_field(tcg, table, buf)) < 0) { + sprintf(p->err,"Calibration file '%s' doesn't contain field '%s'", filename,buf); + return p->errc = 1; + } + + for (j = 0; j < p->devchan; j++) { + inkmask imask = icx_index2ink(p->devmask, j); + sprintf(buf, "%s_%s",bident,icx_ink2char(imask)); + if ((spi[1+j] = tcg->find_field(tcg, table, buf)) < 0) { + sprintf(p->err,"Calibration file '%s' doesn't contain field '%s'", filename,buf); + return p->errc = 1; + } + } + + /* Read in each channels values and put them in a rspl */ + for (j = 0; j < p->devchan; j++) { + datai low,high; + int gres[MXDI]; + double smooth = 1.0; + co *dpoints; + + low[0] = 0.0; + high[0] = 1.0; + gres[0] = tcg->t[table].nsets; + + if ((p->cals[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL) { + sprintf(p->err,"new_rspl() failed"); + return p->errc = 2; + } + + if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL) { + sprintf(p->err,"malloc dpoints[%d] failed",gres[0]); + return p->errc = 2; + } + + /* Copy the points to our array */ + for (i = 0; i < gres[0]; i++) { + dpoints[i].p[0] = i/(double)(gres[0]-1); + dpoints[i].v[0] = *((double *)tcg->t[table].fdata[i][spi[1+j]]); + } + + /* Set the rspl */ + p->cals[j]->set_rspl(p->cals[j], + 0, + (void *)dpoints, /* Read points */ + xcal_rsplset, /* Setting function */ + low, high, gres, /* Low, high, resolution of grid */ + NULL, NULL /* Default data scale */ + ); + free(dpoints); + } + free(ident); + free(bident); + + return 0; +} + +/* Read a calibration file */ +/* Return nz if this fails */ +static int xcal_read(xcal *p, char *filename) { + cgats *tcg; /* .cal file */ + int table = 0; + int rv; + + if ((tcg = new_cgats()) == NULL) { + sprintf(p->err, "new_cgats() failed"); + return p->errc = 2; + } + + tcg->add_other(tcg, "CAL"); /* our special input type is Calibration Target */ + + if (tcg->read_name(tcg, filename)) { + strcpy(p->err, tcg->err); + p->errc = tcg->errc; + tcg->del(tcg); + return p->errc; + } + + rv = xcal_read_cgats(p, tcg, table, filename); + + tcg->del(tcg); + + return rv; +} + +/* Write a calibration to a new cgats table */ +/* Return nz if this fails */ +static int xcal_write_cgats(xcal *p, cgats *tcg) { + int oi; + int table; + int i, j, ti; + char *ident, *bident; + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + char buf[100]; + cgats_set_elem *setel; /* Array of set value elements */ + int nsetel = 0; + int calres; + + oi = tcg->add_other(tcg, "CAL"); /* our special type is Calibration Target */ + + table = tcg->add_table(tcg, tt_other, oi); /* Add a table for calibration */ + + tcg->add_kword(tcg, table, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL); + tcg->add_kword(tcg, table, "ORIGINATOR", "Argyll", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + tcg->add_kword(tcg, table, "CREATED",atm, NULL); + + if (p->devclass == icSigInputClass) + tcg->add_kword(tcg, table, "DEVICE_CLASS","INPUT", NULL); + else if (p->devclass == icSigOutputClass) + tcg->add_kword(tcg, table, "DEVICE_CLASS","OUTPUT", NULL); + else if (p->devclass == icSigDisplayClass) + tcg->add_kword(tcg, table, "DEVICE_CLASS","DISPLAY", NULL); + else { + sprintf(p->err,"Unknown device class '%s'",icm2str(icmProfileClassSignature,p->devclass)); + return p->errc = 1; + } + + /* Colorspace */ + ident = icx_inkmask2char(p->devmask, 1); + bident = icx_inkmask2char(p->devmask, 0); + tcg->add_kword(tcg, table, "COLOR_REP", ident, NULL); + + /* Grab any descriptive information */ + if (p->xpi.deviceMfgDesc != NULL) + tcg->add_kword(tcg, table, "MANUFACTURER",p->xpi.deviceMfgDesc, NULL); + if (p->xpi.modelDesc != NULL) + tcg->add_kword(tcg, table, "MODEL",p->xpi.modelDesc, NULL); + if (p->xpi.profDesc != NULL) + tcg->add_kword(tcg, table, "DESCRIPTION",p->xpi.profDesc, NULL); + if (p->xpi.copyright != NULL) + tcg->add_kword(tcg, table, "COPYRIGHT",p->xpi.copyright, NULL); + + sprintf(buf, "%s_I",bident); + tcg->add_field(tcg, table, buf, r_t); + nsetel++; + for (j = 0; j < p->devchan; j++) { + inkmask imask = icx_index2ink(p->devmask, j); + sprintf(buf, "%s_%s",bident,icx_ink2char(imask)); + tcg->add_field(tcg, table, buf, r_t); + nsetel++; + } + if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) { + sprintf(p->err,"Malloc failed"); + return p->errc = 2; + } + + calres = p->cals[0]->g.res[0]; + + for (i = 0; i < calres; i++) { + double vv = i/(calres-1.0); + co tp; + + setel[0].d = vv; + for (j = 0; j < p->devchan; j++) { + tp.p[0] = vv; + p->cals[j]->interp(p->cals[j], &tp); + setel[j+1].d = tp.v[0]; + } + + tcg->add_setarr(tcg, table, setel); + } + + free(setel); + free(ident); + free(bident); + + return 0; +} + +/* Write a calibration file */ +/* Return nz if this fails */ +static int xcal_write(xcal *p, char *filename) { + cgats *tcg; /* .cal file */ + int table = 0; + int rv; + + if ((tcg = new_cgats()) == NULL) { + sprintf(p->err, "new_cgats() failed"); + return p->errc = 2; + } + + if ((rv = xcal_write_cgats(p, tcg)) != 0) { + strcpy(p->err, tcg->err); + p->errc = tcg->errc; + tcg->del(tcg); + return p->errc; + } + + if (tcg->write_name(tcg, filename)) { + strcpy(p->err, tcg->err); + p->errc = tcg->errc; + tcg->del(tcg); + return p->errc; + } + + tcg->del(tcg); + + return rv; +} + +/* Translate values through the curves. */ +static void xcal_interp(xcal *p, double *out, double *in) { + int j; + co tp; + + for (j = 0; j < p->devchan; j++) { + tp.p[0] = in[j]; + p->cals[j]->interp(p->cals[j], &tp); + out[j] = tp.v[0]; + } +} + +#define MAX_INVSOLN 10 /* Rspl maximum reverse solutions */ + +/* Translate a value backwards through the curves. */ +/* Return nz if the inversion fails */ +static int xcal_inv_interp(xcal *p, double *out, double *in) { + int nsoln; /* Number of solutions found */ + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + int j, k; /* Chosen solution */ + double dir = 0.5; /* target if multiple solutions */ + int rv = 0; + + for (j = 0; j < p->devchan; j++) { + pp[0].v[0] = in[j]; + + nsoln = p->cals[j]->rev_interp ( + p->cals[j], /* this */ + RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */ + MAX_INVSOLN, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + NULL, /* Clip vector direction and length */ + pp); /* Input and output values */ + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln == 1) { /* Exactly one solution */ + k = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + rv = 1; + return -1.0; + } else { /* Multiple solutions */ + double bdist = 1e300; + int bsoln = 0; + for (k = 0; k < nsoln; k++) { + double tt; + tt = pp[k].p[0] - dir; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = k; + } + } + k = bsoln; + } + out[j] = pp[k].p[0]; + } + + return rv; +} + +/* Translate a value through one of the curves */ +static double xcal_interp_ch(xcal *p, int ch, double in) { + co tp; + + if (ch < 0 || ch >= p->devchan) + return -1.0; + + tp.p[0] = in; + p->cals[ch]->interp(p->cals[ch], &tp); + return tp.v[0]; +} + +/* Translate a value backwards through one of the curves */ +/* Return -1.0 if the inversion fails */ +static double xcal_inv_interp_ch(xcal *p, int ch, double in) { + int nsoln; /* Number of solutions found */ + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + int k; /* Chosen solution */ + double dir = 0.5; /* target if multiple solutions */ + + if (ch < 0 || ch >= p->devchan) + return -1.0; + + pp[0].v[0] = in; + + nsoln = p->cals[ch]->rev_interp ( + p->cals[ch], /* this */ + RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */ + MAX_INVSOLN, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + NULL, /* Clip vector direction and length */ + pp); /* Input and output values */ + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln == 1) { /* Exactly one solution */ + k = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + return -1.0; + } else { /* Multiple solutions */ + double bdist = 1e300; + int bsoln = 0; + for (k = 0; k < nsoln; k++) { + double tt; + tt = pp[k].p[0] - dir; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = k; + } + } + k = bsoln; + } + return pp[k].p[0]; +} + +/* Delete an xcal */ +static void xcal_del(xcal *p) { + int j; + + if (p->xpi.deviceMfgDesc != NULL) + free(p->xpi.deviceMfgDesc); + if (p->xpi.modelDesc != NULL) + free(p->xpi.modelDesc); + if (p->xpi.profDesc != NULL) + free(p->xpi.profDesc); + if (p->xpi.copyright != NULL) + free(p->xpi.copyright); + + for (j = 0; j < p->devchan; j++) { + if (p->cals[j] != NULL) + p->cals[j]->del(p->cals[j]); + } + free(p); +} + +/* Create a new, uninitialised xcal */ +xcal *new_xcal(void) { + xcal *p; + + if ((p = (xcal *)calloc(1, sizeof(xcal))) == NULL) + return NULL; + + /* Init method pointers */ + p->del = xcal_del; + p->read_cgats = xcal_read_cgats; + p->read = xcal_read; + p->write_cgats = xcal_write_cgats; + p->write = xcal_write; + p->interp = xcal_interp; + p->inv_interp = xcal_inv_interp; + p->interp_ch = xcal_interp_ch; + p->inv_interp_ch = xcal_inv_interp_ch; + + return p; +} + diff --git a/xicc/xcal.h b/xicc/xcal.h new file mode 100644 index 0000000..99f1c9c --- /dev/null +++ b/xicc/xcal.h @@ -0,0 +1,78 @@ + +#ifndef XCAL_H +#define XCAL_H + +/* + * Argyll Color Correction System + * Calibration curve class. + * + * Author: Graeme W. Gill + * Date: 30/10/2005 + * + * Copyright 2005 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * This class allows reading and using a calibration file. + * Creation is currently left up to specialized programs (dispcal, printcal). + * This is also used by xicc to automatically take calibration into account + * when computing ink limits. + */ + +struct _xcal { + + /* Public: */ + void (*del)(struct _xcal *p); + + /* Read a calibration file from a CGATS table */ + /* Return nz if this fails (filename is for error messages) */ + int (*read_cgats) (struct _xcal *p, cgats *cg, int table, char *filename); + + /* Read a calibration file */ + /* Return nz if this fails */ + int (*read) (struct _xcal *p, char *filename); + + /* Write a calibration to a new cgats table */ + /* Return nz if this fails */ + int (*write_cgats)(struct _xcal *p, cgats *tcg); + + /* Write a calibration file */ + /* Return nz if this fails */ + int (*write)(struct _xcal *p, char *filename); + + /* Translate values through the curves curves. */ + void (*interp) (struct _xcal *p, double *out, double *in); + + /* Translate a value backwards through the curves. */ + /* Return nz if the inversion fails */ + int (*inv_interp) (struct _xcal *p, double *out, double *in); + + /* Translate a value through one of the curves */ + double (*interp_ch) (struct _xcal *p, int ch, double in); + + /* Translate a value backwards through one of the curves */ + /* Return -1.0 if the inversion fails */ + double (*inv_interp_ch) (struct _xcal *p, int ch, double in); + + int noramdac; /* Set to nz if there was no VideoLUT access */ + + /* Private: */ + icProfileClassSignature devclass; /* Type of device */ + inkmask devmask; /* ICX ink mask of device space */ + icColorSpaceSignature colspace; /* Corresponding ICC device space sig (0 if none) */ + int devchan; /* Number of chanels in device space */ + profxinf xpi; /* Extra calibration description information */ + + char err[CGATS_ERRM_LENGTH]; /* Error message */ + int errc; /* Error code */ + + rspl *cals[MAX_CHAN]; + +}; typedef struct _xcal xcal; + +/* Create a new, uninitialised xcal */ +xcal *new_xcal(void); + +#endif /* XCAL */ + diff --git a/xicc/xcam.c b/xicc/xcam.c new file mode 100644 index 0000000..99f3c67 --- /dev/null +++ b/xicc/xcam.c @@ -0,0 +1,214 @@ + +/* + * Abstract interface to color appearance model transforms. + * + * This is to allow the rest of Argyll to use a default CAM. + * + * Author: Graeme W. Gill + * Date: 25/7/2004 + * Version: 1.00 + * + * Copyright 2004 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "xcam.h" +#include "cam97s3.h" +#include "cam02.h" + +static void icx_cam_free(icxcam *s); +static int icx_set_view(icxcam *s, ViewingCondition Ev, double Wxyz[3], + double La, double Yb, double Lv, double Yf, double Fxyz[3], + int hk); +static int icx_XYZ_to_cam(struct _icxcam *s, double Jab[3], double XYZ[3]); +static int icx_cam_to_XYZ(struct _icxcam *s, double XYZ[3], double Jab[3]); +static void settrace(struct _icxcam *s, int tracev); + +/* Return the default CAM */ +icxCAM icxcam_default(void) { +// return cam_CIECAM97s3; + return cam_CIECAM02; +} + +/* Return a string describing the given CAM */ +char *icxcam_description(icxCAM which) { + if (which == cam_default) + which = icxcam_default(); + + switch(which) { + case cam_CIECAM97s3: + return "CIECAM97s3"; + case cam_CIECAM02: + return "CIECAM02"; + default: + break; + } + return "Unknown CAM"; +} + +/* Create a cam conversion object */ +icxcam *new_icxcam(icxCAM which) { + icxcam *s; + + if ((s = (icxcam *)calloc(1, sizeof(icxcam))) == NULL) { + fprintf(stderr,"icxcam: malloc failed allocating object\n"); + return NULL; + } + + /* Initialise methods */ + s->del = icx_cam_free; + s->set_view = icx_set_view; + s->XYZ_to_cam = icx_XYZ_to_cam; + s->cam_to_XYZ = icx_cam_to_XYZ; + s->settrace = settrace; + + /* We set the default CAM here */ + if (which == cam_default) + which = icxcam_default(); + + s->tag = which; + + switch(which) { + case cam_CIECAM97s3: + if ((s->p = (void *)new_cam97s3()) == NULL) { + fprintf(stderr,"icxcam: malloc failed allocating object\n"); + free(s); + return NULL; + } + break; + case cam_CIECAM02: + if ((s->p = (void *)new_cam02()) == NULL) { + fprintf(stderr,"icxcam: malloc failed allocating object\n"); + free(s); + return NULL; + } + break; + + default: + fprintf(stderr,"icxcam: unknown CAM type\n"); + free(s); + return NULL; + } + return s; +} + +static void icx_cam_free(icxcam *s) { + if (s != NULL) { + switch(s->tag) { + case cam_CIECAM97s3: { + cam97s3 *pp = (cam97s3 *)s->p; + pp->del(pp); + break; + } + case cam_CIECAM02: { + cam02 *pp = (cam02 *)s->p; + pp->del(pp); + break; + } + default: + break; + } + free(s); + } +} + +static int icx_set_view( +icxcam *s, +ViewingCondition Ev, /* Enumerated Viewing Condition */ +double Wxyz[3], /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ +double La, /* Adapting/Surround Luminance cd/m^2 */ +double Yb, /* Relative Luminance of Background to reference white */ +double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set to other than vc_none */ +double Yf, /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ +double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ +int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ +) { + s->Wxyz[0] = Wxyz[0]; + s->Wxyz[1] = Wxyz[1]; + s->Wxyz[2] = Wxyz[2]; + + switch(s->tag) { + case cam_CIECAM97s3: { + cam97s3 *pp = (cam97s3 *)s->p; + return pp->set_view(pp, Ev, Wxyz, La, Yb, Lv, Yf, Fxyz, hk); + } + case cam_CIECAM02: { + cam02 *pp = (cam02 *)s->p; + return pp->set_view(pp, Ev, Wxyz, La, Yb, Lv, Yf, Fxyz, hk); + } + default: + break; + } + return 0; +} + +/* Conversions */ +static int icx_XYZ_to_cam( +struct _icxcam *s, +double Jab[3], +double XYZ[3] +) { + switch(s->tag) { + case cam_CIECAM97s3: { + cam97s3 *pp = (cam97s3 *)s->p; + return pp->XYZ_to_cam(pp, Jab, XYZ); + } + case cam_CIECAM02: { + cam02 *pp = (cam02 *)s->p; + return pp->XYZ_to_cam(pp, Jab, XYZ); + } + default: + break; + } + return 0; +} + +static int icx_cam_to_XYZ( +struct _icxcam *s, +double XYZ[3], +double Jab[3] +) { + switch(s->tag) { + case cam_CIECAM97s3: { + cam97s3 *pp = (cam97s3 *)s->p; + return pp->cam_to_XYZ(pp, XYZ, Jab); + } + case cam_CIECAM02: { + cam02 *pp = (cam02 *)s->p; + return pp->cam_to_XYZ(pp, XYZ, Jab); + } + default: + break; + } + return 0; +} + +/* Debug */ +static void settrace( +struct _icxcam *s, +int tracev +) { + switch(s->tag) { + case cam_CIECAM97s3: { + cam97s3 *pp = (cam97s3 *)s->p; + pp->trace = tracev; + } + case cam_CIECAM02: { + cam02 *pp = (cam02 *)s->p; + pp->trace = tracev; + } + default: + break; + } +} + + + + + diff --git a/xicc/xcam.h b/xicc/xcam.h new file mode 100644 index 0000000..d4f4857 --- /dev/null +++ b/xicc/xcam.h @@ -0,0 +1,82 @@ + +#ifndef _XCAM_H_ + +/* + * Abstract interface to color appearance model transforms. + * + * This is to allow the rest of Argyll to use a default CAM. + * + * Author: Graeme W. Gill + * Date: 25/7/2004 + * Version: 1.00 + * + * Copyright 2004 Graeme W. Gill + * Please refer to COPYRIGHT file for details. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* ---------------------------------- */ + +/* The range of CAMs supported */ +typedef enum { + cam_default = 0, /* Default CAM */ + cam_CIECAM97s3 = 1, /* CIECAM97, version 3 */ + cam_CIECAM02 = 2 /* CIECAM02 */ +} icxCAM; + +/* The enumerated viewing conditions: */ +typedef enum { + vc_notset = -1, + vc_none = 0, /* Figure out from Lv and La */ + vc_dark = 1, + vc_dim = 2, + vc_average = 3, + vc_cut_sheet = 4 /* Transparencies on a Light Box */ +} ViewingCondition; + +struct _icxcam { +/* Public: */ + void (*del)(struct _icxcam *s); /* We're done with it */ + + /* Always returns 0 */ + int (*set_view)( + struct _icxcam *s, + ViewingCondition Ev, /* Enumerated Viewing Condition */ + double Wxyz[3], /* Reference/Adapted White XYZ (Y scale 1.0) */ + double La, /* Adapting/Surround Luminance cd/m^2 */ + double Yb, /* Luminance of Background relative to reference white (range 0.0 .. 1.0) */ + double Lv, /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set */ + double Yf, /* Flare as a fraction of the reference white (range 0.0 .. 1.0) */ + double Fxyz[3], /* The Flare white coordinates (typically the Ambient color) */ + int hk /* Flag, NZ to use Helmholtz-Kohlraush effect */ + ); + + /* Conversions */ + int (*XYZ_to_cam)(struct _icxcam *s, double *out, double *in); + int (*cam_to_XYZ)(struct _icxcam *s, double *out, double *in); + + /* Debug */ + void (*settrace)(struct _icxcam *s, int tracev); + +/* Private: */ + icxCAM tag; /* Type */ + void *p; /* Pointer to implementation */ + double Wxyz[3]; /* Copy of Wxyz */ + +}; typedef struct _icxcam icxcam; + +/* Create a new CAM conversion object */ +icxcam *new_icxcam(icxCAM which); + +/* Return the default CAM */ +icxCAM icxcam_default(); + +/* Return a string describing the given CAM */ +char *icxcam_description(icxCAM ct); + +#define _XCAM_H_ +#endif /* _XCAM_H_ */ + + diff --git a/xicc/xcolorants.c b/xicc/xcolorants.c new file mode 100644 index 0000000..84a7720 --- /dev/null +++ b/xicc/xcolorants.c @@ -0,0 +1,768 @@ + +/* + * International Color Consortium color transform expanded support + * Known colorants support. + * + * Author: Graeme W. Gill + * Date: 24/2/2002 + * Version: 1.00 + * + * Copyright 2002 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* TTBD: + Would like to have some short string way of defining whether + the ink combination is additive or subtractive, instead + of just guessing. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "icc.h" +#include "xcolorants.h" +#include "sort.h" + +/* Colorant table for N color device characterisation. */ +/* This is ordered to match the ICC colorspace convention. */ +/* NOTE:- need to keep these in same order as ink mask */ +/* enumeration (lsb to msb), or strange things result! */ +static struct { + inkmask m; /* Mask value */ + char *c; /* 1/2 Character name */ + char *s; /* Everyday name */ + char *ps; /* Postscript colorant name */ + double aXYZ[3]; /* Rough XYZ values (0..1) for that additive colorant */ + double sXYZ[3]; /* Rough XYZ values (0..1) for that subtractive colorant */ +} icx_ink_table[] = { + { ICX_CYAN, ICX_C_CYAN, ICX_S_CYAN, ICX_PS_CYAN, + { 0.12, 0.18, 0.48 }, + { 0.12, 0.18, 0.48 } }, + { ICX_MAGENTA, ICX_C_MAGENTA, ICX_S_MAGENTA, ICX_PS_MAGENTA, + { 0.38, 0.19, 0.20 }, + { 0.38, 0.19, 0.20 } }, + { ICX_YELLOW, ICX_C_YELLOW, ICX_S_YELLOW, ICX_PS_YELLOW, + { 0.76, 0.81, 0.11 }, + { 0.76, 0.81, 0.11 } }, + { ICX_BLACK, ICX_C_BLACK, ICX_S_BLACK, ICX_PS_BLACK, + { 0.01, 0.01, 0.01 }, + { 0.04, 0.04, 0.04 } }, + { ICX_ORANGE, ICX_C_ORANGE, ICX_S_ORANGE, ICX_PS_ORANGE, + { 0.59, 0.41, 0.03 }, + { 0.59, 0.41, 0.05 } }, + { ICX_RED, ICX_C_RED, ICX_S_RED, ICX_PS_RED, + { 0.412414, 0.212642, 0.019325 }, + { 0.40, 0.21, 0.05 } }, + { ICX_GREEN, ICX_C_GREEN, ICX_S_GREEN, ICX_PS_GREEN, + { 0.357618, 0.715136, 0.119207 }, + { 0.11, 0.27, 0.21 } }, + { ICX_BLUE, ICX_C_BLUE, ICX_S_BLUE, ICX_PS_BLUE, + { 0.180511, 0.072193, 0.950770 }, + { 0.11, 0.27, 0.47 } }, + { ICX_WHITE, ICX_C_WHITE, ICX_S_WHITE, ICX_PS_WHITE, + { 0.950543, 1.0, 1.089303 }, /* D65 ? */ + { 0.9642, 1.00, 0.8249 } }, /* D50 */ + { ICX_LIGHT_CYAN, ICX_C_LIGHT_CYAN, ICX_S_LIGHT_CYAN, ICX_PS_LIGHT_CYAN, + { 0.76, 0.89, 1.08 }, + { 0.76, 0.89, 1.08 } }, + { ICX_LIGHT_MAGENTA, ICX_C_LIGHT_MAGENTA, ICX_S_LIGHT_MAGENTA, ICX_PS_LIGHT_MAGENTA, + { 0.83, 0.74, 1.02 }, + { 0.83, 0.74, 1.02 } }, + { ICX_LIGHT_YELLOW, ICX_C_LIGHT_YELLOW, ICX_S_LIGHT_YELLOW, ICX_PS_LIGHT_YELLOW, + { 0.88, 0.97, 0.72 }, + { 0.88, 0.97, 0.72 } }, + { ICX_LIGHT_BLACK, ICX_C_LIGHT_BLACK, ICX_S_LIGHT_BLACK, ICX_PS_LIGHT_BLACK, + { 0.56, 0.60, 0.65 }, + { 0.56, 0.60, 0.65 } }, + { ICX_MEDIUM_CYAN, ICX_C_MEDIUM_CYAN, ICX_S_MEDIUM_CYAN, ICX_PS_MEDIUM_CYAN, + { 0.61, 0.81, 1.07 }, + { 0.61, 0.81, 1.07 } }, + { ICX_MEDIUM_MAGENTA, ICX_C_MEDIUM_MAGENTA, ICX_S_MEDIUM_MAGENTA, ICX_PS_MEDIUM_MAGENTA, + { 0.74, 0.53, 0.97 }, + { 0.74, 0.53, 0.97 } }, + { ICX_MEDIUM_YELLOW, ICX_C_MEDIUM_YELLOW, ICX_S_MEDIUM_YELLOW, ICX_PS_MEDIUM_YELLOW, + { 0.82, 0.93, 0.40 }, + { 0.82, 0.93, 0.40 } }, + { ICX_MEDIUM_BLACK, ICX_C_MEDIUM_BLACK, ICX_S_MEDIUM_BLACK, ICX_PS_MEDIUM_BLACK, + { 0.27, 0.29, 0.31 }, + { 0.27, 0.29, 0.31 } }, + { ICX_LIGHT_LIGHT_BLACK, ICX_C_LIGHT_LIGHT_BLACK, ICX_S_LIGHT_LIGHT_BLACK, ICX_PS_LIGHT_LIGHT_BLACK, + { 0.76, 0.72, 0.65 }, /* Very rough - should substiture real numbers */ + { 0.76, 0.72, 0.65 } }, + { 0, "", "", "", { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } } +}; + + +/* Colorant table for N color device characterisation */ +static struct { + inkmask m; /* Mask combination */ + inkmask rm; /* Light ink reduced mask combination */ + icColorSpaceSignature psig; /* Appropriate primary ICC signature */ + icColorSpaceSignature ssig; /* Appropriate secondary ICC signature */ + char *desc; /* Description */ +} icx_colcomb_table[] = { + { ICX_K, ICX_K, icSigGrayData, icSigGrayData, + "Print grey" }, + { ICX_W, ICX_W, icSigGrayData, icSigGrayData, + "Video grey" }, + { ICX_IRGB, ICX_IRGB, icSigRgbData, icSigRgbData, + "Print RGB" }, + { ICX_RGB, ICX_RGB, icSigRgbData, icSigRgbData, + "Video RGB" }, + { ICX_CMYK, ICX_CMYK, icSigCmykData, icSigCmykData, + "CMYK" }, + { ICX_CMY, ICX_CMY, icSigCmyData, icSigCmyData, + "CMY" }, + { ICX_CMYKcm, ICX_CMYK, icSig6colorData, icSigMch6Data, + "CMYK + Light CM" }, + { ICX_CMYKcmk, ICX_CMYK, icSig7colorData, icSig7colorData, + "CMYK + Light CMK" }, + { ICX_CMYKRB, ICX_W, icSig6colorData, icSigMch6Data, + "CMYK + Red + Blue" }, + { ICX_CMYKOG, ICX_W, icSig6colorData, icSigMch6Data, + "CMYK + Orange + Green" }, + { ICX_CMYKcmk1k, ICX_CMYK, icSig8colorData, icSigMch8Data, + "CMYK + Light CMK + Light Light K" }, + { ICX_CMYKOGcm, ICX_CMYKOG, icSig8colorData, icSigMch8Data, + "CMYK + Orange + Green + Light CM" }, + { ICX_CMYKcm2c2m, ICX_CMYK, icSig8colorData, icSigMch8Data, + "CMYK + Light CM + Medium CM" }, + { 0, 0, 0, 0, "" } +}; + + +/* Given an ink combination mask, return the number of recognised inks in it */ +int icx_noofinks(inkmask mask) { + int i, count = 0; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask & icx_ink_table[i].m) { + count++; + } + } + return count; +} + +/* Given an ink combination mask, return the 1-2 character based string */ +/* If winv is nz, include ICX_INVERTED indicator if set */ +/* Return NULL on error. free() after use */ +char *icx_inkmask2char(inkmask mask, int winv) { + int i; + char *rv; + + if ((rv = malloc(2 * ICX_MXINKS + 1)) == NULL) + return NULL; + + *rv = '\000'; + + if (winv && (mask & ICX_INVERTED)) + strcat(rv, "i"); + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask & icx_ink_table[i].m) { + strcat(rv, icx_ink_table[i].c); + } + } + + return rv; +} + +/* Given the 1-2 character string, return the ink combination mask */ +/* Note that ICX_ADDITIVE will be guessed */ +/* Return 0 if unrecognised character in string */ +inkmask icx_char2inkmask(char *chstring) { + inkmask mask = 0; + int k, i; + char *cp; + + cp = chstring; + + for (k = 0; ; k++) { + + if (*cp == '\000') { + break; + } + + /* "Inverted" prefix ? */ + if (k == 0 && *cp == 'i') { + mask |= ICX_INVERTED; + cp++; + continue; + } + /* Colorant ? */ + for (i = 0; icx_ink_table[i].m != 0; i++) { + + if (strncmp(cp, icx_ink_table[i].c, strlen(icx_ink_table[i].c)) == 0) { + mask |= icx_ink_table[i].m; + cp += strlen(icx_ink_table[i].c); + break; + } + } + if (icx_ink_table[i].m == 0) { /* oops - unrecognised character */ + return 0; + } + } + + /* Check it out againts known combinations to guess additive */ + for (i = 0; icx_colcomb_table[i].m != 0; i++) { + if ((icx_colcomb_table[i].m & ~ICX_ADDITIVE) == mask) { + mask = icx_colcomb_table[i].m; + break; + } + } + + return mask; +} + +/* Given an ink combination mask that may contain light inks, */ +/* return the corresponding ink mask without light inks. */ +/* Return 0 if ink combination not recognised. */ +inkmask icx_ink2primary_ink(inkmask mask) { + int i; + for (i = 0; icx_colcomb_table[i].m != 0; i++) { + if (mask == icx_colcomb_table[i].m) { + return icx_colcomb_table[i].rm; + } + } + return 0; +} + + +/* Given an ink combination mask and a single ink mask, */ +/* return the index number for that ink. */ +/* Return -1 if mask1 not in mask */ +int icx_ink2index(inkmask mask, inkmask mask1) { + int i, count = 0; + + if ((mask1 & mask) == 0) + return -1; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask1 == icx_ink_table[i].m) { + return count; + } + if (mask & icx_ink_table[i].m) { + count++; + } + } + + return -1; +} + +/* Given an ink combination mask and a index number, */ +/* return the single ink mask. */ +/* Return 0 if there are no inks at that index */ +inkmask icx_index2ink(inkmask mask, int ixno) { + int i, count = 0; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask & icx_ink_table[i].m) { + if (ixno == count) + return icx_ink_table[i].m; + count++; + } + } + return 0; +} + + +/* Given a single ink mask, */ +/* return its string representation */ +char *icx_ink2string(inkmask mask) { + int i; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask == icx_ink_table[i].m) + return icx_ink_table[i].s; + } + return NULL; +} + +/* Given a single ink mask, */ +/* return its 1-2 character representation */ +char *icx_ink2char(inkmask mask) { + int i; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask == icx_ink_table[i].m) + return icx_ink_table[i].c; + } + return NULL; +} + +/* Given a single ink mask, */ +/* return its Postscript string representation */ +char *icx_ink2psstring(inkmask mask) { + int i; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (mask == icx_ink_table[i].m) + return icx_ink_table[i].ps; + } + return NULL; +} + + +/* Return an enumerated single colorant description */ +/* Return 0 if no such enumeration, single colorant mask if there is */ +inkmask icx_enum_colorant( +int no, /* Enumeration mask index */ +char **desc /* Return enumeration description */ +) { + int i; + + for (i = 0; icx_ink_table[i].m != 0; i++) { + if (i == no) { + if (desc != NULL) + *desc = icx_ink_table[i].s; + return icx_ink_table[i].m; + } + } + return 0; +} + +/* Return an enumerated colorant combination */ +/* Return 0 if no such enumeration, colorant combination mask if there is */ +inkmask icx_enum_colorant_comb( +int no, /* Enumeration mask index */ +char **desc /* Return enumeration description */ +) { + int i; + + for (i = 0; icx_colcomb_table[i].m != 0; i++) { + if (i == no) { + if (desc != NULL) + *desc = icx_colcomb_table[i].desc; + return icx_colcomb_table[i].m; + } + } + return 0; +} + + +/* Given an colorant combination mask, */ +/* check if it matches the given ICC colorspace signature. */ +/* return NZ if it does. */ +/* (We don't check colorant colors for multi-colorant devices though) */ +int icx_colorant_comb_match_icc( +inkmask mask, /* Colorant combination mask */ +icColorSpaceSignature sig /* ICC signature */ +) { + int i; + for (i = 0; icx_colcomb_table[i].m != 0; i++) { + if (mask == icx_colcomb_table[i].m) { + if (sig == icx_colcomb_table[i].psig + || sig == icx_colcomb_table[i].ssig) { + return 1; + } else { + return 0; + } + } + } + return 0; +} + +/* Given an ICC colorspace signature, return the appropriate */ +/* colorant combination mask. Return 0 if ambiguous signature. */ +inkmask icx_icc_to_colorant_comb(icColorSpaceSignature sig, icProfileClassSignature deviceClass) { + switch (sig) { + case icSigGrayData: + if (deviceClass == icSigOutputClass) + return ICX_K; + return ICX_W; + + case icSigCmyData: + return ICX_CMY; + + case icSigRgbData: + if (deviceClass == icSigOutputClass) + return ICX_IRGB; + return ICX_RGB; + + case icSigCmykData: + return ICX_CMYK; + + default: + break; + } + return 0; +} + +/* Given an ICC colorspace signature, and a matching list */ +/* of the D50 L*a*b* colors of the colorants, return the best matching */ +/* colorant combination mask. Return 0 if not an applicable colorspace. */ +/* (Note we're not dealing with colorant order here.) */ +inkmask icx_icc_cv_to_colorant_comb( +icColorSpaceSignature sig, /* Input ICC colorspace signature */ +icProfileClassSignature deviceClass, /* Device class */ +double cvals[][3] /* Input L*a*b* colorant values */ +) { + int i, j; + int imask; + int ninks, ncol; + double slab[ICX_MXINKS][3]; + double alab[ICX_MXINKS][3]; + typedef struct { int x; double v; } mchstr; + mchstr mch[MAX_CHAN][ICX_MXINKS]; /* match index */ + int used[ICX_MXINKS]; + int co[ICX_MXINKS]; /* Combination counter */ + double cmv; /* Combination match value */ + int bco[ICX_MXINKS]; /* Best Combinat */ + double bcmv; /* Best Match value */ + int order[MAX_CHAN]; /* Place holder, not currently used - return ink order */ + + switch (sig) { + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYCbCrData: + case icSigYxyData: + case icSigHsvData: + case icSigHlsData: + case icSigNamedData: + return 0; + + case icSigGrayData: + if (deviceClass == icSigOutputClass) + return ICX_K; + return ICX_W; + + case icSigCmyData: + return ICX_CMY; + + case icSigRgbData: + if (deviceClass == icSigOutputClass) + return ICX_IRGB; + return ICX_RGB; + + case icSigCmykData: + return ICX_CMYK; + + default: + break; + + } +#ifdef NEVER /* Rely on device class (above) */ + if (sig == icSigGrayData) { + /* This only works reliably if we got the Lab data from a non-ICC */ + /* conformant monochrome profile (one that doesn't force device 0 */ + /* input of the grayTRTC to black), or uses a LUT based monochrome */ + /* profile. */ + /* It also won't work if someone uses a monochrome profile for a */ + /* device with a non grey colorant. */ + /* Really ICC should have icSigWhiteData, icSigBlackData, icSig1colorData ? */ + if (cvals[0][0] > 50.0) { + return ICX_W; + } else { + return ICX_K; + } + } +#endif /* NEVER */ + + /* Compute Lab values of stock inks, and count them */ + for (ninks = 0; ninks < ICX_MXINKS; ninks++) { + if (icx_ink_table[ninks].m == 0) + break; + icmXYZ2Lab(&icmD50, slab[ninks], icx_ink_table[ninks].sXYZ); + icmXYZ2Lab(&icmD50, alab[ninks], icx_ink_table[ninks].aXYZ); + } + + ncol = icmCSSig2nchan(sig); /* Number of colorants */ + + /* Compute ideal matching of device colorants to stock inks */ + for (i = 0; i < ncol; i++) { + for (j = 0; j < ninks; j++) { + double tt; + mch[i][j].x = j; + mch[i][j].v = icmCIE94sq(cvals[i], slab[j]); + tt = icmCIE94sq(cvals[i], alab[j]); + if (tt < mch[i][j].v) + mch[i][j].v = tt; + } + /* Sort the matches for this colorant */ +#define HEAP_COMPARE(A,B) (A.v < B.v) + HEAPSORT(mchstr, mch[i], ninks) +#undef HEAP_COMPARE + +//printf("\n~1 Colorant %d has best matches\n",i); +//for (j = 0; j < ninks; j++) +//printf("~1 ix %d color = '%s' dE %f\n",j, icx_ink_table[mch[i][j].x].s, mch[i][j].v); + } + + /* Do exaustive combination search, using early */ + /* out to keep combination count down.*/ + + /* Reset the combination counter */ + for (j = 0; j < ninks; j++) /* Reset ink used flag */ + used[j] = 0; + cmv = 0.0; + for (i = ncol-1; i >= 0; i--) { + for (j = 0; j < ninks; j++) { /* Set to lowest usable number */ + if (used[mch[i][j].x] == 0) { /* Can use this one */ + used[mch[i][j].x] = 1; /* Now it's matched */ + cmv += mch[i][j].v; + co[i] = j; + break; /* we assume ncol < ninks */ + } + } + } + +//printf("\n~1 Initial combination has cmv = %f\n",cmv); +//for (i = 0; i < ncol; i++) +//printf("~1 Chan %d color = '%s'\n",i, icx_ink_table[mch[i][co[i]].x].s); + + /* Set this as the best initial match */ + for (i = 0; i < ncol; i++) + bco[i] = co[i]; + bcmv = cmv; + + /* Now go through all other combinations */ + for (;;) { + + /* Increment counter */ + for (i = 0;;) { + + /* Work up the digits, incrementing each one */ + for (; i < ncol; i++) { + j = co[i]; + used[mch[i][j].x] = 0; + cmv -= mch[i][j].v; + while (++j < ninks && (used[mch[i][j].x] != 0 || (cmv + mch[i][j].v) >= bcmv)) { + }; + if (j < ninks) { /* No carry */ + used[mch[i][j].x] = 1; + cmv += mch[i][j].v; + co[i] = j; + break; + } + /* Carry to next colorant */ + } + if (i >= ncol) + break; /* Run out of digits to increment */ + + /* Now work down again, resetting digit */ + for (--i; i >= 0; i--) { + for (j = 0; j < ninks; j++) { /* Set to lowest usable number */ + if (used[mch[i][j].x] == 0 && (cmv + mch[i][j].v) < bcmv) { + /* Can use this one */ + used[mch[i][j].x] = 1; /* Now it's matched */ + cmv += mch[i][j].v; + co[i] = j; + break; + } + } + if (j >= ninks) { /* No combination is feasible */ + i++; /* Go back to incrementing next highest digit */ + break; + } + } + if (i < 0) /* We reset all the lower digits */ + break; /* We're at a good combination, so done. */ + } + if (i >= ncol) + break; /* We're done all combinations */ + +//printf("\n~1 New combination has cmv = %f\n",cmv); +//for (i = 0; i < ncol; i++) +//printf("~1 Chan %d color = '%s'\n",i, icx_ink_table[mch[i][co[i]].x].s); + + /* See if this is better (it always should be !) */ + if (cmv < bcmv) { + for (i = 0; i < ncol; i++) + bco[i] = co[i]; + bcmv = cmv; + } + } + + /* Compile the result */ + for (imask = 0, i = 0; i < ncol; i++) { + imask |= icx_ink_table[mch[i][bco[i]].x].m; + order[i] = bco[i]; /* icx ink_table index corresponding to channel */ + } + + /* Slight hack to recognise some additive combinations */ + if (imask == ICX_WHITE) + imask = ICX_W; + else if (imask == (ICX_RED | ICX_GREEN | ICX_BLUE)) + imask = ICX_RGB; + + return imask; +} + +/* Given a colorant combination mask */ +/* return the primary matching ICC colorspace signature. */ +/* return 0 if there is no match */ +icColorSpaceSignature icx_colorant_comb_to_icc( +inkmask mask /* Colorant combination mask */ +) { + int i; + + for (i = 0; icx_colcomb_table[i].m != 0; i++) { + if (mask == icx_colcomb_table[i].m) + return icx_colcomb_table[i].psig; + } + return 0; +} + + + +/* - - - - - - - - - - - - - - - - - */ +/* Approximate device colorant model */ + +/* Given device values, return an estimate of the XYZ value for that color. */ +static void icxColorantLu_to_XYZ( +icxColorantLu *s, +double XYZ[3], /* Output */ +double d[ICX_MXINKS] /* Input */ +) { + int e, j; + + if (s->mask & ICX_ADDITIVE ) { + /* We assume a simple additive model with gamma */ + + XYZ[0] = XYZ[1] = XYZ[2] = 0.0; + + for (e = 0; e < s->di; e++) { + double v = d[e]; + + if (v < 0.0) + v = 0.0; + else if (v > 1.0) + v = 1.0; + if (v <= 0.03928) + v /= 12.92; + else + v = pow((0.055 + v)/1.055, 2.4); /* Gamma */ + + for (j = 0; j < 3; j++) + XYZ[j] += v * icx_ink_table[s->iix[e]].aXYZ[j]; + } + + /* Normalise Y to 1.0, & add black glare */ + for (j = 0; j < 3; j++) { + XYZ[j] *= s->Ynorm; + XYZ[j] = XYZ[j] * (1.0 - icx_ink_table[s->bkix].aXYZ[j]) + icx_ink_table[s->bkix].aXYZ[j]; + } + + } else { + /* We assume a simple screened subtractive filter model, with dot gain */ + + /* start with white */ + XYZ[0] = icx_ink_table[s->whix].sXYZ[0]; + XYZ[1] = icx_ink_table[s->whix].sXYZ[1]; + XYZ[2] = icx_ink_table[s->whix].sXYZ[2]; + + /* And filter it out for each component */ + for (e = 0; e < s->di; e++) { + double v = d[e]; + + if (v < 0.0) + v = 0.0; + else if (v > 1.0) + v = 1.0; + v = 1.0 - pow(1.0 - v, 2.2); /* Compute dot gain */ + + for (j = 0; j < 3; j++) { + double fv; + + /* Normalise filtering effect of this colorant */ + fv = icx_ink_table[s->iix[e]].aXYZ[j]/icx_ink_table[s->whix].sXYZ[j]; + + /* Compute screened filtering effect */ + fv = (1.0 - v) + v * fv; + + /* Apply filter to our current value */ + XYZ[j] *= fv; + } + } + } +} + +/* Given device values, return an estimate of the */ +/* relative Lab value for that color. */ +static void icxColorantLu_to_rLab( +icxColorantLu *s, +double Lab[3], /* Output */ +double d[ICX_MXINKS] /* Input */ +) { + + icxColorantLu_to_XYZ(s, Lab, d); /* Compute XYZ */ + icmXYZ2Lab(&s->wp, Lab, Lab); /* Convert from XYZ to Lab */ +} + +/* We're done with aproximate device model */ +static void icxColorantLu_del(icxColorantLu *s) { + + if (s != NULL) { + free(s); + } +} + +/* Given an ink definition, return an aproximate */ +/* device to CIE color converted object. */ +icxColorantLu *new_icxColorantLu(inkmask mask) { + int i, e; + icxColorantLu *s; + + if ((s = (icxColorantLu *)malloc(sizeof(icxColorantLu))) == NULL) { + fprintf(stderr,"icxColorantLu: malloc failed allocating object\n"); + exit(-1); + } + + /* Initialise methods */ + s->del = icxColorantLu_del; + s->dev_to_XYZ = icxColorantLu_to_XYZ; + s->dev_to_rLab = icxColorantLu_to_rLab; + + /* Init */ + s->mask = mask; + + for (e = i = 0; icx_ink_table[i].m != 0; i++) { + if (ICX_WHITE == icx_ink_table[i].m) + s->whix = i; + if (ICX_BLACK == icx_ink_table[i].m) + s->bkix = i; + if (mask & icx_ink_table[i].m) + s->iix[e++] = i; + } + s->di = e; + + + s->Ynorm = 0.0; + if (mask & ICX_ADDITIVE ) { + for (e = 0; e < s->di; e++) + s->Ynorm += icx_ink_table[s->iix[e]].aXYZ[1]; + s->Ynorm = 1.0/s->Ynorm; + icmAry2XYZ(s->wp, icx_ink_table[s->whix].aXYZ); + } else { + icmAry2XYZ(s->wp, icx_ink_table[s->whix].sXYZ); + } + + return s; +} + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xcolorants.h b/xicc/xcolorants.h new file mode 100644 index 0000000..460c4b0 --- /dev/null +++ b/xicc/xcolorants.h @@ -0,0 +1,326 @@ +#ifndef XCOLORANTS_H +#define XCOLORANTS_H +/* + * International Color Consortium color transform expanded support + * Known colorant definitions. + * + * Author: Graeme W. Gill + * Date: 24/2/2002 + * Version: 1.00 + * + * Copyright 2002 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* Some standard defines for known generic ink colors */ + +/* + Note that we don't handle the issue of an arbitrary ink order, + and we are only partially coping with multi-ink ICC profiles + mapping to xcolorants and back. This is supported by mapping + inkmask to ICC ColorantTable names and back (if needed), + or by guessing colorants to match profile (icx_icc_cv_to_colorant_comb()). + + default xcolorant order matches the concrete ICC order, but + ICC icSigXXcolorData could be in any order, and xcolorants + currently doesn't handle it. + + One way of encapsulating things would be to change inkmask to: + + struct { + unsigned int attr; // Attributes (ie. Additive) + unsigned int mask[(ICX_MXINKS + 31)/32]; // Ink masks + int inks[MAX_CHAN]; // icx_ink_table[] index + } inkmask; + + Then add appropriate macros & functions to replace current bit mask logic. + + Would it be easier to make all Argyll internal handling conform + to the standard xcolorants order, and translate in the ICC file I/O ? + MPP and .ti? files are assumed/made to conform to xcolorants order. + + Another change would be to make xcolorants an object, + with dynamic colorant and colorant combination values. + These would be initialised to defaults, but could then + be added to at run time. + + Handling the "colorant" of non-device type color channels + is also a challenge (ie., Lab, Hsv etc.) + + */ + +/* Maximum number of simultanious inks allowed for */ +#define ICX_MXINKS 31 + +/* Note that new inks need to be added to icx_ink_table[] in xcolorants.c ! */ + +/* The type of the inkmask (allow for expansion) */ +typedef unsigned int inkmask; + +/* The ink mask enumeration */ +#define ICX_ADDITIVE 0x80000000 /* Special flag indicating addive colorants */ +#define ICX_INVERTED 0x40000000 /* Special flag indicating actual device is */ + /* the inverse of the perported additive/subtractive */ +#define ICX_CYAN 0x00000001 +#define ICX_MAGENTA 0x00000002 +#define ICX_YELLOW 0x00000004 +#define ICX_BLACK 0x00000008 +#define ICX_ORANGE 0x00000010 +#define ICX_RED 0x00000020 +#define ICX_GREEN 0x00000040 +#define ICX_BLUE 0x00000080 +#define ICX_WHITE 0x00000100 +#define ICX_LIGHT_CYAN 0x00010000 +#define ICX_LIGHT_MAGENTA 0x00020000 +#define ICX_LIGHT_YELLOW 0x00040000 +#define ICX_LIGHT_BLACK 0x00080000 +#define ICX_MEDIUM_CYAN 0x00100000 +#define ICX_MEDIUM_MAGENTA 0x00200000 +#define ICX_MEDIUM_YELLOW 0x00400000 +#define ICX_MEDIUM_BLACK 0x00800000 +#define ICX_LIGHT_LIGHT_BLACK 0x01000000 + +/* Character representation */ +#define ICX_C_CYAN "C" +#define ICX_C_MAGENTA "M" +#define ICX_C_YELLOW "Y" +#define ICX_C_BLACK "K" +#define ICX_C_ORANGE "O" +#define ICX_C_RED "R" +#define ICX_C_GREEN "G" +#define ICX_C_BLUE "B" +#define ICX_C_WHITE "W" +#define ICX_C_LIGHT_CYAN "c" +#define ICX_C_LIGHT_MAGENTA "m" +#define ICX_C_LIGHT_YELLOW "y" +#define ICX_C_LIGHT_BLACK "k" +#define ICX_C_MEDIUM_CYAN "2c" +#define ICX_C_MEDIUM_MAGENTA "2m" +#define ICX_C_MEDIUM_YELLOW "2y" +#define ICX_C_MEDIUM_BLACK "2k" +#define ICX_C_LIGHT_LIGHT_BLACK "1k" + +/* Everyday String representation (max 31 chars) */ +#define ICX_S_CYAN "Cyan" +#define ICX_S_MAGENTA "Magenta" +#define ICX_S_YELLOW "Yellow" +#define ICX_S_BLACK "Black" +#define ICX_S_ORANGE "Orange" +#define ICX_S_RED "Red" +#define ICX_S_GREEN "Green" +#define ICX_S_BLUE "Blue" +#define ICX_S_WHITE "White" +#define ICX_S_LIGHT_CYAN "Light Cyan" +#define ICX_S_LIGHT_MAGENTA "Light Magenta" +#define ICX_S_LIGHT_YELLOW "Light Yellow" +#define ICX_S_LIGHT_BLACK "Light Black" +#define ICX_S_MEDIUM_CYAN "Medium Cyan" +#define ICX_S_MEDIUM_MAGENTA "Medium Magenta" +#define ICX_S_MEDIUM_YELLOW "Medium Yellow" +#define ICX_S_MEDIUM_BLACK "Medium Black" +#define ICX_S_LIGHT_LIGHT_BLACK "Light Light Black" + +/* Postscript string representation */ +#define ICX_PS_CYAN "Cyan" +#define ICX_PS_MAGENTA "Magenta" +#define ICX_PS_YELLOW "Yellow" +#define ICX_PS_BLACK "Black" +#define ICX_PS_ORANGE "Orange" +#define ICX_PS_RED "Red" +#define ICX_PS_GREEN "Green" +#define ICX_PS_BLUE "Blue" +#define ICX_PS_WHITE "White" +#define ICX_PS_LIGHT_CYAN "LightCyan" +#define ICX_PS_LIGHT_MAGENTA "LightMagenta" +#define ICX_PS_LIGHT_YELLOW "LightYellow" +#define ICX_PS_LIGHT_BLACK "LightBlack" +#define ICX_PS_MEDIUM_CYAN "MediumCyan" +#define ICX_PS_MEDIUM_MAGENTA "MediumMagenta" +#define ICX_PS_MEDIUM_YELLOW "MediumYellow" +#define ICX_PS_MEDIUM_BLACK "MediumBlack" +#define ICX_PS_LIGHT_LIGHT_BLACK "LightLightBlack" + +/* Common colorant combinations */ +#define ICX_W /* Video style "grey" */ \ + (ICX_ADDITIVE | ICX_WHITE) + +#define ICX_RGB /* Classic video RGB */ \ + (ICX_ADDITIVE | ICX_RED | ICX_GREEN | ICX_BLUE) + +#define ICX_K /* Printer style "grey" */ \ + (ICX_BLACK) + +#define ICX_CMY /* Classic printing CMY */ \ + (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW) + +#define ICX_IRGB /* Fake printer RGB (== Inverted CMY) */ \ + (ICX_ADDITIVE | ICX_INVERTED | ICX_RED | ICX_GREEN | ICX_BLUE) + +#define ICX_CMYK /* Classic printing CMYK */ \ + (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK) + +#define ICX_CMYKcm /* Your bog standard "6 color" printers */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_LIGHT_CYAN | ICX_LIGHT_MAGENTA) + +#define ICX_CMYKcmk /* A more unusual "7 color" printer */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_LIGHT_CYAN | ICX_LIGHT_MAGENTA | ICX_LIGHT_BLACK) + +#define ICX_CMYKcmk1k /* A more unusual "8 color" printer */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_LIGHT_CYAN | ICX_LIGHT_MAGENTA | ICX_LIGHT_BLACK \ + | ICX_LIGHT_LIGHT_BLACK) + +#define ICX_CMYKOG /* A "hexachrome" style extended gamut printer */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_ORANGE | ICX_GREEN) + +#define ICX_CMYKRB /* A 6 color printer with red and blue. */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_RED | ICX_BLUE) + +#define ICX_CMYKOGcm /* An 8 color extended gamut printer */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_ORANGE | ICX_GREEN | ICX_LIGHT_CYAN | ICX_LIGHT_MAGENTA) + +#define ICX_CMYKcm2c2m /* An 8 color printer that wishes it had variable dot size */ \ + ( ICX_CYAN | ICX_MAGENTA | ICX_YELLOW | ICX_BLACK \ + | ICX_LIGHT_CYAN | ICX_LIGHT_MAGENTA | ICX_MEDIUM_CYAN | ICX_MEDIUM_MAGENTA) + +/* ------------------------------------------ */ + +/* Given an ink combination mask, return the number of recognised inks in it */ +int icx_noofinks(inkmask mask); + +/* Given an ink combination mask, return the 1-2 character based string */ +/* If winv is nz, include ICX_INVERTED indicator if set */ +/* Return NULL on error. free() after use */ +char *icx_inkmask2char(inkmask mask, int winv); + +/* Given the 1-2 character based string, return the ink combination mask */ +/* Note that ICX_ADDITIVE will be guessed */ +/* Return 0 if unrecognised character in string */ +inkmask icx_char2inkmask(char *chstring); + +/* Given an ink combination mask that may contain light inks, */ +/* return the corresponding ink mask without light inks. */ +/* Return 0 if ink combination not recognised. */ +inkmask icx_ink2primary_ink(inkmask mask); + + +/* Given an ink combination mask and a single ink mask, */ +/* return the index number for that ink. */ +/* Return -1 if mask1 not in mask */ +int icx_ink2index(inkmask mask, inkmask mask1); + +/* Given an ink combination mask and a index number, */ +/* return the single ink mask. */ +/* Return 0 if there are no inks at that index */ +inkmask icx_index2ink(inkmask mask, int ixno); + + +/* Given a single ink mask, */ +/* return its string representation */ +char *icx_ink2string(inkmask mask); + +/* Given a single ink mask, */ +/* return its 1-2 character representation */ +char *icx_ink2char(inkmask mask); + +/* Given a single ink mask, */ +/* return its Postscript string representation */ +char *icx_ink2psstring(inkmask mask); + + +/* Return an enumerated single colorant description */ +/* Return 0 if no such enumeration, single colorant mask if there is */ +inkmask icx_enum_colorant(int no, char **desc); + +/* Return an enumerated colorant combination inkmask and description */ +/* Return 0 if no such enumeration, colorant combination mask if there is */ +inkmask icx_enum_colorant_comb(int no, char **desc); + + +/* Given an colorant combination mask, */ +/* check if it matches the given ICC colorspace signature. */ +/* return NZ if it does. */ +int icx_colorant_comb_match_icc(inkmask mask, icColorSpaceSignature sig); + +/* Given an ICC colorspace signature, return the appropriate */ +/* colorant combination mask. Return 0 if ambiguous signature. */ +inkmask icx_icc_to_colorant_comb(icColorSpaceSignature sig, icProfileClassSignature deviceClass); + +/* Given an ICC colorspace signature, and a matching list */ +/* of the D50 L*a*b* colors of the colorants, return the best matching */ +/* colorant combination mask. Return 0 if not applicable to colorspace. */ +inkmask icx_icc_cv_to_colorant_comb(icColorSpaceSignature sig, icProfileClassSignature deviceClass, + double cvals[][3]); + +/* Given an colorant combination mask */ +/* return the primary matching ICC colorspace signature. */ +/* return 0 if there is no match */ +icColorSpaceSignature icx_colorant_comb_to_icc(inkmask mask); + +/* --------------------------------------------------------- */ +/* An aproximate device colorant model object lookup object: */ + +struct _icxColorantLu { +/* Public: */ + void (*del)(struct _icxColorantLu *s); /* We're done with it */ + + /* Conversions */ + void (*dev_to_XYZ)(struct _icxColorantLu *s, double *out, double *in); /* Absolute */ + void (*dev_to_rLab)(struct _icxColorantLu *s, double *out, double *in); /* Relative */ + +/* Private: */ + inkmask mask; /* Colorant mask for this instance */ + int di; /* Dimensionality */ + int whix, bkix; /* White and Black Indexes into icx_inkTable[] */ + icmXYZNumber wp; /* White point XYZ value */ + int iix[ICX_MXINKS]; /* Device Indexes into icx_inkTable[] */ + double Ynorm; /* Y normalisation factor for Additive */ +}; typedef struct _icxColorantLu icxColorantLu; + +/* Create a icxColorantLu conversion object */ +icxColorantLu *new_icxColorantLu(inkmask mask); + +#endif /* XCOLORANTS_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xcolorantslu.c b/xicc/xcolorantslu.c new file mode 100644 index 0000000..6c537dc --- /dev/null +++ b/xicc/xcolorantslu.c @@ -0,0 +1,228 @@ + +/* + * xcolorant lookup/test utility + * + * Author: Graeme W. Gill + * Date: 24/4/2002 + * Version: 1.00 + * + * Copyright 2002 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "icc.h" +#include "xcolorants.h" + +void error(char *fmt, ...), warning(char *fmt, ...); + +void usage(void) { + fprintf(stderr,"Translate colors through xcolorant model, V1.00\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: xcolorantlu \n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -a Additive model (default subtractive)\n"); + fprintf(stderr," -m mask Colorant mask combination, from:\n"); + fprintf(stderr," C Cyan\n"); + fprintf(stderr," M Magenta\n"); + fprintf(stderr," Y Yellow\n"); + fprintf(stderr," K Black\n"); + fprintf(stderr," O Orange\n"); + fprintf(stderr," R Red\n"); + fprintf(stderr," G Green\n"); + fprintf(stderr," B Blue\n"); + fprintf(stderr," W White\n"); + fprintf(stderr," c Light Cyan\n"); + fprintf(stderr," m Light Magenta\n"); + fprintf(stderr," y Light Yellow\n"); + fprintf(stderr," k Light Black\n"); + fprintf(stderr," 2c Medium Cyan\n"); + fprintf(stderr," 2m Medium Magenta\n"); + fprintf(stderr," 2y Medium Yellow\n"); + fprintf(stderr," 2k Medium Black\n"); + fprintf(stderr," -x XYZ output (default L*a*b*)\n"); + fprintf(stderr,"\n"); + fprintf(stderr," The colors to be translated should be fed into stdin,\n"); + fprintf(stderr," one input color per line, white space separated.\n"); + fprintf(stderr," A line starting with a # will be ignored.\n"); + fprintf(stderr," A line not starting with a number will terminate the program.\n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int rv = 0; + int mask = 0; + int xyz = 0; + char buf[200]; + double in[MAX_CHAN], out[MAX_CHAN]; + int inn, outn = 3; + icxColorantLu *luo; + char *ident; + char *odent; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + + /* Additive */ + else if (argv[fa][1] == 'a' || argv[fa][1] == 'A') { + mask ^= ICX_ADDITIVE; + } + + /* XYZ output */ + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + verb = 1; + } + + /* Mask */ + else if (argv[fa][1] == 'm' || argv[fa][1] == 'm') { + int tm; + fa = nfa; + if (na == NULL) usage(); + if ((tm = icx_char2inkmask(na)) == 0) + usage(); + mask ^= tm; + } + + else + usage(); + } else + break; + } + + inn = icx_noofinks(mask); + + /* Create a icxColorantLu conversion object */ + if ((luo = new_icxColorantLu(mask)) == NULL) + error ("Creating xcolorant lookup failed\n"); + + ident = icx_inkmask2char(mask, 1); + if (xyz) + odent = "XYZ"; + else + odent = "Lab"; + + /* Process colors to translate */ + for (;;) { + int i,j; + char *bp, *nbp; + + /* Read in the next line */ + if (fgets(buf, 200, stdin) == NULL) + break; + if (buf[0] == '#') { + fprintf(stdout,"%s\n",buf); + continue; + } + /* For each input number */ + for (bp = buf-1, nbp = buf, i = 0; i < MAX_CHAN; i++) { + bp = nbp; + in[i] = strtod(bp, &nbp); + if (nbp == bp) + break; /* Failed */ + } + if (i == 0) + break; + + /* Do conversion */ + if (xyz) + luo->dev_to_XYZ(luo, out, in); + else + luo->dev_to_rLab(luo, out, in); + + /* Output the results */ + for (j = 0; j < inn; j++) { + if (j > 0) + fprintf(stdout," %f",in[j]); + else + fprintf(stdout,"%f",in[j]); + } + printf(" [%s] -> ", ident); + + for (j = 0; j < outn; j++) { + if (j > 0) + fprintf(stdout," %f",out[j]); + else + fprintf(stdout,"%f",out[j]); + } + printf(" [%s]", odent); + + if (rv == 0) + fprintf(stdout,"\n"); + else + fprintf(stdout," (clip)\n"); + + } + + /* Done with lookup object */ + luo->del(luo); + free(ident); + + return 0; +} + + +/* Basic printf type error() and warning() routines */ + +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"icclu: Error - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + exit (-1); +} + +void +warning(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"icclu: Warning - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} diff --git a/xicc/xdevlin.c b/xicc/xdevlin.c new file mode 100644 index 0000000..a5f7bd6 --- /dev/null +++ b/xicc/xdevlin.c @@ -0,0 +1,299 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 8/9/01 + * Version: 1.00 + * + * Copyright 2001 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* TTBD: + * + * Should switch this over to the linearisation used in + * the current xlut.c (== xlut2.c), rather than linearise + * w.r.t. the Y value. + */ + +/* + * This class handles the creation of device chanel linearisation + * curves, given a callback function that maps the device chanels + * to the value that should be linearised. + * + * This class is independent of other icc or icx classes. + * + * Its usual use is to create an Lab linerisation curve + * for native XYZ profiles. + */ + +#undef DEBUG /* Plot 1d Luts */ + +#include <sys/types.h> +#ifdef __sun +#include <unistd.h> +#endif +#include "numlib.h" +#include "rspl.h" +#include "xdevlin.h" /* definitions for this class */ + +#ifdef DEBUG +#include "plot.h" +#endif + +/* Free up the xdevlin */ +static void xdevlin_del(struct _xdevlin *p) { + int e; + for (e = 0; e < p->di; e++) { + if (p->curves[e] != NULL) + p->curves[e]->del(p->curves[e]); + } + free (p); +} + +/* Return the linearisation values given the device values */ +static void xdevlin_lin( +struct _xdevlin *p, /* this */ +double *out, /* di input */ +double *in /* di output */ +) { + co tc; + int e; + for (e = 0; e < p->di; e++) { + tc.p[0] = in[e]; + p->curves[e]->interp(p->curves[e], &tc); + out[e] = tc.v[0]; + } +} + +#define MAX_INVSOLN 5 + +/* Return the inverse linearisation */ +static void xdevlin_invlin( +struct _xdevlin *p, /* this */ +double *out, /* di input */ +double *in /* di output */ +) { + int i, j; + int nsoln; /* Number of solutions found */ + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + double cdir; + + for (i = 0; i < p->di; i++) { + pp[0].p[0] = p->clipc[i]; + pp[0].v[0] = in[i]; + cdir = p->clipc[i] - in[i]; /* Clip towards output range */ + + nsoln = p->curves[i]->rev_interp ( + p->curves[i], /* this */ + 0, /* No flags */ + MAX_INVSOLN, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + &cdir, /* Clip vector direction and length */ + pp); /* Input and output values */ + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln == 1) { /* Exactly one solution */ + j = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + error("~~~1 Unexpected failure to find reverse solution for linearisation curve"); + return; + } else { /* Multiple solutions */ + /* Use a simple minded resolution - choose the one closest to the center */ + double bdist = 1e300; + int bsoln = 0; +/* Don't expect this - 1D luts are meant to be monotonic */ +printf("~~~1 got %d reverse solutions\n",nsoln); +printf("~~~1 solution 0 = %f\n",pp[0].p[0]); +printf("~~~1 solution 1 = %f\n",pp[1].p[0]); + for (j = 0; j < nsoln; j++) { + double tt; + tt = pp[i].p[0] - p->clipc[i]; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = j; + } + } + j = bsoln; + } + out[i] = pp[j].p[0]; + } +} + +/* Callback function that is used to set each chanels grid value */ +static void set_curve(void *cbntx, double *out, double *in) { + xdevlin *p = (xdevlin *)cbntx; + int e, ee = p->setch; + double tin[MXDI], tout[MXDO]; + double tt; + + /* setup input value */ + for (e = 0; e < p->di; e++) + tin[e] = p->pol ? p->max[e] : p->min[e]; + tin[ee] = in[0]; + + p->lookup(p->lucntx, tout, tin); + + tt = (tout[0] - p->lmin)/(p->lmax - p->lmin); /* Normalise from L */ + + out[0] = tt * (p->max[ee] - p->min[ee]) + p->min[ee]; /* Back to device range */ +} + +/* Create an appropriate linearisation from the callback */ +/* This code is very similar to that in xlut.c when creating */ +/* a device profiles linearisation curves. */ +xdevlin *new_xdevlin( +int di, /* Device dimenstionality */ +double *min, double *max, /* Min & max range of device values, NULL = 0.0 - 1.0 */ +void *lucntx, /* Context for callback */ +void (*lookup) (void *lucntx, double *lin, double *dev) +) { + int ee, e; + xdevlin *p; + + /* Do the basic class initialisation */ + if ((p = (xdevlin *) calloc(1,sizeof(xdevlin))) == NULL) + return NULL; + p->del = xdevlin_del; + p->lin = xdevlin_lin; + p->invlin = xdevlin_invlin; + + /* And then set it up */ + p->di = di; + p->lucntx = lucntx; + p->lookup = lookup; + + /* Setup the clipping center */ + for (e = 0; e < p->di; e++) { + p->min[e] = min[e]; + p->max[e] = max[e]; + p->clipc[e] = 0.5 * (min[e] + max[e]); + } + + /* Determine what level to set the chanels we're not interested in */ + { + double tin[MXDI], tout[MXDO]; + double l00, l01, l10, l11; /* Resulting levels */ + + for (e = 0; e < p->di; e++) + tin[e] = min[e]; + lookup(lucntx, tout, tin); + l00 = tout[0]; /* All minimum */ + + tin[0] = max[0]; + lookup(lucntx, tout, tin); + l01 = tout[0]; /* First chanel max, rest min */ + + for (e = 0; e < p->di; e++) + tin[e] = max[e]; + lookup(lucntx, tout, tin); + l11 = tout[0]; /* All maximum */ + + tin[0] = min[0]; + lookup(lucntx, tout, tin); + l10 = tout[0]; /* First chanel min, rest max */ + + if (fabs(l11 - l10) > fabs(l00 - l01)) + p->pol = 1; /* Set other chanels to max */ + else + p->pol = 0; /* Set other chanels to min */ + } + + /* For each chanel, create an rspl */ + + for (ee = 0; ee < p->di; ee++) { + double tin[MXDI], tout[MXDO]; + int gres = 100; // 4096 +#ifdef DEBUG + #define XRES 100 + double xx[XRES]; + double y1[XRES]; +#endif /* DEBUG */ + + if ((p->curves[ee] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + error("Creation of rspl failed in xdevlin"); + } + + p->setch = ee; + + /* Figure the L min and max */ + for (e = 0; e < p->di; e++) + tin[e] = p->pol ? max[e] : min[e]; + tin[ee] = min[ee]; + lookup(lucntx, tout, tin); + p->lmin = tout[0]; + tin[ee] = max[ee]; + lookup(lucntx, tout, tin); + p->lmax = tout[0]; + + p->curves[ee]->set_rspl( + p->curves[ee], + 0, + p, /* Opaque function context */ + set_curve, /* Function to set from */ + min, max, /* Grid low scale, grid high scale */ + &gres, /* Grid resolution */ + min, max /* Data value normalsie low and high */ + ); + +#ifdef DEBUG + { + int i; + /* Display the result curve */ + for (i = 0; i < XRES; i++) { + double x; + co c; + x = i/(double)(XRES-1); + xx[i] = x; + c.p[0] = x; + p->curves[ee]->interp(p->curves[ee], &c); + y1[i] = c.v[0]; + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* DEBUG */ + + } + + p->lookup = NULL; /* Not valid after function return */ + return p; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xdevlin.h b/xicc/xdevlin.h new file mode 100644 index 0000000..7e51e40 --- /dev/null +++ b/xicc/xdevlin.h @@ -0,0 +1,96 @@ +#ifndef XDEVLIN_H +#define XDEVLIN_H +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 29/8/01 + * Version: 1.00 + * + * Copyright 2001 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This class handles the creation of device chanel linearisation + * curves, given a callback function that maps the device chanels + * to the value that should be linearised. + * + * This class is independent of other icc or icx classes. + * + * Its usual use is to create an Lab linerisation curve + * for native XYZ profiles. + */ + +/* The device linearisation class */ +struct _xdevlin { + + /* Private: */ + int di; /* Device dimentionality */ + rspl *curves[MXDI]; /* di Linearisation curves */ + double clipc[MXDI]; /* center of device range */ + double min[MXDI], max[MXDI]; /* Device chanel min/max */ + int pol; /* Polarity, 0 for minimise other chanels */ + int setch; /* Chanel to set */ + double lmin, lmax; /* Linear min & max to rescale */ + void *lucntx; /* Lookup context */ + void (*lookup) (void *lucntx, double *lin, double *dev); /* Callback function */ + + /* Public: */ + void (*del)(struct _xdevlin *p); + + /* Return the linearisation values given the device values */ + void (*lin)(struct _xdevlin *p, double *out, double *in); + + /* Return the inverse linearisation */ + void (*invlin)(struct _xdevlin *p, double *out, double *in); + +}; typedef struct _xdevlin xdevlin; + +xdevlin *new_xdevlin( + int di, /* Device dimenstionality */ + double *min, double *max, /* Min & max range of device values, NULL = 0.0 - 1.0 */ + void *cntx, /* Context for callback */ + void (*lookup) (void *cntx, double lin[MXDO], double dev[MXDI]) + /* Callback function, return linear parameter as lin[0] */ +); + +#endif /* XDEVLIN_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xdgb.c b/xicc/xdgb.c new file mode 100644 index 0000000..80914b8 --- /dev/null +++ b/xicc/xdgb.c @@ -0,0 +1,109 @@ + +/* + * International Color Consortium color transform expanded support + * eXpanded Device Gamut Boundary support. + * + * Author: Graeme W. Gill + * Date: 2008/11/17 + * Version: 1.00 + * + * Copyright 2008 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* + * This module provides device gamut boundary functionality used to + * locate the non-monotonic device boundary surface, enabling + * fast and accurate PCS gamut boundary plotting as well (for CMYK) + * as allowing the location of device boundary overlaps, so that they + * can be eliminated. This also allows rapid K min/max finding, + * and rapid smoothed K map creation. + */ + +#include <sys/types.h> +#include <string.h> +#include <ctype.h> +#ifdef __sun +#include <unistd.h> +#endif +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "rspl.h" +#include "xicc.h" +#include "plot.h" +#include "xdgb.h" +#include "sort.h" + + +/* + * TTBD: + * + */ + +#undef DEBUG /* Verbose debug information */ + +/* - - - - - - - - - - - - - - - - - */ + +/* Diagnostic */ +static void plot_xdgb( +xdgb *p +) { +} + +/* - - - - - - - - - */ + +/* - - - - - - - - - */ +/* create the device surface points. */ +/* return nz on error */ +int xdgb_fit( + struct _xdgb *p, + int flags, /* Flag values */ + int di, /* Input dimensions */ + int fdi /* Output dimensions */ +) { + int i, e, f; + double *b; /* Base of parameters for this section */ + int poff; + + p->flags = flags; + if (flags & XDGB_VERB) + p->verb = 1; + else + p->verb = 0; + p->di = di; + p->fdi = fdi; + +#ifdef DEBUG + printf("xdgbc called with flags = 0x%x, di = %d, fdi = %d\n",flags,di,fdi); +#endif + + return 0; +} + +/* We're done with an xdgb */ +static void xdgb_del(xdgb *p) { + free(p); +} + +/* Create a transform fitting object */ +/* return NULL on error */ +xdgb *new_xdgb( +) { + xdgb *p; + + if ((p = (xdgb *)calloc(1, sizeof(xdgb))) == NULL) { + return NULL; + } + + /* Set method pointers */ + p->del = xdgb_del; + + return p; +} + + + diff --git a/xicc/xdgb.h b/xicc/xdgb.h new file mode 100644 index 0000000..bfb1752 --- /dev/null +++ b/xicc/xdgb.h @@ -0,0 +1,71 @@ + +#ifndef XDGB_H +#define XDGB_H + +/* + * International Color Consortium color transform expanded support + * eXpanded Device Gamut Boundary support. + * + * Author: Graeme W. Gill + * Date: 2008/11/17 + * Version: 1.00 + * + * Copyright 2008 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* Flag values */ +#define XDGB_VERB 0x0001 /* Verbose output during fitting */ + + +/* Object holding device gamut representation */ +struct _xdgb { + int verb; /* Verbose */ + int flags; /* Behaviour flags */ + int di, fdi; /* Dimensionaluty of input and output */ + + /* Methods */ + void (*del)(struct _xdgb *p); + +}; typedef struct _xdgb xdgb; + +xdgb *new_xdgb(); + +#endif /* XDGB_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xfbview.c b/xicc/xfbview.c new file mode 100644 index 0000000..6abf176 --- /dev/null +++ b/xicc/xfbview.c @@ -0,0 +1,703 @@ + +/* + * View inv fwd table device interp of an ICC file, V1.23\n"); + * + * Author: Graeme W. Gill + * Date: 2000/12/8 + * Version: 1.23 + * + * Copyright 2000 Graeme W. Gill + * Please refer to License.txt file for details. + */ + +/* TTBD: + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xicc.h" + +#define RW 0.5 /* Device Delta */ + +/* - - - - - - - - - - - - - */ + +/* Return maximum difference */ +double maxdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + if ((tt = fabs(in1[0] - in2[0])) > rv) + rv = tt; + if ((tt = fabs(in1[1] - in2[1])) > rv) + rv = tt; + if ((tt = fabs(in1[2] - in2[2])) > rv) + rv = tt; + return rv; +} + +/* Return absolute difference */ +double absdiff(double in1[3], double in2[3]) { + double tt, rv = 0.0; + tt = in1[0] - in2[0]; + rv += tt * tt; + tt = in1[1] - in2[1]; + rv += tt * tt; + tt = in1[2] - in2[2]; + rv += tt * tt; + return sqrt(rv); +} + +/* ---------------------------------------- */ + +void usage(void) { + fprintf(stderr,"View inv fwd table device interp of an ICC file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: fbtest [options] infile\n"); + fprintf(stderr," -v verbose\n"); + fprintf(stderr," -f Show PCS target -> reference clipped PCS vectors\n"); + fprintf(stderr," -d Show PCS target -> average of device ref clippped PCS\n"); + fprintf(stderr," -b Show PCS target -> B2A lookup clipped PCS\n"); + fprintf(stderr," -e Show reference cliped PCS -> B2A lookup clipped PCS\n"); + fprintf(stderr," -r res Resolution of test grid\n"); + fprintf(stderr," -g Do full grid, not just L = 0\n"); + fprintf(stderr," -c Do all values, not just clipped ones\n"); + fprintf(stderr," -l tlimit set total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set black ink limit, 0 - 100%% (estimate by default)\n"); + exit(1); +} + +int +main( + int argc, + char *argv[] +) { + int fa,nfa; /* argument we're looking at */ + int verb = 0; + int doaxes = 0; + double tlimit = -1.0; /* Total ink limit */ + double klimit = -1.0; /* Black ink limit */ + int tres = 33; + int doref = 0; /* Show reference lookups */ + int dodelta = 0; /* Show device interp delta variation */ + int dob2a = 0; /* Show B2A lookups */ + int doeee = 0; /* Show clipped ref PCS to B2A clipped PCS */ + int dilzero = 1; /* Do just L = 0 plane */ + int doclip = 1; /* Do just clipped values */ + char in_name[100]; + char *xl, out_name[100]; + icmFile *rd_fp; + icc *rd_icco; + int rv = 0; + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + + error_program = argv[0]; + + if (argc < 2) + usage(); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + /* Verbosity */ + if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + } + /* Show reference */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + doref = 1; + } + /* Show device delta variation */ + else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { + dodelta = 1; + } + /* Show B2A table lookup */ + else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') { + dob2a = 1; + } + /* Show reference clipped PCS to clipped B2A PCS */ + else if (argv[fa][1] == 'e' || argv[fa][1] == 'E') { + doeee = 1; + } + /* Do the full grid, not just L = 0 */ + else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') { + dilzero = 0; + } + /* Do all values, not just clipped ones */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + doclip = 0; + } + /* Ink limit */ + else if (argv[fa][1] == 'l') { + fa = nfa; + if (na == NULL) usage(); + tlimit = atoi(na)/100.0; + } + else if (argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage(); + klimit = atoi(na)/100.0; + } + + /* Resolution */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage(); + tres = atoi(na); + } + + else if (argv[fa][1] == '?') + usage(); + else + usage(); + } + else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(in_name,argv[fa]); + + strcpy(out_name, in_name); + if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ + xl = out_name + strlen(out_name); + strcpy(xl,".wrl"); + + /* Open up the file for reading */ + if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((rd_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + /* Read the header and tag list */ + if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) + error ("Read: %d, %s",rv,rd_icco->err); + + /* Run the target Lab values through the bwd and fwd tables, */ + /* to compute the overall error. */ + { +#define GAMUT_LCENT 50.0 + xicc *xicco; + icxLuBase *luo; + icxInk ink; /* Ink parameters */ + FILE *wrl; + struct { + double x, y, z; + double wx, wy, wz; + double r, g, b; + } axes[5] = { + { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */ + { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */ + { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */ + { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */ + { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */ + }; + int coa[4]; + int i, j; + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(rd_icco)) == NULL) + error ("Creation of xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(xicco, &ink.tlimit, tlimit, &ink.klimit, klimit); + + if (verb) { + if (ink.tlimit >= 0.0) + printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); + if (ink.klimit >= 0.0) + printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); + } + + ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */ + ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + ink.c.Kstle = 0.5; /* Min K at white end */ + ink.c.Kstpo = 0.5; /* Start of transition is at white */ + ink.c.Kenle = 0.5; /* Max K at black end */ + ink.c.Kenpo = 0.5; /* End transition at black */ + ink.c.Kshap = 1.0; /* Linear transition */ + + /* Get a Device to PCS conversion object */ + if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) { + if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + /* Get details of conversion */ + luo->spaces(luo, &ins, NULL, &outs, NULL, NULL, NULL, NULL, NULL); + + if (ins != icSigCmykData) { + error("Expecting CMYK device"); + } + + if ((wrl = fopen(out_name,"w")) == NULL) { + fprintf(stderr,"Error opening output file '%s'\n",out_name); + return 2; + } + + /* Spit out a VRML 2 Object surface of gamut */ + + fprintf(wrl,"#VRML V2.0 utf8\n"); + fprintf(wrl,"\n"); + fprintf(wrl,"# Created by the Argyll CMS\n"); + fprintf(wrl,"Transform {\n"); + fprintf(wrl,"children [\n"); + fprintf(wrl," NavigationInfo {\n"); + fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n"); + fprintf(wrl," } # We'll add our own light\n"); + fprintf(wrl,"\n"); + fprintf(wrl," DirectionalLight {\n"); + fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n"); + fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + fprintf(wrl," Viewpoint {\n"); + fprintf(wrl," position 0 0 340 # Position we view from\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"\n"); + if (doaxes != 0) { + fprintf(wrl,"# Lab axes as boxes:\n"); + for (i = 0; i < 5; i++) { + fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z); + fprintf(wrl,"\tchildren [\n"); + fprintf(wrl,"\t\tShape{\n"); + fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n", + axes[i].wx, axes[i].wy, axes[i].wz); + fprintf(wrl,"\t\t\tappearance Appearance { material Material "); + fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b); + fprintf(wrl,"\t\t}\n"); + fprintf(wrl,"\t]\n"); + fprintf(wrl,"}\n"); + } + fprintf(wrl,"\n"); + } + + /* ---------------------------------------------- */ + /* The PCS target -> Reference clipped vectors */ + + if (doref) { + if (verb) + printf("Doing PCS target to reference clipped PCS Vectors\n"); + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); + + i = 0; + for (coa[0] = 0; coa[0] < tres; coa[0]++) { + for (coa[1] = 0; coa[1] < tres; coa[1]++) { + for (coa[2] = 0; coa[2] < tres; coa[2]++) { + double in[4], dev[4], out[4]; + double temp[4]; + int rv1, rv2; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = coa[1]/(tres-1.0); + temp[2] = coa[2]/(tres-1.0); + + /* PCS values */ + in[0] = temp[0] * 100.0; + in[1] = 200.0 * temp[1] -100.0; + in[2] = 200.0 * temp[2] -100.0; + + /* Do reference lookup */ + + /* PCS -> Device */ + if ((rv2 = luo->inv_lookup(luo, dev, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (doclip && rv2 != 1) /* Not clip */ + continue; + + /* Device -> PCS */ + if ((rv1 = luo->lookup(luo, out, dev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (verb) + printf("."), fflush(stdout); + + /* Input PCS to ideal (Inverse AtoB) clipped PCS values */ + fprintf(wrl,"%f %f %f,\n",in[1], in[2], in[0]-GAMUT_LCENT); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-GAMUT_LCENT); + i++; + } + } + if (dilzero) + break; + } + + if (verb) + printf("\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (j = 0; j < i; j++) { + fprintf(wrl,"%d, %d, -1,\n", j * 2, j * 2 + 1); + } + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"appearance Appearance { material Material { emissiveColor 1.0 0.1 0.1} }\n"); + fprintf(wrl,"} # end shape\n"); + } + + /* ---------------------------------------------- */ + /* The Device Delta lines */ + /* The PCS target -> clipped from average of surrounding device values, vectors */ + + if (dodelta) { + if (verb) + printf("Doing target PCS to average of 4 surrounding device to PCS Vectors\n"); + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); + + i = 0; + for (coa[0] = 0; coa[0] < tres; coa[0]++) { + for (coa[1] = 0; coa[1] < tres; coa[1]++) { + for (coa[2] = 0; coa[2] < tres; coa[2]++) { + double in[4], dev[4], out[4]; + double in4[4], check[4]; + double temp[4], adev[4]; + double dev0[4], dev1[4], dev2[4], dev3[4]; + int rv1, rv2; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = coa[1]/(tres-1.0); + temp[2] = coa[2]/(tres-1.0); + + in[0] = temp[0] * 100.0; + in[1] = 200.0 * temp[1] -100.0; + in[2] = 200.0 * temp[2] -100.0; + + /* Do reference lookup */ + + /* PCS -> ideal Device */ + if ((rv2 = luo->inv_lookup(luo, dev, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (doclip && rv2 != 1) /* Not clip */ + continue; + + /* Device -> PCS check value */ + if ((rv1 = luo->lookup(luo, check, dev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* - - - - - - - - - - - - - - - */ + /* Now do average in device space of two points */ + temp[0] = coa[0]/(tres-1.0); + temp[1] = (coa[1]-RW)/(tres-1.0); + temp[2] = (coa[2]-RW)/(tres-1.0); + + in4[0] = temp[0] * 100.0; + in4[1] = 200.0 * temp[1] -100.0; + in4[2] = 200.0 * temp[2] -100.0; + + /* PCS -> Device */ + if ((rv2 = luo->inv_lookup(luo, dev0, in4)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + adev[0] = 0.25 * dev0[0]; + adev[1] = 0.25 * dev0[1]; + adev[2] = 0.25 * dev0[2]; + adev[3] = 0.25 * dev0[3]; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = (coa[1]+RW)/(tres-1.0); + temp[2] = (coa[2]-RW)/(tres-1.0); + + in4[0] = temp[0] * 100.0; + in4[1] = 200.0 * temp[1] -100.0; + in4[2] = 200.0 * temp[2] -100.0; + + /* PCS -> Device */ + if ((rv2 = luo->inv_lookup(luo, dev1, in4)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + adev[0] += 0.25 * dev1[0]; + adev[1] += 0.25 * dev1[1]; + adev[2] += 0.25 * dev1[2]; + adev[3] += 0.25 * dev1[3]; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = (coa[1]-RW)/(tres-1.0); + temp[2] = (coa[2]+RW)/(tres-1.0); + + in4[0] = temp[0] * 100.0; + in4[1] = 200.0 * temp[1] -100.0; + in4[2] = 200.0 * temp[2] -100.0; + + /* PCS -> Device */ + if ((rv2 = luo->inv_lookup(luo, dev2, in4)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + adev[0] += 0.25 * dev2[0]; + adev[1] += 0.25 * dev2[1]; + adev[2] += 0.25 * dev2[2]; + adev[3] += 0.25 * dev2[3]; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = (coa[1]+RW)/(tres-1.0); + temp[2] = (coa[2]+RW)/(tres-1.0); + + in4[0] = temp[0] * 100.0; + in4[1] = 200.0 * temp[1] -100.0; + in4[2] = 200.0 * temp[2] -100.0; + + /* PCS -> Device */ + if ((rv2 = luo->inv_lookup(luo, dev3, in4)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + adev[0] += 0.25 * dev3[0]; + adev[1] += 0.25 * dev3[1]; + adev[2] += 0.25 * dev3[2]; + adev[3] += 0.25 * dev3[3]; + + /* Device -> PCS */ + if ((rv1 = luo->lookup(luo, out, adev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (verb) + printf("."), fflush(stdout); + + fprintf(wrl,"%f %f %f,\n",in[1], in[2], in[0]-GAMUT_LCENT); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-GAMUT_LCENT); + i++; + } + } + if (dilzero) + break; + } + + if (verb) + printf("\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (j = 0; j < i; j++) { + fprintf(wrl,"%d, %d, -1,\n", j * 2, j * 2 + 1); + } + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"appearance Appearance { material Material { emissiveColor 0.9 0.9 0.9} }\n"); + fprintf(wrl,"} # end shape\n"); + + } + + /* ---------------------------------------------- */ + /* The target PCS -> clipped PCS using B2A table vectore */ + + if (dob2a) { + icxLuBase *luoB; + + /* Get a PCS to Device conversion object */ + if ((luoB = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, icAbsoluteColorimetric, + icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) { + if ((luoB = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, icmDefaultIntent, + icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + + if (verb) + printf("Doing target PCS to B2A clipped PCS Vectors\n"); + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); + + i = 0; + for (coa[0] = 0; coa[0] < tres; coa[0]++) { + for (coa[1] = 0; coa[1] < tres; coa[1]++) { + for (coa[2] = 0; coa[2] < tres; coa[2]++) { + double in[4], dev[4], out[4]; + double temp[4]; + int rv1, rv2; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = coa[1]/(tres-1.0); + temp[2] = coa[2]/(tres-1.0); + + in[0] = temp[0] * 100.0; + in[1] = 200.0 * temp[1] -100.0; + in[2] = 200.0 * temp[2] -100.0; + + /* PCS -> Device */ + if ((rv2 = luoB->lookup(luoB, dev, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (doclip && rv2 != 1) /* Not clip */ + continue; + + /* Device -> PCS */ + if ((rv1 = luo->lookup(luo, out, dev)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (verb) + printf("."), fflush(stdout); + + fprintf(wrl,"%f %f %f,\n",in[1], in[2], in[0]-GAMUT_LCENT); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-GAMUT_LCENT); + i++; + } + } + if (dilzero) + break; + } + + if (verb) + printf("\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (j = 0; j < i; j++) { + fprintf(wrl,"%d, %d, -1,\n", j * 2, j * 2 + 1); + } + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"appearance Appearance { material Material { emissiveColor 0.9 0.9 0.9} }\n"); + fprintf(wrl,"} # end shape\n"); + + luoB->del(luoB); + } + + /* ---------------------------------------------- */ + /* The reference clipped PCS -> B2A clipped PCS vectore */ + + if (doeee) { + icxLuBase *luoB; + + /* Get a PCS to Device conversion object */ + if ((luoB = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, icAbsoluteColorimetric, + icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) { + if ((luoB = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, icmDefaultIntent, + icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) + error ("%d, %s",rd_icco->errc, rd_icco->err); + } + + if (verb) + printf("Doing reference clipped PCS to B2A table clipped PCS Vectors\n"); + + fprintf(wrl,"\n"); + fprintf(wrl,"Shape {\n"); + fprintf(wrl," geometry IndexedLineSet { \n"); + fprintf(wrl," coord Coordinate { \n"); + fprintf(wrl," point [\n"); + + i = 0; + for (coa[0] = 0; coa[0] < tres; coa[0]++) { + for (coa[1] = 0; coa[1] < tres; coa[1]++) { + for (coa[2] = 0; coa[2] < tres; coa[2]++) { + double in[4], dev[4], out[4]; + double check[4]; + double temp[4]; + int rv1, rv2; + + temp[0] = coa[0]/(tres-1.0); + temp[1] = coa[1]/(tres-1.0); + temp[2] = coa[2]/(tres-1.0); + + in[0] = temp[0] * 100.0; + in[1] = 200.0 * temp[1] -100.0; + in[2] = 200.0 * temp[2] -100.0; + + /* Do reference lookup */ + /* PCS -> Device */ + if ((rv1 = luo->inv_lookup(luo, dev, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* Device -> PCS */ + if (luo->lookup(luo, check, dev) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* Do B2A table lookup */ + /* PCS -> Device */ + if ((rv2 = luoB->lookup(luoB, dev, in)) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + /* Device -> PCS */ + if (luo->lookup(luo, out, dev) > 1) + error ("%d, %s",rd_icco->errc,rd_icco->err); + + if (doclip && rv1 != 1 && rv2 != 1) /* Not clip */ + continue; + + if (verb) + printf("."), fflush(stdout); + + fprintf(wrl,"%f %f %f,\n",check[1], check[2], check[0]-GAMUT_LCENT); + fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]-GAMUT_LCENT); + i++; + } + } + if (dilzero) + break; + } + + if (verb) + printf("\n"); + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl," coordIndex [\n"); + + for (j = 0; j < i; j++) { + fprintf(wrl,"%d, %d, -1,\n", j * 2, j * 2 + 1); + } + fprintf(wrl," ]\n"); + fprintf(wrl," }\n"); + fprintf(wrl,"appearance Appearance { material Material { emissiveColor 0.9 0.9 0.9} }\n"); + fprintf(wrl,"} # end shape\n"); + + luoB->del(luoB); + } + + /* ---------------------------------------------- */ + + fprintf(wrl,"\n"); + fprintf(wrl," ] # end of children for world\n"); + fprintf(wrl,"}\n"); + + if (fclose(wrl) != 0) { + fprintf(stderr,"Error closing output file '%s'\n",out_name); + return 2; + } + + /* Done with lookup object */ + luo->del(luo); + xicco->del(xicco); + } + + rd_icco->del(rd_icco); + rd_fp->del(rd_fp); + + return 0; +} + diff --git a/xicc/xfit.c b/xicc/xfit.c new file mode 100644 index 0000000..50916b3 --- /dev/null +++ b/xicc/xfit.c @@ -0,0 +1,2829 @@ + + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000 - 2007 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the xlut.c code. + */ + +/* + * TTBD: + * + * Need to use this for B2A tables rather than inverting + * A2B curves. Need to add grid sizing to cover just gamut range + * (including level axis gamut, but watch out for devices that + * have values below the black point), 3x3 matrix optimization, + * and white point to grid node mapping for B2A. + * + * Currently the Lab A2B output tables are adjusted for ab symetry + * to make the B2A white point land on a grid point, given that + * the icc code forces a symetric ab range. This only works + * because the B2A is using the same per channel curves. + * Done properly, it should be possible to know where grid + * points land within the range, and to modify the B2A input curves + * to make the white point land on a grid point. + * (For B2A the pseudo least squares adjustment needs to be turned + * off for that grid point too.) + * + * Note that one quandry is that the curve fitting doesn't + * fit well when the input data has an offset and/or plateaus. + */ + +/* + * This module provides curve and matrix fitting functionality used to + * create per channel input and/or output curves for clut profiles, + * and optionally creates the rspl to fit the data as well. + * + * The approach used is to initialy creating input and output shaper + * curves that minimize the overall delta E of the test point set, + * when a linear matrix is substituted for a clut is used. + * (This is the same as pre-V0.70 approach. ) + * + * The residual error between the test point set and the shaper/matrix/shaper + * model is then computed, and mapped against each input channel, and + * a positioning mapping curve created that aims to map rspl grid + * locations more densly where residual errors are high, and more + * sparsely where they are low. + * The input shaper and positioning curves are then combined together, + * so that the positioning curve determines which input values map + * to rspl grid points, and the shaper curve determines the mapping + * between the grid points. The width of the grid cells is computed + * in the shaper mapped input space, and then fed to the rspl fitting + * code, so that its smoothness evaluation function can take account + * of the non-uniform spacing of the grid points. + */ + +#include <sys/types.h> +#include <string.h> +#include <ctype.h> +#ifdef __sun +#include <unistd.h> +#endif +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "rspl.h" +#include "xicc.h" +#include "plot.h" +#include "xfit.h" +#include "sort.h" + + +#undef DEBUG /* Verbose debug information */ +#undef DEBUG_PLOT /* Plot in & out curves */ +#undef SPECIAL_FORCE /* Check rspl nodes against linear XYZ model */ +#undef SPECIAL_FORCE_GAMMA /* Force correct gamma shaper curves */ +#undef SPECIAL_TEST_GAMMA /* Use gamma on reference model */ +#undef SPECIAL_TEST_LAB + +#define RSPLFLAGS (0 /* | RSPL_2PASSSMTH */ /* | RSPL_EXTRAFIT2 */) + +#undef EXTEND_GRID /* [Undef] Use extended RSPL grid around interpolation */ +#define EXTEND_GRID_BYN 2 /* Rows to extend grid. */ + +#undef NODDV /* Use slow non d/dv powell else use conjgrad */ +#define CURVEPOW 1.0 /* Power to raise deltaE squared to in setting in/out curves */ + /* This provides a means of punishing high maximum errors. */ + +#define POWTOL 1e-4 /* Shaper Powell optimiser tollerance in delta E squared ^ CURVEPOW */ +#define MAXITS 2000 /* Shaper number of itterations before giving up */ +#define PDDEL 1e-6 /* Fake partial derivative del */ + +/* Weights for shaper in/out curve parameters, to minimise unconstrained "wiggles" */ +#define SHAPE_WEIGHT 1.0 /* Overal shaper weight contribution - err on side of smoothness */ +#define SHAPE_HW01 0.002 /* 0 & 1 harmonic weights */ +#define SHAPE_HBREAK 4 /* Harmonic that has HWBR */ +#define SHAPE_HWBR 20.0 /* Base weight of harmonics HBREAK up */ +#define SHAPE_HWINC 60.0 /* Increase in weight for each harmonic above HWBR */ + +/* Weights for the positioning curve parameters */ +#define PSHAPE_MINE 0.02 /* Minum background residual error level */ +#define PSHAPE_DIST 1.0 /* Agressivness of grid distribution */ + +/* - - - - - - - - - - - - - - - - - */ + +#ifdef DEBUG +static void dump_xfit(xfit *p); +#endif + +#ifdef DEBUG_PLOT /* Not currently used in runtime code*/ + +/* Lookup a value though an input position curve */ +static double xfit_poscurve(xfit *p, double in, int chan) { + double rv = in; + if (p->tcomb & oc_p) + rv = icxSTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], rv, + p->in_min[chan], p->in_max[chan]); + return rv; +} + +/* Lookup di values though the input position curves */ +static void xfit_poscurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) { + double val = in[e]; + if (p->tcomb & oc_i) + val = icxSTransFunc(p->v + p->pos_offs[e], p->iluord[e], val, + p->in_min[e], p->in_max[e]); + out[e] = val; + } +} + +/* Inverse Lookup a value though an input position curve */ +static double xfit_invposcurve(xfit *p, double in, int chan) { + double rv = in; + if (p->tcomb & oc_i) + rv = icxInvSTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], rv, + p->in_min[chan], p->in_max[chan]); + return rv; +} + +/* Inverse Lookup di values though input position curves */ +static void xfit_invposcurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) { + double val = in[e]; + if (p->tcomb & oc_i) + val = icxInvSTransFunc(p->v + p->pos_offs[e], p->iluord[e], val, + p->in_min[e], p->in_max[e]); + out[e] = val; + } +} +#endif /* DEBUG_PLOT */ + +/* - - - - - - - - - - - - - - - - - */ +/* Lookup a value though input shape curve */ +static double xfit_shpcurve(xfit *p, double in, int chan) { + double rv = in; + + if (p->tcomb & oc_i) { +#ifdef SPECIAL_FORCE_GAMMA + double gam; + if (chan == 0) + gam = 1.9; + else if (chan == 1) + gam = 2.0; + else if (chan == 2) + gam = 2.1; + else + gam = 1.0; + rv = pow(in, gam); +#else + rv = icxSTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], rv, + p->in_min[chan], p->in_max[chan]); +#endif + } + return rv; +} + +#ifdef NEVER /* Not currently used */ +/* Lookup a value though shape curves */ +static void xfit_shpcurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) { + double val = in[e]; + if (p->tcomb & oc_i) + val = icxSTransFunc(p->v + p->shp_offs[e], p->iluord[e], val, + p->in_min[e], p->in_max[e]); + out[e] = val; + } +} +#endif /* NEVER */ + +/* Inverse Lookup a value though a shape curve */ +static double xfit_invshpcurve(xfit *p, double in, int chan) { + double rv = in; + + if (p->tcomb & oc_i) { +#ifdef SPECIAL_FORCE_GAMMA + double gam; + if (chan == 0) + gam = 1.9; + else if (chan == 1) + gam = 2.0; + else if (chan == 2) + gam = 2.1; + else + gam = 1.0; + rv = pow(rv, 1.0/gam); +#else + rv = icxInvSTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], rv, + p->in_min[chan], p->in_max[chan]); +#endif + } + return rv; +} + +#ifdef NEVER /* Not currently used */ +/* Inverse Lookup a value though shape curves */ +static void xfit_invshpcurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) { + double val = in[e]; + if (p->tcomb & oc_i) + val = icxInvSTransFunc(p->v + p->shp_offs[e], p->iluord[e], val, + p->in_min[e], p->in_max[e]); + out[e] = val; + } +} +#endif /* NEVER */ + +/* - - - - - - - - - - - - - - - - - */ + +/* Lookup values through the shaper/matrix/shaper model */ +static void xfit_shmatsh(xfit *p, double *out, double *in) { + double tin[MXDI]; + int e, f; + + for (e = 0; e < p->di; e++) + tin[e] = icxSTransFunc(p->v + p->shp_offs[e], p->iluord[e], in[e], + p->in_min[e], p->in_max[e]); + + icxCubeInterp(p->v + p->mat_off, p->fdi, p->di, out, tin); + + for (f = 0; f < p->fdi; f++) + out[f] = icxSTransFunc(p->v + p->out_offs[f], p->oluord[f], out[f], + p->out_min[f], p->out_max[f]); +} + +/* - - - - - - - - - - - - - - - - - - - - */ +/* Combined input positioning & shaper transfer curve functions */ + +//int db = 0; + +/* Lookup a value though the input positioning and shaper curves */ +static double xfit_inpscurve(xfit *p, double in, int chan) { + double rv; + /* Just shaper curve */ + if ((p->tcomb & oc_ip) == oc_i) { + rv = icxSTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], in, + p->in_min[chan], p->in_max[chan]); + + /* shaper and positioning */ + } else if ((p->tcomb & oc_ip) == oc_ip) { + double nin, npind; /* normalized in, normalized position in' */ + int six; /* Span index */ + double npind0, npind1; /* normalized position in' span values */ + double npin0, npin1; /* normalized position in span values */ + double nsind0, nsind1; /* normalized shaper in' span values */ + double nsind; /* normalised shaper in' value */ + + /* Normalize */ + nin = (in - p->in_min[chan])/(p->in_max[chan] - p->in_min[chan]); + +//if (db) printf("\n~1 inpscurve: cha %d, input value %f, norm %f\n",chan,in,nin); + + /* Locate the span the input point will be in after positioning lookup */ + npind = icxTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], nin); + + /* Quantize position space value to grid */ + six = (int)floor(npind * (p->gres[chan]-1.0)); + if (six > (p->gres[chan]-2)) + six = (p->gres[chan]-2); + + /* Compute span position position in' values */ + npind0 = six / (p->gres[chan]-1.0); + npind1 = (six + 1.0) / (p->gres[chan]-1.0); + +//if (db) printf("~1 npind %f, six %d, npind0 %f, npind1 %f\n",npind,six,npind0,npind1); + + /* Compute span in values */ + npin0 = icxInvTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], npind0); + npin1 = icxInvTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], npind1); + +//if (db) printf("~1 npin0 %f, npin1 %f\n",npin0,npin1); + + /* Compute shaper space values of in' and spane */ +#ifdef NEVER + nsind = icxTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], nin); + nsind0 = icxTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], npin0); + nsind1 = icxTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], npin1); +#else + nsind = xfit_shpcurve(p, nin, chan); + nsind0 = xfit_shpcurve(p, npin0, chan); + nsind1 = xfit_shpcurve(p, npin1, chan); +#endif + +//if (db) printf("~1 nsind %f, nsind0 %f, nsind1 %f\n",nsind,nsind0,nsind1); + + /* Offset and scale shaper in' value to match position span */ + rv = (nsind - nsind0)/(nsind1 - nsind0) * (npind1 - npind0) + npind0; + +//if (db) printf("~1 scale offset ind %f\n",rv); + + /* de-normalize */ + rv = rv * (p->in_max[chan] - p->in_min[chan]) + p->in_min[chan]; +//if (db) printf("~1 returning %d\n",rv); + + /* Just positioning curve */ + } else if ((p->tcomb & oc_ip) == oc_p) { + rv = icxSTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], in, + p->in_min[chan], p->in_max[chan]); + } else { + rv = in; + } + return rv; +} + +/* Lookup di values though the input positioning and shaper curves */ +static void xfit_inpscurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) + out[e] = xfit_inpscurve(p, in[e], e); +} + +/* Inverse Lookup a value though the input positioning and shaper curves */ +static double xfit_invinpscurve(xfit *p, double in, int chan) { + double rv; + /* Just shaper curve */ + if ((p->tcomb & oc_ip) == oc_i) { + rv = icxInvSTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], in, + p->in_min[chan], p->in_max[chan]); + + /* shaper and positioning */ + } else if ((p->tcomb & oc_ip) == oc_ip) { + double nind, nin; /* normalized in', normalized in */ + int six; /* Span index */ + double npind0, npind1; /* normalized position in' span values */ + double npin0, npin1; /* normalized position in span values */ + double nsind0, nsind1; /* normalized shaper in' span values */ + double nsind; /* normalized shaper in' value */ + + /* Normalize */ + nind = (in - p->in_min[chan])/(p->in_max[chan] - p->in_min[chan]); + +//if (db) printf("\n~1 invinpscurve: cha %d, input value %f, norm %f\n",chan,in,nind); + + /* Quantize to grid */ + six = (int)floor(nind * (p->gres[chan]-1.0)); + if (six > (p->gres[chan]-2)) + six = (p->gres[chan]-2); + + /* Compute span in' values */ + npind0 = six / (p->gres[chan]-1.0); + npind1 = (six + 1.0) / (p->gres[chan]-1.0); + +//if (db) printf("~1 six %d, npind0 %f, npind1 %f\n",six,npind0,npind1); + + /* Lookup span in values through position curve */ + npin0 = icxInvTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], npind0); + npin1 = icxInvTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], npind1); + +//if (db) printf("~1 npin0 %f, npin1 %f\n",npin0,npin1); + + /* Compute span shaper in' values */ +#ifdef NEVER + nsind0 = icxTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], npin0); + nsind1 = icxTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], npin1); +#else + nsind0 = xfit_shpcurve(p, npin0, chan); + nsind1 = xfit_shpcurve(p, npin1, chan); +#endif + + /* Offset and scale position in' value to match shaper span */ + nsind = (nind - npind0)/(npind1 - npind0) * (nsind1 - nsind0) + nsind0; + +//if (db) printf("~1 nsind %f, nsind0 %f, nsind1 %f\n",nsind,nsind0,nsind1); + + /* Invert through shaper curve */ +#ifdef NEVER + nin = icxInvTransFunc(p->v + p->shp_offs[chan], p->iluord[chan], nsind); +#else + nin = xfit_invshpcurve(p, nsind, chan); +#endif + + /* de-normalize */ + rv = nin * (p->in_max[chan] - p->in_min[chan]) + p->in_min[chan]; + +//if (db) printf("\n~1 nin = %f, returning %f\n",nin,rv); + + /* Just positioning curve */ + } else if ((p->tcomb & oc_ip) == oc_p) { + rv = icxInvSTransFunc(p->v + p->pos_offs[chan], p->iluord[chan], in, + p->in_min[chan], p->in_max[chan]); + } else { + rv = in; + } + return rv; +} + +#ifdef NEVER +/* Check that inverse is working */ +static double _xfit_inpscurve(xfit *p, double in, int chan) { + double inv, rv, iinv; + + inv = in; + rv = xfit_inpscurve(p, in, chan); + iinv = xfit_invinpscurve(p, rv, chan); + + if (fabs(in - iinv) > 1e-5) + warning("xfit_inpscurve check, got %f, should be %f\n",iinv,in); + + return rv; +} +#define xfit_inpscurve _xfit_inpscurve +#endif /* NEVER */ + +/* Inverse Lookup di values though the input positioning and shaper curves */ +static void xfit_invinpscurves(xfit *p, double *out, double *in) { + int e; + + for (e = 0; e < p->di; e++) + out[e] = xfit_invinpscurve(p, in[e], e); +} + +/* - - - - - - - - - - - - - - - - - - - - */ +/* Combined output transfer curve functions */ + +/* Lookup a value though an output curve */ +static double xfit_outcurve(xfit *p, double in, int chan) { + double rv; + if (p->tcomb & oc_o) + rv = icxSTransFunc(p->v + p->out_offs[chan], p->oluord[chan], in, + p->out_min[chan], p->out_max[chan]); + else + rv = in; + return rv; +} + +/* Lookup fdi values though the output curves */ +static void xfit_outcurves(xfit *p, double *out, double *in) { + int f; + + for (f = 0; f < p->fdi; f++) { + double val = in[f]; + if (p->tcomb & oc_o) + val = icxSTransFunc(p->v + p->out_offs[f], p->oluord[f], val, + p->out_min[f], p->out_max[f]); + out[f] = val; + } +} + +/* Inverse Lookup a value though an output curve */ +static double xfit_invoutcurve(xfit *p, double in, int chan) { + double rv; + if (p->tcomb & oc_o) + rv = icxInvSTransFunc(p->v + p->out_offs[chan], p->oluord[chan], in, + p->out_min[chan], p->out_max[chan]); + else + rv = in; + return rv; +} + +/* Inverse Lookup fdi values though output curves */ +static void xfit_invoutcurves(xfit *p, double *out, double *in) { + int f; + + for (f = 0; f < p->fdi; f++) { + double val = in[f]; + if (p->tcomb & oc_o) + val = icxInvSTransFunc(p->v + p->out_offs[f], p->oluord[f], val, + p->out_min[f], p->out_max[f]); + out[f] = val; + } +} + +/* - - - - - - - - - */ + +/* Convert an output value from absolute */ +/* to relative using the current white point. */ +static void xfit_abs_to_rel(xfit *p, double *out, double *in) { + if (p->flags & XFIT_OUT_WP_REL) { + if (p->flags & XFIT_OUT_LAB) { + icmLab2XYZ(&icmD50, out, in); + icmMulBy3x3(out, p->fromAbs, out); + icmXYZ2Lab(&icmD50, out, out); + } else { + icmMulBy3x3(out, p->fromAbs, in); + } + } else { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + } +} + +/* - - - - - - - - - */ + +/* return a weighting for the magnitude of the in and out */ +/* shaping parameters squared. This is to reduce unconstrained "wiggles" */ +static double shapmag( +xfit *p /* Base of optimisation structure */ +) { + double tt, w; + double *b; /* Base of parameters for this section */ + int di = p->di; + int fdi = p->fdi; + int e, f, k; + double iparam = 0.0; + double oparam = 0.0; + double dd; + + if (p->opt_msk & oc_i) { + dd = SHAPE_WEIGHT/(double)(di); + b = p->v + p->shp_off; + for (e = 0; e < di; e++) { + for (k = 0; k < p->iluord[e]; k++) { + if (k <= 1) { + w = SHAPE_HW01; + } else if (k <= SHAPE_HBREAK) { + double bl = (k - 1.0)/(SHAPE_HBREAK - 1.0); + w = (1.0 - bl) * SHAPE_HW01 + bl * SHAPE_HWBR; + w *= p->shp_smooth[e]; + } else { + w = SHAPE_HWBR + (k-SHAPE_HBREAK) * SHAPE_HWINC; + w *= p->shp_smooth[e]; + } + tt = *b++; + tt *= tt; /* Squared */ + iparam += w * tt; + } + } + iparam *= dd; + } + + if (p->opt_msk & oc_o) { + dd = SHAPE_WEIGHT/(double)(fdi); + b = p->v + p->out_off; + for (f = 0; f < fdi; f++) { + for (k = 0; k < p->oluord[f]; k++) { + if (k <= 1) { + w = SHAPE_HW01; + } else if (k <= SHAPE_HBREAK) { + double bl = (k - 1.0)/(SHAPE_HBREAK - 1.0); + w = (1.0 - bl) * SHAPE_HW01 + bl * SHAPE_HWBR; + w *= p->out_smooth[f]; + } else { + w = SHAPE_HWBR + (k-SHAPE_HBREAK) * SHAPE_HWINC; + w *= p->out_smooth[f]; + } + tt = *b++; + tt *= tt; /* Squared */ + oparam += w * tt; + } + } + oparam *= dd; + } + return iparam + oparam; +} + +/* return a weighting for the magnitude of the in and out */ +/* shaping parameters. This is to reduce unconstrained "wiggles" */ +/* Also sum the partial derivative for the parameters involved */ +static double dshapmag( +xfit *p, /* Base of optimisation structure */ +double *dav /* Sum del's */ +) { + double tt, w; + double *b, *c; /* Base of parameters for this section */ + int di = p->di; + int fdi = p->fdi; + int e, f, k; + double iparam = 0.0; + double oparam = 0.0; + double dd; + + if (p->opt_msk & oc_i) { + dd = SHAPE_WEIGHT/(double)(di); + b = p->v + p->shp_off; + c = dav + p->shp_off; + for (e = 0; e < di; e++) { + for (k = 0; k < p->iluord[e]; k++) { + if (k <= 1) { + w = SHAPE_HW01; + } else if (k <= SHAPE_HBREAK) { + double bl = (k - 1.0)/(SHAPE_HBREAK - 1.0); + w = (1.0 - bl) * SHAPE_HW01 + bl * SHAPE_HWBR; + w *= p->shp_smooth[e]; + } else { + w = SHAPE_HWBR + (k-SHAPE_HBREAK) * SHAPE_HWINC; + w *= p->shp_smooth[e]; + } + tt = *b++; + *c++ += 2.0 * dd * w * tt; + tt *= tt; /* Squared */ + iparam += w * tt; + + } + } + iparam *= dd; + } + + if (p->opt_msk & oc_o) { + dd = SHAPE_WEIGHT/(double)(fdi); + b = p->v + p->out_off; + c = dav + p->out_off; + for (f = 0; f < fdi; f++) { + for (k = 0; k < p->oluord[f]; k++) { + if (k <= 1) { + w = SHAPE_HW01; + } else if (k <= SHAPE_HBREAK) { + double bl = (k - 1.0)/(SHAPE_HBREAK - 1.0); + w = (1.0 - bl) * SHAPE_HW01 + bl * SHAPE_HWBR; + w *= p->out_smooth[f]; + } else { + w = SHAPE_HWBR + (k-SHAPE_HBREAK) * SHAPE_HWINC; + w *= p->out_smooth[f]; + } + tt = *b++; + *c++ += 2.0 * dd * w * tt; + tt *= tt; /* Squared */ + oparam += w * tt; + } + } + oparam *= dd; + } + return iparam + oparam; +} + +/* Scale the shaper derivatives */ +static void dshapscale( +xfit *p, /* Base of optimisation structure */ +double *dav, /* del's */ +double scale /* Scale factor */ +) { + double tt, w; + double *b, *c; /* Base of parameters for this section */ + int di = p->di; + int fdi = p->fdi; + int e, f, k; + + if (p->opt_msk & oc_i) { + c = dav + p->shp_off; + for (e = 0; e < di; e++) { + for (k = 0; k < p->iluord[e]; k++) { + *c++ *= scale; + } + } + } + + if (p->opt_msk & oc_o) { + c = dav + p->out_off; + for (f = 0; f < fdi; f++) { + for (k = 0; k < p->oluord[f]; k++) { + *c++ *= scale; + } + } + } +} + +/* Progress function */ +static void xfitprog(void *pdata, int perc) { + xfit *p = (xfit *)pdata; + + if (p->verb) { + printf("%c% 3d%%",cr_char,perc); + if (perc == 100) + printf("\n"); + fflush(stdout); + } +} + + +int xfitfunc_trace = 1; + +/* Shaper+Matrix optimisation function handed to powell() */ +/* We simply minimize the total delta E squared, consistent with smoothness */ +static double xfitfunc(void *edata, double *v) { + xfit *p = (xfit *)edata; + double tw = 0.0; /* Total weight */ + double ev = 0.0, rv, smv; + double tin[MXDI], out[MXDO]; + int di = p->di; + int fdi = p->fdi; + int i, e, f; + + /* Copy the parameters being optimised into xfit structure */ + + /* Special case - a single shaper curve. The first sm_iluord params */ + /* are the common curve parameters, and the remainder are the matrix onwards */ + if (p->opt_ssch) { + + for (e = 0; e < di; e++) { /* Duplicate and extend to per channel curve params */ + for (i = 0; i < p->sm_iluord; i++) + p->v[p->shp_offs[e] + i] = v[i]; + for (; i < p->iluord[e]; i++) + p->v[p->shp_offs[e] + i] = 0.0; + } + for (i = p->sm_iluord; i < p->opt_cnt; i++) + p->v[p->mat_off + i - p->sm_iluord] = v[i]; + } else { + for (i = 0; i < p->opt_cnt; i++) { +//printf("~1 param %d = %f\n",i,v[i]); + p->v[p->opt_off + i] = v[i]; + } + } + + /* For all our data points */ + for (i = 0; i < p->nodp; i++) { + double del; + + /* Apply input shaper channel curves */ + for (e = 0; e < di; e++) + tin[e] = icxSTransFunc(p->v + p->shp_offs[e], p->iluord[e], p->rpoints[i].p[e], + p->in_min[e], p->in_max[e]); + + /* Apply matrix cube interpolation */ + icxCubeInterp(p->v + p->mat_off, fdi, di, out, tin); + + /* Apply output channel curves */ + for (f = 0; f < fdi; f++) + out[f] = icxSTransFunc(p->v + p->out_offs[f], p->oluord[f], out[f], + p->out_min[f], p->out_max[f]); + + /* Evaluate the error squared */ + if (p->flags & XFIT_FM_INPUT) { + double pp[MXDI]; + for (e = 0; e < di; e++) + pp[e] = p->rpoints[i].p[e]; + for (f = 0; f < fdi; f++) { + double t1 = p->rpoints[i].v[f] - out[f]; /* Error in output */ + + /* Create input point offset by equivalent delta to output point */ + /* error, in proportion to the partial derivatives for that output. */ + for (e = 0; e < di; e++) + pp[e] += t1 * p->piv[i].ide[f][e]; + } + del = p->to_de2(p->cntx2, pp, p->rpoints[i].p); + } else { + del = p->to_de2(p->cntx2, out, p->rpoints[i].v); + } + if (CURVEPOW > 1.0) + del = pow(del, CURVEPOW); + tw += p->rpoints[i].w; + ev += p->rpoints[i].w * del; + } + + /* Normalise error to be an average delta E squared */ + ev /= tw; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + smv = shapmag(p); + if (CURVEPOW > 1.0) + smv = pow(smv, CURVEPOW); + rv = ev + smv; + +#ifdef DEBUG +if (xfitfunc_trace) +fprintf(stdout,"~1(sm %f, ev %f)xfitfunc returning %f\n",smv,ev,rv); +#endif + + return rv; +} + +/* Shaper+Matrix optimisation function with partial derivatives, */ +/* handed to conjgrad() */ +static double dxfitfunc(void *edata, double *dv, double *v) { + xfit *p = (xfit *)edata; + double tw = 0.0; /* Total weight */ + double ev = 0.0, rv, smv; + double tin[MXDI], out[MXDO]; + + double dav[MXPARMS]; /* Overall del due to del param vals */ + double sdav[MXPARMS]; /* Overall del due to del smooth param vals */ + + double dtin_iv[MXDI * MXLUORD]; /* Del in itrans out due to del itrans param vals */ + double dmato_mv[1 << MXDI]; /* Del in mat out due to del in matrix param vals */ + double dmato_tin[MXDO * MXDI]; /* Del in mat out due to del in matrix input values */ + double dout_ov[MXDO * MXLUORD]; /* Del in otrans out due to del in otrans param values */ + double dout_mato[MXDO]; /* Del in otrans out due to del in otrans input values */ + + double dout_de[2][MXDIDO]; /* Del in DE due to two output values */ + + int di = p->di; + int fdi = p->fdi; + int i, jj, k, e, ee, f, ff; + + /* Copy the parameters being optimised into xfit structure */ + + /* Special case - a single shaper curve. The first sm_iluord params */ + /* are the common curve parameters, and the remainder are the matrix onwards */ + if (p->opt_ssch) { + for (e = 0; e < di; e++) { /* Duplicate and extend to per channel curve params */ + for (i = 0; i < p->sm_iluord; i++) + p->v[p->shp_offs[e] + i] = v[i]; + for (; i < p->iluord[e]; i++) + p->v[p->shp_offs[e] + i] = 0.0; + } + for (i = p->sm_iluord; i < p->opt_cnt; i++) + p->v[p->mat_off + i - p->sm_iluord] = v[i]; + + } else { + for (i = 0; i < p->opt_cnt; i++) { +//printf("~1 param %d = %f\n",i,v[i]); + p->v[p->opt_off + i] = v[i]; + } + } + + /* Zero the accumulated partial derivatives */ + /* We compute deriv for all parameters (not just current optimised) */ + for (i = 0; i < p->tot_cnt; i++) + dav[i] = 0.0; + + /* For all our data points */ + for (i = 0; i < p->nodp; i++) { + double del; + + /* Apply input channel curves */ + for (e = 0; e < di; e++) + tin[e] = icxdpSTransFunc(p->v + p->shp_offs[e], &dtin_iv[p->shp_offs[e] - p->shp_off], + p->iluord[e], p->rpoints[i].p[e], p->in_min[e], p->in_max[e]); + + /* Apply matrix cube interpolation */ + icxdpdiCubeInterp(p->v + p->mat_off, dmato_mv, dmato_tin, fdi, di, out, tin); + + /* Apply output channel curves */ + for (f = 0; f < fdi; f++) + out[f] = icxdpdiSTransFunc(p->v + p->out_offs[f], + &dout_ov[p->out_offs[f] - p->out_off], &dout_mato[f], + p->oluord[f], out[f], p->out_min[f], p->out_max[f]); + + + /* Convert to Delta E and compute pde's into dout_de squared */ + if (p->flags & XFIT_FM_INPUT) { + double tdout_de[2][MXDIDO]; + double pp[MXDI]; + for (e = 0; e < di; e++) + pp[e] = p->rpoints[i].p[e]; + for (f = 0; f < fdi; f++) { + double t1 = p->rpoints[i].v[f] - out[f]; /* Error in output */ + + /* Create input point offset by equivalent delta to output point */ + /* error, in proportion to the partial derivatives for that output. */ + for (e = 0; e < di; e++) + pp[e] += t1 * p->piv[i].ide[f][e]; + } + del = p->to_dde2(p->cntx2, tdout_de, pp, p->rpoints[i].p); + if (CURVEPOW > 1.0) { + double dadj; + dadj = CURVEPOW * pow(del, CURVEPOW - 1.0); /* Adjust derivative accordingly */ + del = pow(del, CURVEPOW); + for (e = 0; e < di; e++) + tdout_de[0][e] *= dadj; + } + + /* Compute partial derivative */ + for (e = 0; e < di; e++) { + dout_de[0][e] = 0.0; + for (f = 0; f < fdi; f++) { + dout_de[0][e] += tdout_de[0][e] * p->piv[i].ide[f][e]; + } + } + } else { + del = p->to_dde2(p->cntx2, dout_de, out, p->rpoints[i].v); + if (CURVEPOW > 1.0) { + double dadj; + dadj = CURVEPOW * pow(del, CURVEPOW - 1.0); /* Adjust derivative accordingly */ + del = pow(del, CURVEPOW); + for (f = 0; f < fdi; f++) + dout_de[0][f] *= dadj; + } + } + + /* Accumulate total weighted delta E squared */ + tw += p->rpoints[i].w; + ev += p->rpoints[i].w * del; + + /* Compute and accumulate partial difference values for each parameter value */ + if (p->opt_msk & oc_i) { + /* Input transfer parameters */ + for (ee = 0; ee < di; ee++) { /* Parameter input chanel */ + for (k = 0; k < p->iluord[ee]; k++) { /* Param within channel */ + double vv = 0.0; + jj = p->shp_offs[ee] - p->shp_off + k; /* Overall input trans param */ + +// for (ff = 0; ff < 3; ff++) { /* Lab channels */ + for (ff = 0; ff < fdi; ff++) { /* Output channels */ + vv += dout_de[0][ff] * dout_mato[ff] + * dmato_tin[ff * di + ee] * dtin_iv[jj]; + } + dav[p->shp_off + jj] += p->rpoints[i].w * vv; + } + } + } + + if (p->opt_msk & oc_m) { + /* Matrix parameters */ + for (ff = 0; ff < fdi; ff++) { /* Parameter output chanel */ + for (ee = 0; ee < (1 << di); ee++) { /* Matrix input combination chanel */ + double vv = 0.0; + jj = ff * (1 << di) + ee; /* Matrix Parameter index */ + + vv += dout_de[0][ff] * dout_mato[ff] * dmato_mv[ee]; + dav[p->mat_off + jj] += p->rpoints[i].w * vv; + } + } + } + + if (p->opt_msk & oc_o) { + /* Output transfer parameters */ + for (ff = 0; ff < fdi; ff++) { /* Parameter output chanel */ + for (k = 0; k < p->oluord[ff]; k++) { /* Param within channel */ + double vv = 0.0; + jj = p->out_offs[ff] - p->out_off + k; /* Overall output trans param */ + + vv += dout_de[0][ff] * dout_ov[jj]; + dav[p->out_off + jj] += p->rpoints[i].w * vv; + } + } + } + } + + /* Normalise error to be an average delta E squared */ + ev /= tw; + for (i = 0; i < p->tot_cnt; i++) { + dav[i] /= tw; + sdav[i] = 0.0; + } + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + /* Compute partial derivative wrt those parameters too */ + smv = dshapmag(p, sdav); + if (CURVEPOW > 1.0) { + double dadj; + dadj = CURVEPOW * pow(smv, CURVEPOW - 1.0); /* Adjust derivative accordingly */ + smv = pow(smv, CURVEPOW); + dshapscale(p, sdav, dadj); /* Scale the partial derivatives */ + } + rv = ev + smv; + + /* Sum the del for parameters being optimised and copy to return array */ + + if (p->opt_ssch) { + for (i = 0; i < p->sm_iluord; i++) + dv[i] = 0.0; + for (e = 0; e < di; e++) { /* Combine per channel curve de's */ + for (i = 0; i < p->sm_iluord; i++) + dv[i] += dav[p->shp_offs[e] + i] = sdav[p->shp_offs[e] + i]; + } + for (i = p->sm_iluord; i < p->opt_cnt; i++) /* matrix and rest de's */ + dv[i] = dav[p->mat_off + i - p->sm_iluord] + sdav[p->mat_off + i - p->sm_iluord]; + + } else { + for (i = 0; i < p->opt_cnt; i++) + dv[i] = dav[p->opt_off + i] + sdav[p->opt_off + i]; + } + +#ifdef DEBUG +fprintf(stdout,"~1(sm %f, ev %f)dxfitfunc returning %f\n",smv,ev,rv); +#endif + + return rv; +} + +#ifdef NEVER +/* Check partial derivative function within xfitfunc() [Intensive check] */ + +static double _xfitfunc(void *edata, double *v) { + xfit *p = (xfit *)edata; + int i; + double dv[MXPARMS]; + double rv, drv; + double trv; + int verb; + + rv = xfitfunc(edata, v); + verb = p->verb; + p->verb = 0; + drv = dxfitfunc(edata, dv, v); + p->verb = verb; + + if (fabs(rv - drv) > 1e-6) + printf("######## RV MISMATCH is %f should be %f ########\n",rv,drv); + + /* Check each parameter delta */ + xfitfunc_trace = 0; + for (i = 0; i < p->opt_cnt; i++) { + double del; + + v[i] += 1e-7; + trv = xfitfunc(edata, v); + v[i] -= 1e-7; + + /* Check that del is correct */ + del = (trv - rv)/1e-7; + if (fabs(dv[i] - del) > 0.04) { +//printf("~1 del = %f from (trv %f - rv %f)/0.1\n",del,trv,rv); + printf("######## EXCESSIVE at v[%d] is %f should be %f ########\n",i,dv[i],del); + } + } + xfitfunc_trace = 1; + return rv; +} + +#define xfitfunc _xfitfunc +#endif /* NEVER */ + +#ifdef NEVER +/* Check partial derivative function within dxfitfunc() [Less intensive check] */ + +static double _dxfitfunc(void *edata, double *dv, double *v) { + xfit *p = (xfit *)edata; + int i; + double rv, drv; + double trv; + int verb; + int exec = 0; + + rv = xfitfunc(edata, v); + verb = p->verb; + p->verb = 0; + drv = dxfitfunc(edata, dv, v); + p->verb = verb; + + if (fabs(rv - drv) > 1e-6) + printf("######## RV MISMATCH is %f should be %f ########\n",rv,drv); + + /* Check each parameter delta */ + xfitfunc_trace = 0; + for (i = 0; i < p->opt_cnt; i++) { + double del; + + v[i] += 1e-7; + trv = xfitfunc(edata, v); + v[i] -= 1e-7; + + /* Check that del is correct */ + del = (trv - rv)/1e-7; + if (fabs(dv[i] - del) > 0.04) { +//printf("~1 del = %f from (trv %f - rv %f)/0.1\n",del,trv,rv); + printf("######## EXCESSIVE at v[%d] is %f should be %f ########\n",i,dv[i],del); + exec = 1; + } + } +#ifdef NEVER + if (exec) { + printf("~1 parameters are:\n"); + for (i = 0; i < p->opt_cnt; i++) + printf("p->wv[%d] = %f;\n",i,v[i]); + exit(1); + } +#endif + xfitfunc_trace = 1; + return rv; +} + +#define dxfitfunc _dxfitfunc +#endif /* NEVER */ + +/* - - - - - - - - - */ + +/* Output curve symetry optimisation function handed to powell() */ +/* Just the order 0 value will be adjusted */ +static double symoptfunc(void *edata, double *v) { + xfit *p = (xfit *)edata; + double out[1], in[1] = { 0.0 }; + int ch = p->opt_ch; /* Output channel being adjusted for symetry */ + double rv; + + /* Copy the parameter being tested back into xfit */ + p->v[p->out_offs[ch]] = v[0]; + *out = icxSTransFunc(p->v + p->out_offs[ch], p->oluord[ch], *in, + p->out_min[ch], p->out_max[ch]); + + rv = out[0] * out[0]; + +#ifdef DEBUG +printf("~1symoptfunc returning %f\n",rv); +#endif + return rv; +} + +/* - - - - - - - - - */ + +/* Set up for an optimisation run: */ +/* Figure out parameters being optimised, */ +/* copy them to start values, */ +/* init and scale the search radius */ +static void setup_xfit( +xfit *p, +double *wv, /* Return parameters to hand to optimiser */ +double *sa, /* Return search radius to hand to optimiser */ +double transrad,/* Nominal transfer curve radius, 0.0 - 3.0 */ +double matrad /* Nominal matrix radius, 0.0 - 1.0 */ +) { + int i; + p->opt_off = -1; + p->opt_cnt = 0; + + if (p->opt_msk & oc_i) { + + if (p->opt_ssch) { /* Special case - should only be used first, */ + /* Fitting a sigle common input shaper curve. */ + if (p->opt_off < 0) + p->opt_off = p->mat_off - p->sm_iluord; /* Shouldn't be used... */ + p->opt_cnt += p->sm_iluord; + + for (i = 0; i < p->sm_iluord; i++) { + *wv++ = 0.0; + *sa++ = transrad; + } + + } else { /* Initial or continuing fitting of all the curves */ + if (p->opt_off < 0) + p->opt_off = p->shp_off; + p->opt_cnt += p->shp_cnt; + + for (i = 0; i < p->shp_cnt; i++) { + *wv++ = p->v[p->shp_off + i]; + *sa++ = transrad; + } + } + } + if (p->opt_msk & oc_m) { + if (p->opt_off < 0) + p->opt_off = p->mat_off; + p->opt_cnt += p->mat_cnt; + + for (i = 0; i < p->mat_cnt; i++) { + *wv++ = p->v[p->mat_off + i]; + *sa++ = matrad; + } + } + if (p->opt_msk & oc_o) { + if (p->opt_off < 0) + p->opt_off = p->out_off; + p->opt_cnt += p->out_cnt; + + for (i = 0; i < p->out_cnt; i++) { + *wv++ = p->v[p->out_off + i]; + *sa++ = transrad; + } + } + if (p->opt_cnt > MXPARMS) + error("setup_xfit: asert, %d exceeded MXPARMS %d",p->opt_cnt,MXPARMS); + +#ifdef DEBUG + printf("setup_xfit() got opt_msk 0x%x, opt_off %d, opt_cnt = %d\n", + p->opt_msk,p->opt_off,p->opt_cnt); +#endif /* DEBUG */ +} + +#ifdef DEBUG +/* Diagnostic */ +static void dump_xfit( +xfit *p +) { + int i, e, f; + double *b; /* Base of parameters for this section */ + int di, fdi; + di = p->di; + fdi = p->fdi; + + /* Input positioning curve */ + b = p->v + p->pos_off; + for (e = 0; e < di; b += p->iluord[e], e++) { + printf("pos %d = ",e); + for (i = 0; i < p->iluord[e]; i++) + printf("%f ",b[i]); + printf("\n"); + } + + /* Input shaper curve */ + b = p->v + p->shp_off; + for (e = 0; e < di; b += p->iluord[e], e++) { + printf("shp %d = ",e); + for (i = 0; i < p->iluord[e]; i++) + printf("%f ",b[i]); + printf("\n"); + } + + /* Matrix */ + b = p->v + p->mat_off; + for (e = 0; e < (1 << di); e++) { + printf("mx %d = ",e); + for (f = 0; f < fdi; f++) + printf("%f ",*b++); + printf("\n"); + } + + /* Output curve */ + b = p->v + p->out_off; + for (f = 0; f < fdi; b += p->oluord[f], f++) { + printf("out %d = ",f); + for (i = 0; i < p->oluord[f]; i++) + printf("%f ",b[i]); + printf("\n"); + } +} +#endif /* DEBUG */ + +/* - - - - - - - - - */ + +/* Setup the pseudo inverse information for each test point, */ +/* using the current model. */ +static void setup_piv(xfit *p) { + int di = p->di; + int fdi = p->fdi; + int i, e, f; + + /* Estimate in -> out partial derivatives */ + for (i = 0; i < p->nodp; i++) { + double pd[MXDO][MXDI]; + double pp[MXDI]; + double vv[MXDIDO]; + + /* Estimate in -> out partial derivatives */ + for (e = 0; e < di; e++) + pp[e] = p->ipoints[i].p[e]; + + /* Apply input shaper channel curves */ + for (e = 0; e < di; e++) + vv[e] = icxSTransFunc(p->v + p->shp_offs[e], p->iluord[e], pp[e], + p->in_min[e], p->in_max[e]); + + /* Apply matrix cube interpolation */ + icxCubeInterp(p->v + p->mat_off, fdi, di, vv, vv); + + /* Apply output channel curves */ + for (f = 0; f < fdi; f++) + vv[f] = icxSTransFunc(p->v + p->out_offs[f], p->oluord[f], vv[f], + p->out_min[f], p->out_max[f]); + + + for (e = 0; e < di; e++) { + double tt[MXDIDO]; + + pp[e] += 1e-4; + + /* Apply input shaper channel curves */ + for (e = 0; e < di; e++) + tt[e] = icxSTransFunc(p->v + p->shp_offs[e], p->iluord[e], pp[e], + p->in_min[e], p->in_max[e]); + + /* Apply matrix cube interpolation */ + icxCubeInterp(p->v + p->mat_off, fdi, di, tt, tt); + + /* Apply output channel curves */ + for (f = 0; f < fdi; f++) + tt[f] = icxSTransFunc(p->v + p->out_offs[f], p->oluord[f], tt[f], + p->out_min[f], p->out_max[f]); + + + for (f = 0; f < p->fdi; f++) + pd[f][e] = (tt[f] - vv[f])/1e-4; + + pp[e] -= 1e-4; + } + + /* Compute a psudo inverse matrix to map rout delta E to */ + /* in delta E in proportion to the pd magnitude. */ + for (f = 0; f < fdi; f++) { + double ss = 0.0; + + for (e = 0; e < di; e++) /* Sum of pd's ^4 */ + ss += pd[f][e] * pd[f][e] * pd[f][e] * pd[f][e]; + ss = sqrt(ss); + if (ss > 1e-8) { + for (e = 0; e < di; e++) + p->piv[i].ide[f][e] = pd[f][e]/ss; + } else { /* Hmm. */ + for (e = 0; e < di; e++) + p->piv[i].ide[f][e] = 0.0; + } + } + } +} + +/* - - - - - - - - - */ + +/* Function to pass to rspl to re-set output values, */ +/* to account for skeleton model offset. */ +static void +skm_rspl_out( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + xfit *p = (xfit *)pp; + int f, fdi = p->fdi; + double inval[MXDI]; + double skval[MXDO]; + + /* Look up the skeleton value for this grid point */ + xfit_invinpscurves(p, inval, in); /* Back to input values */ + p->skm->lookup(p->skm, skval, inval); /* Skm */ + xfit_abs_to_rel(p, skval, skval); + xfit_invoutcurves(p, skval, skval); + + for (f = 0; f < fdi; f++) + out[f] += skval[f]; /* Add it back */ +} + +/* Weak function rspl callback (not used) */ +void skm_weak(void *cbntx, double *out, double *in) { + xfit *p = (xfit *)cbntx; + +#ifndef NEVER + int f, fdi = p->fdi; + + for (f = 0; f < fdi; f++) + out[f] = 0.0; /* Deviation from skeleton should tend to zero */ + +#else /* Skeleton as weak atractor */ + int f, fdi = p->fdi; + double inval[MXDI]; + + /* Look up the skeleton value for this grid point */ + xfit_invinpscurves(p, inval, in); /* Back to input values */ + p->skm->lookup(p->skm, out, inval); /* Skm */ + xfit_abs_to_rel(p, out, out); + xfit_invoutcurves(p, out, out); + +#endif +} + +/* - - - - - - - - - */ + +/* Function to pass to rspl to re-set output values, */ +/* to make them relative to the white and black points */ +static void +conv_rspl_out( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + xfit *p = (xfit *)pp; + double tt[3]; + + /* Convert the clut values to output values */ + xfit_outcurves(p, tt, out); + + if (p->flags & XFIT_OUT_LAB) { + icmLab2XYZ(&icmD50, tt, tt); + icmMulBy3x3(out, p->cmat, tt); + icmXYZ2Lab(&icmD50, out, out); + + } else { /* We are all in XYZ */ + icmMulBy3x3(out, p->cmat, tt); + } + + /* And then convert them back to clut values */ + xfit_invoutcurves(p, out, out); +} + +/* Function to pass to rspl to re-set output values, */ +/* to clip any with Y over 1.0 to D50 */ +static void +clip_rspl_out( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + xfit *p = (xfit *)pp; + double tt[3]; + + /* Convert the clut values to output values */ + xfit_outcurves(p, tt, out); + + if (p->flags & XFIT_OUT_LAB) { + if (tt[0] > 100.0) + icmCpy3(out, p->cmat[0]); + } else { + if (tt[1] > 1.0) + icmCpy3(out, p->cmat[0]); + } +} + +//#ifdef SPECIAL_TEST +/* - - - - - - - - - */ + +/* Execute the linear XYZ device model */ +static void domodel(double *out, double *in) { + double tmp[3]; + int i, j; + double col[3][3]; /* sRGB additive colorant values in XYZ :- [out][in] */ + + col[0][0] = 0.412424; /* X from R */ + col[0][1] = 0.357579; /* X from G */ + col[0][2] = 0.180464; /* X from B */ + col[1][0] = 0.212656; /* Y from R */ + col[1][1] = 0.715158; /* Y from G */ + col[1][2] = 0.0721856; /* Y from B */ + col[2][0] = 0.0193324; /* Z from R */ + col[2][1] = 0.119193; /* Z from G */ + col[2][2] = 0.950444; /* Z from B */ + +#ifdef SPECIAL_TEST_GAMMA + tmp[0] = pow(in[0], 1.9); + tmp[1] = pow(in[1], 2.0); + tmp[2] = pow(in[2], 2.1); +#else + tmp[0] = in[0]; + tmp[1] = in[1]; + tmp[2] = in[2]; +#endif + + for (j = 0; j < 3; j++) { + out[j] = 0.0; + for (i = 0; i < 3; i++) + out[j] += col[j][i] * tmp[i]; + } +} + +#ifdef SPECIAL_FORCE +/* Function to pass to rspl to set nodes against */ +/* synthetic model. */ +static void +set_rspl_out1( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + xfit *p = (xfit *)pp; + double tt[3], tout[3]; + + /* Convert the input' values to input values */ + xfit_invinpscurves(p, tt, in); + + /* Synthetic linear rgb->XYZ model */ + domodel(tout, tt); + + /* Apply abs->rel white point adjustment */ + icmMulBy3x3(tout, p->cmat, tout); + +#ifdef SPECIAL_TEST_LAB + icmXYZ2Lab(&icmD50, tout, tout); +#endif + /* And then convert them back to clut values */ + xfit_invoutcurves(p, tout, tout); + +#ifdef DEBUG +printf("~1 changing %f %f %f -> %f %f %f\n", out[0], out[1], out[2], tout[0], tout[1], tout[2]); +#endif + + out[0] = tout[0]; + out[1] = tout[1]; + out[2] = tout[2]; +} + +#endif /* SPECIAL_FORCE */ + +/* - - - - - - - - - */ +/* Do the fitting. */ +/* return nz on error */ +/* 1 = malloc or other error */ +int xfit_fit( + struct _xfit *p, + int flags, /* Flag values */ + int di, /* Input dimensions */ + int fdi, /* Output dimensions */ + int rsplflags, /* clut rspl creation flags */ + double *wp, /* if flags & XFIT_OUT_WP_REL or XFIT_OUT_WP_REL_US, */ + /* Initial white point, returns final wp */ + double *dw, /* Device white value to adjust to be D50 */ + double wpscale, /* If >= 0.0 scale final wp */ + double *dgw, /* Device space gamut boundary white for XFIT_OUT_WP_REL_US */ + /* (ie. RGB 1,1,1 CMYK 0,0,0,0, etc) */ + cow *ipoints, /* Array of data points to fit - referece taken */ + int nodp, /* Number of data points */ + icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */ + double in_min[MXDI], /* Input value scaling/domain minimum */ + double in_max[MXDI], /* Input value scaling/domain maximum */ + int gres[MXDI], /* clut resolutions being optimised for/returned */ + double out_min[MXDO], /* Output value scaling/range minimum */ + double out_max[MXDO], /* Output value scaling/range maximum */ + double smooth, /* clut rspl smoothing factor */ + double oavgdev[MXDO], /* Average output value deviation */ + int iord[], /* Order of input pos/shaper curve for each dimension */ + int sord[], /* Order of input sub-grid shaper curve (not used) */ + int oord[], /* Order of output shaper curve for each dimension */ + double shp_smooth[MXDI],/* Smoothing factors for each curve, nom = 1.0 */ + double out_smooth[MXDO], + optcomb tcomb, /* Flag - target elements to fit. */ + void *cntx2, /* Context of callbacks */ + /* Callback to convert two fit values delta E squared */ + double (*to_de2)(void *cntx, double *in1, double *in2), + /* Same as above, with partial derivatives */ + double (*to_dde2)(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) +) { + int i, e, f; + double *b; /* Base of parameters for this section */ + int poff; + + if (tcomb & oc_io) /* If we're doing anything, we need the matrix */ + tcomb |= oc_m; + + p->flags = flags; + if (flags & XFIT_VERB) + p->verb = 1; + else + p->verb = 0; + p->di = di; + p->fdi = fdi; + p->wp = wp; /* Take reference, so modified wp can be returned */ + p->dw = dw; + p->nodp = nodp; + p->skm = skm; /* This isn't current used by profin, because it doesn't help.. */ + p->ipoints = ipoints; + p->tcomb = tcomb; + p->cntx2 = cntx2; + for (e = 0; e < di; e++) + p->gres[e] = gres[e]; + p->to_de2 = to_de2; + p->to_dde2 = to_dde2; + +#ifdef DEBUG + printf("xfit_fit called with flags = 0x%x, di = %d, fdi = %d, nodp = %d, tcomb = 0x%x\n",flags,di,fdi,nodp,tcomb); +#endif + +//printf("~1 out min = %f %f %f max = %f %f %f\n", out_min[0], out_min[1], out_min[2], out_max[0], out_max[1], out_max[2]); + + /* Sanity protect shaper orders */ + /* and save scaling and smoothness factors. */ + p->sm_iluord = MXLUORD+1; + for (e = 0; e < di; e++) { + if (iord[e] > MXLUORD) + p->iluord[e] = MXLUORD; + else + p->iluord[e] = iord[e]; + if (p->iluord[e] < p->sm_iluord) + p->sm_iluord = p->iluord[e]; + p->in_min[e] = in_min[e]; + p->in_max[e] = in_max[e]; + p->shp_smooth[e] = shp_smooth[e]; + } + for (f = 0; f < fdi; f++) { + if (oord[f] > MXLUORD) + p->oluord[f] = MXLUORD; + else + p->oluord[f] = oord[f]; + p->out_min[f] = out_min[f]; + p->out_max[f] = out_max[f]; + p->out_smooth[f] = out_smooth[f]; + } + + + /* Compute parameter offset and count information */ + p->shp_off = 0; + for (poff = p->shp_off, p->shp_cnt = 0, e = 0; e < di; e++) { + p->shp_offs[e] = poff; + p->shp_cnt += p->iluord[e]; + poff += p->iluord[e]; + } + + p->mat_off = p->shp_off + p->shp_cnt; + for (poff = p->mat_off, p->mat_cnt = 0, f = 0; f < fdi; f++) { + p->mat_offs[f] = poff; + p->mat_cnt += (1 << di); + poff += (1 << di); + } + + p->out_off = p->mat_off + p->mat_cnt; + for (poff = p->out_off, p->out_cnt = 0, f = 0; f < fdi; f++) { + p->out_offs[f] = poff; + p->out_cnt += p->oluord[f]; + poff += p->oluord[f]; + } + + p->pos_off = p->out_off + p->out_cnt; + for (poff = p->pos_off, p->pos_cnt = 0, e = 0; e < di; e++) { + p->pos_offs[e] = poff; + p->pos_cnt += p->iluord[e]; + poff += p->iluord[e]; + } + + p->tot_cnt = p->shp_cnt + p->mat_cnt + p->out_cnt + p->pos_cnt; + if (p->tot_cnt > MXPARMS) + error("xfit_fit: assert tot_cnt exceeds MXPARMS"); + + /* Allocate space for parameter values */ + if (p->v != NULL) { + free(p->v); + p->v = NULL; + } + if (p->wv != NULL) { + free(p->wv); + p->wv = NULL; + } + if (p->sa != NULL) { + free(p->sa); + p->sa = NULL; + } + if ((p->v = (double *)calloc(p->tot_cnt, sizeof(double))) == NULL) + return 1; + if ((p->wv = (double *)calloc(p->tot_cnt, sizeof(double))) == NULL) + return 1; + if ((p->sa = (double *)calloc(p->tot_cnt, sizeof(double))) == NULL) + return 1; + + /* Setup initial white point abs->rel conversions */ + if ((p->flags & XFIT_OUT_WP_REL) != 0) { + icmXYZNumber _wp; + + icmAry2XYZ(_wp, p->wp); + + /* Absolute->Aprox. Relative Adaptation matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, p->fromAbs); + + /* Aproximate relative to absolute conversion matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, p->toAbs); + + if (p->verb) { + double lab[3]; + icmXYZ2Lab(&icmD50, lab, p->wp); + printf("Initial White Point XYZ %f %f %f, Lab %f %f %f\n", + p->wp[0], p->wp[1], p->wp[2], lab[0], lab[1], lab[2]); + } + } else { + icmSetUnity3x3(p->fromAbs); + icmSetUnity3x3(p->toAbs); + } + + /* Setup input position/shape curves to be linear initially */ + b = p->v + p->shp_off; + for (e = 0; e < di; b += p->iluord[e], e++) { + for (i = 0; i < p->iluord[e]; i++) { + b[i] = 0.0; + } + } + + /* Setup matrix to be pure colorant' values initially */ + b = p->v + p->mat_off; + for (e = 0; e < (1 << di); e++) { /* For each colorant combination */ + int j, k, bk = 0; + double bdif = 1e6; + double ov[MXDO]; + + /* Search the patch list to find the one closest to this input combination */ + for (k = 0; k < p->nodp; k++) { + double dif = 0.0; + for (j = 0; j < di; j++) { + double tt; + if (e & (1 << j)) + tt = p->in_max[j] - p->ipoints[k].p[j]; + else + tt = p->in_min[j] - p->ipoints[k].p[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bk = k; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + xfit_abs_to_rel(p, ov, p->ipoints[bk].v); + + for (f = 0; f < fdi; f++) + b[f * (1 << di) + e] = ov[f]; + } + + /* Setup output curves to be linear initially */ + b = p->v + p->out_off; + for (f = 0; f < fdi; b += p->oluord[f], f++) { + for (i = 0; i < p->oluord[f]; i++) { + b[i] = 0.0; + } + } + + /* Setup positioning curves to be linear initially */ + b = p->v + p->pos_off; + for (e = 0; e < di; b += p->iluord[e], e++) { + for (i = 0; i < p->iluord[e]; i++) { + b[i] = 0.0; + } + } + + /* Create copy of input points with output converted to white relative */ + if (p->rpoints == NULL) { + if ((p->rpoints = (cow *)malloc(p->nodp * sizeof(cow))) == NULL) + return 1; + } + for (i = 0; i < p->nodp; i++) { + p->rpoints[i].w = p->ipoints[i].w; + for (e = 0; e < di; e++) + p->rpoints[i].p[e] = p->ipoints[i].p[e]; + for (f = 0; f < fdi; f++) + p->rpoints[i].v[f] = p->ipoints[i].v[f]; + + /* out -> rout */ + xfit_abs_to_rel(p, p->rpoints[i].v, p->rpoints[i].v); + } + + /* Allocate array of pseudo-inverse matricies */ + if ((p->flags & XFIT_FM_INPUT) != 0 && p->piv == NULL) { + if ((p->piv = (xfit_piv *)malloc(p->nodp * sizeof(xfit_piv))) == NULL) + return 1; + } + + /* Allocate array of span DE's for current opt channel */ + { + int lres = 0; + for (e = 0; e < di; e++) { + if (p->gres[e] > lres) + lres = p->gres[e]; + } + if ((p->uerrv = (double *)malloc(lres * sizeof(double))) == NULL) + return 1; + } + + /* Do the fitting one part at a time, then together */ + /* Shaper curves are created if position or shaper curves are requested */ + + /* Fit just the matrix */ + if ((p->tcomb & oc_ipo) != 0 + && (p->tcomb & oc_m) == oc_m) { /* Only bother with matrix if in and/or out */ + double rerr; + + if (p->verb) + printf("About to optimise temporary matrix\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + +#ifdef DEBUG +printf("\nBefore matrix opt:\n"); +dump_xfit(p); +#endif + /* Optimise matrix on its own */ + p->opt_ssch = 0; + p->opt_ch = -1; + p->opt_msk = oc_m; + setup_xfit(p, p->wv, p->sa, 0.0, 0.5); + +#ifdef NODDV + if (powell(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Powell failed to converge, residual error = %f",rerr); +#else + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Conjgrad failed to converge, residual error = %f", rerr); +#endif + for (i = 0; i < p->opt_cnt; i++) /* Copy optimised values back */ + p->v[p->opt_off + i] = p->wv[i]; + +#ifdef DEBUG +printf("\nAfter matrix opt:\n"); +dump_xfit(p); +#endif + } + + /* Optimise input and matrix together */ + if ((p->tcomb & oc_im) == oc_im) { + double rerr; + + if (p->verb) + printf("About to optimise a common input curve and matrix\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + + p->opt_ssch = 1; + p->opt_ch = -1; + p->opt_msk = oc_im; + setup_xfit(p, p->wv, p->sa, 0.5, 0.3); + +#ifdef NODDV + if (powell(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, (void *)p, xfitprog, (void *)p) != 0) { +#ifdef DEBUG + warning("xfit_fit: Powell failed to converge, residual error = %f",rerr); +#endif + } +#else + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) { +#ifdef DEBUG + warning("xfit_fit: Conjgrad failed to converge, residual error = %f",rerr); +#endif + } +#endif /* !NODDV */ + for (e = 0; e < di; e++) { /* Copy optimised values back */ + for (i = 0; i < p->sm_iluord; i++) + p->v[p->shp_offs[e] + i] = p->wv[i]; + for (; i < p->iluord[e]; i++) + p->v[p->shp_offs[e] + i] = 0.0; + } + for (i = p->sm_iluord; i < p->opt_cnt; i++) + p->v[p->mat_off + i - p->sm_iluord] = p->wv[i]; +#ifdef DEBUG +printf("\nAfter input and matrix opt:\n"); +dump_xfit(p); +#endif + + /* - - - - - - - - - - - */ + if (p->verb) + printf("About to optimise input curves and matrix\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + + p->opt_ssch = 0; + p->opt_ch = -1; + p->opt_msk = oc_im; + setup_xfit(p, p->wv, p->sa, 0.5, 0.3); + /* Suppress the warnings the first time through - it's better to cut off the */ + /* itterations and move on to the output curve, and worry about it not */ + /* converging the second time through. */ +#ifdef NODDV + if (powell(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, (void *)p, xfitprog, (void *)p) != 0) { +#ifdef DEBUG + warning("xfit_fit: Powell failed to converge, residual error = %f",rerr); +#endif + } +#else + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) { +#ifdef DEBUG + warning("xfit_fit: Conjgrad failed to converge, residual error = %f",rerr); +#endif + } +#endif /* !NODDV */ + for (i = 0; i < p->opt_cnt; i++) /* Copy optimised values back */ + p->v[p->opt_off + i] = p->wv[i]; +#ifdef DEBUG +printf("\nAfter input and matrix opt:\n"); +dump_xfit(p); +#endif + } + + /* Optimise the matrix and output curves together */ + if ((p->tcomb & oc_mo) == oc_mo) { + double rerr; + + if (p->verb) + printf("About to optimise output curves and matrix\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + + p->opt_ssch = 0; + p->opt_ch = -1; + p->opt_msk = oc_mo; + setup_xfit(p, p->wv, p->sa, 0.3, 0.3); +#ifdef NODDV + if (powell(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Powell failed to converge, residual error = %f",rerr); +#else + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, xfitfunc, + dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Conjgrad failed to converge, residual error = %f",rerr); +#endif + for (i = 0; i < p->opt_cnt; i++) /* Copy optimised values back */ + p->v[p->opt_off + i] = p->wv[i]; +#ifdef DEBUG +printf("\nAfter output opt:\n"); +dump_xfit(p); +#endif + + /* Optimise input and matrix together again, after altering matrix */ + if ((p->tcomb & oc_im) == oc_im) { + + if (p->verb) + printf("About to optimise input curves and matrix again\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + + p->opt_ssch = 0; + p->opt_ch = -1; + p->opt_msk = oc_im; + setup_xfit(p, p->wv, p->sa, 0.2, 0.2); +#ifdef NODDV + if (powell(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Powell failed to converge, residual error = %f",rerr); +#else + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Conjgrad failed to converge, residual error = %f",rerr); +#endif + for (i = 0; i < p->opt_cnt; i++) /* Copy optimised values back */ + p->v[p->opt_off + i] = p->wv[i]; + } +#ifdef DEBUG +printf("\nAfter 2nd input and matrix opt:\n"); +dump_xfit(p); +#endif + +#ifndef NODDV + /* Optimise all together */ + if ((p->tcomb & oc_imo) == oc_imo) { + + if (p->verb) + printf("About to optimise input, matrix and output together\n"); + + /* Setup pseudo-inverse if we need it */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + + p->opt_ssch = 0; + p->opt_ch = -1; + p->opt_msk = oc_imo; + setup_xfit(p, p->wv, p->sa, 0.1, 0.1); + if (conjgrad(&rerr, p->opt_cnt, p->wv, p->sa, POWTOL, MAXITS, + xfitfunc, dxfitfunc, (void *)p, xfitprog, (void *)p) != 0) + warning("xfit_fit: Conjgrad failed to converge, residual error = %f",rerr); + for (i = 0; i < p->opt_cnt; i++) /* Copy optimised values back */ + p->v[p->opt_off + i] = p->wv[i]; + + /* Setup final pseudo-inverse from shaper/matrix/out */ + if (p->flags & XFIT_FM_INPUT) + setup_piv(p); + } + +#ifdef DEBUG +printf("\nAfter all together opt:\n"); +dump_xfit(p); +#endif + +#endif /* !NODDV */ + + /* Adjust output curve white point. */ + /* This is for the benefit of the B2A table */ + if (p->flags & XFIT_OUT_ZERO) { + + if (p->verb) + printf("About to adjust a and b output curves for white point\n"); + + for (f = 1; f < 3 && f < p->fdi; f++) { + p->opt_ch = f; + p->wv[0] = p->v[p->out_offs[f]]; /* Current parameter value */ + p->sa[0] = 0.1; /* Search radius */ + if (powell(&rerr, 1, p->wv, p->sa, 0.0000001, 1000, + symoptfunc, (void *)p, NULL, NULL) != 0) + error("xfit_fit: Powell failed to converge, residual error = %f",rerr); + p->v[p->out_offs[f]] = p->wv[0]; /* Copy results back */ + } + } + } + + /* In case we don't generate position curves, */ + /* copy the input curves to the position, so that */ + /* ipos is computed correctly */ + { + double *bb; + + b = p->v + p->shp_off; + bb = p->v + p->pos_off; + for (e = 0; e < di; b += p->iluord[e], bb += p->iluord[e], e++) { + for (i = 0; i < p->iluord[e]; i++) { + bb[i] = b[i]; + } + } + } + + + /* If we want position curves, generate them */ + /* (This could possibly be improved by using some sort */ + /* of optimization drivel approach rather than the predictive */ + /* method used here.) */ + if (p->tcomb & oc_p) { + int ee; + + if (p->verb) + printf("About to create grid position input curves\n"); + + /* Allocate in->rout duplicate point set */ + if (p->rpoints == NULL) { + if ((p->rpoints = (cow *)malloc(p->nodp * sizeof(cow))) == NULL) + return 1; + } + + /* Create a set of 1D rspl setup points that contains */ + /* the residual error */ + for (i = 0; i < p->nodp; i++) { + double tv[MXDO]; /* Target output value */ + double mv[MXDO]; /* Model output value */ + double ev; + + xfit_abs_to_rel(p, tv, p->ipoints[i].v); + xfit_shmatsh(p, mv, p->ipoints[i].p); + + /* Evaluate the error squared */ + if (p->flags & XFIT_FM_INPUT) { + double pp[MXDI]; + for (e = 0; e < di; e++) + pp[e] = p->ipoints[i].p[e]; + for (f = 0; f < fdi; f++) { + double t1 = tv[f] - mv[f]; /* Error in output */ + + /* Create input point offset by equivalent delta to output point */ + /* error, in proportion to the partial derivatives for that output. */ + for (e = 0; e < di; e++) + pp[e] += t1 * p->piv[i].ide[f][e]; + } + ev = p->to_de2(p->cntx2, pp, p->ipoints[i].p); + } else { + ev = p->to_de2(p->cntx2, mv, tv); + } + p->rpoints[i].v[0] = ev; + p->rpoints[i].w = p->ipoints[i].w; + } + + /* Do each input axis in turn */ + for (ee = 0; ee < p->di; ee++) { + rspl *resid; + double imin[1],imax[1],omin[1],omax[1]; + int resres[1] = { 1024 }; +#define NPGP 100 + mcv *posc; + mcvco pgp[NPGP]; + double vo, vs; + double *pms; + + /* Create a rspl that plots the residual error */ + /* vs the axis value */ + for (i = 0; i < p->nodp; i++) + p->rpoints[i].p[0] = p->ipoints[i].p[ee]; + + imin[0] = in_min[ee]; + imax[0] = in_max[ee]; + omin[0] = 0.0; + omax[0] = 0.0; + + if ((resid = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) + return 1; + + resid->fit_rspl_w(resid, RSPLFLAGS, p->rpoints, p->nodp, imin, imax, resres, + omin, omax, 2.0, NULL, NULL); + +#ifdef DEBUG_PLOT + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + + printf("Input residual error channel %d\n",ee); + for (i = 0; i < XRES; i++) { + co pp; + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (imax[0] - imin[0]) + imin[0]; + pp.p[0] = xx[i]; + resid->interp(resid, &pp); + y1[i] = pp.v[0]; + if (y1[i] < 0.0) + y1[i] = 0.0; + y1[i] = pow(y1[i], 0.5); /* Convert from error^2 to error */ + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* DEBUG_PLOT */ + + /* Create a set of guide points that contain */ + /* the accumulated residual error vs. the axis value */ + for (i = 0; i < NPGP; i++) { + co pp; + double vv; + + pp.p[0] = i/(NPGP-1.0); + resid->interp(resid, &pp); + + pgp[i].p = (pp.p[0] - in_min[ee])/(in_max[ee] - in_min[ee]); + pgp[i].w = 1.0; + + vv = pp.v[0]; + if (vv < 0.0) + vv = 0.0; + vv = pow(vv, 0.5); /* Convert from error^2 to error */ + vv += PSHAPE_MINE; /* In case error is near zero */ + vv = pow(vv, PSHAPE_DIST); /* Agressivness of grid distribution */ + + if (i == 0) + pgp[i].v = vv; + else + pgp[i].v = pgp[i-1].v + vv; + } + resid->del(resid); + + /* Normalize the output range */ + vo = pgp[0].v; + vs = pgp[NPGP-1].v - vo; + + for (i = 0; i < NPGP; i++) { + pgp[i].v = (pgp[i].v - vo)/vs; + +//printf("~1 guide point %d: %f -> %f\n",i,pgp[i].p,pgp[i].v); + } + /* Fit the non-monotonic parameters to the guide points */ + if ((posc = new_mcv_noos()) == NULL) + return 1; + + posc->fit(posc, 0, p->iluord[ee], pgp, NPGP, 0.1); // ~~99 + +#ifdef DEBUG_PLOT + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + + printf("Position curve %d\n",ee); + for (i = 0; i < XRES; i++) { + xx[i] = i/(double)(XRES-1); + y1[i] = posc->interp(posc, xx[i]); + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* DEBUG_PLOT */ + + /* Transfer parameters to xfit pos (skip offset and scale) */ + posc->get_params(posc, &pms); + + for (i = 0; i < p->iluord[ee]; i++) { + p->v[p->pos_offs[ee] + i] = pms[i+2]; + } +//p->v[p->in_offs[ee]] = -1.5; + + free(pms); + posc->del(posc); + } + } + +#ifdef DEBUG +printf("Final parameters:\n"); +dump_xfit(p); +#endif + +#ifdef DEBUG_PLOT + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + + for (e = 0; e < p->di; e++) { + printf("Input position curve channel %d\n",e); + for (i = 0; i < XRES; i++) { + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (p->in_max[e] - p->in_min[e]) + p->in_min[e]; + y1[i] = xfit_poscurve(p, x, e); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + + for (e = 0; e < p->di; e++) { + printf("Input shape curve channel %d\n",e); + for (i = 0; i < XRES; i++) { + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (p->in_max[e] - p->in_min[e]) + p->in_min[e]; + y1[i] = xfit_shpcurve(p, x, e); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + + for (e = 0; e < p->di; e++) { + printf("Combined input curve channel %d\n",e); + for (i = 0; i < XRES; i++) { + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (p->in_max[e] - p->in_min[e]) + p->in_min[e]; + y1[i] = p->incurve(p, x, e); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + + for (f = 0; f < p->fdi; f++) { + printf("Output curve channel %d\n",f); + for (i = 0; i < XRES; i++) { + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (p->out_max[f] - p->out_min[f]) + p->out_min[f]; + y1[i] = p->outcurve(p, x, f); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + } +#endif /* DEBUG_PLOT */ + + /* Create final clut rspl using the established pos/shape/output curves */ + /* and white point */ + if (flags & XFIT_MAKE_CLUT) { + double *ipos[MXDI]; + + /* Create an in' -> rout' scattered test point set */ + if (p->rpoints == NULL) { + if ((p->rpoints = (cow *)malloc(p->nodp * sizeof(cow))) == NULL) + return 1; + } + for (i = 0; i < p->nodp; i++) { + p->rpoints[i].w = p->ipoints[i].w; + xfit_inpscurves(p, p->rpoints[i].p, p->ipoints[i].p); + for (f = 0; f < fdi; f++) + p->rpoints[i].v[f] = p->ipoints[i].v[f]; + xfit_abs_to_rel(p, p->rpoints[i].v, p->rpoints[i].v); + xfit_invoutcurves(p, p->rpoints[i].v, p->rpoints[i].v); + + if (p->skm) { + /* Look up the skeleton value */ + double skval[MXDO]; + p->skm->lookup(p->skm, skval, p->ipoints[i].p); + xfit_abs_to_rel(p, skval, skval); + xfit_invoutcurves(p, skval, skval); + +//printf("~1 point %d at %f %f %f, targ %f %f %f skm %f %f %f\n", +//i,p->ipoints[i].p[0],p->ipoints[i].p[1],p->ipoints[i].p[2], +//p->rpoints[i].v[0],p->rpoints[i].v[1],p->rpoints[i].v[2], +//skval[0], skval[1], skval[2]); + /* Subtract it from value at this point, */ + /* so rspl will fit difference to skeleton model */ + for (f = 0; f < fdi; f++) + p->rpoints[i].v[f] -= skval[f]; + } +//printf("~1 point %d, w %f, %f %f %f %f -> %f %f %f\n", +//i,p->rpoints[i].w,p->rpoints[i].p[0], p->rpoints[i].p[1], p->rpoints[i].p[2], p->rpoints[i].p[3], +//p->rpoints[i].v[0], p->rpoints[i].v[1], p->rpoints[i].v[2]); + } + + /* Create ipos[] arrays, that hold the shaper space */ + /* grid position due to the positioning curves. */ + /* This tells the rspl scattered data interpolator */ + /* the grid spacing that smoothness should be */ + /* measured against. */ +#ifdef DEBUG +printf("~1 about to setup ipos\n"); +#endif + for (e = 0; e < p->di; e++) { +//printf("~1 e = %d\n",e); + if ((ipos[e] = (double *)malloc((p->gres[e]) * sizeof(double))) == NULL) + return 1; +//printf("~1 about to do %d spans\n",p->gres[e]); + for (i = 0; i < p->gres[e]; i++) { + double cv; + cv = (double)i/p->gres[e]; + +//printf("~1 i = %d, pos space = %f\n",i,cv); + /* Inverse lookup grid position through positioning curve */ + /* to give device space type value */ + cv = icxInvTransFunc(p->v + p->pos_offs[e], p->iluord[e], cv); + +//printf("~1 dev space = %f\n",cv); + /* Forward lookup device type value through the shaper curve */ + /* to give value in shape linearized space. */ + cv = icxTransFunc(p->v + p->shp_offs[e], p->iluord[e], cv); +//printf("~1 shape space = %f\n",cv); + ipos[e][i] = cv; +#ifdef DEBUG +printf("~1 ipos[%d][%d] = %f\n",e,i,cv); +#endif + } + } + + if (p->clut != NULL) + p->clut->del(p->clut); + if ((p->clut = new_rspl(RSPL_NOFLAGS, di, fdi)) == NULL) + return 1; + + if (p->verb) + printf("Create final clut from scattered data\n"); + +#ifdef EXTEND_GRID +#define XN EXTEND_GRID_BYN + /* Try increasing the grid by one row all around */ + { +#pragma message("!!!!!!!!!!!! Experimental rspl fitting resolution !!!!!!!!!") + double xin_min[MXDI]; + double xin_max[MXDI]; + int xgres[MXDI]; + double del; + double *xipos[MXDI]; + + for (e = 0; e < p->di; e++) { + del = (in_max[e] - in_min[e])/(gres[e]-1.0); /* Extension */ + xin_min[e] = in_min[e] - XN * del; + xin_max[e] = in_max[e] + XN * del; + xgres[e] = gres[e] + 2 * XN; +//printf("~1 xgres %d, gres %d\n",xgres[e], gres[e]); + + if ((xipos[e] = (double *)malloc((xgres[e]) * sizeof(double))) == NULL) + return 1; + for (i = 0; i < xgres[e]; i++) { + if (i < XN) { /* Extrapolate bottom */ + xipos[e][i] = ipos[e][0] - (XN - i) * (ipos[e][1] - ipos[e][0]); +//printf("~1 xipos[%d] %f from ipos[%d] %f and ipos[%d] %f\n",i,xipos[e][i],0,ipos[e][0],1,ipos[e][1]); + } else if (i >= (xgres[e]-XN)) { /* Extrapolate top */ + xipos[e][i] = ipos[e][gres[e]-1] + (i - xgres[e] + XN + 1) * (ipos[e][gres[e]-1] - ipos[e][gres[e]-2]); +//printf("~1 xipos[%d] %f from ipos[%d] %f and ipos[%d] %f\n",i,xipos[e][i],gres[e]-1,ipos[e][gres[e]-1],gres[e]-2,ipos[e][gres[e]-2]); + } else { + xipos[e][i] = ipos[e][i-XN]; +//printf("~1 xipos[%d] %f from ipos[%d] %f\n",i,xipos[e][i],i-XN,ipos[e][i-XN]); + } + } + } + + p->clut->fit_rspl_w(p->clut, rsplflags, p->rpoints, p->nodp, xin_min, xin_max, xgres, + out_min, out_max, smooth, oavgdev, xipos); + + for (e = 0; e < p->di; e++) { + free(xipos[e]); + } + } +#undef XN +#else +// if (p->skm) { +// /* This doesn't seem to work as well as some explicit neutral axis points.. */ +// p->clut->fit_rspl_w_df(p->clut, rsplflags, p->rpoints, p->nodp, in_min, in_max, gres, +// out_min, out_max, smooth, oavgdev, ipos, 1.0, (void *)p, skm_weak); +// } else + p->clut->fit_rspl_w(p->clut, rsplflags, p->rpoints, p->nodp, in_min, in_max, gres, + out_min, out_max, smooth, oavgdev, ipos); +#endif + if (p->verb) + printf("\n"); + + for (e = 0; e < p->di; e++) + free(ipos[e]); + + /* If we used a skeleton model, add it into the resulting rspl values */ + if (p->skm) { + + /* Undo the input point change to allow diagnostic code to work */ + for (i = 0; i < p->nodp; i++) { + /* Look up the skeleton value */ + double skval[MXDO]; + p->skm->lookup(p->skm, skval, p->ipoints[i].p); + xfit_abs_to_rel(p, skval, skval); + xfit_invoutcurves(p, skval, skval); + + /* Subtract it from value at this point, */ + /* so rspl will fit difference to skeleton model */ + for (f = 0; f < fdi; f++) + p->rpoints[i].v[f] += skval[f]; + } + + /* Undo the skm from the resultant rspl */ + p->clut->re_set_rspl( + p->clut, /* this */ + 0, /* Combination of flags */ + (void *)p, /* Opaque function context */ + skm_rspl_out /* Function to set from */ + ); + + } + + /* The overall device to absolute conversion is now what we want */ + /* (as dictated by the points, weighting and best fit), */ + /* but we need to adjust the device to relative conversion */ + /* to make device white map exactly to D50, without touching */ + /* the overall absolute behaviour. */ + if (p->flags & XFIT_OUT_WP_REL) { + co wcc; /* device white + aprox rel. white */ + icmXYZNumber _wp; /* Uncorrected dw maps to _wp */ + + if (p->verb) + printf("Doing White point fine tune:\n"); + + /* See what the relative and absolute white point has turned out to be, */ + /* by looking up the device white in the current conversion */ + xfit_inpscurves(p, wcc.p, dw); + p->clut->interp(p->clut, &wcc); + xfit_outcurves(p, wcc.v, wcc.v); + if (p->flags & XFIT_OUT_LAB) + icmLab2XYZ(&icmD50, wcc.v, wcc.v); + + if (p->verb) { + double labwp[3]; + icmXYZ2Lab(&icmD50, labwp, wcc.v); + printf("Before fine tune, rel WP = XYZ %f %f %f, Lab %f %f %f\n", + wcc.v[0], wcc.v[1],wcc.v[2], labwp[0], labwp[1], labwp[2]); + } + + /* Matrix needed to correct approx rel wp to target D50 */ + icmAry2XYZ(_wp, wcc.v); /* Aprox relative target white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, p->cmat); /* Correction */ + + /* Compute the actual white point, and return it to caller */ + icmMulBy3x3(wp, p->toAbs, wcc.v); + icmAry2Ary(p->wp, wp); + + /* Apply correction to fine tune rspl data. */ + /* NOTE: this doesn't always give us a perfect D50 white for */ + /* Lab PCS input profiles because the dev white may land */ + /* within a cell, and the clipping of Lab PCS values in the grid */ + /* may introduce errors in the interpolated value. */ + p->clut->re_set_rspl( + p->clut, /* this */ + 0, /* Combination of flags */ + (void *)p, /* Opaque function context */ + conv_rspl_out /* Function to set from */ + ); + + /* Fix absolute conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, p->fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, p->toAbs); + + if (p->verb) { + double labwp[3]; + + /* Lookup white again */ + xfit_inpscurves(p, wcc.p, dw); + p->clut->interp(p->clut, &wcc); + xfit_outcurves(p, wcc.v, wcc.v); + if (p->flags & XFIT_OUT_LAB) + icmLab2XYZ(&icmD50, wcc.v, wcc.v); + icmXYZ2Lab(&icmD50, labwp, wcc.v); + printf("After fine tune, rel WP = XYZ %f %f %f, Lab %f %f %f\n", + wcc.v[0], wcc.v[1], wcc.v[2], labwp[0], labwp[1], labwp[2]); + printf(" abs WP = XYZ %s, Lab %s\n", icmPdv(3, wp), icmPLab(wp)); + } + } + + /* Create default wpscale */ + if (wpscale < 0.0) { + wpscale = 1.0; + } else { + if (p->verb) { + printf("White manual point scale %f\n", wpscale); + } + } + + /* If we are going to auto scale the WP to avoid clipping */ + /* cLUT values above the WP: */ + if ((p->flags & XFIT_OUT_WP_REL_US) == XFIT_OUT_WP_REL_US) { + co wcc; + double bw[3]; + icmXYZNumber _wp; + double uswpscale = 1.0; + double mxd, mxY; + double ndw[3]; + + /* See what device space gamut boundary white (ie. 1,1,1) maps to */ + xfit_inpscurves(p, wcc.p, dgw); + p->clut->interp(p->clut, &wcc); + xfit_outcurves(p, wcc.v, wcc.v); + if (p->flags & XFIT_OUT_LAB) + icmLab2XYZ(&icmD50, wcc.v, wcc.v); + icmMulBy3x3(wcc.v, p->toAbs, wcc.v); /* Convert to absolute */ + + mxY = wcc.v[1]; + icmCpy3(bw, wcc.v); +//printf("~1 1,1,1 Y = %f\n",wcc.v[1]); + + /* See what the device white point value scaled to 1 produces */ + mxd = -1.0; + for (e = 0; e < p->di; e++) { + if (dw[e] > mxd) + mxd = dw[e]; + } + for (e = 0; e < p->di; e++) + ndw[e] = dw[e]/mxd; + + xfit_inpscurves(p, wcc.p, ndw); + p->clut->interp(p->clut, &wcc); + xfit_outcurves(p, wcc.v, wcc.v); + if (p->flags & XFIT_OUT_LAB) + icmLab2XYZ(&icmD50, wcc.v, wcc.v); + icmMulBy3x3(wcc.v, p->toAbs, wcc.v); /* Convert to absolute */ + +//printf("~1 ndw = %f %f %f Y = %f\n",ndw[0],ndw[1],ndw[2],wcc.v[1]); + if (wcc.v[1] > mxY) { + mxY = wcc.v[1]; + icmCpy3(bw, wcc.v); + } + + /* Compute WP scale factor needed to fit mxY */ + if (mxY > wp[1]) { + uswpscale = mxY/wp[1]; + wpscale *= uswpscale; + if (p->verb) { + printf("Dev boundary white XYZ %s, scale WP by %f, total WP scale %f\n", + icmPdv(3, bw), uswpscale, wpscale); + } + } + } + + /* If the scaled WP would have Y > 1.0, clip it to 1.0 */ + if (p->flags & XFIT_CLIP_WP) { + + if ((wp[1] * wpscale) > 1.0) { + wpscale = 1.0/wp[1]; /* Make wp Y = 1.0 */ + if (p->verb) { + printf("WP Y would ve > 1.0. scale by %f to clip it\n",wpscale); + } + } + } + + /* Apply our total wp scale factor */ + if (wpscale != 1.0) { + icmXYZNumber _wp; + + /* Create inverse scaling matrix for relative rspl data */ + icmSetUnity3x3(p->cmat); + icmScale3x3(p->cmat, p->cmat, 1.0/wpscale); + + /* Inverse scale the rspl */ + p->clut->re_set_rspl( + p->clut, /* this */ + 0, /* Combination of flags */ + (void *)p, /* Opaque function context */ + conv_rspl_out /* Function to set from */ + ); + + /* Scale the WP */ + icmScale3(wp, wp, wpscale); + + /* return scaled white point to caller */ + icmAry2Ary(p->wp, wp); + + /* Fix absolute conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, p->fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, p->toAbs); + } + + /* Clip any values in the grid over D50 L to D50 */ + if ((p->flags & XFIT_OUT_WP_REL_C) == XFIT_OUT_WP_REL_C) { + + /* Compute the rspl D50 value to avoid calc in clip_rspl_out() */ + if (p->flags & XFIT_OUT_LAB) { + p->cmat[0][0] = 100.0; + p->cmat[0][1] = 0.0; + p->cmat[0][2] = 0.0; + } else { + icmXYZ2Ary(p->cmat[0], icmD50); + } + xfit_invoutcurves(p, p->cmat[0], p->cmat[0]); + + if (p->verb) + printf("Clipping any cLUT grid points with Y > 1 to D50\n"); + + p->clut->re_set_rspl( + p->clut, /* this */ + 0, /* Combination of flags */ + (void *)p, /* Opaque function context */ + clip_rspl_out /* Function to set from */ + ); + } + +#ifdef SPECIAL_FORCE + /* Replace the rspl nodes with ones directly computed */ + /* from the synthetic linear RGB->XYZ model */ + { + double twp[3]; + icmXYZNumber _wp; + + /* See what current device white maps to */ + twp[0] = twp[1] = twp[2] = 1.0; + domodel(twp, twp); + + icmAry2XYZ(_wp, twp); + + /* Matrix needed to correct to D50 */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, p->cmat); + + p->clut->re_set_rspl( + p->clut, /* this */ + 0, /* Combination of flags */ + (void *)p, /* Opaque function context */ + set_rspl_out1 /* Function to set from */ + ); + } +#endif /* SPECIAL_FORCE */ + + /* Evaluate the residual error now, with the rspl in place */ +#ifdef DEBUG_PLOT + { + int ee; + double maxe = 0.0; + double avee = 0.0; + + /* Allocate in->rout duplicate point set */ + if (p->rpoints == NULL) { + if ((p->rpoints = (cow *)malloc(p->nodp * sizeof(cow))) == NULL) + return 1; + } + + /* Create a set of 1D rspl setup points that contains */ + /* the residual error */ + for (i = 0; i < p->nodp; i++) { + double tv[MXDO]; /* Target output value */ + double mv[MXDO]; /* Model output value */ + co pp; + double ev; + + xfit_abs_to_rel(p, tv, p->ipoints[i].v); + + for (e = 0; e < p->di; e++) + pp.p[e] = p->ipoints[i].p[e]; + + xfit_inpscurves(p, pp.p, pp.p); + p->clut->interp(p->clut, &pp); + xfit_outcurves(p, pp.v, pp.v); + + for (f = 0; f < p->fdi; f++) + mv[f] = pp.v[f]; + + /* Evaluate the residual error suqared */ + if (p->flags & XFIT_FM_INPUT) { + double pp[MXDI]; + for (e = 0; e < di; e++) + pp[e] = p->ipoints[i].p[e]; + for (f = 0; f < fdi; f++) { + double t1 = tv[f] - mv[f]; /* Error in output */ + + /* Create input point offset by equivalent delta to output point */ + /* error, in proportion to the partial derivatives for that output. */ + for (e = 0; e < di; e++) + pp[e] += t1 * p->piv[i].ide[f][e]; + } + ev = p->to_de2(p->cntx2, pp, p->ipoints[i].p); + } else { + ev = p->to_de2(p->cntx2, mv, tv); + } +//printf("~1 point %d, loc %f %f %f %f\n",i, p->rpoints[i].p[0], p->rpoints[i].p[1], p->rpoints[i].p[2], p->rpoints[i].p[3]); +//printf(" targ %f %f %f, is %f %f %f\n", tv[0], tv[1], tv[2], mv[0], mv[1], mv[2]); + + p->rpoints[i].v[0] = ev; + p->rpoints[i].w = p->ipoints[i].w; + + ev = sqrt(ev); + if (ev > maxe) + maxe = ev; + avee += ev; + } + printf("Max resid err = %f, avg err = %f\n",maxe, avee/(double)p->nodp); + + /* Evaluate each input axis in turn */ + for (ee = 0; ee < p->di; ee++) { + rspl *resid; + double imin[1],imax[1],omin[1],omax[1]; + int resres[1] = { 1024 }; + + /* Create a rspl that gives the residual error squared */ + /* vs. the axis value */ + for (i = 0; i < p->nodp; i++) + p->rpoints[i].p[0] = p->ipoints[i].p[ee]; + + imin[0] = in_min[ee]; + imax[0] = in_max[ee]; + omin[0] = 0.0; + omax[0] = 0.0; + + if ((resid = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) + return 1; + + resid->fit_rspl_w(resid, RSPLFLAGS, p->rpoints, p->nodp, imin, imax, resres, + omin, omax, 2.0, NULL, NULL); + + { +#define XRES 100 + double xx[XRES]; + double y1[XRES]; + + printf("Finale residual error vs. input channel %d\n",ee); + for (i = 0; i < XRES; i++) { + co pp; + double x; + x = i/(double)(XRES-1); + xx[i] = x = x * (imax[0] - imin[0]) + imin[0]; + pp.p[0] = xx[i]; + resid->interp(resid, &pp); + y1[i] = sqrt(fabs(pp.v[0])); + } + do_plot(xx,y1,NULL,NULL,XRES); + } + resid->del(resid); + } + } +#endif /* DEBUG_PLOT */ + + /* Special test code to figure out what's wrong with position curves */ +#ifdef NEVER + { + double rgb[3], xyz[3]; + co pp; + +extern int rspldb; +db = 1; +rspldb = 1; + printf("~1 gres = %d %d %d\n", gres[0], gres[1], gres[2]); + for (i = 0; i < 3; i++) { + + if (i == 0) { + /* Test point on a grid point */ + printf("\n~1 ##########################################\n"); + printf("~1 testing input at first diagonal grid point\n"); + + rgb[0] = 1.0/(gres[0]-1.0); + rgb[1] = 1.0/(gres[0]-1.0); + rgb[2] = 1.0/(gres[0]-1.0); + + printf("~1 target rgb' = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + xfit_invinpscurves(p, rgb, rgb); + + } else if (i == 1) { + /* Test point half way through a grid point */ + printf("\n~1 ##########################################\n"); + printf("\n~1 testing half way through diagonal grid point\n"); + + rgb[0] = 0.5/(gres[0]-1.0); + rgb[1] = 0.5/(gres[0]-1.0); + rgb[2] = 0.5/(gres[0]-1.0); + + printf("~1 target rgb' = %f %f %f\n", rgb[0], rgb[1], rgb[2]); + xfit_invinpscurves(p, rgb, rgb); + + } else { + printf("\n~1 ##########################################\n"); + printf("\n~1 testing worst case point\n"); + + rgb[0] = 0.039915; + rgb[1] = 0.053148; + rgb[2] = 0.230610; + } + + pp.p[0] = rgb[0]; + pp.p[1] = rgb[1]; + pp.p[2] = rgb[2]; + + printf("~1 rgb = %f %f %f\n", pp.p[0], pp.p[1], pp.p[2]); + + xfit_inpscurves(p, pp.p, pp.p); + + printf("~1 rgb' = %f %f %f\n", pp.p[0], pp.p[1], pp.p[2]); + + p->clut->interp(p->clut, &pp); + + printf("~1 xyz' = %f %f %f\n", pp.v[0], pp.v[1], pp.v[2]); + + xfit_outcurves(p, pp.v, pp.v); + + printf("~1 xyz = %f %f %f\n", pp.v[0], pp.v[1], pp.v[2]); + + /* Synthetic linear rgb->XYZ model */ + domodel(xyz, rgb); + + /* Apply abs->rel white point adjustment */ + icmMulBy3x3(xyz, p->mat, xyz); + + printf("~1 ref = %f %f %f, de = %f\n", xyz[0], xyz[1], xyz[2],icmXYZLabDE(&icmD50,xyz,pp.v)); + } + +db = 0; +rspldb = 0; +// exit(0); + } +#endif /* NEVER */ + + } + return 0; +} + +/* We're done with an xfit */ +static void xfit_del(xfit *p) { + if (p->v != NULL) + free(p->v); + if (p->wv != NULL) + free(p->wv); + if (p->sa != NULL) + free(p->sa); + if (p->rpoints != NULL) + free(p->rpoints); + if (p->piv != NULL) + free(p->piv); + if (p->uerrv != NULL) + free(p->uerrv); + free(p); +} + +/* Create a transform fitting object */ +/* return NULL on error */ +xfit *new_xfit( +) { + xfit *p; + + if ((p = (xfit *)calloc(1, sizeof(xfit))) == NULL) { + return NULL; + } + + /* Set method pointers */ + p->fit = xfit_fit; + p->incurve = xfit_inpscurve; + p->invincurve = xfit_invinpscurve; + p->outcurve = xfit_outcurve; + p->invoutcurve = xfit_invoutcurve; + p->del = xfit_del; + + return p; +} + + + diff --git a/xicc/xfit.h b/xicc/xfit.h new file mode 100644 index 0000000..f9f3233 --- /dev/null +++ b/xicc/xfit.h @@ -0,0 +1,239 @@ + +#ifndef XFIT_H +#define XFIT_H + +/* + * Clut per channel curve fitting + * + * Author: Graeme W. Gill + * Date: 27/5/2007 + * Version: 1.00 + * + * Copyright 2000 - 2007 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the rspl.c and xlut.c code. + */ + +#define MXLUORD 60 /* Maximum possible shaper harmonic orders to use */ + +#define PCSD 3 /* PCS dimensions */ + +#define MXPARMS (2 * MXDI * MXLUORD + (1 << MXDI) * MXDO + MXDO * MXLUORD) + +#define MAX_NTP 11 /* Maximum test points within spans */ + +/* Optimisation mask legal combinations */ +typedef enum { + oc_i = 1, /* Input Shaper curve */ + oc_p = 2, /* Input Positioning curve */ + oc_m = 4, /* Temporary matrix */ + oc_o = 8, /* Output */ + + oc_io = oc_i + oc_o, + oc_im = oc_i + oc_m, + oc_mo = oc_m + oc_o, + oc_imo = oc_i + oc_m + oc_o, + + oc_pm = oc_p + oc_m, + oc_pmo = oc_p + oc_m + oc_o, + + oc_ip = oc_i + oc_p, + oc_ipo = oc_i + oc_p + oc_o +} optcomb; + +/* Flag values */ +#define XFIT_VERB 0x0001 /* Verbose output during fitting */ + +#define XFIT_FM_INPUT 0x0002 /* Use input space for fit metric (default is output) */ + +#define XFIT_IN_ZERO 0x0004 /* Adjust input curves 1 & 2 for zero (~~not impd. yet) */ +#define XFIT_OPTGRID_RANGE 0x0008 /* Optimize inner grid around used range (~~not impd. yet) */ + +#define XFIT_OUT_WP_REL 0x0010 /* Extract the white point and make output relative */ +#define XFIT_OUT_WP_REL_US 0x0030 /* Same as above but scale to avoid clipping above WP */ +#define XFIT_OUT_WP_REL_C 0x0050 /* Same as above but clip any cLUT values over D50 */ +#define XFIT_CLIP_WP 0x0080 /* Clip white point to have Y <= 1.0 (conflict with above) */ +#define XFIT_OUT_LAB 0x0100 /* Output space is LAB else XYZ for reading WP */ + +#define XFIT_OUT_ZERO 0x0200 /* Adjust output curves 1 & 2 for zero */ + +#define XFIT_MAKE_CLUT 0x1000 /* Create rspl clut, even if not needed */ + + +/* xfit reverse transform information for each test point */ +typedef struct { + double ide[MXDO][MXDI]; /* pseudo inverse: rout -> in */ +} xfit_piv; + +/* Context for optimising input and output luts */ +struct _xfit { + int verb; /* Verbose */ + int flags; /* Behaviour flags */ + int di, fdi; /* Dimensionaluty of input and output */ + optcomb tcomb; /* Target 1D curve elements to fit */ + + icxMatrixModel *skm; /* Optional skeleton model (used for input profiles) */ + + double *wp; /* Ref. to current white point if XFIT_OUT_WP_REL */ + double *dw; /* Ref. to device value that should map to D50 if XFIT_OUT_WP_REL */ + double fromAbs[3][3]; /* From abs to relative (used by caller) */ + double toAbs[3][3]; /* To abs from relative (used by caller) */ + int gres[MXDI]; /* clut resolutions being optimised for */ + rspl *clut; /* final rspl clut */ + + void *cntx2; /* Context of callback */ + double (*to_de2)(void *cntx, double *in1, double *in2); + /* callback to convert in or out value to fit metric squared */ + double (*to_dde2)(void *cntx, double dout[2][MXDIDO], double *in1, double *in2); + /* Same, but with partial derivatives */ + + int iluord[MXDI]; /* Input Shaper order actualy used (must be <= MXLUORD) */ + int sm_iluord; /* Smallest Input Shaper order used */ + int oluord[MXDO]; /* Output Shaper order actualy used (must be <= MXLUORD) */ + int sluord[MXDI]; /* Sub-grid shaper order */ + double in_min[MXDI]; /* Input value scaling minimum */ + double in_max[MXDI]; /* Input value scaling maximum */ + double out_min[MXDO]; /* Output value scaling minimum */ + double out_max[MXDO]; /* Output value scaling maximum */ + + int shp_off; /* Input parameters offset */ + int shp_offs[MXDI]; /* Input parameter offsets for each in channel from v[0] */ + int shp_cnt; /* Input parameters count */ + int mat_off; /* Matrix parameters offset from v[0] */ + int mat_offs[MXDO]; /* Matrix parameter offsets for each out channel from v[0] */ + int mat_cnt; /* Matrix parameters count */ + int out_off; /* Output parameters offset from v[0] */ + int out_offs[MXDO]; /* Output parameter offsets for each out channel from v[0] */ + int out_cnt; /* Output parameters count */ + int pos_off; /* Position parameters offset */ + int pos_offs[MXDI]; /* Position parameter offsets for each in channel from v[0] */ + int pos_cnt; /* Position parameters count */ + int tot_cnt; /* Total parameter count */ + + double *v; /* Holder for parameters */ + /* Optimisation parameters are layed out: */ + /* */ + /* Input pos or shape curves:, di groups of iluord[e] parameters */ + /* */ + /* (temp) Matrix: fdi groups of 2 ^ di parameters */ + /* */ + /* Output curves:, fdi groups of oluord[f] parameters */ + /* */ + /* Shaper curves:, di groups of iluord[e] parameters */ + /* */ + + int nodp; /* Number of data points */ + cow *ipoints; /* Reference to test points as in->out */ + cow *rpoints; /* Modified version of ipoints */ + xfit_piv *piv; /* Point inverse information for XFIT_FM_INPUT */ + double *uerrv; /* Array holding span width in DE for current opt chan */ + + double mat[3][3]; /* XYZ White point aprox relative to accurate relative matrix */ + double cmat[3][3]; /* Final rspl correction matrix */ + + double shp_smooth[MXDI];/* Smoothing factors for each input shape curve, nom = 1.0 */ + double out_smooth[MXDO]; + + /* Optimisation state */ + optcomb opt_msk; /* Optimisation mask: 3 = i+m, 2 = m, 6 = m+o, 7 = i+m+o */ + int opt_ssch; /* Single shaper channel mode flag */ + int opt_off; /* Optimisation parameters offset from v[0] */ + int opt_cnt; /* Optimisation parameters count */ + double *wv; /* Parameters being optimised */ + double *sa; /* Search area */ + int opt_ch; /* Channel being optimized */ + + /* Methods */ + void (*del)(struct _xfit *p); + + /* Do the fitting. Return nz on error */ + int (*fit)( + struct _xfit *p, + int flags, /* Xfit flag values */ + int di, /* Input dimensions */ + int fdi, /* Output dimensions */ + int rsplflags, /* clut rspl creation flags */ + double *wp, /* if flags & XFIT_OUT_WP_REL, */ + /* Initial white point, returns final wp */ + double *dw, /* Device white value to adjust to be D50 */ + double wpscale, /* If >= 0.0 scale final wp */ + double *dgw, /* Device space gamut boundary white for XFIT_OUT_WP_REL_US */ + /* (ie. RGB 1,1,1 CMYK 0,0,0,0, etc) */ + cow *ipoints, /* Array of data points to fit - referece taken */ + int nodp, /* Number of data points */ + icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */ + double in_min[MXDI], /* Input value scaling/domain minimum */ + double in_max[MXDI], /* Input value scaling/domain maximum */ + int gres[MXDI], /* clut resolutions being optimised for/returned */ + double out_min[MXDO], /* Output value scaling/range minimum */ + double out_max[MXDO], /* Output value scaling/range maximum */ + double smooth, /* clut rspl smoothing factor */ + double oavgdev[MXDO], /* Average output value deviation */ + int iord[], /* Order of input positioning/shaper curve for each dimension */ + int sord[], /* Order of input sub-grid shaper curve (not used) */ + int oord[], /* Order of output shaper curve for each dimension */ + double shp_smooth[MXDI],/* Smoothing factors for each curve, nom = 1.0 */ + double out_smooth[MXDO], + optcomb tcomb, /* Flag - target elements to fit. */ + void *cntx2, /* Context of callbacks */ + /* Callback to convert two fit values delta E squared */ + double (*to_de2)(void *cntx, double *in1, double *in2), + /* Same as above, with partial derivatives */ + double (*to_dde2)(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) + ); + + /* Lookup a value though a combined input positioning and shaper curves */ + double (*incurve)(struct _xfit *p, double in, int chan); + + /* Inverse Lookup a value though a combined input positioning and shaper curves */ + double (*invincurve)(struct _xfit *p, double in, int chan); + + /* Lookup a value though an output curve */ + double (*outcurve)(struct _xfit *p, double in, int chan); + + /* Inverse Lookup a value though an output curve */ + double (*invoutcurve)(struct _xfit *p, double in, int chan); + +}; typedef struct _xfit xfit; + +xfit *new_xfit(); + +#endif /* XFIT_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xicc.c b/xicc/xicc.c new file mode 100644 index 0000000..9b6d867 --- /dev/null +++ b/xicc/xicc.c @@ -0,0 +1,3607 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000, 2001 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * This module expands the basic icclib functionality, + * providing more functionality in exercising. + * The implementation for the three different types + * of profile representation, are in their own source files. + */ + +/* + * TTBD: + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + * + */ + +#include <sys/types.h> +#include <string.h> +#include <ctype.h> +#ifdef __sun +#include <unistd.h> +#endif +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "numlib.h" +#include "counters.h" +#include "plot.h" +#include "../h/sort.h" +#include "xicc.h" /* definitions for this library */ + +#define USE_CAM /* Use CIECAM02 for clipping and gamut mapping, else use Lab */ + +#undef DEBUG /* Plot 1d Luts */ + +#ifdef DEBUG +#include "plot.h" +#endif + +#define MAX_INVSOLN 4 + +static void xicc_del(xicc *p); +icxLuBase * xicc_get_luobj(xicc *p, int flags, icmLookupFunc func, icRenderingIntent intent, + icColorSpaceSignature pcsor, icmLookupOrder order, + icxViewCond *vc, icxInk *ink); +static icxLuBase *xicc_set_luobj(xicc *p, icmLookupFunc func, icRenderingIntent intent, + icmLookupOrder order, int flags, int no, int nobw, cow *points, + icxMatrixModel *skm, + double dispLuminance, double wpscale, double smooth, double avgdev, + icxViewCond *vc, icxInk *ink, xcal *cal, int quality); +static void icxLutSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn, + icColorSpaceSignature *outs, int *outn, + icColorSpaceSignature *pcs); +static void icxLuSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn, + icColorSpaceSignature *outs, int *outn, + icmLuAlgType *alg, icRenderingIntent *intt, + icmLookupFunc *fnc, icColorSpaceSignature *pcs); +static void icxLu_get_native_ranges (icxLuBase *p, + double *inmin, double *inmax, double *outmin, double *outmax); +static void icxLu_get_ranges (icxLuBase *p, + double *inmin, double *inmax, double *outmin, double *outmax); +static void icxLuEfv_wh_bk_points(icxLuBase *p, double *wht, double *blk, double *kblk); +int xicc_get_viewcond(xicc *p, icxViewCond *vc); + +/* The different profile types are in their own source filesm */ +/* and are included to keep their functions private. (static) */ +#include "xmono.c" +#include "xmatrix.c" +#include "xlut.c" /* New xfit3 in & out optimising based profiles */ +//#include "xlut1.c" /* Old xfit1 device curve based profiles */ + +#ifdef NT /* You'd think there might be some standards.... */ +# ifndef __BORLANDC__ +# define stricmp _stricmp +# endif +#else +# define stricmp strcasecmp +#endif +/* Utilities */ + +/* Return a string description of the given enumeration value */ +const char *icx2str(icmEnumType etype, int enumval) { + + if (etype == icmColorSpaceSignature) { + if (((icColorSpaceSignature)enumval) == icxSigJabData) + return "Jab"; + else if (((icColorSpaceSignature)enumval) == icxSigJChData) + return "JCh"; + else if (((icColorSpaceSignature)enumval) == icxSigLChData) + return "LCh"; + } else if (etype == icmRenderingIntent) { + if (((icRenderingIntent)enumval) == icxAppearance) + return "icxAppearance"; + else if (((icRenderingIntent)enumval) == icxAbsAppearance) + return "icxAbsAppearance"; + else if (((icRenderingIntent)enumval) == icxPerceptualAppearance) + return "icxPerceptualAppearance"; + else if (((icRenderingIntent)enumval) == icxAbsPerceptualAppearance) + return "icxAbsPerceptualAppearance"; + else if (((icRenderingIntent)enumval) == icxSaturationAppearance) + return "icxSaturationAppearance"; + else if (((icRenderingIntent)enumval) == icxAbsSaturationAppearance) + return "icxAbsSaturationAppearance"; + } + return icm2str(etype, enumval); +} + +/* Common xicc stuff */ + +/* Return information about the native lut in/out colorspaces. */ +/* Any pointer may be NULL if value is not to be returned */ +static void +icxLutSpaces( + icxLuBase *p, /* This */ + icColorSpaceSignature *ins, /* Return input color space */ + int *inn, /* Return number of input components */ + icColorSpaceSignature *outs, /* Return output color space */ + int *outn, /* Return number of output components */ + icColorSpaceSignature *pcs /* Return PCS color space */ +) { + p->plu->lutspaces(p->plu, ins, inn, outs, outn, pcs); +} + +/* Return information about the overall lookup in/out colorspaces, */ +/* including allowance for any PCS override. */ +/* Any pointer may be NULL if value is not to be returned */ +static void +icxLuSpaces( + icxLuBase *p, /* This */ + icColorSpaceSignature *ins, /* Return input color space */ + int *inn, /* Return number of input components */ + icColorSpaceSignature *outs, /* Return output color space */ + int *outn, /* Return number of output components */ + icmLuAlgType *alg, /* Return type of lookup algorithm used */ + icRenderingIntent *intt, /* Return the intent implemented */ + icmLookupFunc *fnc, /* Return the profile function being implemented */ + icColorSpaceSignature *pcs /* Return the effective PCS */ +) { + icmLookupFunc function; + icColorSpaceSignature npcs; /* Native PCS */ + + p->plu->spaces(p->plu, NULL, inn, NULL, outn, alg, NULL, &function, &npcs, NULL); + + if (intt != NULL) + *intt = p->intent; + + if (fnc != NULL) + *fnc = function; + + if (ins != NULL) + *ins = p->ins; + + if (outs != NULL) + *outs = p->outs; + + if (pcs != NULL) + *pcs = p->pcs; +} + +/* Return the native (internaly visible) colorspace value ranges */ +static void +icxLu_get_native_ranges ( +icxLuBase *p, +double *inmin, double *inmax, /* Return maximum range of inspace values */ +double *outmin, double *outmax /* Return maximum range of outspace values */ +) { + int i; + if (inmin != NULL) { + for (i = 0; i < p->inputChan; i++) + inmin[i] = p->ninmin[i]; + } + if (inmax != NULL) { + for (i = 0; i < p->inputChan; i++) + inmax[i] = p->ninmax[i]; + } + if (outmin != NULL) { + for (i = 0; i < p->outputChan; i++) + outmin[i] = p->noutmin[i]; + } + if (outmax != NULL) { + for (i = 0; i < p->outputChan; i++) + outmax[i] = p->noutmax[i]; + } +} + +/* Return the effective (externaly visible) colorspace value ranges */ +static void +icxLu_get_ranges ( +icxLuBase *p, +double *inmin, double *inmax, /* Return maximum range of inspace values */ +double *outmin, double *outmax /* Return maximum range of outspace values */ +) { + int i; + if (inmin != NULL) { + for (i = 0; i < p->inputChan; i++) + inmin[i] = p->inmin[i]; + } + if (inmax != NULL) { + for (i = 0; i < p->inputChan; i++) + inmax[i] = p->inmax[i]; + } + if (outmin != NULL) { + for (i = 0; i < p->outputChan; i++) + outmin[i] = p->outmin[i]; + } + if (outmax != NULL) { + for (i = 0; i < p->outputChan; i++) + outmax[i] = p->outmax[i]; + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Routine to figure out a suitable black point for CMYK */ + +/* Structure to hold optimisation information */ +typedef struct { + icmLuBase *p; + int kch; /* K channel, -1 if none */ + double tlimit, klimit; /* Ink limit values */ + int inn; /* Number of input channels */ + icColorSpaceSignature outs; /* Output space */ + double p1[3]; /* white pivot point in abs Lab */ + double p2[3]; /* Point on vector towards black in abs Lab */ + double toll; /* Tollerance of black direction */ +} bpfind; + +/* Optimise device values to minimise L, while remaining */ +/* within the ink limit, and staying in line between p1 (white) and p2 (black dir) */ +static double bpfindfunc(void *adata, double pv[]) { + bpfind *b = (bpfind *)adata; + double rv = 0.0; + double Lab[3]; + double lr, ta, tb, terr; /* L ratio, target a, target b, target error */ + double ovr = 0.0; + int e; + + /* Compute amount outside total limit */ + if (b->tlimit >= 0.0) { + double sum; + for (sum = 0.0, e = 0; e < b->inn; e++) + sum += pv[e]; + if (sum > b->tlimit) { + ovr = sum - b->tlimit; +#ifdef DEBUG + printf("~1 total ink ovr = %f\n",ovr); +#endif + } + } + + /* Compute amount outside black limit */ + if (b->klimit >= 0.0 && b->kch >= 0) { + double kval = pv[b->kch] - b->klimit; + if (kval > ovr) { + ovr = kval; +#ifdef DEBUG + printf("~1 black ink ovr = %f\n",ovr); +#endif + } + } + /* Compute amount outside device value limits 0.0 - 1.0 */ + { + double dval; + for (dval = -1.0, e = 0; e < b->inn; e++) { + if (pv[e] < 0.0) { + if (-pv[e] > dval) + dval = -pv[e]; + } else if (pv[e] > 1.0) { + if ((pv[e] - 1.0) > dval) + dval = pv[e] - 1.0; + } + } + if (dval > ovr) + ovr = dval; + } + + /* Compute the Lab value: */ + b->p->lookup(b->p, Lab, pv); + if (b->outs == icSigXYZData) + icmXYZ2Lab(&icmD50, Lab, Lab); + +#ifdef DEBUG + printf("~1 p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]); + printf("~1 device value %f %f %f %f, Lab = %f %f %f\n",pv[0],pv[1],pv[2],pv[3],Lab[0],Lab[1],Lab[2]); +#endif + + /* Primary aim is to minimise L value */ + rv = Lab[0]; + + /* See how out of line from p1 to p2 we are */ + lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */ + ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */ + tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */ + + terr = (ta - Lab[1]) * (ta - Lab[1]) + + (tb - Lab[2]) * (tb - Lab[2]); + + if (terr < b->toll) /* Tollerance error doesn't count until it's over tollerance */ + terr = 0.0; + +#ifdef DEBUG + printf("~1 target error %f\n",terr); +#endif + rv += XICC_BLACK_FIND_ABERR_WEIGHT * terr; /* Make ab match more important than min. L */ + +#ifdef DEBUG + printf("~1 out of range error %f\n",ovr); +#endif + rv += 200 * ovr; + +#ifdef DEBUG + printf("~1 black find tc ret %f\n",rv); +#endif + return rv; +} + +/* Try and compute a real black point in XYZ given an iccLu, */ +/* and also return the K only black or the normal black if the device doesn't have K */ +/* black[] will be unchanged if black cannot be computed. */ +/* Note that the black point will be in the space of the Lu */ +/* converted to XYZ, so will have the Lu's intent etc. */ +/* (Note that this is duplicated in xlut.c set_icxLuLut() !!!) */ +static void icxLu_comp_bk_point( +icxLuBase *x, +int gblk, /* If nz, compute black if possible. */ +double *white, /* XYZ Input, used for computing black */ +double *black, /* XYZ Input & Output. Set if gblk NZ and can be computed */ +double *kblack /* XYZ Output. Looked up if possible or set to black[] otherwise */ +) { + icmLuBase *p = x->plu; + icmLuBase *op = p; /* Original icmLu, in case we replace p */ + icc *icco = p->icp; + icmHeader *h = icco->header; + icColorSpaceSignature ins, outs; + int inn, outn; + icmLuAlgType alg; + icRenderingIntent intt; + icmLookupFunc fnc; + icmLookupOrder ord; + int kch = -1; + double dblack[MAX_CHAN]; /* device black value */ + int e; + +#ifdef DEBUG + printf("~1 icxLu_comp_bk_point() called, gblk %d, white = %s, black = %s\n",gblk,icmPdv(3, white),icmPdv(3,black)); +#endif + /* Default return incoming black as K only black */ + kblack[0] = black[0]; + kblack[1] = black[1]; + kblack[2] = black[2]; + + /* Get the effective characteristics of the Lu */ + p->spaces(p, &ins, &inn, &outs, &outn, &alg, &intt, &fnc, NULL, &ord); + + if (fnc == icmBwd) { /* Hmm. We've got PCS to device, and we want device to PCS. */ + + /* Strictly speaking this is a dubious approach, since for a cLut profile */ + /* the B2A table could make the effective white and black points */ + /* anything it likes, and they don't have to match what the corresponding */ + /* A2B table does. In our usage it's probably OK, since we tend */ + /* to use colorimetric B2A */ +#ifdef DEBUG + printf("~1 getting icmFwd\n"); +#endif + if ((p = icco->get_luobj(icco, icmFwd, intt, ins, ord)) == NULL) + error("icxLu_comp_bk_point: assert: getting Fwd Lookup failed!"); + + p->spaces(p, &ins, &inn, &outs, &outn, &alg, &intt, &fnc, NULL, &ord); + } + + if (outs != icSigXYZData && outs != icSigLabData) { + error("icxLu_comp_bk_point: assert: icc Lu output is not XYZ or Lab!, outs = 0x%x, "); + } + +#ifdef DEBUG + printf("~1 icxLu_comp_bk_point called for inn = %d, ins = %s\n", inn, icx2str(icmColorSpaceSignature,ins)); +#endif + + switch (ins) { + + case icSigXYZData: + case icSigLabData: + case icSigLuvData: + case icSigYxyData: +#ifdef DEBUG + printf("~1 Assuming CIE colorspace black is 0.0\n"); +#endif + if (gblk) { + for (e = 0; e < inn; e++) + black[0] = 0.0; + } + kblack[0] = black[0]; + kblack[1] = black[1]; + kblack[2] = black[2]; + return; + + case icSigRgbData: +#ifdef DEBUG + printf("~1 RGB:\n"); +#endif + for (e = 0; e < inn; e++) + dblack[e] = 0.0; + break; + + case icSigGrayData: { /* Could be additive or subtractive */ + double dval[1]; + double minv[3], maxv[3]; +#ifdef DEBUG + printf("~1 Gray:\n"); +#endif + /* Check out 0 and 100% colorant */ + dval[0] = 0.0; + p->lookup(p, minv, dval); + if (outs == icSigXYZData) + icmXYZ2Lab(&icmD50, minv, minv); + dval[0] = 1.0; + p->lookup(p, maxv, dval); + if (outs == icSigXYZData) + icmXYZ2Lab(&icmD50, maxv, maxv); + + if (minv[0] < maxv[0]) + dblack[0] = 0.0; + else + dblack[0] = 1.0; + } + break; + + case icSigCmyData: + for (e = 0; e < inn; e++) + dblack[e] = 1.0; + break; + + case icSigCmykData: +#ifdef DEBUG + printf("~1 CMYK:\n"); +#endif + kch = 3; + dblack[0] = 0.0; + dblack[1] = 0.0; + dblack[2] = 0.0; + dblack[3] = 1.0; + if (alg == icmLutType) { + icxLuLut *pp = (icxLuLut *)x; + + if (pp->ink.tlimit >= 0.0) + dblack[kch] = pp->ink.tlimit; + }; + break; + + /* Use a heursistic. */ + /* This duplicates code in icxGetLimits() :-( */ + /* Colorant guessing should go in icclib ? */ + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + case icSigMch5Data: + case icSigMch6Data: + case icSigMch7Data: + case icSigMch8Data: { + double dval[MAX_CHAN]; + double ncval[3]; + double cvals[MAX_CHAN][3]; + int nlighter, ndarker; + + /* Decide if the colorspace is additive or subtractive */ +#ifdef DEBUG + printf("~1 N channel:\n"); +#endif + + /* First the no colorant value */ + for (e = 0; e < inn; e++) + dval[e] = 0.0; + p->lookup(p, ncval, dval); + if (outs == icSigXYZData) + icmXYZ2Lab(&icmD50, ncval, ncval); + + /* Then all the colorants */ + nlighter = ndarker = 0; + for (e = 0; e < inn; e++) { + dval[e] = 1.0; + p->lookup(p, cvals[e], dval); + if (outs == icSigXYZData) + icmXYZ2Lab(&icmD50, cvals[e], cvals[e]); + dval[e] = 0.0; + if (fabs(cvals[e][0] - ncval[0]) > 5.0) { + if (cvals[e][0] > ncval[0]) + nlighter++; + else + ndarker++; + } + } + if (ndarker == 0 && nlighter > 0) { /* Assume additive */ + for (e = 0; e < inn; e++) + dblack[e] = 0.0; +#ifdef DEBUG + printf("~1 N channel is additive:\n"); +#endif + + } else if (ndarker > 0 && nlighter == 0) { /* Assume subtractive. */ + double pbk[3] = { 0.0,0.0,0.0 }; /* Perfect black */ + double smd = 1e10; /* Smallest distance */ + +#ifdef DEBUG + printf("~1 N channel is subtractive:\n"); +#endif + /* See if we can guess the black channel */ + for (e = 0; e < inn; e++) { + double tt; + tt = icmNorm33sq(pbk, cvals[e]); + if (tt < smd) { + smd = tt; + kch = e; + } + } + /* See if the black seems sane */ + if (cvals[kch][0] > 40.0 + || fabs(cvals[kch][1]) > 10.0 + || fabs(cvals[kch][2]) > 10.0) { + if (p != op) + p->del(p); +#ifdef DEBUG + printf("~1 black doesn't look sanem so assume nothing\n"); +#endif + return; /* Assume nothing */ + } + + /* Chosen kch as black */ + for (e = 0; e < inn; e++) + dblack[e] = 0.0; + dblack[kch] = 1.0; + if (alg == icmLutType) { + icxLuLut *pp = (icxLuLut *)x; + + if (pp->ink.tlimit >= 0.0) + dblack[kch] = pp->ink.tlimit; + }; +#ifdef DEBUG + printf("~1 N channel K = chan %d\n",kch); +#endif + } else { + if (p != op) + p->del(p); +#ifdef DEBUG + printf("~1 can't figure if additive or subtractive, so assume nothing\n"); +#endif + return; /* Assume nothing */ + } + } + break; + + default: +#ifdef DEBUG + printf("~1 unhandled colorspace, so assume nothing\n"); +#endif + if (p != op) + p->del(p); + return; /* Don't do anything */ + } + + /* Lookup the K only value */ + if (kch >= 0) { + p->lookup(p, kblack, dblack); + + /* We always return XYZ */ + if (outs == icSigLabData) + icmLab2XYZ(&icmD50, kblack, kblack); + } + + if (gblk == 0) { /* That's all we have to do */ +#ifdef DEBUG + printf("~1 gblk == 0, so only return kblack\n"); +#endif + if (p != op) + p->del(p); + return; + } + + /* Lookup the device black or K only value as a default */ + p->lookup(p, black, dblack); /* May be XYZ or Lab */ +#ifdef DEBUG + printf("~1 Got default lu black %f %f %f, kch = %d\n", black[0],black[1],black[2],kch); +#endif + + /* !!! Hmm. For CMY and RGB we are simply using the device */ + /* combination values as the black point. In reality we might */ + /* want to have the option of using a neutral black point, */ + /* just like CMYK ?? */ + + if (kch >= 0) { /* The space is subtractive with a K channel. */ + /* If XICC_NEUTRAL_CMYK_BLACK then locate the darkest */ + /* CMYK within limits with the same chromaticity as the white point, */ + /* otherwise locate the device value within the ink limits that is */ + /* in the direction of the K channel */ + bpfind bfs; /* Callback context */ + double sr[MXDO]; /* search radius */ + double tt[MXDO]; /* Temporary */ + double rs0[MXDO], rs1[MXDO]; /* Random start candidates */ + int trial; + double brv; + + /* Setup callback function context */ + bfs.p = p; + bfs.inn = inn; + bfs.outs = outs; + + bfs.kch = kch; + bfs.tlimit = -1.0; + bfs.klimit = -1.0; + bfs.toll = XICC_BLACK_POINT_TOLL; + + if (alg == icmLutType) { + icxLuLut *pp = (icxLuLut *)x; + + pp->kch = kch; + bfs.tlimit = pp->ink.tlimit; + bfs.klimit = pp->ink.klimit; +#ifdef DEBUG + printf("~1 tlimit = %f, klimit = %f\n",bfs.tlimit,bfs.klimit); +#endif + }; + +#ifdef XICC_NEUTRAL_CMYK_BLACK +#ifdef DEBUG + printf("~1 Searching for neutral black\n"); +#endif + /* white has been given to us in XYZ */ + icmXYZ2Lab(&icmD50, bfs.p1, white); /* pivot Lab */ + icmCpy3(bfs.p2, white); /* temp white XYZ */ + icmScale3(bfs.p2, bfs.p2, 0.02); /* Scale white XYZ towards 0,0,0 */ + icmXYZ2Lab(&icmD50, bfs.p2, bfs.p2); /* Convert black direction to Lab */ + +#else /* Use K directin black */ +#ifdef DEBUG + printf("~1 Searching for K direction black\n"); +#endif + icmXYZ2Lab(&icmD50, bfs.p1, white); /* Pivot */ + + /* Now figure abs Lab value of K only, as the direction */ + /* to use for the rich black. */ + for (e = 0; e < inn; e++) + dblack[e] = 0.0; + if (bfs.klimit < 0.0) + dblack[kch] = 1.0; + else + dblack[kch] = bfs.klimit; /* K value */ + + p->lookup(p, black, dblack); + + if (outs == icSigXYZData) { + icmXYZ2Lab(&icmD50, bfs.p2, black); /* K direction */ + } else { + icmAry2Ary(bfs.p2, black); + } +#endif + +#ifdef DEBUG + printf("~1 Lab pivot %f %f %f, Lab K direction %f %f %f\n",bfs.p1[0],bfs.p1[1],bfs.p1[2],bfs.p2[0],bfs.p2[1],bfs.p2[2]); +#endif + /* Start with the K only as the current best value */ + brv = bpfindfunc((void *)&bfs, dblack); +#ifdef DEBUG + printf("~1 initial brv for K only = %f\n",brv); +#endif + + /* Set the random start 0 location as 000K */ + /* and the random start 1 location as CMY0 */ + { + double tt; + + for (e = 0; e < inn; e++) + dblack[e] = rs0[e] = 0.0; + if (bfs.klimit < 0.0) + dblack[kch] = rs0[kch] = 1.0; + else + dblack[kch] = rs0[kch] = bfs.klimit; /* K value */ + + if (bfs.tlimit < 0.0) + tt = 1.0; + else + tt = bfs.tlimit/(inn - 1.0); + for (e = 0; e < inn; e++) + rs1[e] = tt; + rs1[kch] = 0.0; /* K value */ + } + + /* Find the device black point using optimization */ + /* Do several trials to avoid local minima. */ + rand32(0x12345678); /* Make trial values deterministic */ + for (trial = 0; trial < 200; trial++) { + double rv; /* Temporary */ + + /* Start first trial at 000K */ + if (trial == 0) { + for (e = 0; e < inn; e++) { + tt[e] = rs0[e]; + sr[e] = 0.1; + } + + } else { + /* Base is random between 000K and CMY0: */ + if (trial < 100) { + rv = d_rand(0.0, 1.0); + for (e = 0; e < inn; e++) { + tt[e] = rv * rs0[e] + (1.0 - rv) * rs1[e]; + sr[e] = 0.1; + } + /* Base on current best */ + } else { + for (e = 0; e < inn; e++) { + tt[e] = dblack[e]; + sr[e] = 0.1; + } + } + + /* Then add random start offset */ + for (rv = 0.0, e = 0; e < inn; e++) { + tt[e] += d_rand(-0.5, 0.5); + if (tt[e] < 0.0) + tt[e] = 0.0; + else if (tt[e] > 1.0) + tt[e] = 1.0; + } + } + + /* Clip black */ + if (bfs.klimit >= 0.0 && tt[kch] > bfs.klimit) + tt[kch] = bfs.klimit; + + /* Compute amount outside total limit */ + if (bfs.tlimit >= 0.0) { + for (rv = 0.0, e = 0; e < inn; e++) + rv += tt[e]; + + if (rv > bfs.tlimit) { + rv /= (double)inn; + for (e = 0; e < inn; e++) + tt[e] -= rv; + } + } + + if (powell(&rv, inn, tt, sr, 0.000001, 1000, bpfindfunc, + (void *)&bfs, NULL, NULL) == 0) { +#ifdef DEBUG + printf("~1 trial %d, rv %f bp %f %f %f %f\n",trial,rv,tt[0],tt[1],tt[2],tt[3]); +#endif + if (rv < brv) { +#ifdef DEBUG + printf("~1 new best\n"); +#endif + brv = rv; + for (e = 0; e < inn; e++) + dblack[e] = tt[e]; + } + } + } + if (brv > 1000.0) + error("icxLu_comp_bk_point: Black point powell failed"); + + for (e = 0; e < inn; e++) { /* Make sure device values are in range */ + if (dblack[e] < 0.0) + dblack[e] = 0.0; + else if (dblack[e] > 1.0) + dblack[e] = 1.0; + } + /* Now have device black in dblack[] */ +#ifdef DEBUG + printf("~1 got device black %f %f %f %f\n",dblack[0], dblack[1], dblack[2], dblack[3]); +#endif + + p->lookup(p, black, dblack); /* Convert to PCS */ + } + + if (p != op) + p->del(p); + + /* We always return XYZ */ + if (outs == icSigLabData) + icmLab2XYZ(&icmD50, black, black); + +#ifdef DEBUG + printf("~1 returning %f %f %f\n", black[0], black[1], black[2]); +#endif + + return; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Return the media white and black points */ +/* in the xlu effective PCS colorspace. Pointers may be NULL. */ +/* (ie. these will be relative values for relative intent etc.) */ +static void icxLuEfv_wh_bk_points( +icxLuBase *p, +double *wht, +double *blk, +double *kblk /* K only black */ +) { + double white[3], black[3], kblack[3]; + + /* Get the Lu PCS converted to XYZ icc black and white points in XYZ */ + if (p->plu->lu_wh_bk_points(p->plu, white, black)) { + /* Black point is assumed. We should determine one instead. */ + /* Lookup K only black too */ + icxLu_comp_bk_point(p, 1, white, black, kblack); + + } else { + /* Lookup a possible K only black */ + icxLu_comp_bk_point(p, 0, white, black, kblack); + } + +//printf("~1 white %f %f %f, black %f %f %f, kblack %f %f %f\n",white[0],white[1],white[2],black[0],black[1],black[2],kblack[0],kblack[1],kblack[2]); + /* Convert to possibl xicc override PCS */ + switch (p->pcs) { + case icSigXYZData: + break; /* Don't have to do anyting */ + case icSigLabData: + icmXYZ2Lab(&icmD50, white, white); /* Convert from XYZ to Lab */ + icmXYZ2Lab(&icmD50, black, black); + icmXYZ2Lab(&icmD50, kblack, kblack); + break; + case icxSigJabData: + p->cam->XYZ_to_cam(p->cam, white, white); /* Convert from XYZ to Jab */ + p->cam->XYZ_to_cam(p->cam, black, black); + p->cam->XYZ_to_cam(p->cam, kblack, kblack); + break; + default: + break; + } + +//printf("~1 icxLuEfv_wh_bk_points: pcsor %s White %f %f %f, Black %f %f %f\n", icx2str(icmColorSpaceSignature,p->pcs), white[0], white[1], white[2], black[0], black[1], black[2]); + if (wht != NULL) { + wht[0] = white[0]; + wht[1] = white[1]; + wht[2] = white[2]; + } + + if (blk != NULL) { + blk[0] = black[0]; + blk[1] = black[1]; + blk[2] = black[2]; + } + + if (kblk != NULL) { + kblk[0] = kblack[0]; + kblk[1] = kblack[1]; + kblk[2] = kblack[2]; + } +} + +/* Create an instance of an xicc object */ +xicc *new_xicc( +icc *picc /* icc we are expanding */ +) { + xicc *p; + if ((p = (xicc *) calloc(1,sizeof(xicc))) == NULL) + return NULL; + p->pp = picc; + p->del = xicc_del; + p->get_luobj = xicc_get_luobj; + p->set_luobj = xicc_set_luobj; + p->get_viewcond = xicc_get_viewcond; + + /* Create an xcal if there is the right tag in the profile */ + p->cal = xiccReadCalTag(p->pp); + p->nodel_cal = 0; /* We created it, we will delete it */ + + return p; +} + +/* Do away with the xicc (but not the icc!) */ +static void xicc_del( +xicc *p +) { + if (p->cal != NULL && p->nodel_cal == 0) + p->cal->del(p->cal); + free (p); +} + + +/* Return an expanded lookup object, initialised */ +/* from the icc. */ +/* Return NULL on error, check errc+err for reason. */ +/* Set the pcsor & intent to consistent and values if */ +/* Jab and/or icxAppearance has been requested. */ +/* Create the underlying icm lookup object that is used */ +/* to create and implement the icx one. The icm will be used */ +/* to translate from native to effective PCS, unless the */ +/* effective PCS is Jab, in which case the icm will be set to */ +/* have an effective PCS of XYZ. Since native<->effecive PCS conversion */ +/* is done at the to/from_abs() stage, none of this affects the individual */ +/* conversion steps, which will all talk the native PCS (unless merged). */ +icxLuBase *xicc_get_luobj( +xicc *p, /* this */ +int flags, /* clip, merge flags */ +icmLookupFunc func, /* Functionality */ +icRenderingIntent intent, /* Intent */ +icColorSpaceSignature pcsor,/* PCS override (0 = def) */ +icmLookupOrder order, /* Search Order */ +icxViewCond *vc, /* Viewing Condition (may be NULL if pcsor is not CIECAM) */ +icxInk *ink /* inking details (NULL for default) */ +) { + icmLuBase *plu; + icxLuBase *xplu; + icmLuAlgType alg; + icRenderingIntent n_intent = intent; /* Native Intent to request */ + icColorSpaceSignature n_pcs = icmSigDefaultData; /* Native PCS to request */ + +//printf("~1 xicc_get_luobj got intent %s and pcsor %s\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSignature,pcsor)); + + /* Ensure that appropriate PCS is slected for an appearance intent */ + if (intent == icxAppearance + || intent == icxAbsAppearance + || intent == icxPerceptualAppearance + || intent == icxAbsPerceptualAppearance + || intent == icxSaturationAppearance + || intent == icxAbsSaturationAppearance) { + pcsor = icxSigJabData; + + /* Translate non-Jab intents to the equivalent appearance "intent" if pcsor == Jab. */ + /* This is how we get these when the UI's don't list all the apperances intents, */ + /* we select the analogous non-apperance intent with pcsor = Jab. */ + /* Note that Abs/non-abs selects between Apperance and AbsAppearance. */ + } else if (pcsor == icxSigJabData) { + if (intent == icRelativeColorimetric) + intent = icxAppearance; + else if (intent == icAbsoluteColorimetric) + intent = icxAbsAppearance; + else if (intent == icPerceptual) + intent = icxPerceptualAppearance; + else if (intent == icmAbsolutePerceptual) + intent = icxAbsPerceptualAppearance; + else if (intent == icSaturation) + intent = icxSaturationAppearance; + else if (intent == icmAbsoluteSaturation) + intent = icxAbsSaturationAppearance; + else + intent = icxAppearance; + } + + /* Translate intent asked for into intent needed in icclib */ + if (intent == icxAppearance + || intent == icxAbsAppearance) + n_intent = icAbsoluteColorimetric; + else if (intent == icxPerceptualAppearance + || intent == icxAbsPerceptualAppearance) + n_intent = icmAbsolutePerceptual; + else if (intent == icxSaturationAppearance + || intent == icxAbsSaturationAppearance) + n_intent = icmAbsoluteSaturation; + + if (pcsor != icmSigDefaultData) + n_pcs = pcsor; /* There is an icclib override */ + + if (pcsor == icxSigJabData) /* xicc override */ + n_pcs = icSigXYZData; /* Translate to XYZ */ + +//printf("~1 xicc_get_luobj processed intent %s and pcsor %s\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSignature,pcsor)); +//printf("~1 xicc_get_luobj icclib intent %s and pcsor %s\n",icx2str(icmRenderingIntent,n_intent),icx2str(icmColorSpaceSignature,n_pcs)); + /* Get icclib lookup object */ + if ((plu = p->pp->get_luobj(p->pp, func, n_intent, n_pcs, order)) == NULL) { + p->errc = p->pp->errc; /* Copy error */ + strcpy(p->err, p->pp->err); + return NULL; + } + + /* Figure out what the algorithm is */ + plu->spaces(plu, NULL, NULL, NULL, NULL, &alg, NULL, NULL, &n_pcs, NULL); + + /* make sure its "Abs CAM" */ + if (vc!= NULL + && (intent == icxAbsAppearance + || intent == icxAbsPerceptualAppearance + || intent == icxAbsSaturationAppearance)) { /* make sure its "Abs CAM" */ + /* Set white point and flare color to D50 */ + /* (Hmm. This doesn't match what happens within collink with absolute intent!!) */ + vc->Wxyz[0] = icmD50.X/icmD50.Y; + vc->Wxyz[1] = icmD50.Y/icmD50.Y; // Normalise white reference to Y = 1 ? + vc->Wxyz[2] = icmD50.Z/icmD50.Y; + + vc->Fxyz[0] = icmD50.X; + vc->Fxyz[1] = icmD50.Y; + vc->Fxyz[2] = icmD50.Z; + } + + /* Call xiccLu wrapper creation */ + switch (alg) { + case icmMonoFwdType: + xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 0); + break; + case icmMonoBwdType: + xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 1); + break; + case icmMatrixFwdType: + xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 0); + break; + case icmMatrixBwdType: + xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 1); + break; + case icmLutType: + xplu = new_icxLuLut(p, flags, plu, func, intent, pcsor, vc, ink); + break; + default: + xplu = NULL; + break; + } + + return xplu; +} + + +/* Return an expanded lookup object, initialised */ +/* from the icc, and then overwritten by a conversion */ +/* created from the supplied scattered data points. */ +/* The Lut is assumed to be a device -> native PCS profile. */ +/* If the SET_WHITE and/or SET_BLACK flags are set, */ +/* discover the white/black point, set it in the icc, */ +/* and make the Lut relative to them. */ +/* Return NULL on error, check errc+err for reason */ +static icxLuBase *xicc_set_luobj( +xicc *p, /* this */ +icmLookupFunc func, /* Functionality */ +icRenderingIntent intent, /* Intent */ +icmLookupOrder order, /* Search Order */ +int flags, /* white/black point, verbose flags etc. */ +int no, /* Number of points */ +int nobw, /* Number of points to look for white & black patches in */ +cow *points, /* Array of input points in target PCS space */ +icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */ +double dispLuminance, /* > 0.0 if display luminance value and is known */ +double wpscale, /* > 0.0 if input white point is to be scaled */ +double smooth, /* RSPL smoothing factor, -ve if raw */ +double avgdev, /* reading Average Deviation as a proportion of the input range */ +icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */ +icxInk *ink, /* inking details (NULL for default) */ +xcal *cal, /* Optional cal, will override any existing (not deleted with xicc)*/ +int quality /* Quality metric, 0..3 */ +) { + icmLuBase *plu; + icxLuBase *xplu = NULL; + icmLuAlgType alg; + + if (cal != NULL) { + if (p->cal != NULL && p->nodel_cal == 0) + p->cal->del(p->cal); + p->cal = cal; + p->nodel_cal = 1; /* We were given it, so don't delete it */ + } + + if (func != icmFwd) { + p->errc = 1; + sprintf(p->err,"Can only create Device->PCS profiles from scattered data."); + xplu = NULL; + return xplu; + } + + /* Get icclib lookup object */ + if ((plu = p->pp->get_luobj(p->pp, func, intent, 0, order)) == NULL) { + p->errc = p->pp->errc; /* Copy error */ + strcpy(p->err, p->pp->err); + return NULL; + } + + /* Figure out what the algorithm is */ + plu->spaces(plu, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL, NULL); + + /* Call xiccLu wrapper creation */ + switch (alg) { + case icmMonoFwdType: + p->errc = 1; + sprintf(p->err,"Setting Monochrome Fwd profile from scattered data not supported."); + plu->del(plu); + xplu = NULL; /* Not supported yet */ + break; + + case icmMatrixFwdType: + if (smooth < 0.0) + smooth = -smooth; + xplu = set_icxLuMatrix(p, plu, flags, no, nobw, points, skm, dispLuminance, wpscale, quality, smooth); + break; + + case icmLutType: + /* ~~~ Should add check that it is a fwd profile ~~~ */ + xplu = set_icxLuLut(p, plu, func, intent, flags, no, nobw, points, skm, dispLuminance, wpscale, smooth, avgdev, vc, ink, quality); + break; + + default: + break; + } + + return xplu; +} + +/* ------------------------------------------------------ */ +/* Viewing Condition Parameter stuff */ + +#ifdef NEVER /* Not currently used */ + +/* Guess viewing parameters from the technology signature */ +static void guess_from_techsig( +icTechnologySignature tsig, +double *Ybp +) { +double Yb = -1.0; + + switch (tsig) { + /* These are all inputing either a representation of */ + /* a natural scene captured on another medium, or are assuming */ + /* that the medium is the original. A _good_ system would */ + /* let the user indicate which is the case. */ + case icSigReflectiveScanner: + case icSigFilmScanner: + Yb = 0.2; + break; + + /* Direct scene to value devices. */ + case icSigDigitalCamera: + case icSigVideoCamera: + Yb = 0.2; + break; + + /* Emmisive displays. */ + /* We could try tweaking the white point on the assumption */ + /* that the viewer will be adapted to a combination of both */ + /* the CRT white point, and the ambient light. */ + case icSigVideoMonitor: + case icSigCRTDisplay: + case icSigPMDisplay: + case icSigAMDisplay: + Yb = 0.2; + break; + + /* Photo CD has its own viewing definitions */ + /* (It represents original scene colors) */ + case icSigPhotoCD: + Yb = 0.2; + break; + + /* Projection devices, either direct, or */ + /* via another intermediate medium. */ + case icSigProjectionTelevision: + Yb = 0.1; /* Assume darkened room, little background */ + break; + case icSigFilmWriter: + Yb = 0.0; /* Assume a dark room - no background */ + break; + + /* Printed media devices. */ + case icSigInkJetPrinter: + case icSigThermalWaxPrinter: + case icSigElectrophotographicPrinter: + case icSigElectrostaticPrinter: + case icSigDyeSublimationPrinter: + case icSigPhotographicPaperPrinter: + case icSigPhotoImageSetter: + case icSigGravure: + case icSigOffsetLithography: + case icSigSilkscreen: + case icSigFlexography: + Yb = 0.2; + break; + + default: + Yb = 0.2; + } + + if (Ybp != NULL) + *Ybp = Yb; +} + +#endif /* NEVER */ + + +/* See if we can read or guess the viewing conditions */ +/* for an ICC profile. */ +/* Return value 0 if it is well defined */ +/* Return value 1 if it is a guess */ +/* Return value 2 if it is not possible/appropriate */ +int xicc_get_viewcond( +xicc *p, /* Expanded profile we're working with */ +icxViewCond *vc /* Viewing parameters to return */ +) { + icc *pp = p->pp; /* Base ICC */ + + /* Numbers we're trying to find */ + ViewingCondition Ev = vc_none; + double Wxyz[3] = {-1.0, -1.0, -1.0}; /* Adapting white color */ + double La = -1.0; /* Adapting luminance */ + double Ixyz[3] = {-1.0, -1.0, -1.0}; /* Illuminant color */ + double Li = -1.0; /* Illuminant luminance */ + double Lb = -1.0; /* Backgrount luminance */ + double Yb = -1.0; /* Background relative luminance to Lv */ + double Lve = -1.0; /* Emissive device image luminance */ + double Lvr = -1.0; /* Reflective device image luminance */ + double Lv = -1.0; /* device image luminance */ + double Yf = -1.0; /* Flare relative luminance to Lv */ + double Fxyz[3] = {-1.0, -1.0, -1.0}; /* Flare color */ + icTechnologySignature tsig = icMaxEnumTechnology; /* Technology Signature */ + icProfileClassSignature devc = icMaxEnumClass; + int trans = -1; /* Set to 0 if not transparency, 1 if it is */ + + /* Collect all the information we can find */ + + /* Emmisive devices image white luminance */ + { + icmXYZArray *luminanceTag; + + if ((luminanceTag = (icmXYZArray *)pp->read_tag(pp, icSigLuminanceTag)) != NULL + && luminanceTag->ttype == icSigXYZType && luminanceTag->size >= 1) { + Lve = luminanceTag->data[0].Y; /* Copy structure */ + } + } + + /* Flare: */ + { + icmMeasurement *ro; + + if ((ro = (icmMeasurement *)pp->read_tag(pp, icSigMeasurementTag)) != NULL + && ro->ttype == icSigMeasurementType) { + + Yf = ro->flare; + /* ro->illuminant ie D50, D65, D93, A etc. */ + } + } + + /* Media White Point */ + { + icmXYZArray *whitePointTag; + + if ((whitePointTag = (icmXYZArray *)pp->read_tag(pp, icSigMediaWhitePointTag)) != NULL + && whitePointTag->ttype == icSigXYZType && whitePointTag->size >= 1) { + Wxyz[0] = whitePointTag->data[0].X; + Wxyz[1] = whitePointTag->data[0].Y; + Wxyz[2] = whitePointTag->data[0].Z; + } + } + + /* ViewingConditions: */ + { + icmViewingConditions *ro; + + if ((ro = (icmViewingConditions *)pp->read_tag(pp, icSigViewingConditionsTag)) != NULL + && ro->ttype == icSigViewingConditionsType) { + + /* ro->illuminant.X */ + /* ro->illuminant.Z */ + + Li = ro->illuminant.Y; + + /* Reflect illuminant off the media white */ + Lvr = Li * Wxyz[1]; + + /* Illuminant color */ + Ixyz[0] = ro->illuminant.X/ro->illuminant.Y; + Ixyz[1] = 1.0; + Ixyz[2] = ro->illuminant.Z/ro->illuminant.Y; + + /* Assume ICC surround is CICAM97 background */ + /* ro->surround.X */ + /* ro->surround.Z */ + La = ro->surround.Y; + + /* ro->stdIlluminant ie D50, D65, D93, A etc. */ + } + } + + /* Stuff we might need */ + + /* Technology: */ + { + icmSignature *ro; + + /* Try and read the tag from the file */ + if ((ro = (icmSignature *)pp->read_tag(pp, icSigTechnologyTag)) != NULL + && ro->ttype != icSigSignatureType) { + + tsig = ro->sig; + } + } + + devc = pp->header->deviceClass; /* Type of profile */ + if (devc == icSigLinkClass + || devc == icSigAbstractClass + || devc == icSigColorSpaceClass + || devc == icSigNamedColorClass) + return 2; + + /* + icSigInputClass + icSigDisplayClass + icSigOutputClass + */ + + if ((pp->header->flags & icTransparency) != 0) + trans = 1; + else + trans = 0; + + + /* figure Lv if we have the information */ + if (Lve >= 0.0) + Lv = Lve; /* Emmisive image white luminance */ + else + Lv = Lvr; /* Reflectance image white luminance */ + + /* Fudge the technology signature */ + if (tsig == icMaxEnumTechnology) { + if (devc == icSigDisplayClass) + tsig = icSigCRTDisplay; + } + +#ifndef NEVER + printf("Enumeration = %d\n", Ev); + printf("Viewing Conditions:\n"); + printf("White adaptation color %f %f %f\n",Wxyz[0], Wxyz[1], Wxyz[2]); + printf("Adapting Luminance La = %f\n",La); + printf("Illuminant color %f %f %f\n",Ixyz[0], Ixyz[1], Ixyz[2]); + printf("Illuminant Luminance Li = %f\n",Li); + printf("Background Luminance Lb = %f\n",Lb); + printf("Relative Background Yb = %f\n",Yb); + printf("Emissive Image White Lve = %f\n",Lve); + printf("Reflective Image White Lvr = %f\n",Lvr); + printf("Device Image White Lv = %f\n",Lv); + printf("Relative Flare Yf = %f\n",Yf); + printf("Flare color %f %f %f\n",Fxyz[0], Fxyz[1], Fxyz[2]); + printf("Technology = %s\n",tag2str(tsig)); + printf("deviceClass = %s\n",tag2str(devc)); + printf("Transparency = %d\n",trans); +#endif + + /* See if the viewing conditions are completely defined as ICC can do it */ + if (Wxyz[0] >= 0.0 && Wxyz[1] >= 0.0 && Wxyz[2] >= 0.0 + && La >= 0.0 + && Yb >= 0.0 + && Lv >= 0.0 + && Yf >= 0.0 + && Fxyz[0] >= 0.0 && Fxyz[1] >= 0.0 && Fxyz[2] >= 0.0) { + + vc->Ev = vc_none; + vc->Wxyz[0] = Wxyz[0]; + vc->Wxyz[1] = Wxyz[1]; + vc->Wxyz[2] = Wxyz[2]; + vc->La = La; + vc->Yb = Yb; + vc->Lv = Lv; + vc->Yf = Yf; + vc->Fxyz[0] = Fxyz[0]; + vc->Fxyz[1] = Fxyz[1]; + vc->Fxyz[2] = Fxyz[2]; + return 0; + } + + /* Hmm. We didn't get all the info an ICC can contain. */ + /* We will try to guess some reasonable defaults */ + + /* Have we at least got an adaptation white point ? */ + if (Wxyz[0] < 0.0 || Wxyz[1] < 0.0 || Wxyz[2] < 0.0) + return 2; /* No */ + + /* Have we got the technology ? */ + if (tsig == icMaxEnumTechnology) + return 2; /* Hopeless */ + + /* Guess from the technology */ + switch (tsig) { + + /* This is inputing either a representation of */ + /* a natural scene captured on another a print medium, or */ + /* are is assuming that the medium is the original. */ + /* We will assume that the print is the original. */ + case icSigReflectiveScanner: + { + if (La < 0.0) /* No adapting luminance */ + La = 34.0; /* Use a practical print evaluation number */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.01; /* Assume 1% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* This is inputing either a representation of */ + /* a natural scene captured on another a photo medium, or */ + /* are is assuming that the medium is the original. */ + /* We will assume a compromise media original, natural scene */ + case icSigFilmScanner: + { + if (La < 0.0) /* No adapting luminance */ + La = 50.0; /* Use bright indoors, dull outdoors */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.005; /* Assume 0.5% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* Direct scene to value devices. */ + case icSigDigitalCamera: + case icSigVideoCamera: + { + if (La < 0.0) /* No adapting luminance */ + La = 110.0; /* Use very bright indoors, usual outdoors */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.0; /* Assume 0% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* Emmisive displays. */ + /* Assume a video monitor is in a darker environment than a CRT */ + case icSigVideoMonitor: + { + if (La < 0.0) /* No adapting luminance */ + La = 4.0; /* Darkened work environment */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_dim; /* Assume dim viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.01; /* Assume 1% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + + /* Assume a typical work environment */ + case icSigCRTDisplay: + case icSigPMDisplay: + case icSigAMDisplay: + { + if (La < 0.0) /* No adapting luminance */ + La = 33.0; /* Typical work environment */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.02; /* Assume 2% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* Photo CD has its own viewing definitions */ + /* (It represents original scene colors) */ + case icSigPhotoCD: + { + if (La < 0.0) /* No adapting luminance */ + La = 320.0; /* Bright outdoors */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.00; /* Assume 0% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* Projection devices, either direct, or */ + /* via another intermediate medium. */ + /* Assume darkened room, little background */ + case icSigProjectionTelevision: + { + if (La < 0.0) /* No adapting luminance */ + La = 7.0; /* Dark environment */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.1; /* Assume little background */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_dim; /* Dim environment */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.01; /* Assume 1% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + /* Assume very darkened room, no background */ + case icSigFilmWriter: + { + if (La < 0.0) /* No adapting luminance */ + La = 7.0; /* Dark environment */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.0; /* Assume no background */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_dark; /* Dark environment */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.01; /* Assume 1% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + /* Printed media devices. */ + /* Assume a normal print viewing environment */ + case icSigInkJetPrinter: + case icSigThermalWaxPrinter: + case icSigElectrophotographicPrinter: + case icSigElectrostaticPrinter: + case icSigDyeSublimationPrinter: + case icSigPhotographicPaperPrinter: + case icSigPhotoImageSetter: + case icSigGravure: + case icSigOffsetLithography: + case icSigSilkscreen: + case icSigFlexography: + { + if (La < 0.0) /* No adapting luminance */ + La = 40.0; /* Use a practical print evaluation number */ + if (Yb < 0.0) /* No background relative luminance */ + Yb = 0.2; /* Assume grey world */ + if (Lv < 0.0) /* No device image luminance */ + Ev = vc_average; /* Assume average viewing conditions */ + if (Yf < 0.0) /* No flare figure */ + Yf = 0.01; /* Assume 1% flare */ + if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ + Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; + break; + } + + default: + { + return 2; + } + } + + return 1; +} + +/* Write our viewing conditions to the underlying ICC profile, */ +/* using a private tag. */ +void xicc_set_viewcond( +xicc *p, /* Expanded profile we're working with */ +icxViewCond *vc /* Viewing parameters to return */ +) { + //icc *pp = p->pp; /* Base ICC */ + + // ~~1 Not implemented yet +} + + + +/* Return an enumerated viewing condition */ +/* Return enumeration if OK, -999 if there is no such enumeration. */ +/* xicc may be NULL if just the description is wanted, */ +/* or an explicit white point is provided. */ +int xicc_enum_viewcond( +xicc *p, /* Expanded profile to get white point (May be NULL if desc NZ) */ +icxViewCond *vc, /* Viewing parameters to return, May be NULL if desc is nz */ +int no, /* Enumeration to return, -1 for default, -2 for none */ +char *as, /* String alias to number, NULL if none */ +int desc, /* NZ - Just return a description of this enumeration in vc */ +double *wp /* Provide white point if xicc is NULL */ +) { + + if (desc == 0) { /* We're setting the viewing condition */ + icc *pp; /* Base ICC */ + icmXYZArray *whitePointTag; + + if (vc == NULL) + return -999; + + if (p == NULL) { + if (wp == NULL) + return -999; + vc->Wxyz[0] = wp[0]; + vc->Wxyz[1] = wp[1]; + vc->Wxyz[2] = wp[2]; + } else { + + pp = p->pp; + if ((whitePointTag = (icmXYZArray *)pp->read_tag(pp, icSigMediaWhitePointTag)) != NULL + && whitePointTag->ttype == icSigXYZType && whitePointTag->size >= 1) { + vc->Wxyz[0] = whitePointTag->data[0].X; + vc->Wxyz[1] = whitePointTag->data[0].Y; + vc->Wxyz[2] = whitePointTag->data[0].Z; + } else { + if (wp == NULL) { + sprintf(p->err,"Enum VC: Failed to read Media White point"); + p->errc = 2; + return -999; + } + vc->Wxyz[0] = wp[0]; + vc->Wxyz[1] = wp[1]; + vc->Wxyz[2] = wp[2]; + } + } + + /* Set a default flare color */ + vc->Fxyz[0] = vc->Wxyz[0]; + vc->Fxyz[1] = vc->Wxyz[1]; + vc->Fxyz[2] = vc->Wxyz[2]; + } + + /* + + Typical adapting field luminances and white luminance in reflective setup: + + E = illuminance in Lux + Lv = White luminance assuming 100% reflectance + La = Adapting field luminance in cd/m^2, assuming 20% reflectance from surround + + E La Lv Condition + 11 0.7 4 Twilight + 32 2 10 Subdued indoor lighting + 64 4 20 Less than typical office light; sometimes recommended for + display-only workplaces (sRGB) + 350 22 111 Typical Office (sRGB annex D) + 500 32 160 Practical print evaluationa (ISO-3664 P2) + 1000 64 318 Good Print evaluation (CIE 116-1995) + 1000 64 318 Television Studio lighting + 1000 64 318 Overcast Outdoors + 2000 127 637 Critical print evaluation (ISO-3664 P1) + 10000 637 3183 Typical outdoors, full daylight + 50000 3185 15915 Bright summers day + + */ + + if (no == -1 + || (as != NULL && stricmp(as,"d") == 0)) { + + no = -1; + if (vc != NULL) { + vc->desc = " d - Default Viewing Condition"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 50.0; /* Practical to Good lighting */ + vc->Lv = 250.0; /* Average viewing conditions ratio */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 0 + || (as != NULL && stricmp(as,"pp") == 0)) { + + no = 0; + if (vc != NULL) { + vc->desc = " pp - Practical Reflection Print (ISO-3664 P2)"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 32.0; /* Use a practical print evaluation number */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 1 + || (as != NULL && stricmp(as,"pe") == 0)) { + + no = 1; + if (vc != NULL) { + vc->desc = " pe - Print evaluation environment (CIE 116-1995)"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 64.0; /* Good */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 2 + || (as != NULL && stricmp(as,"pc") == 0)) { + + no = 2; + if (vc != NULL) { + vc->desc = " pc - Critical print evaluation environment (ISO-3664 P1)"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 127.0; /* Critical */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 3 + || (as != NULL && stricmp(as,"mt") == 0)) { + + no = 3; + if (vc != NULL) { + vc->desc = " mt - Monitor in typical work environment"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 22.0; /* Typical work environment */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.02; /* 2% flare */ + } + } + else if (no == 4 + || (as != NULL && stricmp(as,"mb") == 0)) { + + no = 4; + if (vc != NULL) { + vc->desc = " mb - Bright monitor in bright work environment"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 42.0; /* Bright work environment */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.02; /* 2% flare */ + } + } + else if (no == 5 + || (as != NULL && stricmp(as,"md") == 0)) { + + no = 5; + if (vc != NULL) { + vc->desc = " md - Monitor in darkened work environment"; + vc->Ev = vc_dim; /* Dim viewing conditions */ + vc->La = 4.0; /* Darkened work environment */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 6 + || (as != NULL && stricmp(as,"jm") == 0)) { + + no = 6; + if (vc != NULL) { + vc->desc = " jm - Projector in dim environment"; + vc->Ev = vc_dim; /* Dim viewing conditions */ + vc->La = 10.0; /* Adaptation is from display */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare */ + } + } + else if (no == 7 + || (as != NULL && stricmp(as,"jd") == 0)) { + + no = 7; + if (vc != NULL) { + vc->desc = " jd - Projector in dark environment"; + vc->Ev = vc_dark; /* Dark viewing conditions */ + vc->La = 10.0; /* Adaptation is from display */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare ? */ + } + } + else if (no == 8 + || (as != NULL && stricmp(as,"pcd") == 0)) { + + no = 8; + if (vc != NULL) { + vc->desc = "pcd - Photo CD - original scene outdoors"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 320.0; /* Typical outdoors, 1600 cd/m^2 */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.00; /* 0% flare */ + } + } + else if (no == 9 + || (as != NULL && stricmp(as,"ob") == 0)) { + + no = 9; + if (vc != NULL) { + vc->desc = " ob - Original scene - Bright Outdoors"; + vc->Ev = vc_average; /* Average viewing conditions */ + vc->La = 2000.0; /* Bright Outdoors */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.00; /* 0% flare */ + } + } + else if (no == 10 + || (as != NULL && stricmp(as,"cx") == 0)) { + + no = 10; + if (vc != NULL) { + vc->desc = " cx - Cut Sheet Transparencies on a viewing box"; + vc->Ev = vc_cut_sheet; /* Cut sheet viewing conditions */ + vc->La = 53.0; /* Dim, adapted to slide ? */ + vc->Yb = 0.2; /* Grey world */ + vc->Yf = 0.01; /* 1% flare ? */ + } + } + else { + if (p != NULL) { + sprintf(p->err,"Enum VC: Unrecognised enumeration %d",no); + p->errc = 1; + } + return -999; + } + + return no; +} + +/* Debug: dump a Viewing Condition to standard out */ +void xicc_dump_viewcond( +icxViewCond *vc +) { + printf("Viewing Condition:\n"); + if (vc->Ev == vc_dark) + printf(" Surround to Image: Dark\n"); + else if (vc->Ev == vc_dim) + printf(" Surround to Image: Dim\n"); + else if (vc->Ev == vc_average) + printf(" Surround to Image: Average\n"); + else if (vc->Ev == vc_cut_sheet) + printf(" Transparency on Light box\n"); + printf(" Adapted white = %f %f %f\n",vc->Wxyz[0], vc->Wxyz[1], vc->Wxyz[2]); + printf(" Adapted luminance = %f cd/m^2\n",vc->La); + printf(" Background to image ratio = %f\n",vc->Yb); + if (vc->Ev == vc_none) + printf(" Image luminance = %f cd/m^2\n",vc->Lv); + printf(" Flare to image ratio = %f\n",vc->Yf); + printf(" Flare color = %f %f %f\n",vc->Fxyz[0], vc->Fxyz[1], vc->Fxyz[2]); +} + + +/* Debug: dump an Inking setup to standard out */ +void xicc_dump_inking(icxInk *ik) { + printf("Inking settings:\n"); + if (ik->tlimit < 0.0) + printf("No total limit\n"); + else + printf("Total limit = %f%%\n",ik->tlimit * 100.0); + + if (ik->klimit < 0.0) + printf("No black limit\n"); + else + printf("Black limit = %f%%\n",ik->klimit * 100.0); + + if (ik->KonlyLmin) + printf("K only black as locus Lmin\n"); + else + printf("Normal black as locus Lmin\n"); + + if (ik->k_rule == icxKvalue) { + printf("Inking rule is a fixed K target\n"); + } if (ik->k_rule == icxKlocus) { + printf("Inking rule is a fixed locus target\n"); + } if (ik->k_rule == icxKluma5 || ik->k_rule == icxKluma5k) { + if (ik->k_rule == icxKluma5) + printf("Inking rule is a 5 parameter locus function of L\n"); + else + printf("Inking rule is a 5 parameter K function of L\n"); + printf("Ksmth = %f\n",ik->c.Ksmth); + printf("Kskew = %f\n",ik->c.Kskew); + printf("Kstle = %f\n",ik->c.Kstle); + printf("Kstpo = %f\n",ik->c.Kstpo); + printf("Kenpo = %f\n",ik->c.Kenpo); + printf("Kenle = %f\n",ik->c.Kenle); + printf("Kshap = %f\n",ik->c.Kshap); + } if (ik->k_rule == icxKl5l || ik->k_rule == icxKl5lk) { + if (ik->k_rule == icxKl5l) + printf("Inking rule is a 2x5 parameter locus function of L and K aux\n"); + else + printf("Inking rule is a 2x5 parameter K function of L and K aux\n"); + printf("Min Ksmth = %f\n",ik->c.Ksmth); + printf("Min Kskew = %f\n",ik->c.Kskew); + printf("Min Kstle = %f\n",ik->c.Kstle); + printf("Min Kstpo = %f\n",ik->c.Kstpo); + printf("Min Kenpo = %f\n",ik->c.Kenpo); + printf("Min Kenle = %f\n",ik->c.Kenle); + printf("Min Kshap = %f\n",ik->c.Kshap); + printf("Max Ksmth = %f\n",ik->x.Ksmth); + printf("Max Kskew = %f\n",ik->x.Kskew); + printf("Max Kstle = %f\n",ik->x.Kstle); + printf("Max Kstpo = %f\n",ik->x.Kstpo); + printf("Max Kenpo = %f\n",ik->x.Kenpo); + printf("Max Kenle = %f\n",ik->x.Kenle); + printf("Max Kshap = %f\n",ik->x.Kshap); + } +} + +/* ------------------------------------------------------ */ +/* Gamut Mapping Intent stuff */ + +/* Return an enumerated gamut mapping intent */ +/* Return enumeration if OK, icxIllegalGMIntent if there is no such enumeration. */ +int xicc_enum_gmapintent( +icxGMappingIntent *gmi, /* Gamut Mapping parameters to return */ +int no, /* Enumeration selected, icxNoGMIntent for none */ +char *as /* Alias string selector, NULL for none */ +) { +#ifdef USE_CAM + int colccas = 0x2; /* Use cas clipping for colorimetric style intents */ + int perccas = 0x1; /* Use cas for perceptual style intents */ +#else + int colccas = 0x0; /* Use Lab for colorimetric style intents */ + int perccas = 0x0; /* Use Lab for perceptual style intents */ + fprintf(stderr,"!!!!!! Warning, USE_CAM is off in xicc.c !!!!!!\n"); +#endif + + /* Assert default if no guidance given */ + if (no == icxNoGMIntent && as == NULL) + no = icxDefaultGMIntent; + + if (no == 0 + || no == icxAbsoluteGMIntent + || (as != NULL && stricmp(as,"a") == 0)) { + /* Map Absolute appearance space Jab to Jab and clip out of gamut */ + no = 0; + gmi->as = "a"; + gmi->desc = " a - Absolute Colorimetric (in Jab) [ICC Absolute Colorimetric]"; + gmi->icci = icAbsoluteColorimetric; + gmi->usecas = colccas; /* Use absolute appearance space */ + gmi->usemap = 0; /* Don't use gamut mapping */ + gmi->greymf = 0.0; + gmi->glumwcpf = 0.0; + gmi->glumwexf = 0.0; + gmi->glumbcpf = 0.0; + gmi->glumbexf = 0.0; + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 1 + || (as != NULL && stricmp(as,"aw") == 0)) { + + /* I'm not sure how often this intent is useful. It's less likely than */ + /* I though that a printer white point won't fit within the gamut */ + /* of a display profile, since the display white always has Y = 1.0, */ + /* and no paper has better than about 95% reflectance. */ + /* Perhaps it may be more useful for targeting printer profiles ? */ + + /* Map Absolute Jab to Jab and scale source to avoid clipping the white point */ + no = 1; + gmi->as = "aw"; + gmi->desc = "aw - Absolute Colorimetric (in Jab) with scaling to fit white point"; + gmi->icci = icAbsoluteColorimetric; + gmi->usecas = 0x100 | colccas; /* Absolute Appearance space with scaling */ + /* to avoid clipping the source white point */ + gmi->usemap = 0; /* Don't use gamut mapping */ + gmi->greymf = 0.0; + gmi->glumwcpf = 0.0; + gmi->glumwexf = 0.0; + gmi->glumbcpf = 0.0; + gmi->glumbexf = 0.0; + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 2 + || (as != NULL && stricmp(as,"aa") == 0)) { + + /* Map appearance space Jab to Jab and clip out of gamut */ + no = 2; + gmi->as = "aa"; + gmi->desc = "aa - Absolute Appearance"; + gmi->icci = icRelativeColorimetric; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 0; /* Don't use gamut mapping */ + gmi->greymf = 0.0; + gmi->glumwcpf = 0.0; + gmi->glumwexf = 0.0; + gmi->glumbcpf = 0.0; + gmi->glumbexf = 0.0; + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 3 + || no == icxRelativeGMIntent + || (as != NULL && stricmp(as,"r") == 0)) { + + /* Align neutral axes and linearly map white point, then */ + /* map appearance space Jab to Jab and clip out of gamut */ + no = 3; + gmi->as = "r"; + gmi->desc = " r - White Point Matched Appearance [ICC Relative Colorimetric]"; + gmi->icci = icRelativeColorimetric; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* Fully align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 0.0; /* No compression at black end */ + gmi->glumbexf = 0.0; /* No expansion at black end */ + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 4 + || (as != NULL && stricmp(as,"la") == 0)) { + + /* Align neutral axes and linearly map white and black points, then */ + /* map appearance space Jab to Jab and clip out of gamut */ + no = 4; + gmi->as = "la"; + gmi->desc = "la - Luminance axis matched Appearance"; + gmi->icci = icRelativeColorimetric; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* Fully align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ + gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ + gmi->glumknf = 0.0; /* No knee on grey mapping */ + gmi->gamcpf = 0.0; /* No gamut compression */ + gmi->gamexf = 0.0; /* No gamut expansion */ + gmi->gamcknf = 0.0; /* No knee in gamut compress */ + gmi->gamxknf = 0.0; /* No knee in gamut expand */ + gmi->gampwf = 0.0; /* No Perceptual surface weighting factor */ + gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 5 + || no == icxDefaultGMIntent + || no == icxPerceptualGMIntent + || (as != NULL && stricmp(as,"p") == 0)) { + + /* Align neutral axes and perceptually map white and black points, */ + /* perceptually compress out of gamut and map appearance space Jab to Jab. */ + no = 5; + gmi->as = "p"; + gmi->desc = " p - Perceptual (Preferred) (Default) [ICC Perceptual]"; + gmi->icci = icPerceptual; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* Fully align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ + gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ + gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ + gmi->gamcpf = 1.0; /* Full gamut compression */ + gmi->gamexf = 0.0; /* No gamut expansion */ + gmi->gamcknf = 0.8; /* High Sigma knee in gamut compress */ + gmi->gamxknf = 0.0; /* No knee in gamut expand */ + gmi->gampwf = 1.0; /* Full Perceptual surface weighting factor */ + gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 6 + || (as != NULL && stricmp(as,"pa") == 0)) { + + /* Don't align neutral axes, but perceptually compress out of gamut */ + /* and map appearance space Jab to Jab. */ + no = 5; + gmi->as = "pa"; + gmi->desc = "pa - Perceptual Apperance "; + gmi->icci = icPerceptual; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 0.0; /* Don't align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ + gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ + gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ + gmi->gamcpf = 1.0; /* Full gamut compression */ + gmi->gamexf = 0.0; /* No gamut expansion */ + gmi->gamcknf = 0.8; /* High Sigma knee in gamut compress */ + gmi->gamxknf = 0.0; /* No knee in gamut expand */ + gmi->gampwf = 1.0; /* Full Perceptual surface weighting factor */ + gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 7 + || (as != NULL && stricmp(as,"ms") == 0)) { + + /* Align neutral axes and perceptually map white and black points, */ + /* perceptually compress and expand to match gamuts and map Jab to Jab. */ + no = 6; + gmi->as = "ms"; + gmi->desc = "ms - Saturation"; + gmi->icci = icSaturation; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* Fully align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ + gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ + gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ + gmi->gamcpf = 1.0; /* Full gamut compression */ + gmi->gamexf = 1.0; /* Full gamut expansion */ + gmi->gamcknf = 1.0; /* High Sigma knee in gamut compress/expand */ + gmi->gamxknf = 0.4; /* Moderate Sigma knee in gamut compress/expand */ + gmi->gampwf = 0.2; /* Slight perceptual surface weighting factor */ + gmi->gamswf = 0.8; /* Most saturation surface weighting factor */ + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 8 + || no == icxSaturationGMIntent + || (as != NULL && stricmp(as,"s") == 0)) { + + /* Same as "ms" but enhance saturation */ + no = 7; + gmi->as = "s"; + gmi->desc = " s - Enhanced Saturation [ICC Saturation]"; + gmi->icci = icSaturation; + gmi->usecas = perccas; /* Appearance space */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* Fully align grey axis */ + gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ + gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ + gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ + gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ + gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ + gmi->gamcpf = 1.0; /* Full gamut compression */ + gmi->gamexf = 1.0; /* Full gamut expansion */ + gmi->gamcknf = 1.0; /* High sigma knee in gamut compress */ + gmi->gamxknf = 0.5; /* Moderate sigma knee in gamut expand */ + gmi->gampwf = 0.0; /* No Perceptual surface weighting factor */ + gmi->gamswf = 1.0; /* Full Saturation surface weighting factor */ + gmi->satenh = 0.9; /* Medium saturation enhancement */ + } + else if (no == 9 + || (as != NULL && stricmp(as,"al") == 0)) { + + /* Map absolute L*a*b* to L*a*b* and clip out of gamut */ + no = 8; + gmi->as = "al"; + gmi->desc = "al - Absolute Colorimetric (Lab)"; + gmi->icci = icAbsoluteColorimetric; + gmi->usecas = 0x0; /* Don't use appearance space, use L*a*b* */ + gmi->usemap = 0; /* Don't use gamut mapping */ + gmi->greymf = 0.0; + gmi->glumwcpf = 0.0; + gmi->glumwexf = 0.0; + gmi->glumbcpf = 0.0; + gmi->glumbexf = 0.0; + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else if (no == 10 + || (as != NULL && stricmp(as,"rl") == 0)) { + + /* Align neutral axes and linearly map white point, then */ + /* map L*a*b* to L*a*b* and clip out of gamut */ + no = 3; + gmi->as = "rl"; + gmi->desc = "rl - White Point Matched Appearance (Lab)"; + gmi->icci = icRelativeColorimetric; + gmi->usecas = 0x0; /* Don't use appearance space, use L*a*b* */ + gmi->usemap = 1; /* Use gamut mapping */ + gmi->greymf = 1.0; /* And linearly map white point */ + gmi->glumwcpf = 1.0; + gmi->glumwexf = 1.0; + gmi->glumbcpf = 0.0; + gmi->glumbexf = 0.0; + gmi->glumknf = 0.0; + gmi->gamcpf = 0.0; + gmi->gamexf = 0.0; + gmi->gamcknf = 0.0; + gmi->gamxknf = 0.0; + gmi->gampwf = 0.0; + gmi->gamswf = 0.0; + gmi->satenh = 0.0; /* No saturation enhancement */ + } + else { /* icxIllegalGMIntent */ + return icxIllegalGMIntent; + } + + return no; +} + + +/* Debug: dump a Gamut Mapping specification */ +void xicc_dump_gmi( +icxGMappingIntent *gmi /* Gamut Mapping parameters to return */ +) { + printf(" Gamut Mapping Specification:\n"); + if (gmi->desc != NULL) + printf(" Description = '%s'\n",gmi->desc); + printf(" Closest ICC intent = '%s'\n",icm2str(icmRenderingIntent,gmi->icci)); + + if ((gmi->usecas & 0xff) == 0) + printf(" Not using Color Apperance Space\n"); + else if ((gmi->usecas & 0xff) == 1) + printf(" Using Color Apperance Space\n"); + else if ((gmi->usecas & 0xff) == 2) + printf(" Using Absolute Color Apperance Space\n"); + + if ((gmi->usecas & 0x100) != 0) + printf(" Scaling source to avoid white point clipping\n"); + + if (gmi->usemap == 0) + printf(" Not using Mapping\n"); + else { + printf(" Using Mapping with parameters:\n"); + printf(" Grey axis alignment factor %f\n", gmi->greymf); + printf(" Grey axis white compression factor %f\n", gmi->glumwcpf); + printf(" Grey axis white expansion factor %f\n", gmi->glumwexf); + printf(" Grey axis black compression factor %f\n", gmi->glumbcpf); + printf(" Grey axis black expansion factor %f\n", gmi->glumbexf); + printf(" Grey axis knee factor %f\n", gmi->glumknf); + printf(" Gamut compression factor %f\n", gmi->gamcpf); + printf(" Gamut expansion factor %f\n", gmi->gamexf); + printf(" Gamut compression knee factor %f\n", gmi->gamcknf); + printf(" Gamut expansion knee factor %f\n", gmi->gamxknf); + printf(" Gamut Perceptual mapping weighting factor %f\n", gmi->gampwf); + printf(" Gamut Saturation mapping weighting factor %f\n", gmi->gamswf); + printf(" Saturation enhancement factor %f\n", gmi->satenh); + } +} + +/* ------------------------------------------------------ */ +/* Turn xicc xcal into limit calibration callback */ + +/* Given an icc profile, try and create an xcal */ +/* Return NULL on error or no cal */ +xcal *xiccReadCalTag(icc *p) { + xcal *cal = NULL; + icTagSignature sig = icmMakeTag('t','a','r','g'); + icmText *ro; + int oi, tab; + +//printf("~1 about to look for CAL in profile\n"); + if ((ro = (icmText *)p->read_tag(p, sig)) != NULL) { + cgatsFile *cgf; + cgats *icg; + + if (ro->ttype != icSigTextType) + return NULL; + +//printf("~1 found 'targ' tag\n"); + if ((icg = new_cgats()) == NULL) { + return NULL; + } + if ((cgf = new_cgatsFileMem(ro->data, ro->size)) != NULL) { + icg->add_other(icg, "CTI3"); + oi = icg->add_other(icg, "CAL"); + +//printf("~1 created cgats object from 'targ' tag\n"); + if (icg->read(icg, cgf) == 0) { + + for (tab = 0; tab < icg->ntables; tab++) { + if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) { + break; + } + } + if (tab < icg->ntables) { +//printf("~1 found CAL table\n"); + + if ((cal = new_xcal()) == NULL) { + icg->del(icg); + cgf->del(cgf); + return NULL; + } + if (cal->read_cgats(cal, icg, tab, "'targ' tag") != 0) { +#ifdef DEBUG + printf("read_cgats on cal tag failed\n"); +#endif + cal->del(cal); + cal = NULL; + } +//else printf("~1 read CAL and creaded xcal object OK\n"); + } + } + cgf->del(cgf); + } + icg->del(icg); + } + return cal; +} + +/* A callback that uses an xcal, that can be used with icc get_tac */ +void xiccCalCallback(void *cntx, double *out, double *in) { + xcal *cal = (xcal *)cntx; + + cal->interp(cal, out, in); +} + +/* ---------------------------------------------- */ + +/* Utility function - given an open icc profile, */ +/* guess which channel is the black. */ +/* Return -1 if there is no black channel or it can't be guessed */ +int icxGuessBlackChan(icc *p) { + int kch = -1; + + switch (p->header->colorSpace) { + case icSigCmykData: + kch = 3; + break; + + /* Use a heuristic to detect the black channel. */ + /* This duplicates code in icxLu_comp_bk_point() :-( */ + /* Colorant guessing should go in icclib ? */ + case icSig2colorData: + case icSig3colorData: + case icSig4colorData: + case icSig5colorData: + case icSig6colorData: + case icSig7colorData: + case icSig8colorData: + case icSig9colorData: + case icSig10colorData: + case icSig11colorData: + case icSig12colorData: + case icSig13colorData: + case icSig14colorData: + case icSig15colorData: + case icSigMch5Data: + case icSigMch6Data: + case icSigMch7Data: + case icSigMch8Data: { + icmLuBase *lu; + double dval[MAX_CHAN]; + double ncval[3]; + double cvals[MAX_CHAN][3]; + int inn, e, nlighter, ndarker; + + /* Grab a lookup object */ + if ((lu = p->get_luobj(p, icmFwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) + error("icxGetLimits: assert: getting Fwd Lookup failed!"); + + lu->spaces(lu, NULL, &inn, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + /* Decide if the colorspace is aditive or subtractive */ + + /* First the no colorant value */ + for (e = 0; e < inn; e++) + dval[e] = 0.0; + lu->lookup(lu, ncval, dval); + + /* Then all the colorants */ + nlighter = ndarker = 0; + for (e = 0; e < inn; e++) { + dval[e] = 1.0; + lu->lookup(lu, cvals[e], dval); + dval[e] = 0.0; + if (fabs(cvals[e][0] - ncval[0]) > 5.0) { + if (cvals[e][0] > ncval[0]) + nlighter++; + else + ndarker++; + } + } + + if (ndarker > 0 && nlighter == 0) { /* Assume subtractive. */ + double pbk[3] = { 0.0,0.0,0.0 }; /* Perfect black */ + double smd = 1e10; /* Smallest distance */ + + /* Guess the black channel */ + for (e = 0; e < inn; e++) { + double tt; + tt = icmNorm33sq(pbk, cvals[e]); + if (tt < smd) { + smd = tt; + kch = e; + } + } + /* See if the black seems sane */ + if (cvals[kch][0] > 40.0 + || fabs(cvals[kch][1]) > 10.0 + || fabs(cvals[kch][2]) > 10.0) { + kch = -1; + } + } + lu->del(lu); + } + break; + + default: + break; + } + + return kch; +} + +/* Utility function - given an open icc profile, */ +/* estmate the total ink limit and black ink limit. */ +/* Note that this is rather rough, because ICC profiles */ +/* don't have a tag for this information, and ICC profiles */ +/* don't have any straightforward way of identifying particular */ +/* color channels for > 4 color. */ +/* If there are no limits, or they are not discoverable or */ +/* applicable, return values of -1.0 */ + +void icxGetLimits( +xicc *xp, +double *tlimit, +double *klimit +) { + icc *p = xp->pp; + int nch; + double max[MAX_CHAN]; /* Max of each channel */ + double total; + + total = p->get_tac(p, max, xp->cal != NULL ? xiccCalCallback : NULL, (void *)xp->cal); + + if (total < 0.0) { /* Not valid */ + if (tlimit != NULL) + *tlimit = -1.0; + if (klimit != NULL) + *klimit = -1.0; + return; + } + + nch = icmCSSig2nchan(p->header->colorSpace); + + /* No effective limit */ + if (tlimit != NULL) { + if (total >= (double)nch) { + *tlimit = -1.0; + } else { + *tlimit = total; + } + } + + if (klimit != NULL) { + int kch; + + kch = icxGuessBlackChan(p); + + if (kch < 0 || max[kch] >= 1.0) { + *klimit = -1.0; + } else { + *klimit = max[kch]; + } + } +} + +/* Replace a non-set limit (ie. < 0.0) with the heuristic from */ +/* the given profile. */ +void icxDefaultLimits( +xicc *xp, +double *tlout, +double tlin, +double *klout, +double klin +) { + if (tlin < 0.0 || klin < 0.0) { + double tl, kl; + + icxGetLimits(xp, &tl, &kl); + + if (tlin < 0.0) + tlin = tl; + + if (klin < 0.0) + klin = kl; + } + + if (tlout != NULL) + *tlout = tlin; + + if (klout != NULL) + *klout = klin; +} + +/* Structure to hold optimisation information */ +typedef struct { + xcal *cal; + double ilimit; + double uilimit; +} ulimctx; + +/* Callback to find equivalent underlying total limit */ +/* and try and maximize it while remaining within gamut */ +static double ulimitfunc(void *cntx, double pv[]) { + ulimctx *cx = (ulimctx *)cntx; + xcal *cal = cx->cal; + int devchan = cal->devchan; + int i; + double dv, odv; + double og = 0.0, rv = 0.0; + + double usum = 0.0, sum = 0.0; + + /* Comute calibrated sum of channels except last */ + for (i = 0; i < (devchan-1); i++) { + double dv = pv[i]; /* Underlying (pre-calibration) device value */ + usum += dv; /* Underlying sum */ + if (dv < 0.0) { + og += -dv; + dv = 0.0; + } else if (dv > 1.0) { + og += dv - 1.0; + dv = 1.0; + } else + dv = cal->interp_ch(cal, i, dv); /* Calibrated device value */ + sum += dv; /* Calibrated device sum */ + } + /* Compute the omitted channel value */ + dv = cx->ilimit - sum; /* Omitted calibrated device value */ + if (dv < 0.0) { + og += -dv; + dv = 0.0; + } else if (dv > 1.0) { + og += dv - 1.0; + dv = 1.0; + } else + dv = cal->inv_interp_ch(cal, i, dv); /* Omitted underlying device value */ + usum += dv; /* Underlying sum */ + cx->uilimit = usum; + + rv = 10000.0 * og - usum; /* Penalize out of gamut, maximize underlying sum */ + +//printf("~1 returning %f from %f %f %f %f\n",rv,pv[0],pv[1],pv[2],dv); + return rv; +} + +/* Given a calibrated total ink limit and an xcal, return the */ +/* equivalent underlying (pre-calibration) total ink limit. */ +/* This is the maximum equivalent, that makes sure that */ +/* the calibrated limit is met or exceeded. */ +double icxMaxUnderlyingLimit(xcal *cal, double ilimit) { + ulimctx cx; + int i; + double dv[MAX_CHAN]; + double sr[MAX_CHAN]; + double rv; /* Residual value */ + + if (cal->devchan <= 1) { + return cal->inv_interp_ch(cal, 0, ilimit); + } + + cx.cal = cal; + cx.ilimit = ilimit; + + for (i = 0; i < (cal->devchan-1); i++) { + sr[i] = 0.05; + dv[i] = 0.1; + } + if (powell(&rv, cal->devchan-1, dv, sr, 0.000001, 1000, ulimitfunc, + (void *)&cx, NULL, NULL) != 0) { + warning("icxUnderlyingLimit() failed for chan %d, ilimit %f\n",cal->devchan,ilimit); + return ilimit; + } + ulimitfunc((void *)&cx, dv); + + return cx.uilimit; +} + +/* ------------------------------------------------------ */ +/* Conversion and deltaE formular that include partial */ +/* derivatives, for use within fit parameter optimisations. */ + +/* CIE XYZ to perceptual Lab with partial derivatives. */ +void icxdXYZ2Lab(icmXYZNumber *w, double *out, double dout[3][3], double *in) { + double wp[3], tin[3], dtin[3]; + int i; + + wp[0] = w->X, wp[1] = w->Y, wp[2] = w->Z; + + for (i = 0; i < 3; i++) { + tin[i] = in[i]/wp[i]; + dtin[i] = 1.0/wp[i]; + + if (tin[i] > 0.008856451586) { + dtin[i] *= pow(tin[i], -2.0/3.0) / 3.0; + tin[i] = pow(tin[i],1.0/3.0); + } else { + dtin[i] *= 7.787036979; + tin[i] = 7.787036979 * tin[i] + 16.0/116.0; + } + } + + out[0] = 116.0 * tin[1] - 16.0; + dout[0][0] = 0.0; + dout[0][1] = 116.0 * dtin[1]; + dout[0][2] = 0.0; + + out[1] = 500.0 * (tin[0] - tin[1]); + dout[1][0] = 500.0 * dtin[0]; + dout[1][1] = 500.0 * -dtin[1]; + dout[1][2] = 0.0; + + out[2] = 200.0 * (tin[1] - tin[2]); + dout[2][0] = 0.0; + dout[2][1] = 200.0 * dtin[1]; + dout[2][2] = 200.0 * -dtin[2]; +} + + +/* Return the normal Delta E squared, given two Lab values, */ +/* including partial derivatives. */ +double icxdLabDEsq(double dout[2][3], double *Lab0, double *Lab1) { + double rv = 0.0, tt; + + tt = Lab0[0] - Lab1[0]; + dout[0][0] = 2.0 * tt; + dout[1][0] = -2.0 * tt; + rv += tt * tt; + tt = Lab0[1] - Lab1[1]; + dout[0][1] = 2.0 * tt; + dout[1][1] = -2.0 * tt; + rv += tt * tt; + tt = Lab0[2] - Lab1[2]; + dout[0][2] = 2.0 * tt; + dout[1][2] = -2.0 * tt; + rv += tt * tt; + return rv; +} + +/* Return the CIE94 Delta E color difference measure, squared */ +/* including partial derivatives. */ +double icxdCIE94sq(double dout[2][3], double Lab0[3], double Lab1[3]) { + double desq, _desq[2][3]; + double dlsq; + double dcsq, _dcsq[2][2]; /* == [x][1,2] */ + double c12, _c12[2][2]; /* == [x][1,2] */ + double dhsq, _dhsq[2][2]; /* == [x][1,2] */ + double rv; + + { + double dl, da, db; + + dl = Lab0[0] - Lab1[0]; + dlsq = dl * dl; /* dl squared */ + da = Lab0[1] - Lab1[1]; + db = Lab0[2] - Lab1[2]; + + + /* Compute normal Lab delta E squared */ + desq = dlsq + da * da + db * db; + _desq[0][0] = 2.0 * dl; + _desq[1][0] = -2.0 * dl; + _desq[0][1] = 2.0 * da; + _desq[1][1] = -2.0 * da; + _desq[0][2] = 2.0 * db; + _desq[1][2] = -2.0 * db; + } + + { + double c1, c2, dc, tt; + + /* Compute chromanance for the two colors */ + c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]); + c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]); + c12 = sqrt(c1 * c2); /* Symetric chromanance */ + + tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12); + _c12[0][0] = Lab0[1] * tt; + _c12[0][1] = Lab0[2] * tt; + tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12); + _c12[1][0] = Lab1[1] * tt; + _c12[1][1] = Lab1[2] * tt; + + /* delta chromanance squared */ + dc = c2 - c1; + dcsq = dc * dc; + if (c1 < 1e-12 || c2 < 1e-12) { + c1 += 1e-12; + c2 += 1e-12; + } + _dcsq[0][0] = -2.0 * Lab0[1] * (c2 - c1)/c1; + _dcsq[0][1] = -2.0 * Lab0[2] * (c2 - c1)/c1; + _dcsq[1][0] = 2.0 * Lab1[1] * (c2 - c1)/c2; + _dcsq[1][1] = 2.0 * Lab1[2] * (c2 - c1)/c2; + } + + /* Compute delta hue squared */ + dhsq = desq - dlsq - dcsq; + if (dhsq >= 0.0) { + _dhsq[0][0] = _desq[0][1] - _dcsq[0][0]; + _dhsq[0][1] = _desq[0][2] - _dcsq[0][1]; + _dhsq[1][0] = _desq[1][1] - _dcsq[1][0]; + _dhsq[1][1] = _desq[1][2] - _dcsq[1][1]; + } else { + dhsq = 0.0; + _dhsq[0][0] = 0.0; + _dhsq[0][1] = 0.0; + _dhsq[1][0] = 0.0; + _dhsq[1][1] = 0.0; + } + + { + double sc, scsq, scf; + double sh, shsq, shf; + + /* Weighting factors for delta chromanance & delta hue */ + sc = 1.0 + 0.048 * c12; + scsq = sc * sc; + + sh = 1.0 + 0.014 * c12; + shsq = sh * sh; + + rv = dlsq + dcsq/scsq + dhsq/shsq; + + scf = 0.048 * -2.0 * dcsq/(scsq * sc); + shf = 0.014 * -2.0 * dhsq/(shsq * sh); + dout[0][0] = _desq[0][0]; + dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf + + _dhsq[0][0]/shsq + _c12[0][0] * shf; + dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf + + _dhsq[0][1]/shsq + _c12[0][1] * shf; + dout[1][0] = _desq[1][0]; + dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf + + _dhsq[1][0]/shsq + _c12[1][0] * shf; + dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf + + _dhsq[1][1]/shsq + _c12[1][1] * shf; + return rv; + } +} + +// ~~99 not sure if these are correct: + +/* Return the normal Delta E given two Lab values, */ +/* including partial derivatives. */ +double icxdLabDE(double dout[2][3], double *Lab0, double *Lab1) { + double rv = 0.0, tt; + + tt = Lab0[0] - Lab1[0]; + dout[0][0] = 1.0 * tt; + dout[1][0] = -1.0 * tt; + rv += tt * tt; + tt = Lab0[1] - Lab1[1]; + dout[0][1] = 1.0 * tt; + dout[1][1] = -1.0 * tt; + rv += tt * tt; + tt = Lab0[2] - Lab1[2]; + dout[0][2] = 1.0 * tt; + dout[1][2] = -1.0 * tt; + rv += tt * tt; + return sqrt(rv); +} + +/* Return the CIE94 Delta E color difference measure */ +/* including partial derivatives. */ +double icxdCIE94(double dout[2][3], double Lab0[3], double Lab1[3]) { + double desq, _desq[2][3]; + double dlsq; + double dcsq, _dcsq[2][2]; /* == [x][1,2] */ + double c12, _c12[2][2]; /* == [x][1,2] */ + double dhsq, _dhsq[2][2]; /* == [x][1,2] */ + double rv; + + { + double dl, da, db; + + dl = Lab0[0] - Lab1[0]; + dlsq = dl * dl; /* dl squared */ + da = Lab0[1] - Lab1[1]; + db = Lab0[2] - Lab1[2]; + + + /* Compute normal Lab delta E squared */ + desq = dlsq + da * da + db * db; + _desq[0][0] = 1.0 * dl; + _desq[1][0] = -1.0 * dl; + _desq[0][1] = 1.0 * da; + _desq[1][1] = -1.0 * da; + _desq[0][2] = 1.0 * db; + _desq[1][2] = -1.0 * db; + } + + { + double c1, c2, dc, tt; + + /* Compute chromanance for the two colors */ + c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]); + c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]); + c12 = sqrt(c1 * c2); /* Symetric chromanance */ + + tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12); + _c12[0][0] = Lab0[1] * tt; + _c12[0][1] = Lab0[2] * tt; + tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12); + _c12[1][0] = Lab1[1] * tt; + _c12[1][1] = Lab1[2] * tt; + + /* delta chromanance squared */ + dc = c2 - c1; + dcsq = dc * dc; + if (c1 < 1e-12 || c2 < 1e-12) { + c1 += 1e-12; + c2 += 1e-12; + } + _dcsq[0][0] = -1.0 * Lab0[1] * (c2 - c1)/c1; + _dcsq[0][1] = -1.0 * Lab0[2] * (c2 - c1)/c1; + _dcsq[1][0] = 1.0 * Lab1[1] * (c2 - c1)/c2; + _dcsq[1][1] = 1.0 * Lab1[2] * (c2 - c1)/c2; + } + + /* Compute delta hue squared */ + dhsq = desq - dlsq - dcsq; + if (dhsq >= 0.0) { + _dhsq[0][0] = _desq[0][1] - _dcsq[0][0]; + _dhsq[0][1] = _desq[0][2] - _dcsq[0][1]; + _dhsq[1][0] = _desq[1][1] - _dcsq[1][0]; + _dhsq[1][1] = _desq[1][2] - _dcsq[1][1]; + } else { + dhsq = 0.0; + _dhsq[0][0] = 0.0; + _dhsq[0][1] = 0.0; + _dhsq[1][0] = 0.0; + _dhsq[1][1] = 0.0; + } + + { + double sc, scsq, scf; + double sh, shsq, shf; + + /* Weighting factors for delta chromanance & delta hue */ + sc = 1.0 + 0.048 * c12; + scsq = sc * sc; + + sh = 1.0 + 0.014 * c12; + shsq = sh * sh; + + rv = dlsq + dcsq/scsq + dhsq/shsq; + + scf = 0.048 * -1.0 * dcsq/(scsq * sc); + shf = 0.014 * -1.0 * dhsq/(shsq * sh); + dout[0][0] = _desq[0][0]; + dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf + + _dhsq[0][0]/shsq + _c12[0][0] * shf; + dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf + + _dhsq[0][1]/shsq + _c12[0][1] * shf; + dout[1][0] = _desq[1][0]; + dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf + + _dhsq[1][0]/shsq + _c12[1][0] * shf; + dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf + + _dhsq[1][1]/shsq + _c12[1][1] * shf; + return sqrt(rv); + } +} + +/* ------------------------------------------------------ */ +/* A power-like function, based on Graphics Gems adjustment curve. */ +/* Avoids "toe" problem of pure power. */ +/* Adjusted so that "power" 2 and 0.5 agree with real power at 0.5 */ + +double icx_powlike(double vv, double pp) { + double tt, g; + + if (pp >= 1.0) { + g = 2.0 * (pp - 1.0); + vv = vv/(g - g * vv + 1.0); + } else { + g = 2.0 - 2.0/pp; + vv = (vv - g * vv)/(1.0 - g * vv); + } + + return vv; +} + +/* Compute the necessary aproximate power, to transform */ +/* the given value from src to dst. They are assumed to be */ +/* in the range 0.0 .. 1.0 */ +double icx_powlike_needed(double src, double dst) { + double pp, g; + + if (dst <= src) { + g = -((src - dst)/(dst * src - dst)); + pp = (0.5 * g) + 1.0; + } else { + g = -((src - dst)/((dst - 1.0) * src)); + pp = 1.0/(1.0 - 0.5 * g); + } + + return pp; +} + +/* ------------------------------------------------------ */ +/* Parameterized transfer/dot gain function. */ +/* Used for device modelling. Including partial */ +/* derivative for input and parameters. */ + +/* NOTE that clamping the input values seems to cause */ +/* conjgrad() problems. */ + +/* Transfer function */ +double icxTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int ord; + + /* Process all the shaper orders from low to high. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* are monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. */ + for (ord = 0; ord < luord; ord++) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} + +/* Inverse transfer function */ +double icxInvTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int ord; + + /* Process the shaper orders in reverse from high to low. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* are monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. */ + for (ord = luord-1; ord >= 0; ord--) { + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = -v[ord]; /* Inverse parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + return vv; +} + +/* Transfer function with offset and scale */ +double icxSTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +) { + max -= min; + + vv = (vv - min)/max; + vv = icxTransFunc(v, luord, vv); + vv = (vv * max) + min; + return vv; +} + +/* Inverse Transfer function with offset and scale */ +double icxInvSTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +) { + max -= min; + + vv = (vv - min)/max; + vv = icxInvTransFunc(v, luord, vv); + vv = (vv * max) + min; + return vv; +} + +/* Transfer function with partial derivative */ +/* with respect to the parameters. */ +double icxdpTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g; + int i, ord; + + /* Process all the shaper orders from high to low. */ + for (ord = 0; ord < luord; ord++) { + double dsv; /* del for del in g */ + double ddv; /* del for del in vv */ + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) { + g = -g; /* Alternate action in each section */ + } + vv -= sec; + if (g >= 0.0) { + double tt = g - g * vv + 1.0; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (g + 1.0)/(tt * tt); + vv = vv/tt; + } else { + double tt = 1.0 - g * vv; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (1.0 - g)/(tt * tt); + vv = (vv - g * vv)/tt; + } + + vv += sec; + vv /= (double)nsec; + dsv /= (double)nsec; + if (((int)sec) & 1) + dsv = -dsv; + + dv[ord] = dsv; + for (i = ord - 1; i >= 0; i--) + dv[i] *= ddv; + } + + return vv; +} + +/* Transfer function with offset and scale, and */ +/* partial derivative with respect to the parameters. */ +double icxdpSTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +) { + int i; + max -= min; + + vv = (vv - min)/max; + vv = icxdpTransFunc(v, dv, luord, vv); + vv = (vv * max) + min; + for (i = 0; i < luord; i++) + dv[i] *= max; + return vv; +} + + +/* Transfer function with partial derivative */ +/* with respect to the input value. */ +double icxdiTransFunc( +double *v, /* Pointer to first parameter */ +double *pdin, /* Return derivative wrt source value */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g, din; + int ord; + +#ifdef NEVER + if (vv < 0.0 || vv > 1.0) { + if (vv < 0.0) + vv = 0.0; + else + vv = 1.0; + + *pdin = 0.0; + return vv; + } +#endif + din = 1.0; + + /* Process all the shaper orders from high to low. */ + for (ord = 0; ord < luord; ord++) { + double ddv; /* del for del in vv */ + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) { + g = -g; /* Alternate action in each section */ + } + vv -= sec; + if (g >= 0.0) { + double tt = g - g * vv + 1.0; + ddv = (g + 1.0)/(tt * tt); + vv = vv/tt; + } else { + double tt = 1.0 - g * vv; + ddv = (1.0 - g)/(tt * tt); + vv = (vv - g * vv)/tt; + } + + vv += sec; + vv /= (double)nsec; + din *= ddv; + } + + *pdin = din; + return vv; +} + +/* Transfer function with offset and scale, and */ +/* partial derivative with respect to the input value. */ +double icxdiSTransFunc( +double *v, /* Pointer to first parameter */ +double *pdv, /* Return derivative wrt source value */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +) { + max -= min; + + vv = (vv - min)/max; + vv = icxdiTransFunc(v, pdv, luord, vv); + vv = (vv * max) + min; + return vv; +} + + +/* Transfer function with partial derivative */ +/* with respect to the parameters and the input value. */ +double icxdpdiTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter */ +double *pdin, /* Return derivative wrt source value */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +) { + double g, din; + int i, ord; + +#ifdef NEVER + if (vv < 0.0 || vv > 1.0) { + if (vv < 0.0) + vv = 0.0; + else + vv = 1.0; + + for (ord = 0; ord < luord; ord++) + dv[ord] = 0.0; + *pdin = 0.0; + return vv; + } +#endif + din = 1.0; + + /* Process all the shaper orders from high to low. */ + for (ord = 0; ord < luord; ord++) { + double dsv; /* del for del in g */ + double ddv; /* del for del in vv */ + int nsec; /* Number of sections */ + double sec; /* Section */ + + g = v[ord]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) { + g = -g; /* Alternate action in each section */ + } + vv -= sec; + if (g >= 0.0) { + double tt = g - g * vv + 1.0; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (g + 1.0)/(tt * tt); + vv = vv/tt; + } else { + double tt = 1.0 - g * vv; + dsv = (vv * vv - vv)/(tt * tt); + ddv = (1.0 - g)/(tt * tt); + vv = (vv - g * vv)/tt; + } + + vv += sec; + vv /= (double)nsec; + dsv /= (double)nsec; + if (((int)sec) & 1) + dsv = -dsv; + + dv[ord] = dsv; + for (i = ord - 1; i >= 0; i--) + dv[i] *= ddv; + din *= ddv; + } + + *pdin = din; + return vv; +} + +/* Transfer function with offset and scale, and */ +/* partial derivative with respect to the */ +/* parameters and the input value. */ +double icxdpdiSTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter */ +double *pdin, /* Return derivative wrt source value */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +) { + int i; + max -= min; + + vv = (vv - min)/max; + vv = icxdpdiTransFunc(v, dv, pdin, luord, vv); + vv = (vv * max) + min; + for (i = 0; i < luord; i++) + dv[i] *= max; + return vv; +} + +/* ------------------------------------------------------ */ +/* Multi-plane interpolation, used for device modelling. */ +/* Including partial derivative for input and parameters. */ +/* A simple flat plane is used for each output. */ + +/* Multi-plane interpolation - uses base + di slope values. */ +/* Parameters are assumed to be fdi groups of di + 1 parameters. */ +void icxPlaneInterp( +double *v, /* Pointer to first parameter [fdi * (di + 1)] */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +) { + int e, f; + + for (f = 0; f < fdi; f++) { + for (out[f] = 0.0, e = 0; e < di; e++, v++) { + out[f] += in[e] * *v; + } + out[f] += *v; + } +} + + +/* Multii-plane interpolation with partial derivative */ +/* with respect to the input and parameters. */ +void icxdpdiPlaneInterp( +double *v, /* Pointer to first parameter value [fdi * (di + 1)] */ +double *dv, /* Return [1 + di] deriv. wrt each parameter v */ +double *din, /* Return [fdi * di] deriv. wrt each input value */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +) { + int e, ee, f, g; + int dip2 = (di + 1); /* Output dim increment through parameters */ + + /* Compute the output values */ + for (f = 0; f < fdi; f++) { + for (out[f] = 0.0, e = 0; e < di; e++) + out[f] += in[e] * v[f * dip2 + e]; + out[f] += v[f * dip2 + e]; + } + + /* Since interpolation is verys simple, derivative are also simple */ + + /* Copy del for parameter to return array */ + for (e = 0; e < di; e++) + dv[e] = in[e]; + dv[e] = 1.0; + + /* Compute del of out[] from in[] */ + for (f = 0; f < fdi; f++) { + for (e = 0; e < di; e++) { + din[f * di + e] = v[f * dip2 + e]; + } + } +} + +/* ------------------------------------------------------ */ +/* Matrix cube interpolation, used for device modelling. */ +/* Including partial derivative for input and parameters. */ + +/* Matrix cube interpolation - interpolate between 2^di output corner values. */ +/* Parameters are assumed to be fdi groups of 2^di parameters. */ +void icxCubeInterp( +double *v, /* Pointer to first parameter */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +) { + int e, f, g; + double gw[1 << MXDI]; /* weight for each matrix grid cube corner */ + + /* Compute corner weights needed for interpolation */ + gw[0] = 1.0; + for (e = 0, g = 1; e < di; e++, g *= 2) { + int i; + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * in[e]; + gw[i] *= (1.0 - in[e]); + } + } + + /* Now compute the output values */ + for (f = 0; f < fdi; f++) { + out[f] = 0.0; /* For each output value */ + for (e = 0; e < (1 << di); e++) { /* For all corners of cube */ + out[f] += gw[e] * *v; + v++; + } + } +} + + +/* Matrix cube interpolation. with partial derivative */ +/* with respect to the input and parameters. */ +void icxdpdiCubeInterp( +double *v, /* Pointer to first parameter value [fdi * 2^di] */ +double *dv, /* Return [2^di] deriv. wrt each parameter v */ +double *din, /* Return [fdi * di] deriv. wrt each input value */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +) { + int e, ee, f, g; + int dip2 = (1 << di); + double gw[1 << MXDI]; /* weight for each matrix grid cube corner */ + + /* Compute corner weights needed for interpolation */ + gw[0] = 1.0; + for (e = 0, g = 1; e < di; e++, g *= 2) { + int i; + for (i = 0; i < g; i++) { + gw[g+i] = gw[i] * in[e]; + gw[i] *= (1.0 - in[e]); + } + } + + /* Now compute the output values */ + for (f = 0; f < fdi; f++) { + out[f] = 0.0; /* For each output value */ + for (ee = 0; ee < dip2; ee++) { /* For all corners of cube */ + out[f] += gw[ee] * v[f * dip2 + ee]; + } + } + + /* Copy del for parameter to return array */ + for (ee = 0; ee < dip2; ee++) { /* For all other corners of cube */ + dv[ee] = gw[ee]; /* del from parameter */ + } + + /* Compute del from in[] value we want */ + for (e = 0; e < di; e++) { /* For input we want del wrt */ + + for (f = 0; f < fdi; f++) + din[f * di + e] = 0.0; /* Zero del ready of accumulation */ + + for (ee = 0; ee < dip2; ee++) { /* For all corners of cube weights, */ + int e2; /* accumulate del from in[] we want. */ + double vv = 1.0; + + /* Compute in[] weighted cube corners for all except del of in[] we want */ + for (e2 = 0; e2 < di; e2++) { /* const from non del inputs */ + if (e2 == e) + continue; + if (ee & (1 << e2)) + vv *= in[e2]; + else + vv *= (1.0 - in[e2]); + } + + /* Accumulate contribution of in[] we want for corner to out[] we want */ + if (ee & (1 << e)) { + for (f = 0; f < fdi; f++) + din[f * di + e] += v[f * dip2 + ee] * vv; + } else { + for (f = 0; f < fdi; f++) + din[f * di + e] -= v[f * dip2 + ee] * vv; + } + } + } +} + +/* ------------------------------------------------------ */ +/* Matrix cube simplex interpolation, used for device modelling. */ + +/* Matrix cube simplex interpolation - interpolate between 2^di output corner values. */ +/* Parameters are assumed to be fdi groups of 2^di parameters. */ +void icxCubeSxInterp( +double *v, /* Pointer to first parameter */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +) { + int si[MAX_CHAN]; /* in[] Sort index, [0] = smalest */ + +//{ +// double tout[MXDO]; +// +// icxCubeInterp(v, fdi, di, tout, in); +//printf("\n~1 Cube interp result = %f\n",tout[0]); +//} + +//printf("~1 icxCubeSxInterp: %f %f %f\n", in[0], in[1], in[2]); + /* Do insertion sort on coordinates, smallest to largest. */ + { + int ff, vf; + unsigned int e; + double v; + for (e = 0; e < di; e++) + si[e] = e; /* Initial unsorted indexes */ + + for (e = 1; e < di; e++) { + ff = e; + v = in[si[ff]]; + vf = ff; + while (ff > 0 && in[si[ff-1]] > v) { + si[ff] = si[ff-1]; + ff--; + } + si[ff] = vf; + } + } +//printf("~1 sort order %d %d %d\n", si[0], si[1], si[2]); +//printf(" from %f %f %f\n", in[si[0]], in[si[1]], in[si[2]]); + + /* Now compute the weightings, simplex vertices and output values */ + { + unsigned int e, f; + double w; /* Current vertex weight */ + + w = 1.0 - in[si[di-1]]; /* Vertex at base of cell */ + for (f = 0; f < fdi; f++) { + out[f] = w * v[f * (1 << di)]; +//printf("~1 out[%d] = %f = %f * %f\n",f,out[f],w,v[f * (1 << di)]); + } + + for (e = di-1; e > 0; e--) { /* Middle verticies */ + w = in[si[e]] - in[si[e-1]]; + v += (1 << si[e]); /* Move to top of cell in next largest dimension */ + for (f = 0; f < fdi; f++) { + out[f] += w * v[f * (1 << di)]; +//printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]); + } + } + + w = in[si[0]]; + v += (1 << si[0]); /* Far corner from base of cell */ + for (f = 0; f < fdi; f++) { + out[f] += w * v[f * (1 << di)]; +//printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]); + } + } +} + +/* ------------------------------------------------------ */ +/* Matrix multiplication, used for device modelling. */ +/* Including partial derivative for input and parameters. */ + + +/* 3x3 matrix multiplication, with the matrix in a 1D array */ +/* with respect to the input and parameters. */ +void icxMulBy3x3Parm( + double out[3], /* Return input multiplied by matrix */ + double mat[9], /* Matrix organised in [slow][fast] order */ + double in[3] /* Input values */ +) { + double *v = mat, ov[3]; + int e, f; + + /* Compute the output values */ + for (f = 0; f < 3; f++) { + ov[f] = 0.0; /* For each output value */ + for (e = 0; e < 3; e++) { + ov[f] += *v++ * in[e]; + } + } + out[0] = ov[0]; + out[1] = ov[1]; + out[2] = ov[2]; +} + + +/* 3x3 matrix multiplication, with partial derivatives */ +/* with respect to the input and parameters. */ +void icxdpdiMulBy3x3Parm( + double out[3], /* Return input multiplied by matrix */ + double dv[3][9], /* Return deriv for each [output] with respect to [param] */ + double din[3][3], /* Return deriv for each [output] with respect to [input] */ + double mat[9], /* Matrix organised in [slow][fast] order */ + double in[3] /* Input values */ +) { + double *v, ov[3]; + int e, f; + + /* Compute the output values */ + v = mat; + for (f = 0; f < 3; f++) { + ov[f] = 0.0; /* For each output value */ + for (e = 0; e < 3; e++) { + ov[f] += *v++ * in[e]; + } + } + + /* Compute deriv. with respect to the matrix parameter % 3 */ + /* This is pretty simple for a matrix ... */ + for (f = 0; f < 3; f++) { + for (e = 0; e < 9; e++) { + if (e/3 == f) + dv[f][e] = in[e % 3]; + else + dv[f][e] = 0.0; + } + } + + /* Compute deriv. with respect to the input values */ + /* This is pretty simple for a matrix ... */ + v = mat; + for (f = 0; f < 3; f++) + for (e = 0; e < 3; e++) + din[f][e] = *v++; + + out[0] = ov[0]; + out[1] = ov[1]; + out[2] = ov[2]; +} + +#undef stricmp + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xicc.h b/xicc/xicc.h new file mode 100644 index 0000000..3969ebe --- /dev/null +++ b/xicc/xicc.h @@ -0,0 +1,945 @@ +#ifndef XICC_H +#define XICC_H +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 28/6/00 + * Version: 1.00 + * + * Copyright 2000, 2011 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUB LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * This library expands the icclib functionality, + * particularly in creating and exercising color transforms. + * + * For the moment, this support is concentrated around the + * Lut and Matrix subsets of the ICC profile tranforms. + * + * It is intended to allow color transformation, storage and retrieval + * of ICC profiles, while permitting inititialization from scattered + * data, reversing of the transform, and specific manipulations of + * the internal processing elements. + * + * This class bases much of it's functionality on that of the + * underlying icclib icmLuBase. + */ + +#include "icc.h" /* icclib ICC definitions */ +#include "cgats.h" /* CGAT format */ +#include "rspl.h" /* rspllib thin plate spline definitions */ +#include "gamut.h" /* gamut definitions */ +#include "xutils.h" /* Utility functions */ +#include "xcam.h" /* CAM definitions */ +#include "xspect.h" /* Spectral conversion object */ +#include "xcolorants.h" /* Known colorants support */ +#include "insttypes.h" /* Instrument type support */ +#include "mpp.h" /* model printer profile support */ +#include "moncurve.h" /* monotonic curve support */ + /* (more at the end) */ + +#define XICC_USE_HK 1 /* [Set] Set to 1 to use Helmholtz-Kohlraush in all CAM conversions */ +#define XICC_NEUTRAL_CMYK_BLACK /* Use neutral axis black, else use K direction black. */ +#define XICC_BLACK_POINT_TOLL 0.5 /* Tollerance of CMYK black point location */ +#define XICC_BLACK_FIND_ABERR_WEIGHT 10.0 /* Weight of ab error against min L in BP */ + +/* ------------------------------------------------------------------------------ */ + +/* The "effective" PCS colorspace is the one specified by the PCS override, and */ +/* visible in the overal profile conversion. The "native" PCS colorspace is the one */ +/* that the underlying tags actually represent, and the PCS that the individual */ +/* stages within each profile type handle (unless the ICX_MERGED flag is set, in which */ +/* case the clut == native PCS appears to be the effective one). The conversion between */ +/* the native and effective PCS is generally done in the to/from_abs() conversions. */ + +/* ------------------------------------------------------------------------------ */ + +/* Pseudo intents, valid as parameter to xicc get_luobj(): */ + +/* Default Color Appearance Space, based on absolute colorimetric */ +#define icxAppearance ((icRenderingIntent)994) + +/* Represents icAbsoluteColorimetric, converted to Color Appearance space */ +#define icxAbsAppearance ((icRenderingIntent)995) /* Fixed D50 white point */ + +/* Special Color Appearance Space, based on "absolute" perceptual */ +#define icxPerceptualAppearance ((icRenderingIntent)996) + +/* Default Color Appearance Space, based on "absolute" saturation */ +#define icxSaturationAppearance ((icRenderingIntent)997) + +/* These two are for completeness, they are unlikely to be useful: */ +/* Special Color Appearance Space, based on "absolute" perceptual */ +#define icxAbsPerceptualAppearance ((icRenderingIntent)998) + +/* Default Color Appearance Space, based on "absolute" saturation */ +#define icxAbsSaturationAppearance ((icRenderingIntent)999) + + +/* Pseudo PCS colospace returned as PCS for intent icxAppearanceJab */ +#define icxSigJabData ((icColorSpaceSignature) icmMakeTag('J','a','b',' ')) + +/* Pseudo PCS colospace returned as PCS for intent icxAppearanceJCh */ +#define icxSigJChData ((icColorSpaceSignature) icmMakeTag('J','C','h',' ')) + +/* Pseudo PCS colospace returned as PCS */ +#define icxSigLChData ((icColorSpaceSignature) icmMakeTag('L','C','h',' ')) + +/* Return a string description of the given enumeration value */ +const char *icx2str(icmEnumType etype, int enumval); + + +/* ------------------------------------------------------------------------------ */ + +/* A 3x3 matrix model */ +struct _icxMatrixModel { + void *imp; /* Opaque implementation */ + + int isLab; /* Convert lookup to Lab */ + + void (*force) (struct _icxMatrixModel *p, double *targ, double *in); + void (*lookup) (struct _icxMatrixModel *p, double *out, double *in); + void (*del) (struct _icxMatrixModel *p); + +}; typedef struct _icxMatrixModel icxMatrixModel; + +/* Create a matrix model of a set of points, and return an object to lookup */ +/* points from the model. Return NULL on error. */ +icxMatrixModel *new_MatrixModel( +int verb, /* NZ if verbose */ +int nodp, /* Number of points */ +cow *ipoints, /* Array of input points in XYZ space */ +int isLab, /* nz if data points are Lab */ +int quality, /* Quality metric, 0..3 (-1 == 2 orders only) */ +int isLinear, /* NZ if pure linear, gamma = 1.0 */ +int isGamma, /* NZ if gamma rather than shaper */ +int isShTRC, /* NZ if shared TRCs */ +int shape0gam, /* NZ if zero'th order shaper should be gamma function */ +int clipbw, /* Prevent white > 1 and -ve black */ +int clipprims, /* Prevent primaries going -ve */ +double smooth, /* Smoothing factor (nominal 1.0) */ +double scale /* Scale device values */ +); + +/* ------------------------------------------------------------------------------ */ +/* Basic class keeps extra state for an icc, and provides the */ +/* expanded set of methods. */ + +/* Black generation rule */ +/* The rule is all in terms of device K and L* values */ +typedef enum { + icxKvalue = 0, /* K is specified as output K target by PCS auxiliary */ + icxKlocus = 1, /* K is specified as proportion of K locus by PCS auxiliary */ + icxKluma5 = 2, /* K is specified as locus by 5 parameters as a function of L */ + icxKluma5k = 3, /* K is specified as K value by 5 parameters as a function of L */ + icxKl5l = 4, /* K is specified as locus by 2x5 parameters as a function of L and K locus aux */ + icxKl5lk = 5 /* K is specified as K value by 2x5 parameters as a function of L and K value aux */ +} icxKrule; + +/* Curve parameters */ +typedef struct { + double Ksmth; /* K smoothing filter extent */ + double Kstle; /* K start level at white end (0.0 - 1.0)*/ + double Kstpo; /* K start point as prop. of L locus (0.0 - 1.0) */ + double Kenpo; /* K end point as prop. of L locus (0.0 - 1.0) */ + double Kenle; /* K end level at Black end (0.0 - 1.0) */ + double Kshap; /* K transition shape, 0.0-1.0 concave, 1.0-2.0 convex */ + double Kskew; /* K transition shape skew, 1.0 = even */ +} icxInkCurve; + +/* Default black generation smoothing value */ +#define ICXINKDEFSMTH 0.09 /* Transition window of +/- ICXINKDEFSMTH */ +#define ICXINKDEFSKEW 2.0 /* Curve shape skew (matches typical device behaviour) */ + +/* Structure to convey inking details */ +typedef struct { + double tlimit; /* Total ink limit, > 0.0 to < inputChan, < 0.0 == off */ + double klimit; /* Black limit, > 0.0 to < 1.0, < 0.0 == off */ + icxKrule k_rule; /* type of K generation rule */ + int KonlyLmin; /* Use K only black Locus Lmin */ + icxInkCurve c; /* K curve, or locus minimum curve */ + icxInkCurve x; /* locus maximum curve if icxKl5l */ +} icxInk; + +/* Structure to convey inverse lookup clip handling details */ +struct _icxClip { + int nearclip; /* Flag - use near clipping not vector */ + int LabLike; /* Flag Its an Lab like colorspace */ + int fdi; /* Dimentionality of clip vector */ + double ocent[MXDO]; /* base of center of clut output gamut */ + double ocentv[MXDO]; /* vector direction of clut output clip target line */ + double ocentl; /* clip target line length */ +}; typedef struct _icxClip icxClip; + +/* Structure to convey viewing conditions */ +typedef struct { + ViewingCondition Ev;/* Enumerated Viewing Condition */ + double Wxyz[3]; /* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */ + double La; /* Adapting/Surround Luminance cd/m^2 */ + double Yb; /* Relative Luminance of Background to reference white */ + double Lv; /* Luminance of white in the Viewing/Scene/Image field (cd/m^2) */ + /* Ignored if Ev is set to other than vc_none */ + double Yf; /* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */ + double Fxyz[3]; /* The Flare white coordinates (typically the Ambient color) */ + /* Will be taken from Wxyz if Fxyz == 0.0 */ + char *desc; /* Possible description of this VC */ +} icxViewCond; + +/* Structure to convey gamut mapping intent */ +typedef struct { + int usecas; /* 0x0 Use Lab space */ + /* 0x1 Use Color Appearance Space */ + /* 0x2 Use Absolute Color Appearance Space */ + /* 0x101 Use Color Appearance Space with luminence scaling */ + int usemap; /* NZ if Gamut mapping should be used, else clip */ + double greymf; /* Grey axis hue matching factor, 0.0 - 1.0 */ + double glumwcpf; /* Grey axis luminance white compression factor, 0.0 - 1.0 */ + double glumwexf; /* Grey axis luminance white expansion factor, 0.0 - 1.0 */ + double glumbcpf; /* Grey axis luminance black compression factor, 0.0 - 1.0 */ + double glumbexf; /* Grey axis luminance black expansion factor, 0.0 - 1.0 */ + double glumknf; /* Grey axis luminance knee factor, 0.0 - 1.0 */ + double gamcpf; /* Gamut compression factor, 0.0 - 1.0 */ + double gamexf; /* Gamut expansion factor, 0.0 - 1.0 */ + double gamcknf; /* Gamut compression knee factor, 0.0 - 1.0 */ + double gamxknf; /* Gamut expansion knee factor, 0.0 - 1.0 */ + double gampwf; /* Gamut Perceptual Map weighting factor, 0.0 - 1.0 */ + double gamswf; /* Gamut Saturation Map weighting factor, 0.0 - 1.0 */ + double satenh; /* Saturation enhancement value, 0.0 - Inf */ + char *as; /* Alias string (option name) */ + char *desc; /* Possible description of this VC */ + icRenderingIntent icci; /* Closest ICC intent */ +} icxGMappingIntent; + +struct _xicc { + /* Private: */ + icc *pp; /* ICC profile we expand */ + + struct _xcal *cal; /* Optional device cal, NULL if none */ + int nodel_cal; /* Flag, nz if cal was provided externally and shouldn't be deleted */ + + /* Public: */ + void (*del)(struct _xicc *p); + + /* "use" flags */ +#define ICX_CLIP_VECTOR 0x0000 /* If clipping is needed, clip in vector direction (default) */ +#define ICX_CLIP_NEAREST 0x0010 /* If clipping is needed, clip to nearest */ +#define ICX_MERGE_CLUT 0x0020 /* Merge the output() and out_abs() into the clut(), */ + /* for improved performance on reading, and */ + /* clipping in effective output space on inverse lookup. */ + /* Reduces accuracy noticably though. */ + /* Output output() and out_abs() become NOPs */ +#define ICX_CAM_CLIP 0x0100 /* Use CAM space during invfwd clipping lookup, */ + /* irrespective of the native or effective PCS. */ + /* Ignored if MERGE_CLUT is set or vector clip is used. */ + /* May halve the inverse lookup performance! */ +#define ICX_INT_SEPARATE 0x0400 /* Handle 4 dimensional devices with fixed inking rules */ + /* with an optimised internal separation pass, rather */ + /* than a point by point inverse locus lookup . */ + /* NOT IMPLEMENTED YET */ +#define ICX_FAST_SETUP 0x0800 /* Improve initial setup speed at the cost of throughput */ +#define ICX_VERBOSE 0x8000 /* Turn on verboseness during creation */ + + /* Returm a lookup object from the icc */ + struct _icxLuBase * (*get_luobj) (struct _xicc *p, + int flags, /* clip, merge flags */ + icmLookupFunc func, /* Functionality */ + icRenderingIntent intent,/* Intent */ + icColorSpaceSignature pcsor, /* PCS override (0 = def) */ + icmLookupOrder order, /* Search Order */ + icxViewCond *vc, /* Viewing Condition - only */ + /* used if pcsor == CIECAM. */ + /* or ICX_CAM_CLIP flag. */ + icxInk *ink); /* inking details (NULL = def) */ + + + /* Set a xluobj and icc table from scattered data */ + /* Return appropriate lookup object */ + /* NULL on error, check errc+err for reason */ + /* "create" flags */ +#define ICX_SET_WHITE 0x00010000 /* find, set and make relative to the white point */ +#define ICX_SET_WHITE_US 0x00030000 /* find, set and make relative to the white point hue, */ + /* but not scale to W L value, to avoid input clipping */ +#define ICX_SET_WHITE_C 0x00050000 /* find, set and make relative to the white point hue, */ + /* and clip any cLUT values over D50 to D50 */ +#define ICX_SET_BLACK 0x00080000 /* find and set the black point */ +#define ICX_WRITE_WBL 0x00100000 /* Matrix: write White, Black & Luminance tags */ +#define ICX_CLIP_WB 0x00200000 /* Clip white and black to be < 1 and > 0 respectively */ +#define ICX_CLIP_PRIMS 0x00400000 /* Clip matrix primaries to be > 0 */ +#define ICX_NO_IN_SHP_LUTS 0x00800000 /* Lut/Mtx: Don't create input (Device) shaper curves. */ +#define ICX_NO_IN_POS_LUTS 0x01000000 /* LuLut: Don't create input (Device) postion curves. */ +#define ICX_NO_OUT_LUTS 0x02000000 /* LuLut: Don't create output (PCS) curves. */ +//#define ICX_2PASSSMTH 0x04000000 /* If LuLut: Use Gaussian smoothing */ +//#define ICX_EXTRA_FIT 0x08000000 /* If LuLut: Counteract scat data point errors. */ +/* And ICX_VERBOSE Turn on verboseness during creation */ + struct _icxLuBase * (*set_luobj) (struct _xicc *p, + icmLookupFunc func, /* Functionality to set */ + icRenderingIntent intent, /* Intent to set */ + icmLookupOrder order, /* Search Order */ + int flags, /* white/black point flags */ + int no, /* Total Number of points */ + int nobw, /* Number of points to look */ + /* for white & black patches in */ + cow *points, /* Array of input points */ + icxMatrixModel *skm, /* Optional skeleton model */ + double dispLuminance, /* > 0.0 if display luminance */ + /* value and is known */ + double wpscale, /* > 0.0 if input white pt is */ + /* is to be scaled */ + double smooth, /* RSPL smoothing factor, */ + /* -ve if raw */ + double avgdev, /* Avge Dev. of points */ + icxViewCond *vc, /* Viewing Condition - only */ + /* used if pcsor == CIECAM. */ + /* or ICX_CAM_CLIP flag. */ + icxInk *ink, /* inking details */ + struct _xcal *cal, /* Optional cal Will override any */ + /* existing, not deltd with xicc. */ + int quality); /* Quality metric, 0..3 */ + + + /* Return the devices viewing conditions. */ + /* Return value 0 if it is well defined */ + /* Return value 1 if it is a guess */ + /* Return value 2 if it is not possible/appropriate */ + int (*get_viewcond)(struct _xicc *p, icxViewCond *vc); + + char err[512]; /* Error message */ + int errc; /* Error code */ +}; typedef struct _xicc xicc; + +/* ~~~~~ */ +/* Might be good to add a slow but very precise vector and closest "clip to gamut" */ +/* function for use in setting white and black points. Use this in profile. */ + +xicc *new_xicc(icc *picc); + +/* ------------------------------------------------------------------------------ */ +/* Expanded lookup object support */ +#define XLU_BASE_MEMBERS \ + /* Private: */ \ + int trace; /* Optional run time tracing flag */ \ + struct _xicc *pp; /* Pointer to XICC we're a part of */ \ + icmLuBase *plu; /* Pointer to icm Lu we are expanding */ \ + int flags; /* Flags passed to get_luobj */ \ + icmLookupFunc func; /* Function passed to get_luobj */ \ + icRenderingIntent intent; /* Effective/External Intent */ \ + /* "in" and "out" are in reference to */ \ + /* the requested lookup direction. */ \ + icColorSpaceSignature ins; /* Effective/External Clr space of input */ \ + icColorSpaceSignature outs; /* Effective/External Clr space of output */\ + icColorSpaceSignature pcs; /* Effective/External PCS */ \ + icColorSpaceSignature natis; /* Native input Clr space */ \ + icColorSpaceSignature natos; /* Native output Clr space */ \ + icColorSpaceSignature natpcs; /* Native PCS Clr space */ \ + int inputChan; /* Num of input channels */ \ + int outputChan; /* Num of output channels */ \ + double ninmin[MXDI]; /* icc Native Input color space minimum */ \ + double ninmax[MXDI]; /* icc Native Input color space maximum */ \ + double noutmin[MXDO]; /* icc Native Output color space minimum */ \ + double noutmax[MXDO]; /* icc Native Output color space maximum */ \ + double inmin[MXDI]; /* icx Effective Input color space minimum */ \ + double inmax[MXDI]; /* icx Effective Input color space maximum */ \ + double outmin[MXDO]; /* icx Effective Output color space minimum */ \ + double outmax[MXDO]; /* icx Effective Output color space maximum */ \ + icxViewCond vc; /* Viewing Condition for CIECAM97s */ \ + icxcam *cam; /* CAM conversion */ \ + \ + /* Attributes inhereted by ixcLu's */ \ + int noisluts; /* Flag - If LuLut: Don't create input (Device) shaper curves. */ \ + int noipluts; /* Flag - If LuLut: Don't create input (Device) position curves. */ \ + int nooluts; /* Flag - If LuLut: Don't create output (PCS) curves. */ \ + int nearclip; /* Flag - If clipping occurs, return the nearest solution, */ \ + int mergeclut; /* Flag - If LuLut: Merge output() and out_abs() into clut(). */ \ + int camclip; /* Flag - If LuLut: Use CIECAM for clut reverse lookup clipping */ \ + int intsep; /* Flag - If LuLut: Do internal separation for 4d device */ \ + int fastsetup; /* Flag - If LuLut: Do fast setup at cost of slower throughput */ \ + \ + /* Public: */ \ + void (*del)(struct _icxLuBase *p); \ + \ + /* Return Internal native colorspaces */ \ + void (*lutspaces) (struct _icxLuBase *p, icColorSpaceSignature *ins, int *inn, \ + icColorSpaceSignature *outs, int *outn, \ + icColorSpaceSignature *pcs); \ + \ + /* External effective colorspaces */ \ + void (*spaces) (struct _icxLuBase *p, icColorSpaceSignature *ins, int *inn, \ + icColorSpaceSignature *outs, int *outn, \ + icmLuAlgType *alg, icRenderingIntent *intt, \ + icmLookupFunc *fnc, icColorSpaceSignature *pcs); \ + \ + /* Get the Native input space and output space ranges */ \ + void (*get_native_ranges) (struct _icxLuBase *p, \ + double *inmin, double *inmax, /* Maximum range of inspace values */ \ + double *outmin, double *outmax); /* Maximum range of outspace values */ \ + \ + \ + /* Get the Effective input space and output space ranges */ \ + void (*get_ranges) (struct _icxLuBase *p, \ + double *inmin, double *inmax, /* Maximum range of inspace values */ \ + double *outmin, double *outmax); /* Maximum range of outspace values */ \ + \ + \ + /* Return the media white and black points */ \ + /* in the effective PCS colorspace. */ \ + /* (ie. these will be relative values for relative intent etc.) */ \ + void (*efv_wh_bk_points)(struct _icxLuBase *p, double *wht, double *blk, double *kblk); \ + \ + /* Translate color values through profile */ \ + /* 0 = success */ \ + /* 1 = warning: clipping occured */ \ + /* 2 = fatal: other error */ \ + \ + /* (Note that clipping is not a reliable means of detecting out of gamut in the */ \ + /* lookup(bwd) call for clut based profiles, but is for inv_lookup() calls.) */ \ + \ + int (*lookup) (struct _icxLuBase *p, double *out, double *in); \ + /* Requested conversion */ \ + int (*inv_lookup) (struct _icxLuBase *p, double *out, double *in); \ + /* Inverse conversion */ \ + \ + /* Given an xicc lookup object, returm a gamut object. */ \ + /* Note that the Effective PCS must be Lab or Jab */ \ + /* A icxLuLut type must be icmFwd or icmBwd, */ \ + /* and for icmFwd, the ink limit (if supplied) */ \ + /* will be applied. */ \ + /* Return NULL on error, check xicc errc+err for reason */ \ + gamut * (*get_gamut) (struct _icxLuBase *plu, /* xicc lookup object */ \ + double detail); /* gamut detail level, 0.0 = def */ \ + \ + /* The following two functions expose the relative colorimetric native ICC PCS */ \ + /* <--> absolute/CAM space transform, so that CAM based gamut compression */ \ + /* can be applied in creating the ICC Lut tabls in profout.c. */ \ + \ + /* Given a native ICC relative XYZ or Lab PCS value, convert in the fwd */ \ + /* direction into the nominated Effective output PCS (ie. Absolute, Jab etc.) */ \ + void (*fwd_relpcs_outpcs) (struct _icxLuBase *p, icColorSpaceSignature is, \ + double *out, double *in); \ + \ + /* Given a nominated Effective output PCS (ie. Absolute, Jab etc.), convert it */ \ + /* in the bwd direction into a native ICC relative XYZ or Lab PCS value */ \ + void (*bwd_outpcs_relpcs) (struct _icxLuBase *p, icColorSpaceSignature os, \ + double *out, double *in); \ + \ + + +/* Base xlookup object */ +struct _icxLuBase { + XLU_BASE_MEMBERS +}; typedef struct _icxLuBase icxLuBase; + +/* Monochrome Fwd & Bwd type object */ +struct _icxLuMono { + XLU_BASE_MEMBERS + + /* Overall lookups */ + int (*fwd_lookup) (struct _icxLuBase *p, double *out, double *in); + int (*bwd_lookup) (struct _icxLuBase *p, double *out, double *in); + + /* Components of Device to PCS lookup */ + int (*fwd_curve) (struct _icxLuMono *p, double *out, double *in); + int (*fwd_map) (struct _icxLuMono *p, double *out, double *in); + int (*fwd_abs) (struct _icxLuMono *p, double *out, double *in); + + /* Components of PCS to Device lookup */ + int (*bwd_abs) (struct _icxLuMono *p, double *out, double *in); + int (*bwd_map) (struct _icxLuMono *p, double *out, double *in); + int (*bwd_curve) (struct _icxLuMono *p, double *out, double *in); + +}; typedef struct _icxLuMono icxLuMono; + +/* 3D Matrix Fwd & Bwd type object */ +struct _icxLuMatrix { + XLU_BASE_MEMBERS + + /* Overall lookups */ + int (*fwd_lookup) (struct _icxLuBase *p, double *out, double *in); + int (*bwd_lookup) (struct _icxLuBase *p, double *out, double *in); + + /* Components of Device to PCS lookup */ + int (*fwd_curve) (struct _icxLuMatrix *p, double *out, double *in); + int (*fwd_matrix) (struct _icxLuMatrix *p, double *out, double *in); + int (*fwd_abs) (struct _icxLuMatrix *p, double *out, double *in); + + /* Components of PCS to Device lookup */ + int (*bwd_abs) (struct _icxLuMatrix *p, double *out, double *in); + int (*bwd_matrix) (struct _icxLuMatrix *p, double *out, double *in); + int (*bwd_curve) (struct _icxLuMatrix *p, double *out, double *in); + +}; typedef struct _icxLuMatrix icxLuMatrix; + +/* Multi-D. Lut type object */ +struct _icxLuLut { + XLU_BASE_MEMBERS + + /* private: */ + icmLut *lut; /* ICC Lut that is being used */ + rspl *inputTable[MXDI]; /* The input lookups */ + rspl *clutTable; /* The multi dimention lookup */ + rspl *cclutTable; /* Alternate multi dimention lookup in CAM space */ + rspl *outputTable[MXDO]; /* The output lookups */ + + /* Inverted RSPLs used to speed ink limit calculation */ + /* input' -> input */ + rspl *revinputTable[MXDI]; + + /* In/Out lookup flags used for rspl init. callback */ + int iol_out; /* Non-zero if output lookup */ + int iol_ch; /* Channel */ + + /* In/Out inversion support */ + double inputClipc[MXDI]; /* The input lookups clip center */ + double outputClipc[MXDO]; /* The output lookups clip center */ + + /* clut inversion support */ + double icent[MXDI]; /* center of input gamut */ + double licent[MXDI]; /* last icent value used */ + + icxClip clip; /* Clip setup information */ + + int kch; /* Black ink channel if discovered */ + /* -1 if not known or applicable */ + /* (Only set by icxLu_comp_bk_point()) */ + icxInk ink; /* inking details */ + double Lmin, Lmax; /* L min/max for inking rule */ + + /* Auxiliary parameter flags, non-zero for inputs that will be */ + /* used as auxiliary parameters the rspl input */ + /* dimensionality exceeds the output dimension (i.e. CMYK->Lab) */ + int auxm[MXDI]; + + /* Auxiliar linearization function - NULL if none */ + /* Only the used auxiliary chanels need be calculated. */ + /* ~~ not implimented yet ~~~ */ +// void (*auxlinf)(void *auxlinf_ctx, double inout[MXDI]); + + /* Opaque context for auxlin */ +// void *auxlinf_ctx; + + /* Temporary icm fwd abs XYZ LuLut used for setting up icx clut */ + icmLuBase *absxyzlu; + + /* Optional function to compute the input chanel */ + /* sum from the raw rspl input values. NULL if not used. */ + /* Use this to take account of any transformation beyond */ + /* the input space, or 6 color masquerading as 4 etc. */ + double (*limitf)(void *limitf_ctx, float in[MXDI]); /* ~~ not implimented yet */ + + /* Opaque context for limitf */ + void *limitf_ctx; + + /* Input space sum limit. Points with a limitf() over */ + /* this number will not be considered in gamut. Valid if gt 0 */ + double slimit; + + /* public: */ + + /* Note that black inking rules are always defined and provided */ + /* in dev[] and pcs[] space, even for component functions */ + /* (ie. the implementation of the inking rule deals with */ + /* the dev<->dev' and pcs<->pcs' conversions) */ + + /* Requested direction component lookup */ + int (*in_abs) (struct _icxLuLut *p, double *out, double *in); + int (*matrix) (struct _icxLuLut *p, double *out, double *in); + int (*input) (struct _icxLuLut *p, double *out, double *in); + int (*clut) (struct _icxLuLut *p, double *out, double *in); + int (*clut_aux)(struct _icxLuLut *p, double *out, double *olimit, + double *auxv, double *in); + int (*output) (struct _icxLuLut *p, double *out, double *in); + int (*out_abs) (struct _icxLuLut *p, double *out, double *in); + + /* Inverse direction component lookup (in reverse order) */ + int (*inv_out_abs) (struct _icxLuLut *p, double *out, double *in); + int (*inv_output) (struct _icxLuLut *p, double *out, double *in); + int (*inv_clut) (struct _icxLuLut *p, double *out, double *in); + int (*inv_clut_aux)(struct _icxLuLut *p, double *out, double *auxv, + double *auxr, double *auxt, double *clipd, double *in); + int (*inv_input) (struct _icxLuLut *p, double *out, double *in); + int (*inv_matrix) (struct _icxLuLut *p, double *out, double *in); + int (*inv_in_abs) (struct _icxLuLut *p, double *out, double *in); + + /* Get locus information for a clut (see xlut.c for details) */ + int (*clut_locus) (struct _icxLuLut *p, double *locus, double *out, double *in); + + /* Get various types of information about the LuLut */ + void (*get_info) (struct _icxLuLut *p, icmLut **lutp, + icmXYZNumber *pcswhtp, icmXYZNumber *whitep, + icmXYZNumber *blackp); + + /* Get the matrix contents */ + void (*get_matrix) (struct _icxLuLut *p, double m[3][3]); + +}; typedef struct _icxLuLut icxLuLut; + +/* ------------------------------------------------------------------------------ */ +/* Utility declarations and functions */ + +/* Profile Creation Suplimental Information structure */ +struct _profxinf { + icmSig manufacturer; /* Device manufacturer ICC Sig, 0 for default */ + char *deviceMfgDesc; /* Manufacturer text description, NULL for none */ + + icmSig model; /* Device model ICC Sig, 0 for default */ + char *modelDesc; /* Model text description, NULL for none */ + + icmSig creator; /* Profile creator ICC Sig, 0 for default */ + + char *profDesc; /* Text profile description, NULL for default */ + + char *copyright; /* Copyrigh text, NULL for default */ + + /* Attribute flags */ + int transparency; /* NZ for Trasparency, else Reflective */ + int matte; /* NZ for Matte, else Glossy */ + int negative; /* NZ for Negative, else Positive */ + int blackandwhite; /* NZ for BlackAndWhite, else Color */ + + /* Default intent */ + icRenderingIntent default_ri; /* Default rendering intent */ + + /* Other stuff ICC ?? */ + +}; typedef struct _profxinf profxinf; + +/* Set an icc's Lut tables, and take care of auxiliary continuity problems. */ +/* Only useful if there are auxiliary device output chanels to be set. */ +int icxLut_set_tables_auxfix( +icmLut *p, /* Pointer to icmLut object */ +void *cbctx, /* Opaque callback context pointer value */ +icColorSpaceSignature insig, /* Input color space */ +icColorSpaceSignature outsig, /* Output color space */ +void (*infunc)(void *cbctx, double *out, double *in), + /* Input transfer function, inspace->inspace' (NULL = default) */ +double *inmin, double *inmax, /* Maximum range of inspace' values */ + /* (NULL = default) */ +void (*clutfunc)(void *cbntx, double *out, double *aux, double *auxr, double *pcs, double *in), + /* inspace' -> outspace' transfer function, also */ + /* return the internal target PCS and the (packed) auxiliary locus */ + /* range as [min0, max0, min1, max1...], and the actual auxiliary */ + /* target used. */ +void (*clutpcsfunc)(void *cbntx, double *out, double *aux, double *pcs), + /* Internal PCS + actual aux_target -> outspace' transfer function */ +void (*clutipcsfunc)(void *cbntx, double *pcs, double *olimit, double *auxv, double *in), + /* outspace' -> Internal PCS + auxv check function */ +double *clutmin, double *clutmax, /* Maximum range of outspace' values */ + /* (NULL = default) */ +void (*outfunc)(void *cbntx, double *out, double *in) + /* Output transfer function, outspace'->outspace (NULL = deflt) */ +); + + +/* Return an enumerated viewing condition */ +/* Return enumeration if OK, -999 if there is no such enumeration. */ +/* xicc may be NULL if just the description is wanted, */ +/* or an explicit white point is provided. */ +int xicc_enum_viewcond( +xicc *p, /* Expanded profile to get white point (May be NULL if desc NZ) */ +icxViewCond *vc, /* Viewing parameters to return, May be NULL if desc is nz */ +int no, /* Enumeration to return, -1 for default, -2 for none */ +char *as, /* String alias to number, NULL if none */ +int desc, /* NZ - Just return a description of this enumeration in vc */ +double *wp /* Provide white point if xicc is NULL */ +); + +/* Debug: dump a Viewing Condition to standard out */ +void xicc_dump_viewcond(icxViewCond *vc); + +/* Debug: dump an Inking setup to standard out */ +void xicc_dump_inking(icxInk *ik); + +/* Return enumerated gamut mapping intents */ +/* Return 0 if OK, 1 if there is no such enumeration. */ +/* Note the following fixed numbers meanings: */ +#define icxNoGMIntent -1 +#define icxDefaultGMIntent -2 +#define icxAbsoluteGMIntent -3 +#define icxRelativeGMIntent -4 +#define icxPerceptualGMIntent -5 +#define icxSaturationGMIntent -6 +#define icxIllegalGMIntent -999 +int xicc_enum_gmapintent(icxGMappingIntent *gmi, int no, char *as); +void xicc_dump_gmi(icxGMappingIntent *gmi); + +/* - - - - - - - - - - */ +/* Utility functions: */ + +/* Given an open icc profile, */ +/* guess which channel is the black. */ +/* Return -1 if there is no black channel or it can't be guessed */ +int icxGuessBlackChan(icc *p); + +/* Given an icc profile, try and create an xcal */ +/* Return NULL on error or no cal */ +struct _xcal *xiccReadCalTag(icc *p); + +/* A callback that uses an xcal, that can be used with icc get_tac */ +void xiccCalCallback(void *cntx, double *out, double *in); + +/* Given an xicc icc profile, estmate the total ink limit and black ink limit. */ +void icxGetLimits(xicc *p, double *tlimit, double *klimit); + +/* Using the above function, set default total and black ink values */ +void icxDefaultLimits(xicc *p, double *tlout, double tlin, double *klout, double klin); + +/* Given a calibrated total ink limit and an xcal, return the */ +/* equivalent underlying (pre-calibration) total ink limit. */ +/* This is the maximum equivalent, that makes sure that */ +/* the calibrated limit is met or exceeded. */ +double icxMaxUnderlyingLimit(struct _xcal *cal, double ilimit); + +/* - - - - - - - - - - */ + +/* Utility function - compute the clip vector direction. */ +/* return NULL if vector clip isn't used. */ +double *icxClipVector( +icxClip *p, /* Clipping setup information */ +double *in, /* Target point */ +double *cdirv /* Space for returned clip vector */ +); + +/* - - - - - - - - - - */ + +/* CIE XYZ to perceptual Lab with partial derivatives. */ +void icxdXYZ2Lab(icmXYZNumber *w, double *out, double dout[3][3], double *in); + +/* Return the normal Delta E squared, given two Lab values, */ +/* including partial derivatives. */ +double icxdLabDEsq(double dout[2][3], double *Lab0, double *Lab1); + +/* Return the CIE94 Delta E color difference measure, squared */ +/* including partial derivatives. */ +double icxdCIE94sq(double dout[2][3], double Lab0[3], double Lab1[3]); + +/* Return the normal Delta E given two Lab values, */ +/* including partial derivatives. */ +double icxdLabDE(double dout[2][3], double *Lab0, double *Lab1); + +/* Return the CIE94 Delta E color difference measure */ +/* including partial derivatives. */ +double icxdCIE94(double dout[2][3], double Lab0[3], double Lab1[3]); + +/* - - - - - - - - - - */ +/* Power like function, based on Graphics Gems adjustment curve. */ +/* Avoids "toe" problem of pure power. */ +/* Adjusted so that "power" 2 and 0.5 agree with real power at 0.5 */ +double icx_powlike(double vv, double pp); + +/* Compute the necessary aproximate power, to transform */ +/* the given value from src to dst. They are assumed to be */ +/* in the range 0.0 .. 1.0 */ +double icx_powlike_needed(double src, double dst); + +/* - - - - - - - - - - */ + +/* Transfer function */ +double icxTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +); + +/* Inverse Transfer function */ +double icxInvTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +); + +/* Transfer function with scaling */ +double icxSTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +); + +/* Inverse Transfer function with scaling */ +double icxInvSTransFunc( +double *v, /* Pointer to first parameter */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +); + +/* Transfer function with partial derivative */ +/* with respect to the parameters. */ +double icxdpTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter [luord] */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +); + +/* Transfer function with scaling and */ +/* partial derivative with respect to the parameters. */ +double icxdpSTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter [luord] */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +); + +/* Transfer function with partial derivative */ +/* with respect to the input value. */ +double icxdiTransFunc( +double *v, /* Pointer to first parameter */ +double *pdv, /* Return derivative wrt source value [1] */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +); + +/* Transfer function with scaling and */ +/* partial derivative with respect to the input value. */ +double icxdiSTransFunc( +double *v, /* Pointer to first parameter */ +double *pdv, /* Return derivative wrt source value [1] */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +); + +/* Transfer function with partial derivative */ +/* with respect to the parameters and the input value. */ +double icxdpdiTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter [luord] */ +double *pdin, /* Return derivative wrt source value [1] */ +int luord, /* Number of parameters */ +double vv /* Source of value */ +); + +/* Transfer function with scaling and */ +/* partial derivative with respect to the */ +/* parameters and the input value. */ +double icxdpdiSTransFunc( +double *v, /* Pointer to first parameter */ +double *dv, /* Return derivative wrt each parameter [luord] */ +double *pdin, /* Return derivative wrt source value [1] */ +int luord, /* Number of parameters */ +double vv, /* Source of value */ +double min, /* Scale values */ +double max +); + +/* Should add/move the spectro/moncurve stuff in here, */ +/* since it has offset and scaling. */ + +/* - - - - - - - - - - */ +/* Multi-plane interpolation - uses base + di slope values. */ +/* Parameters are assumed to be fdi groups of di + 1 parameters. */ +void icxPlaneInterp( +double *v, /* Pointer to first parameter [fdi * (di + 1)] */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +); + +/* Matrix cube interpolation. with partial derivative */ +/* with respect to the input and parameters. */ +void icxdpdiPlaneInterp( +double *v, /* Pointer to first parameter value [fdi * (di + 1)] */ +double *dv, /* Return [1 + di] deriv. wrt each parameter v */ +double *din, /* Return [fdi * di] deriv. wrt each input value */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +); + +/* - - - - - - - - - - */ + +/* Matrix cube interpolation - interpolate between 2^di output corner values. */ +/* Parameters are assumed to be fdi groups of 2^di parameters. */ +void icxCubeInterp( +double *v, /* Pointer to first parameter */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +); + +/* Matrix cube interpolation. with partial derivative */ +/* with respect to the input and parameters. */ +void icxdpdiCubeInterp( +double *v, /* Pointer to first parameter value */ +double *dv, /* Return [fdi * 2^di] deriv wrt each parameter v */ +double *din, /* Return [fdi * di] deriv wrt each input value */ +int fdi, /* Number of output channels */ +int di, /* Number of input channels */ +double *out, /* Resulting fdi values */ +double *in /* Input di values */ +); + +/* - - - - - - - - - - */ + +/* 3x3 matrix multiplication, with the matrix in a 1D array */ +/* with respect to the input and parameters. */ +void icxMulBy3x3Parm( + double out[3], /* Return input multiplied by matrix */ + double mat[9], /* Matrix organised in [slow][fast] order */ + double in[3] /* Input values */ +); + +/* 3x3 matrix multiplication, with partial derivatives */ +/* with respect to the input and parameters. */ +void icxdpdiMulBy3x3Parm( + double out[3], /* Return input multiplied by matrix */ + double dv[3][9], /* Return deriv for each [output] with respect to [param] */ + double din[3][3], /* Return deriv for each [output] with respect to [input] */ + double mat[9], /* Matrix organised in [slow][fast] order */ + double in[3] /* Input values */ +); + +/* - - - - - - - - - - */ + +#include "xcal.h" + +#endif /* XICC_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xicclu.c b/xicc/xicclu.c new file mode 100644 index 0000000..aa4a452 --- /dev/null +++ b/xicc/xicclu.c @@ -0,0 +1,1149 @@ + +/* + * xicc lookup/test utility + * + * This program is the analog of icclu, but allows reverse lookup + * of transforms by making use of xicc interpolation code. + * (Based on the old xfmlu.c) + * + * Author: Graeme W. Gill + * Date: 8/7/00 + * Version: 1.00 + * + * Copyright 1999, 2000 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* TTBD: + + Can -ff and -fif be made to work with device link files ? + + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <fcntl.h> +#include <string.h> +#include <math.h> +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "plot.h" +#include "xicc.h" + +#undef SPTEST /* Test rspl gamut surface code */ + +#define USE_NEARCLIP /* Our usual expectation */ +#define XRES 128 /* Plotting resolution */ + +void usage(char *diag) { + int i; + fprintf(stderr,"Translate colors through an xicc, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: xicclu [-options] profile\n"); + if (diag != NULL) + fprintf(stderr,"Diagnostic: %s\n",diag); + fprintf(stderr," -v level Verbosity level 0 - 2 (default = 1)\n"); + fprintf(stderr," -g Plot slice instead of looking colors up. (Default white to black)\n"); + fprintf(stderr," -G s:L:a:b Override plot slice start with Lab or Jab co-ordinate \n"); + fprintf(stderr," -G e:L:a:b Override plot slice end with Lab or Jab co-ordinate \n"); + fprintf(stderr," -f function f = forward, b = backwards, g = gamut, p = preview\n"); + fprintf(stderr," if = inverted forward, ib = inverted backwards\n"); + fprintf(stderr," -i intent a = absolute, r = relative colorimetric\n"); + fprintf(stderr," p = perceptual, s = saturation\n"); +// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n"); + fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n"); + fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n"); + fprintf(stderr," -p oride x = XYZ_PCS, X = XYZ * 100, l = Lab_PCS, L = LCh, y = Yxy\n"); + fprintf(stderr," j = %s Appearance Jab, J = %s Appearance JCh\n",icxcam_description(cam_default),icxcam_description(cam_default)); + fprintf(stderr," -s scale Scale device range 0.0 - scale rather than 0.0 - 1.0\n"); + fprintf(stderr," -k [zhxrlv] Black value target: z = zero K,\n"); + fprintf(stderr," h = 0.5 K, x = max K, r = ramp K (def.)\n"); + fprintf(stderr," l = extra PCS input is portion of K locus\n"); + fprintf(stderr," v = extra PCS input is K target value\n"); + fprintf(stderr," -k p stle stpo enpo enle shape\n"); + fprintf(stderr," stle: K level at White 0.0 - 1.0\n"); + fprintf(stderr," stpo: start point of transition Wh 0.0 - Bk 1.0\n"); + fprintf(stderr," enpo: End point of transition Wh 0.0 - Bk 1.0\n"); + fprintf(stderr," enle: K level at Black 0.0 - 1.0\n"); + fprintf(stderr," shape: 1.0 = straight, 0.0-1.0 concave, 1.0-2.0 convex\n"); + fprintf(stderr," -k q stle0 stpo0 enpo0 enle0 shape0 stle2 stpo2 enpo2 enle2 shape2\n"); + fprintf(stderr," Transfer extra PCS input to dual curve limits\n"); + fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n"); + fprintf(stderr," -l tlimit set total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," -a show actual target values if clipped\n"); + fprintf(stderr," -u warn if output PCS is outside the spectrum locus\n"); + fprintf(stderr," -m merge output processing into clut\n"); + fprintf(stderr," -b use CAM Jab for clipping\n"); +// fprintf(stderr," -S Use internal optimised separation for inverse 4d [NOT IMPLEMENTED]\n"); + +#ifdef SPTEST + fprintf(stderr," -w special gamut surface test PCS space\n"); + fprintf(stderr," -W special gamut surface test, PCS' space\n"); +#endif + fprintf(stderr," -c viewcond set viewing conditions for CIECAM97s,\n"); + fprintf(stderr," either an enumerated choice, or a parameter:value changes\n"); + for (i = 0; ; i++) { + icxViewCond vc; + if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999) + break; + + fprintf(stderr," %s\n",vc.desc); + } + + fprintf(stderr," s:surround n = auto, a = average, m = dim, d = dark,\n"); + fprintf(stderr," c = transparency (default average)\n"); + fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white, Abs: D50)\n"); + fprintf(stderr," w:x:y Adapted white point as x, y\n"); + fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n"); + fprintf(stderr," b:background Background %% of image luminance (default 20)\n"); + fprintf(stderr," l:scenewhite Scene white in cd.m^2 if surround = auto (default 250)\n"); + fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n"); + fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white, Abs: D50)\n"); + fprintf(stderr," f:x:y Flare color as x, y\n"); + fprintf(stderr,"\n"); + fprintf(stderr," The colors to be translated should be fed into standard in,\n"); + fprintf(stderr," one input color per line, white space separated.\n"); + fprintf(stderr," A line starting with a # will be ignored.\n"); + fprintf(stderr," A line not starting with a number will terminate the program.\n"); + fprintf(stderr," Use -v0 for just output colors.\n"); + exit(1); +} + +#ifdef SPTEST + +#pragma message("!!!!!!!!!!!! Experimental gamut boundary test !!!!!!!!!") + +/* Output curve function */ +void spoutf(void *cbntx, double *out, double *in) { + icxLuLut *clu = (icxLuLut *)cbntx; + + clu->output(clu, out, in); + clu->out_abs(clu, out, out); +} + +/* Inverse output curve function */ +void spioutf(void *cbntx, double *out, double *in) { + icxLuLut *clu = (icxLuLut *)cbntx; + + clu->inv_out_abs(clu, out, in); + clu->inv_output(clu, out, out); +} + + +#endif /* SPTEST */ + +int +main(int argc, char *argv[]) { + int fa,nfa; /* argument we're looking at */ + char prof_name[MAXNAMEL+1]; + icmFile *fp; + icc *icco; + xicc *xicco; + int doplot = 0; /* Do grey axis plot */ + double pstart[3] = { -1000.0 }; /* Plot Lab/Jab PCS start point = white */ + double pend[3] = { -1000.0 }; /* Plot Lab/Jab PCS end point = black */ + icxInk ink; /* Ink parameters */ + double tlimit = -1.0; /* Total ink limit */ + double klimit = -1.0; /* Black ink limit */ + int intsep = 0; + icxViewCond vc; /* Viewing Condition for CIECAM97s */ + int vc_e = -1; /* Enumerated viewing condition */ + int vc_s = -1; /* Surround override */ + double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */ + double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */ + double vc_a = -1.0; /* Adapted luminance */ + double vc_b = -1.0; /* Background % overid */ + double vc_l = -1.0; /* Scene luminance override */ + double vc_f = -1.0; /* Flare % overid */ + double vc_fXYZ[3] = {-1.0, -1.0, -1.0}; /* Flare color override in XYZ */ + double vc_fxy[2] = {-1.0, -1.0}; /* Flare color override in x,y */ + int verb = 1; + int actual = 0; + int slocwarn = 0; + int merge = 0; + int camclip = 0; + int repYxy = 0; /* Report Yxy */ + int repJCh = 0; /* Report JCh */ + int repLCh = 0; /* Report LCh */ + int repXYZ100 = 0; /* Scale XYZ by 10 */ + double scale = 0.0; /* Device value scale factor */ + int rv = 0; + char buf[200]; + double uin[MAX_CHAN], in[MAX_CHAN], out[MAX_CHAN], uout[MAX_CHAN]; + + icxLuBase *luo, *aluo = NULL; + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + int inn, outn; /* Number of components */ + icmLuAlgType alg; /* Type of lookup algorithm */ + + /* Lookup parameters */ + icmLookupFunc func = icmFwd; /* Default */ + icRenderingIntent intent = icmDefaultIntent; /* Default */ + icColorSpaceSignature pcsor = icmSigDefaultData; /* Default */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + int inking = 3; /* Default is ramp */ + int locus = 0; /* Default is K value */ + /* K curve params */ + double Kstle = 0.0, Kstpo = 0.0, Kenle = 0.0, Kenpo = 0.0, Kshap = 0.0; + /* K curve params (max) */ + double Kstle1 = 0.0, Kstpo1 = 0.0, Kenle1 = 0.0, Kenpo1 = 0.0, Kshap1 = 0.0; + int invert = 0; + +#ifdef SPTEST + int sptest = 0; + warning("xicc/xicclu.c !!!! special rspl gamut sest code is compiled in !!!!\n"); +#endif + + error_program = argv[0]; + + if (argc < 2) + usage("Too few arguments"); + + /* Process the arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage("Requested usage"); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + fa = nfa; + if (na == NULL) + verb = 2; + else { + if (na[0] == '0') + verb = 0; + else if (na[0] == '1') + verb = 1; + else if (na[0] == '2') + verb = 2; + else + usage("Illegal verbosity level"); + } + } + + /* Locus plot */ + else if (argv[fa][1] == 'g') { + doplot = 1; + } + /* Plot start or end override */ + else if (argv[fa][1] == 'G') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -G"); + if (na[0] == 's' || na[0] == 'S') { + if (sscanf(na+1,":%lf:%lf:%lf",&pstart[0],&pstart[1],&pstart[2]) != 3) + usage("Unrecognised parameters after -Gs"); + } else if (na[0] == 'e' || na[0] == 'E') { + if (sscanf(na+1,":%lf:%lf:%lf",&pend[0],&pend[1],&pend[2]) != 3) + usage("Unrecognised parameters after -Ge"); + } else + usage("Unrecognised parameters after -G"); + } + /* Actual target values */ + else if (argv[fa][1] == 'a') { + actual = 1; + } + /* Warn if output is outside the spectrum locus */ + else if (argv[fa][1] == 'u') { + slocwarn = 1; + } + /* Merge output */ + else if (argv[fa][1] == 'm') { + merge = 1; + } + /* Use CAM Jab for clipping on reverse lookup */ + else if (argv[fa][1] == 'b') { + camclip = 1; + } + /* Use optimised internal separation */ + else if (argv[fa][1] == 'S') { + intsep = 1; + } + /* Device scale */ + else if (argv[fa][1] == 's') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -s"); + scale = atof(na); + if (scale <= 0.0) usage("Illegal scale value"); + } + /* function */ + else if (argv[fa][1] == 'f') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -f"); + switch (na[0]) { + case 'f': + case 'F': + func = icmFwd; + break; + case 'b': + case 'B': + func = icmBwd; + break; + case 'g': + case 'G': + func = icmGamut; + break; + case 'p': + case 'P': + func = icmPreview; + break; + case 'i': + case 'I': + invert = 1; + if (na[1] == 'f' || na[1] == 'F') + func = icmFwd; + else if (na[1] == 'b' || na[1] == 'B') + func = icmBwd; + else + usage("Unknown parameter after flag -fi"); + break; + default: + usage("Unknown parameter after flag -f"); + } + } + + /* Intent */ + else if (argv[fa][1] == 'i') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -i"); + switch (na[0]) { + case 'p': + intent = icPerceptual; + break; + case 'r': + intent = icRelativeColorimetric; + break; + case 's': + intent = icSaturation; + break; + case 'a': + intent = icAbsoluteColorimetric; + break; + /* Argyll special intents to check spaces underlying */ + /* icxPerceptualAppearance & icxSaturationAppearance */ + case 'P': + intent = icmAbsolutePerceptual; + break; + case 'S': + intent = icmAbsoluteSaturation; + break; + default: + usage("Unknown parameter after flag -i"); + } + } + + /* PCS override */ + else if (argv[fa][1] == 'p') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -i"); + switch (na[0]) { + case 'x': + pcsor = icSigXYZData; + repYxy = 0; + repLCh = 0; + repJCh = 0; + repXYZ100 = 0; + break; + case 'X': + pcsor = icSigXYZData; + repYxy = 0; + repLCh = 0; + repJCh = 0; + repXYZ100 = 1; + break; + case 'l': + pcsor = icSigLabData; + repYxy = 0; + repLCh = 0; + repJCh = 0; + break; + case 'L': + pcsor = icSigLabData; + repYxy = 0; + repLCh = 1; + repJCh = 0; + break; + case 'y': + case 'Y': + pcsor = icSigXYZData; + repYxy = 1; + repLCh = 0; + repJCh = 0; + break; + case 'j': + pcsor = icxSigJabData; + repYxy = 0; + repLCh = 0; + repJCh = 0; + break; + case 'J': + pcsor = icxSigJabData; + repYxy = 0; + repLCh = 0; + repJCh = 1; + break; + default: + usage("Unknown parameter after flag -i"); + } + } + + /* Search order */ + else if (argv[fa][1] == 'o') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -o"); + switch (na[0]) { + case 'n': + case 'N': + order = icmLuOrdNorm; + break; + case 'r': + case 'R': + order = icmLuOrdRev; + break; + default: + usage("Unknown parameter after flag -o"); + } + } + + /* Inking rule */ + else if (argv[fa][1] == 'k') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -k"); + if (argv[fa][1] == 'k') + locus = 0; /* K value target */ + else + locus = 1; /* K locus target */ + switch (na[0]) { + case 'z': + case 'Z': + inking = 0; /* Use minimum k */ + break; + case 'h': + case 'H': + inking = 1; /* Use 0.5 k */ + break; + case 'x': + case 'X': + inking = 2; /* Use maximum k */ + break; + case 'r': + case 'R': + inking = 3; /* Use ramping K */ + break; + case 'l': + case 'L': + inking = 4; /* Extra param is locus */ + break; + case 'v': + case 'V': + inking = 5; /* Extra param is K target */ + break; + case 'p': + case 'P': + case 'q': + case 'Q': + inking = 6; /* Use curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + Kshap = atof(argv[fa]); + + if (na[0] == 'q' || na[0] == 'Q') { + inking = 7; /* Use transfer to dual curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + Kstle1 = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + Kstpo1 = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + Kenpo1 = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + Kenle1 = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + Kshap1 = atof(argv[fa]); + + } + break; + default: + usage("Unknown parameter after flag -k"); + } + } + + else if (argv[fa][1] == 'l') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -l"); + tlimit = atoi(na)/100.0; + } + + else if (argv[fa][1] == 'L') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -L"); + klimit = atoi(na)/100.0; + } + +#ifdef SPTEST + else if (argv[fa][1] == 'w') { + sptest = 1; + } + else if (argv[fa][1] == 'W') { + sptest = 2; + } +#endif + /* Viewing conditions */ + else if (argv[fa][1] == 'c') { + fa = nfa; + if (na == NULL) usage("No parameter after flag -c"); +#ifdef NEVER + if (na[0] >= '0' && na[0] <= '9') { + vc_e = atoi(na); + } else +#endif + if (na[1] != ':') { + if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Urecognised Enumerated Viewing conditions"); + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage("Unrecognised parameters after -cs"); + if (na[2] == 'n' || na[2] == 'N') { + vc_s = vc_none; /* Automatic using Lv */ + } else if (na[2] == 'a' || na[2] == 'A') { + vc_s = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc_s = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc_s = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc_s = vc_cut_sheet; + } else + usage("Unrecognised parameters after -cs:"); + } else if (na[0] == 'w' || na[0] == 'W') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_wxy[0] = x; vc_wxy[1] = y; + } else + usage("Unrecognised parameters after -cw"); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage("Unrecognised parameters after -ca"); + vc_a = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage("Unrecognised parameters after -cb"); + vc_b = atof(na+2); + } else if (na[0] == 'l' || na[0] == 'L') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]l) missing ':'"); + vc_l = atof(na+2); + } else if (na[0] == 'f' || na[0] == 'F') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc_fXYZ[0] = x; vc_fXYZ[1] = y; vc_fXYZ[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc_fxy[0] = x; vc_fxy[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc_f = x; + } else + usage("Unrecognised parameters after -cf"); + } else + usage("Unrecognised parameters after -c"); + } + + else + usage("Unknown flag"); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Expecting profile file name"); + strncpy(prof_name,argv[fa],MAXNAMEL); prof_name[MAXNAMEL] = '\000'; + + if (doplot) { + + /* Force PCS to be Lab or Jab */ + repJCh = 0; + repLCh = 0; + if (pcsor != icxSigJabData) + pcsor = icSigLabData; + + if ((invert == 0 && func != icmBwd) + || (invert != 0 && func != icmFwd)) + error("Must use -fb or -fif for grey axis plot"); + } + + /* Open up the profile for reading */ + if ((fp = new_icmFileStd_name(prof_name,"r")) == NULL) + error ("Can't open file '%s'",prof_name); + + if ((icco = new_icc()) == NULL) + error ("Creation of ICC object failed"); + + if ((rv = icco->read(icco,fp,0)) != 0) + error ("%d, %s",rv,icco->err); + + if (doplot) { + if (icco->header->deviceClass != icSigInputClass + && icco->header->deviceClass != icSigDisplayClass + && icco->header->deviceClass != icSigOutputClass) + error("Profile must be a device profile to plot neutral axis"); + } + + if (verb > 1) { + icmFile *op; + if ((op = new_icmFileStd_fp(stdout)) == NULL) + error ("Can't open stdout"); + icco->header->dump(icco->header, op, 1); + op->del(op); + } + + /* Wrap with an expanded icc */ + if ((xicco = new_xicc(icco)) == NULL) + error ("Creation of xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(xicco, &ink.tlimit, tlimit, &ink.klimit, klimit); + + if (verb > 1) { + if (ink.tlimit >= 0.0) + printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); + if (ink.klimit >= 0.0) + printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); + } + + ink.KonlyLmin = 0; /* Use normal black as locus Lmin */ + + ink.c.Ksmth = ICXINKDEFSMTH; /* Default curve smoothing */ + ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + ink.x.Ksmth = ICXINKDEFSMTH; + ink.x.Kskew = ICXINKDEFSKEW; + + if (inking == 0) { /* Use minimum */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; /* Locus or value target */ + ink.c.Kstle = 0.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 0.0; + ink.c.Kshap = 1.0; + } else if (inking == 1) { /* Use 0.5 */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; /* Locus or value target */ + ink.c.Kstle = 0.5; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 0.5; + ink.c.Kshap = 1.0; + } else if (inking == 2) { /* Use maximum */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; /* Locus or value target */ + ink.c.Kstle = 1.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 1.0; + ink.c.Kshap = 1.0; + } else if (inking == 3) { /* Use ramp */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; /* Locus or value target */ + ink.c.Kstle = 0.0; + ink.c.Kstpo = 0.0; + ink.c.Kenpo = 1.0; + ink.c.Kenle = 1.0; + ink.c.Kshap = 1.0; + } else if (inking == 4) { /* Use locus */ + ink.k_rule = icxKlocus; + } else if (inking == 5) { /* Use K target */ + ink.k_rule = icxKvalue; + } else if (inking == 6) { /* Use specified curve */ + ink.k_rule = locus ? icxKluma5 : icxKluma5k; /* Locus or value target */ + ink.c.Kstle = Kstle; + ink.c.Kstpo = Kstpo; + ink.c.Kenpo = Kenpo; + ink.c.Kenle = Kenle; + ink.c.Kshap = Kshap; + } else { /* Use dual curves */ + ink.k_rule = locus ? icxKl5l : icxKl5lk; /* Locus or value target */ + ink.c.Kstle = Kstle; + ink.c.Kstpo = Kstpo; + ink.c.Kenpo = Kenpo; + ink.c.Kenle = Kenle; + ink.c.Kshap = Kshap; + ink.x.Kstle = Kstle1; + ink.x.Kstpo = Kstpo1; + ink.x.Kenpo = Kenpo1; + ink.x.Kenle = Kenle1; + ink.x.Kshap = Kshap1; + } + + /* Setup the viewing conditions */ + if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + +//xicc_dump_viewcond(&vc); + if (vc_e != -1) + if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -999) + error ("%d, %s",xicco->errc, xicco->err); + if (vc_s >= 0) + vc.Ev = vc_s; + if (vc_wXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; + vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; + } + if (vc_wxy[0] >= 0.0) { + double x = vc_wxy[0]; + double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Wxyz[0] = x/y * vc.Wxyz[1]; + vc.Wxyz[2] = z/y * vc.Wxyz[1]; + } + if (vc_a >= 0.0) + vc.La = vc_a; + if (vc_b >= 0.0) + vc.Yb = vc_b/100.0; + if (vc_l >= 0.0) + vc.Lv = vc_l; + if (vc_f >= 0.0) + vc.Yf = vc_f/100.0; + if (vc_fXYZ[1] > 0.0) { + /* Normalise it to current media white */ + vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; + vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; + } + if (vc_fxy[0] >= 0.0) { + double x = vc_fxy[0]; + double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc.Fxyz[0] = x/y * vc.Fxyz[1]; + vc.Fxyz[2] = z/y * vc.Fxyz[1]; + } +//xicc_dump_viewcond(&vc); + + /* Get a expanded color conversion object */ + if ((luo = xicco->get_luobj(xicco, 0 +#ifdef USE_NEARCLIP + | ICX_CLIP_NEAREST +#endif + | (intsep ? ICX_INT_SEPARATE : 0) + | (merge ? ICX_MERGE_CLUT : 0) + | (camclip ? ICX_CAM_CLIP : 0) + | ICX_FAST_SETUP + , func, intent, pcsor, order, &vc, &ink)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + + /* Get details of conversion (Arguments may be NULL if info not needed) */ + if (invert) + luo->spaces(luo, &outs, &outn, &ins, &inn, &alg, NULL, NULL, NULL); + else + luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL); + + /* If we can do check on clipped values */ + if (actual != 0) { + if (invert == 0) { + if (func == icmFwd || func == icmBwd) { + if ((aluo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST, + func == icmFwd ? icmBwd : icmFwd, intent, pcsor, order, &vc, &ink)) == NULL) + error ("%d, %s",xicco->errc, xicco->err); + } + } else { + aluo = luo; /* We can use the same one */ + } + } + + /* More information */ + if (verb > 1) { + int j; + double inmin[MAX_CHAN], inmax[MAX_CHAN]; + double outmin[MAX_CHAN], outmax[MAX_CHAN]; + + luo->get_native_ranges(luo, inmin, inmax, outmin,outmax); + printf("Internal input value range: "); + for (j = 0; j < inn; j++) { + if (j > 0) + fprintf(stdout," %f..%f",inmin[j], inmax[j]); + else + fprintf(stdout,"%f..%f",inmin[j], inmax[j]); + } + printf("\nInternal output value range: "); + for (j = 0; j < outn; j++) { + if (j > 0) + fprintf(stdout," %f..%f",outmin[j], outmax[j]); + else + fprintf(stdout,"%f..%f",outmin[j], outmax[j]); + } + + luo->get_ranges(luo, inmin, inmax, outmin,outmax); + printf("\nInput value range: "); + for (j = 0; j < inn; j++) { + if (j > 0) + fprintf(stdout," %f..%f",inmin[j], inmax[j]); + else + fprintf(stdout,"%f..%f",inmin[j], inmax[j]); + } + printf("\nOutput value range: "); + for (j = 0; j < outn; j++) { + if (j > 0) + fprintf(stdout," %f..%f",outmin[j], outmax[j]); + else + fprintf(stdout,"%f..%f",outmin[j], outmax[j]); + } + printf("\n"); + } + + if (repYxy) { /* report Yxy rather than XYZ */ + if (ins == icSigXYZData) + ins = icSigYxyData; + if (outs == icSigXYZData) + outs = icSigYxyData; + } + + if (repJCh) { /* report JCh rather than Jab */ + if (ins == icxSigJabData) + ins = icxSigJChData; + if (outs == icxSigJabData) + outs = icxSigJChData; + } + if (repLCh) { /* report LCh rather than Lab */ + if (ins == icSigLabData) + ins = icxSigLChData; + if (outs == icSigLabData) + outs = icxSigLChData; + } + +#ifdef SPTEST + if (sptest) { + icxLuLut *clu; + double cent[3] = { 50.0, 0.0, 0.0 }; + + if (luo->plu->ttype != icmLutType) + error("Special test only works on CLUT profiles"); + + clu = (icxLuLut *)luo; + + clu->clutTable->comp_gamut(clu->clutTable, cent, NULL, spoutf, clu, spioutf, clu); + rspl_gam_plot(clu->clutTable, "sp_test.wrl", sptest-1); + exit(0); + } +#endif + + if (doplot) { + int i, j; + double xx[XRES]; + double yy[6][XRES]; + double start[3], end[3]; + + /* Plot from white to black by default */ + luo->efv_wh_bk_points(luo, start, end, NULL); + + if (pstart[0] == -1000.0) + icmCpy3(pstart, start); + + if (pend[0] == -1000.0) + icmCpy3(pend, end); + + if (verb) { + printf("Plotting from white %f %f %f to black %f %f %f\n", + pstart[0], pstart[1], pstart[2], pend[0], pend[1], pend[2]); + } + for (i = 0; i < XRES; i++) { + double ival = (double)i/(XRES-1.0); + + /* Input is always Jab or Lab */ + in[0] = ival * pend[0] + (1.0 - ival) * pstart[0]; + in[1] = ival * pend[1] + (1.0 - ival) * pstart[1]; + in[2] = ival * pend[2] + (1.0 - ival) * pstart[2]; +//in[1] = in[2] = 0.0; + + /* Do the conversion */ + if (invert) { + if ((rv = luo->inv_lookup(luo, out, in)) > 1) + error ("%d, %s",xicco->errc,xicco->err); +//printf("~1 %f: %f %f %f -> %f %f %f %f\n", ival, in[0], in[1], in[2], out[0], out[1], out[2], out[3]); + } else { + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",xicco->errc,xicco->err); + } + + xx[i] = 100.0 * ival; + for (j = 0; j < outn; j++) + yy[j][i] = 100.0 * out[j]; + } +//fflush(stdout); + + /* plot order: Black Red Green Blue Yellow Purple */ + if (outs == icSigRgbData) { + do_plot6(xx, NULL, yy[0], yy[1], yy[2], NULL, NULL, XRES); + + } else if (outs == icSigCmykData) { + do_plot6(xx, yy[3], yy[1], NULL, yy[0], yy[2], NULL, XRES); + + } else { + + switch(outn) { + case 1: + do_plot6(xx, yy[0], NULL, NULL, NULL, NULL, NULL, XRES); + break; + case 2: + do_plot6(xx, yy[0], yy[1], NULL, NULL, NULL, NULL, XRES); + break; + case 3: + do_plot6(xx, yy[0], yy[1], yy[2], NULL, NULL, NULL, XRES); + break; + case 4: + do_plot6(xx, yy[0], yy[1], yy[2], yy[3], NULL, NULL, XRES); + break; + case 5: + do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], NULL, XRES); + break; + case 6: + do_plot6(xx, yy[0], yy[1], yy[2], yy[3], yy[4], yy[5], XRES); + break; + } + } + + + } else { + + if (slocwarn && outs != icSigXYZData + && outs != icSigYxyData + && outs != icSigLabData + && outs != icxSigLChData) { + error("Can't warn if outside spectrum locus unless XYZ like space"); + } + + /* Process colors to translate */ + for (;;) { + int i,j; + char *bp, *nbp; + int outsloc = 0; + + /* Read in the next line */ + if (fgets(buf, 200, stdin) == NULL) + break; + if (buf[0] == '#') { + if (verb > 0) + fprintf(stdout,"%s\n",buf); + continue; + } + /* For each input number */ + for (bp = buf-1, nbp = buf, i = 0; i < MAX_CHAN; i++) { + bp = nbp; + uout[i] = out[i] = in[i] = uin[i] = strtod(bp, &nbp); + if (nbp == bp) + break; /* Failed */ + } + if (i == 0) + break; + + /* If device data and scale */ + if(scale > 0.0 + && ins != icxSigJabData + && ins != icxSigJChData + && ins != icSigXYZData + && ins != icSigLabData + && ins != icxSigLChData + && ins != icSigLuvData + && ins != icSigYCbCrData + && ins != icSigYxyData + && ins != icSigHsvData + && ins != icSigHlsData) { + for (i = 0; i < MAX_CHAN; i++) { + in[i] /= scale; + } + } + + if (repXYZ100 && ins == icSigXYZData) { + in[0] /= 100.0; + in[1] /= 100.0; + in[2] /= 100.0; + } + + if (repYxy && ins == icSigYxyData) { + icmXYZ2Yxy(in, in); + } + + /* JCh -> Jab & LCh -> Lab */ + if ((repJCh && ins == icxSigJChData) + || (repLCh && ins == icxSigLChData)) { + double C = in[1]; + double h = in[2]; + in[1] = C * cos(3.14159265359/180.0 * h); + in[2] = C * sin(3.14159265359/180.0 * h); + } + + /* Do conversion */ + if (invert) { + for (j = 0; j < MAX_CHAN; j++) + out[j] = in[j]; /* Carry any auxiliary value to out for lookup */ + if ((rv = luo->inv_lookup(luo, out, in)) > 1) + error ("%d, %s",xicco->errc,xicco->err); + } else { + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",xicco->errc,xicco->err); + } + + if (slocwarn) { + double xyz[3]; + + if (outs == icSigLabData || outs == icxSigLChData) + icmLab2XYZ(&icmD50, out, xyz); + else + icmCpy3(xyz, out); + + outsloc = icx_outside_spec_locus(xyz, icxOT_CIE_1931_2); + } + + /* Copy conversion out value so that we can create user values */ + for (i = 0; i < MAX_CHAN; i++) + uout[i] = out[i]; + + if (repXYZ100 && outs == icSigXYZData) { + uout[0] *= 100.0; + uout[1] *= 100.0; + uout[2] *= 100.0; + } + + if (repYxy && outs == icSigYxyData) { + icmXYZ2Yxy(out, out); + } + + /* Jab -> JCh and Lab -> LCh */ + if ((repJCh && outs == icxSigJChData) + || (repLCh && outs == icxSigLChData)) { + double a = uout[1]; + double b = uout[2]; + uout[1] = sqrt(a * a + b * b); + uout[2] = (180.0/3.14159265359) * atan2(b, a); + uout[2] = (uout[2] < 0.0) ? uout[2] + 360.0 : uout[2]; + } + + /* If device data and scale */ + if(scale > 0.0 + && outs != icxSigJabData + && outs != icxSigJChData + && outs != icSigXYZData + && outs != icSigLabData + && outs != icxSigLChData + && outs != icSigLuvData + && outs != icSigYCbCrData + && outs != icSigYxyData + && outs != icSigHsvData + && outs != icSigHlsData) { + for (i = 0; i < MAX_CHAN; i++) { + uout[i] *= scale; + } + } + + /* Output the results */ + if (verb > 0) { + for (j = 0; j < inn; j++) { + if (j > 0) + fprintf(stdout," %f",uin[j]); + else + fprintf(stdout,"%f",uin[j]); + } + printf(" [%s] -> %s -> ", icx2str(icmColorSpaceSignature, ins), + icm2str(icmLuAlg, alg)); + } + + for (j = 0; j < outn; j++) { + if (j > 0) + fprintf(stdout," %f",uout[j]); + else + fprintf(stdout,"%f",uout[j]); + } + if (verb > 0) + printf(" [%s]", icx2str(icmColorSpaceSignature, outs)); + + if (verb > 0 && tlimit >= 0) { + double tot; + for (tot = 0.0, j = 0; j < outn; j++) { + tot += out[j]; + } + printf(" Lim %f",tot); + } + if (outsloc) + fprintf(stdout,"(Imaginary)"); + + if (verb == 0 || rv == 0) + fprintf(stdout,"\n"); + else { + fprintf(stdout," (clip)\n"); + + /* This probably isn't right - we need to convert */ + /* in[] to Lab to Jab if it is not in that space, */ + /* so we can do a delta E on it. */ + if (actual && aluo != NULL) { + double cin[MAX_CHAN], de; + if ((rv = aluo->lookup(aluo, cin, out)) > 1) + error ("%d, %s",xicco->errc,xicco->err); + + for (de = 0.0, j = 0; j < inn; j++) { + de += (cin[j] - in[j]) * (cin[j] - in[j]); + } + de = sqrt(de); + printf("[Actual "); + for (j = 0; j < inn; j++) { + if (j > 0) + fprintf(stdout," %f",cin[j]); + else + fprintf(stdout,"%f",cin[j]); + } + printf(", deltaE %f]\n",de); + } + } + } + } + + /* Done with lookup object */ + if (aluo != NULL && aluo != luo) + luo->del(aluo); + luo->del(luo); + + xicco->del(xicco); /* Expansion wrapper */ + icco->del(icco); /* Icc */ + fp->del(fp); + + return 0; +} + diff --git a/xicc/xlut.c b/xicc/xlut.c new file mode 100644 index 0000000..21c286e --- /dev/null +++ b/xicc/xlut.c @@ -0,0 +1,4271 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000, 2001 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * This is the third major version of xlut.c (originally called xlut2.c) + * Based on the old xlut.c code (preserved as xlut1.c) + * This version uses xfit.c to do the curve and rspl fitting. + */ + +/* + * This module provides the expanded icclib functionality + * for lut based profiles. + * This file is #included in xicc.c, to keep its functions private. + * + * This version creates both input and output 1D luts by + * optimising the accuracy of the profile for a linear clut. + * + */ + +/* + TTBD: + + See gamut/gammap.c for notes on the desirability of + a non-minimum delta E colorimetric intent as the default. + + Should fix gamut's so that they have enough information + to spefify a gamut mapping without the need for + a source colorspace profile, and fix the code + to work with just a gamut. This would take us further + towards supporting the PRMG reference gamut interoperability + as an option. + + Should the input profile white point determination + be made a bit smarter about determining the chromaticity ? + ie. not take on the tint of the whitest patch, but an + average of neutral patches ? + + Should xlutfix.c be revived (also adding ICM_CLUT_SET_APXLS support), + to improve "bumpy black" problem ? + */ + +/* + + NOTE :- an alternative to the way display profile absolute is handled here + would be to always chromatically adapt the illuminant to D50, and encode + that in the Chromatic adapation tag. To make absolute colorimetric + do anything useful though, the chromatic adapation tag would + have to be used for absolute intent. + This may be the way of improving compatibility with other systems, + but would break compatibility with existing Argyll profiles, + unless special measures are taken: + + ie. + + 1) if (display profile & using chromatic adaptation tag) + Create Bradford chromatic adapation matrix and store it in tag + Adapt all the readings using Bradford + Create white point and store it in tag (white point will be D50) + Adapt all the readings to the white point using wrong Von-Kries (no change) + Store relative colorimetric cLUT + Set version >= 2.4 + + else + 2) if (display scheme A or using Argyll historical printer scheme) + Create white point and store it in tag + Adapt all the readings to the white point using Bradford + Store relative colorimetric tag + Set version < 2.4 for V2 profile + Add private Absolute Transform Matrix (labels V4 profile) + + 3) else (display scheme B or strict ICC printer compatibility) + Create white point and store it in tag + Adapt all the readings to the white point using Wrong Von-Kries + Store relative colorimetric tag + Set version >= 2.4 + + + Argyll Processing for each type + + 1) if display and chromatic adapation matrix + Un-adapt matrix or cLUT using wrong Von-Kries from white point + Un-adapt matrix or cLUT using chromatic matrix + Un-adapt apparant white point & black point using chromatic transform + + if (not absolute intent) + Create Bradford transfor from white to PCS D50 + Adapt all matrix or cLUT + else + 2) if (display scheme A or using Argyll < V2.4. profile + or find Absolute Transform Matrix) + if (absolute intent) + Un-adapt matrix or cLUT using Bradford from white point + + 3) else (display scheme B or !Argyll profile or ( >= V2.4 profile + and !Absolute Transform Matrix)) + Un-adapt matrix or cLUT using wrong Von-Kries from white point + + if (not absolute intent) + Create Bradford transfor from white to PCS D50 + Adapt all matrix or cLUT to white + + + The problem with this is that it wouldn't do the right thing on old Argyll + type profiles that weren't labeled or recognized. + + Is there a way of recognizing Bradford Absolute transform Matricies if + the color chromaticities are given ? + + */ + +/* + A similar condrum is that it seems that an unwritten convention for + V2 profiles is to scale the black point of the perceptual and + saturation tables to 0 (Part of the V4 spec is to scale to Y = 3.1373). + + To get better gamut mapping we should therefore unscale the perceptual + and saturation A2B table to have the same black point as the colorimetric + table before computing the gamut mapping, and then apply the opposite + transform to our perceptual B2A and A2B tables. + + */ + +/* + There is interesting behaviour for Jab 0,0,0 in, in that + it gets mapped to (something like) Lab -1899.019855 -213.574625 -231.914285 + which after per-component clipping of the inv out tables looks like + 0, -128, -128, which may be mapped to (say) RGB 0.085455 1.000000 0.936951, + which is not black. + + */ + +#include "xfit.h" + +#undef USE_CIE94_DE /* [Undef] Use CIE94 delta E measure when creating in/out curves */ + /* Don't use CIE94 because it makes peak error worse ? */ + +#undef DEBUG /* [Undef] Verbose debug information */ +#undef CNDTRACE /* [Undef] Enable xicc->trace conditional verbosity */ +#undef DEBUG_OLUT /* [Undef] Print steps in overall fwd & rev lookups */ +#undef DEBUG_RLUT /* [Undef] Print values being reverse lookup up */ +#undef DEBUG_SPEC /* [Undef] Debug some specific cases */ +#undef INK_LIMIT_TEST /* [Undef] Turn off input tables for ink limit testing */ +#undef CHECK_ILIMIT /* [Undef] Do sanity checks on meeting ink limit */ +#undef WARN_CLUT_CLIPPING /* [Undef] Print warning if setting clut clips */ +#undef DISABLE_KCURVE_FILTER /* [Undef] don't filter the Kcurve */ +#undef REPORT_LOCUS_SEGMENTS /* [Undef[ Examine how many segments there are in aux inversion */ + +#define SHP_SMOOTH 1.0 /* Input shaper curve smoothing */ +#define OUT_SMOOTH1 1.0 /* Output shaper curve smoothing for L*, X,Y,Z */ +#define OUT_SMOOTH2 1.0 /* Output shaper curve smoothing for a*, b* */ + +#define CAMCLIPTRANS 1.0 /* [1.0] Cam clipping transition region Delta E */ +#define USECAMCLIPSPLINE /* [def] use spline blend */ + +/* + * TTBD: + * + * Reverse lookup of Lab + * Make NEARCLIP the default ?? + * + * XYZ vector clipping isn't implemented. + * + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + */ + +#ifndef _CAT2 +#define _CAT2(n1,n2) n1 ## n2 +#define CAT2(n1,n2) _CAT2(n1,n2) +#endif + + + +static double icxLimitD(icxLuLut *p, double *in); /* For input' */ +#define icxLimitD_void ((double (*)(void *, double *))icxLimitD) /* Cast with void 1st arg */ +static double icxLimit(icxLuLut *p, double *in); /* For input */ +static int icxLuLut_init_clut_camclip(icxLuLut *p); + +/* Debug overall lookup */ +#ifdef DEBUG_OLUT +#undef DBOL +#ifdef CNDTRACE +#define DBOL(xxx) if (p->trace) printf xxx ; +#else +#define DBOL(xxx) printf xxx ; +#endif +#else +#undef DBOL +#define DBOL(xxx) +#endif + +/* Debug reverse lookup */ +#ifdef DEBUG_RLUT +#undef DBR +#ifdef CNDTRACE +#define DBR(xxx) if (p->trace) printf xxx ; +#else +#define DBR(xxx) printf xxx ; +#endif +#else +#undef DBR +#define DBR(xxx) +#endif + +/* Debug some specific cases (fwd_relpcs_outpcs, bwd_outpcs_relpcs) */ +#ifdef DEBUG_SPEC +# undef DBS +# ifdef CNDTRACE +# define DBS(xxx) if (p->trace) printf xxx ; +# else +# define DBS(xxx) printf xxx ; +# endif +#else +# undef DBS +# define DBS(xxx) +#endif + +/* ========================================================== */ +/* xicc lookup code. */ +/* ========================================================== */ + +/* Forward and Backward Multi-Dimensional Interpolation type conversion */ +/* Return 0 on success, 1 if clipping occured, 2 on other error */ + +/* Components of overall lookup, in order */ + +int icxLuLut_in_abs(icxLuLut *p, double *out, double *in) { + int rv = 0; + + if (p->ins == icxSigJabData) { + DBOL(("xlut in_abs: CAM in = %s\n", icmPdv(p->inputChan, in))); + p->cam->cam_to_XYZ(p->cam, out, in); + DBOL(("xlut in_abs: XYZ = %s\n", icmPdv(p->inputChan, out))); + /* Hack to prevent CAM02 weirdness being amplified by inv_abs() */ + /* or any later per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + DBOL(("xlut in_abs: after clipping -Y %s\n",icmPdv(p->outputChan, out))); + } + rv |= ((icmLuLut *)p->plu)->in_abs((icmLuLut *)p->plu, out, out); + DBOL(("xlut in_abs: XYZ out = %s\n", icmPdv(p->inputChan, out))); + } else { + DBOL(("xlut in_abs: PCS in = %s\n", icmPdv(p->inputChan, in))); + rv |= ((icmLuLut *)p->plu)->in_abs((icmLuLut *)p->plu, out, in); + DBOL(("xlut in_abs: PCS out = %s\n", icmPdv(p->inputChan, out))); + } + + return rv; +} + +/* Possible matrix lookup */ +/* input->input (not distinguishing matrix altered input values) */ +int icxLuLut_matrix(icxLuLut *p, double *out, double *in) { + int rv = 0; + rv |= ((icmLuLut *)p->plu)->matrix((icmLuLut *)p->plu, out, in); + return rv; +} + +/* Do input -> input' lookup */ +int icxLuLut_input(icxLuLut *p, double *out, double *in) { +#ifdef NEVER + return ((icmLuLut *)p->plu)->input((icmLuLut *)p->plu, out, in); +#else + int rv = 0; + co tc; + int i; + for (i = 0; i < p->inputChan; i++) { + tc.p[0] = in[i]; + rv |= p->inputTable[i]->interp(p->inputTable[i], &tc); + out[i] = tc.v[0]; + } + return rv; +#endif +} + +/* Do input'->output' lookup, with aux' return */ +/* (The aux' is just extracted from the in' values) */ +int icxLuLut_clut_aux(icxLuLut *p, +double *out, /* output' value */ +double *oink, /* If not NULL, return amount input is over the ink limit, 0 if not */ +double *auxv, /* If not NULL, return aux value used (packed) */ +double *in /* input' value */ +) { + int rv = 0; + co tc; + int i; + + for (i = 0; i < p->inputChan; i++) + tc.p[i] = in[i]; + rv |= p->clutTable->interp(p->clutTable, &tc); + for (i = 0; i < p->outputChan; i++) + out[i] = tc.v[i]; + + if (auxv != NULL) { + int ee = 0; + for (i = 0; i < p->clutTable->di; i++) { + double v = in[i]; + if (p->auxm[i] != 0) { + auxv[ee] = v; + ee++; + } + } + } + + if (oink != NULL) { + double lim = 0.0; + + if (p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0) { + lim = icxLimitD(p, in); + if (lim < 0.0) + lim = 0.0; + } + *oink = lim; + } + + return rv; +} + +/* Do input'->output' lookup */ +int icxLuLut_clut(icxLuLut *p, double *out, double *in) { +#ifdef NEVER + return ((icmLuLut *)p->plu)->clut((icmLuLut *)p->plu, out, in); +#else + return icxLuLut_clut_aux(p, out, NULL, NULL, in); +#endif +} + +/* Do output'->output lookup */ +int icxLuLut_output(icxLuLut *p, double *out, double *in) { + int rv = 0; + + if (p->mergeclut == 0) { +#ifdef NEVER + rv = ((icmLuLut *)p->plu)->output((icmLuLut *)p->plu, out, in); +#else + co tc; + int i; + for (i = 0; i < p->outputChan; i++) { + tc.p[0] = in[i]; + rv |= p->outputTable[i]->interp(p->outputTable[i], &tc); + out[i] = tc.v[0]; + } +#endif + } else { + int i; + for (i = 0; i < p->outputChan; i++) + out[i] = in[i]; + } + return rv; +} + +/* Relative to absolute conversion + PCS to PCS override (Effective PCS) conversion */ +int icxLuLut_out_abs(icxLuLut *p, double *out, double *in) { + int rv = 0; + if (p->mergeclut == 0) { + DBOL(("xlut out_abs: PCS in = %s\n", icmPdv(p->outputChan, in))); + + rv |= ((icmLuLut *)p->plu)->out_abs((icmLuLut *)p->plu, out, in); + + DBOL(("xlut out_abs: ABS PCS out = %s\n", icmPdv(p->outputChan, out))); + + if (p->outs == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + + DBOL(("xlut out_abs: CAM out = %s\n", icmPdv(p->outputChan, out))); + } + } else { + int i; + for (i = 0; i < p->outputChan; i++) + out[i] = in[i]; + } + + return rv; +} + +/* Overall lookup */ +static int +icxLuLut_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + icxLuLut *p = (icxLuLut *)pp; + int rv = 0; + double temp[MAX_CHAN]; + + DBOL(("xicclu: in = %s\n", icmPdv(p->inputChan, in))); + rv |= p->in_abs (p, temp, in); + DBOL(("xicclu: after abs = %s\n", icmPdv(p->inputChan, temp))); + rv |= p->matrix (p, temp, temp); + DBOL(("xicclu: after matrix = %s\n", icmPdv(p->inputChan, temp))); + rv |= p->input (p, temp, temp); + DBOL(("xicclu: after inout = %s\n", icmPdv(p->inputChan, temp))); + rv |= p->clut (p, out, temp); + DBOL(("xicclu: after clut = %s\n", icmPdv(p->outputChan, out))); + if (p->mergeclut == 0) { + rv |= p->output (p, out, out); + DBOL(("xicclu: after output = %s\n", icmPdv(p->outputChan, out))); + rv |= p->out_abs (p, out, out); + DBOL(("xicclu: after outabs = %s\n", icmPdv(p->outputChan, out))); + } + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a relative XYZ or Lab PCS value, convert in the fwd direction into */ +/* the nominated output PCS (ie. Absolute, Jab etc.) */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuLut_fwd_relpcs_outpcs( +icxLuBase *pp, +icColorSpaceSignature is, /* Input space, XYZ or Lab */ +double *out, double *in) { + icxLuLut *p = (icxLuLut *)pp; + + + /* Convert to the ICC PCS */ + if (is == icSigLabData && p->natpcs == icSigXYZData) { + DBS(("fwd_relpcs_outpcs: Lab in = %s\n", icmPdv(p->inputChan, in))); + icmLab2XYZ(&icmD50, out, in); + DBS(("fwd_relpcs_outpcs: XYZ = %s\n", icmPdv(p->inputChan, out))); + } else if (is == icSigXYZData && p->natpcs == icSigLabData) { + DBS(("fwd_relpcs_outpcs: XYZ in = %s\n", icmPdv(p->inputChan, in))); + icmXYZ2Lab(&icmD50, out, in); + DBS(("fwd_relpcs_outpcs: Lab = %s\n", icmPdv(p->inputChan, out))); + } else { + DBS(("fwd_relpcs_outpcs: PCS in = %s\n", icmPdv(p->inputChan, in))); + icmCpy3(out, in); + } + + /* Convert to absolute */ + ((icmLuLut *)p->plu)->out_abs((icmLuLut *)p->plu, out, out); + + DBS(("fwd_relpcs_outpcs: abs PCS = %s\n", icmPdv(p->inputChan, out))); + + if (p->outs == icxSigJabData) { + + /* Convert to CAM */ + p->cam->XYZ_to_cam(p->cam, out, out); + + DBS(("fwd_relpcs_outpcs: Jab = %s\n", icmPdv(p->inputChan, out))); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Components of inverse lookup, in order */ + +/* Utility function - compute the clip vector direction. */ +/* return NULL if vector clip isn't used. */ +double *icxClipVector( +icxClip *p, /* Clipping setup information */ +double *in, /* Target point */ +double *cdirv /* Space for returned clip vector */ +) { + int f; + if (p->nearclip != 0) + return NULL; /* Doing nearest clipping, not vector */ + + /* Default is simple vector clip */ + for (f = 0; f < p->fdi; f++) + cdirv[f] = p->ocent[f] - in[f]; /* Clip towards output gamut center */ + + if (p->ocentl != 0.0) { /* Graduated vector clip */ + double cvl, nll; + + /* Compute (negative) clip vector length */ + for (cvl = 0.0, f = 0; f < p->fdi; f++) { + cvl += cdirv[f] * cdirv[f]; + } + cvl = sqrt(cvl); + if (cvl > 1e-8) { + /* Dot product of clip vector and clip center line */ + for (nll = 0.0, f = 0; f < p->fdi; f++) + nll -= cdirv[f] * p->ocentv[f]; /* (Fix -ve clip vector) */ + nll /= (p->ocentl * p->ocentl); /* Normalised location along line */ + + /* Limit to line */ + if (nll < 0.0) + nll = 0.0; + else if (nll > 1.0) + nll = 1.0; + + if (p->LabLike) { + /* Aim more towards center for saturated targets */ + double sat = sqrt(in[1] * in[1] + in[2] * in[2]); + nll += sat/150.0 * (0.5 - nll); + } + + /* Compute target clip direction */ + for (f = 0; f < p->fdi; f++) + cdirv[f] = p->ocent[f] + nll * p->ocentv[f] - in[f]; + } + } + + return cdirv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Typically inv is used to invert a device->PCS table, */ +/* so it is doing a PCS->device conversion. */ +/* This doesn't always have to be the case though. */ + +/* PCS override (Effective PCS) to PCS conversion + absolute to relative conversion */ +int icxLuLut_inv_out_abs(icxLuLut *p, double *out, double *in) { + int rv = 0; + + DBR(("\nicxLuLut_inv_out_abs got PCS %s\n",icmPdv(p->outputChan, in))); + + if (p->mergeclut == 0) { + if (p->outs == icxSigJabData) { + p->cam->cam_to_XYZ(p->cam, out, in); + DBR(("icxLuLut_inv_out_abs after cam2XYZ %s\n",icmPdv(p->outputChan, out))); + /* Hack to prevent CAM02 weirdness being amplified by inv_out_abs() */ + /* or per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + DBR(("icxLuLut_inv_out_abs after clipping -Y %s\n",icmPdv(p->outputChan, out))); + } + rv |= ((icmLuLut *)p->plu)->inv_out_abs((icmLuLut *)p->plu, out, out); + DBR(("icxLuLut_inv_out_abs after icmLut inv_out_abs %s\n",icmPdv(p->outputChan, out))); + } else { + rv |= ((icmLuLut *)p->plu)->inv_out_abs((icmLuLut *)p->plu, out, in); + DBR(("icxLuLut_inv_out_abs after icmLut inv_out_abs %s\n",icmPdv(p->outputChan, out))); + } + } else { + int i; + for (i = 0; i < p->outputChan; i++) + out[i] = in[i]; + } + DBR(("icxLuLut_inv_out_abs returning PCS %f %f %f\n",out[0],out[1],out[2])) + return rv; +} + +/* Do output->output' inverse lookup */ +int icxLuLut_inv_output(icxLuLut *p, double *out, double *in) { + int rv = 0; + DBR(("icxLuLut_inv_output got PCS %f %f %f\n",in[0],in[1],in[2])) + if (p->mergeclut == 0) { +#ifdef NEVER + rv = ((icmLuLut *)p->plu)->inv_output((icmLuLut *)p->plu, out, in); +#else + int i,j; + int nsoln; /* Number of solutions found */ + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + double cdir; + + for (i = 0; i < p->outputChan; i++) { + pp[0].p[0] = p->outputClipc[i]; + pp[0].v[0] = in[i]; + cdir = p->outputClipc[i] - in[i]; /* Clip towards output range */ + + nsoln = p->outputTable[i]->rev_interp ( + p->outputTable[i], /* this */ + RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */ + MAX_INVSOLN, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + &cdir, /* Clip vector direction and length */ + pp); /* Input and output values */ + + if (nsoln & RSPL_DIDCLIP) + rv = 1; + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln == 1) { /* Exactly one solution */ + j = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + error("xlut: Unexpected failure to find reverse solution for output table"); + return 2; + } else { /* Multiple solutions */ + /* Use a simple minded resolution - choose the one closest to the center */ + double bdist = 1e300; + int bsoln = 0; + /* Don't expect this - 1D luts are meant to be monotonic */ + warning("1D lut inversion got %d reverse solutions\n",nsoln); + warning("solution 0 = %f\n",pp[0].p[0]); + warning("solution 1 = %f\n",pp[1].p[0]); + for (j = 0; j < nsoln; j++) { + double tt; + tt = pp[i].p[0] - p->outputClipc[i]; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = j; + } + } + j = bsoln; + } + out[i] = pp[j].p[0]; + } + +#endif /* NEVER */ + } else { + int i; + for (i = 0; i < p->outputChan; i++) + out[i] = in[i]; + } + DBR(("icxLuLut_inv_output returning PCS' %f %f %f\n",out[0],out[1],out[2])) + return rv; +} + +/* Ink limit+gamut limit calculation function for xLuLut. */ +/* Returns < 0.0 if input value is within limits, */ +/* > 0.0 if outside limits. Value is proportinal to distance to limits. */ +/* We implement device gamut check to improve utility outside rspl, */ +/* in optimisation routines. */ +/* The limits are assumed to be post calibrated device values (ie. real */ +/* final device values) */ +static double icxLimit( +icxLuLut *p, +double *in +) { + double cin[MAX_CHAN]; /* Calibrated input values */ + double tlim, klim; + double ovr, val; + int e; + + if (p->pp->cal != NULL) { /* We have device calibration information */ + p->pp->cal->interp(p->pp->cal, cin, in); + } else { + for (e = 0; e < p->inputChan; e++) + cin[e] = in[e]; + } + + if ((tlim = p->ink.tlimit) < 0.0) + tlim = (double)p->inputChan; /* Default is no limit */ + + if ((klim = p->ink.klimit) < 0.0) + klim = 1.0; + + /* Compute amount outside total limit */ + { /* No calibration */ + double sum; + for (sum = 0.0, e = 0; e < p->inputChan; e++) + sum += cin[e]; + val = sum - tlim; + } + + /* Compute amount outside black limit */ + if (p->ink.klimit >= 0.0) { + double kval = 0.0; + switch(p->natis) { + case icSigCmykData: + kval = cin[3] - klim; + break; + default: + /* NOTE !!! p->kch isn't being initialized !!! */ + if (p->kch >= 0) { + kval = cin[p->kch] - klim; + } else { + error("xlut: Unknown colorspace when black limit specified"); + } + } + if (kval > val) + val = kval; + } + /* Compute amount outside device value limits 0.0 - 1.0 */ + for (ovr = -1.0, e = 0; e < p->inputChan; e++) { + if (in[e] < 0.0) { /* ! Not cin[] */ + if (-in[e] > ovr) + ovr = -in[e]; + } else if (in[e] > 1.0) { + if ((in[e] - 1.0) > ovr) + ovr = in[e] - 1.0; + } + } + if (ovr > val) + val = ovr; + + return val; +} + +/* Same as above, but works with input' values */ +/* (If an ink limit is being used we assume that the */ +/* input space is not PCS, hence inv_in_abs() is doing nothing) */ +static double icxLimitD( +icxLuLut *p, +double *ind +) { + double in[MAX_CHAN]; + co tc; + int e; + + /* Convert input' to input through revinput Luts (for speed) */ + for (e = 0; e < p->inputChan; e++) { + tc.p[0] = ind[e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + in[e] = tc.v[0]; + } + + return icxLimit(p, in); +} + +/* Ink limit+gamut limit clipping function for xLuLut (CMYK). */ +/* Return nz if there was clipping */ +static int icxDoLimit( +icxLuLut *p, +double *out, +double *in +) { + double tlim, klim = -1.0; + double sum; + int clip = 0, e; + int kch = -1; + + for (e = 0; e < p->inputChan; e++) + out[e] = in[e]; + + if ((tlim = p->ink.tlimit) < 0.0) + tlim = (double)p->inputChan; + + if ((klim = p->ink.klimit) < 0.0) + klim = 1.0; + + /* Clip black */ + if (p->natis == icSigCmykData) + kch = 3; + else + kch = p->kch; + + if (kch >= 0) { + if (out[p->kch] > klim) { + out[p->kch] = klim; + clip = 1; + } + } + + /* Compute amount outside total limit */ + for (sum = 0.0, e = 0; e < p->inputChan; e++) + sum += out[e]; + + if (sum > tlim) { + clip = 1; + sum /= (double)p->inputChan; + for (e = 0; e < p->inputChan; e++) + out[e] -= sum; + } + return clip; +} + +#ifdef NEVER +#undef DBK +#define DBK(xxx) printf xxx ; +#else +#undef DBK +#define DBK(xxx) +#endif + +/* helper function that creates our standard K locus curve value, */ +/* given the curve parameters, and the normalised L 0.0 - 1.0 value. */ +/* No filtering version. */ +/* !!! Should add K limit in here so that smoothing takes it into account !!! */ +static double icxKcurveNF(double L, icxInkCurve *c) { + double Kstpo, Kenpo, Kstle, Kenle; + double rv; + + DBK(("icxKcurve got L = %f, smth %f skew %f, Parms %f %f %f %f %f\n",L, c->Ksmth, c->Kskew, c->Kstle, c->Kstpo, c->Kenpo, c->Kenle, c->Kshap)); + + /* Invert sense of L, so that 0.0 = white, 1.0 = black */ + L = 1.0 - L; + + /* Clip L, just in case */ + if (L < 0.0) { + L = 0.0; + } else if (L > 1.0) { + L = 1.0; + } + DBK(("Clipped inverted L = %f\n",L)); + + /* Make sure breakpoints are ordered */ + if (c->Kstpo < c->Kenpo) { + Kstle = c->Kstle; + Kstpo = c->Kstpo; + Kenpo = c->Kenpo; + Kenle = c->Kenle; + } else { /* They're swapped */ + Kstle = c->Kenle; + Kstpo = c->Kenpo; + Kenpo = c->Kstpo; + Kenle = c->Kstle; + } + + if (L <= Kstpo) { + /* We are at white level */ + rv = Kstle; + DBK(("At white level %f\n",rv)); + } else if (L >= Kenpo) { + /* We are at black level */ + rv = Kenle; + DBK(("At black level %f\n",rv)); + } else { + double Lp, g; + /* We must be on the curve from start to end levels */ + + Lp = (L - Kstpo)/(Kenpo - Kstpo); + + DBK(("Curve position %f\n",Lp)); + + Lp = pow(Lp, c->Kskew); + + DBK(("Skewed curve position %f\n",Lp)); + + g = c->Kshap/2.0; + DBK(("Curve bf %f, g %g\n",Lp,g)); + + /* A value of 0.5 will be tranlated to g */ + Lp = Lp/((1.0/g - 2.0) * (1.0 - Lp) + 1.0); + + DBK(("Skewed shaped %f\n",Lp)); + + Lp = pow(Lp, 1.0/c->Kskew); + + DBK(("Shaped %f\n",Lp)); + + /* Transition between start end end levels */ + rv = Lp * (Kenle - Kstle) + Kstle; + + DBK(("Scaled to start and end levele %f\n",rv)); + } + + DBK(("Returning %f\n",rv)); + return rv; +} + +#ifdef DBK +#undef DBK +#define DBK(xxx) +#endif + + +#ifdef NEVER +#undef DBK +#define DBK(xxx) printf xxx ; +#else +#undef DBK +#define DBK(xxx) +#endif + +/* Same as above, but implement transition filters accross inflection points. */ +/* (The convolultion filter previously used could be */ +/* re-instituted if something was done about compressing */ +/* the filter at the boundaries so that the levels are met.) */ +static double icxKcurve(double L, icxInkCurve *c) { + +#ifdef DISABLE_KCURVE_FILTER + return icxKcurveNF(L, c); + +#else /* !DISABLE_KCURVE_FILTER */ + + double Kstpo, Kenpo, Kstle, Kenle, Ksmth; + double rv; + + DBK(("icxKcurve got L = %f, smth %f skew %f, Parms %f %f %f %f %f\n",L, c->Ksmth, c->Kskew, c->Kstle, c->Kstpo, c->Kenpo, c->Kenle, c->Kshap)); + + /* Invert sense of L, so that 0.0 = white, 1.0 = black */ + L = 1.0 - L; + + /* Clip L, just in case */ + if (L < 0.0) { + L = 0.0; + } else if (L > 1.0) { + L = 1.0; + } + DBK(("Clipped inverted L = %f\n",L)); + + /* Make sure breakpoints are ordered */ + if (c->Kstpo < c->Kenpo) { + Kstle = c->Kstle; + Kstpo = c->Kstpo; + Kenpo = c->Kenpo; + Kenle = c->Kenle; + } else { /* They're swapped */ + Kstle = c->Kenle; + Kstpo = c->Kenpo; + Kenpo = c->Kstpo; + Kenle = c->Kstle; + } + Ksmth = c->Ksmth; + + /* Curve value at point */ + rv = icxKcurveNF(1.0 - L, c); + + DBK(("Raw output at iL = %f, rv\n",L)); + + /* Create filtered value */ + { + double wbs, wbe; /* White transitioin start, end */ + double wbl, wfv; /* White blend factor, value at filter band */ + + double mt; /* Middle of the two transitions */ + + double bbs, bbe; /* Black transitioin start, end */ + double bbl, bfv; /* Black blend factor, value at filter band */ + + wbs = Kstpo - Ksmth; + wbe = Kstpo + Ksmth; + + bbs = Kenpo - 1.0 * Ksmth; + bbe = Kenpo + 1.0 * Ksmth; + + mt = 0.5 * (wbe + bbs); + + /* Make sure that the whit & black transition regions */ + /* don't go out of bounts or overlap */ + if (wbs < 0.0) { + wbe += wbs; + wbs = 0.0; + } + if (bbe > 1.0) { + bbs += (bbe - 1.0); + bbe = 1.0; + } + + if (wbe > mt) { + wbs += (wbe - mt); + wbe = mt; + } + + if (bbs < mt) { + bbe += (mt - bbs); + bbs = mt; + } + + DBK(("Transition windows %f - %f, %f - %f\n",wbs, wbe, bbw, bbe)); + if (wbs < wbe) { + wbl = (L - wbe)/(wbs - wbe); + + if (wbl > 0.0 && wbl < 1.0) { + wfv = icxKcurveNF(1.0 - wbe, c); + DBK(("iL = %f, wbl = %f, wfv = %f\n",L,Kstpo,wbl,wfv)); + + wbl = 1.0 - pow(1.0 - wbl, 2.0); + rv = wbl * Kstle + (1.0 - wbl) * wfv; + } + } + if (bbs < bbe) { + bbl = (L - bbe)/(bbs - bbe); + + if (bbl > 0.0 && bbl < 1.0) { + bfv = icxKcurveNF(1.0 - bbs, c); + DBK(("iL = %f, bbl = %f, bfv = %f\n",L,Kstpo,bbl,bfv)); + + bbl = pow(bbl, 2.0); + rv = bbl * bfv + (1.0 - bbl) * Kenle; + } + } + } + + /* To be safe */ + if (rv < 0.0) + rv = 0.0; + else if (rv > 1.0) + rv = 1.0; + + DBK(("Returning %f\n",rv)); + return rv; +#endif /* !DISABLE_KCURVE_FILTER */ +} + +#ifdef DBK +#undef DBK +#define DBK(xxx) +#endif + +/* Do output'->input' lookup with aux details. */ +/* Note that out[] will be used as the inking value if icxKrule is */ +/* icxKvalue, icxKlocus, icxKl5l or icxKl5lk, and that the auxiliar values, PCS ranges */ +/* and icxKrule value will all be evaluated in output->input space (not ' space). */ +/* Note that the ink limit will be computed after converting input' to input, auxt */ +/* will override the inking rule, and auxr[] reflects the available auxiliary range */ +/* that the locus was to choose from, and auxv[] was the actual auxiliary used. */ +/* Returns clip status. */ +int icxLuLut_inv_clut_aux( +icxLuLut *p, +double *out, /* Function return values, plus aux value or locus target input if auxt == NULL */ +double *auxv, /* If not NULL, return aux value used (packed) */ +double *auxr, /* If not NULL, return aux locus range (packed, 2 at a time) */ +double *auxt, /* If not NULL, specify the aux target for this lookup (override ink) */ +double *clipd, /* If not NULL, return DE to gamut on clipi, 0 for not clip */ +double *in /* Function input values to invert (== clut output' values) */ +) { + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + int nsoln; /* Number of solutions found */ + double *cdir, cdirv[MXDO]; /* Clip vector direction and length */ + int e,f,i; + int fdi = p->clutTable->fdi; + int flags = 0; /* reverse interp flags */ + int xflags = 0; /* extra clip/noclip flags */ + double tin[MXDO]; /* PCS value to be inverted */ + double cdist = 0.0; /* clip DE */ + int crv = 0; /* Return value - set to 1 if clipped */ + + if (p->nearclip != 0) + flags |= RSPL_NEARCLIP; /* Use nearest clipping rather than clip vector */ + + DBR(("inv_clut_aux input is %f %f %f\n",in[0], in[1], in[2])) + + if (auxr != NULL) { /* Set a default locus range */ + int ee = 0; + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + auxr[ee++] = 1e60; + auxr[ee++] = -1e60; + } + } + } + + /* Setup for reverse lookup */ + for (f = 0; f < fdi; f++) + pp[0].v[f] = in[f]; /* Target value */ + + /* Compute clip vector, if any */ + cdir = icxClipVector(&p->clip, in, cdirv); + + if (p->clutTable->di > fdi) { /* ie. CMYK->Lab, there will be ambiguity */ + double min[MXDI], max[MXDI]; /* Auxiliary locus range */ + +#ifdef REPORT_LOCUS_SEGMENTS /* Examine how many segments there are */ + { /* ie. CMYK->Lab, there will be ambiguity */ + double smin[10][MXRI], smax[10][MXRI]; /* Auxiliary locus segment ranges */ + double min[MXRI], max[MXRI]; /* Auxiliary min and max locus range */ + + nsoln = p->clutTable->rev_locus_segs( + p->clutTable, /* rspl object */ + p->auxm, /* Auxiliary mask */ + pp, /* Input target and output solutions */ + 10, /* Maximum number of solutions to return */ + smin, smax); /* Returned locus of valid auxiliary values */ + + if (nsoln != 0) { + /* Convert the locuses from input' -> input space */ + /* and get overall min/max locus range */ + for (e = 0; e < p->clutTable->di; e++) { + co tc; + /* (Is speed more important than precision ?) */ + if (p->auxm[e] != 0) { + for (i = 0; i < nsoln; i++) { + tc.p[0] = smin[i][e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + smin[i][e] = tc.v[0]; + tc.p[0] = smax[i][e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + smax[i][e] = tc.v[0]; + printf(" Locus seg %d:[%d] %f -> %f\n",i, e, smin[i][e], smax[i][e]); + } + } + } + } + } +#endif /* REPORT_LOCUS_SEGMENTS */ + + /* Compute auxiliary locus on the fly. This is in dev' == input' space. */ + nsoln = p->clutTable->rev_locus( + p->clutTable, /* rspl object */ + p->auxm, /* Auxiliary mask */ + pp, /* Input target and output solutions */ + min, max); /* Returned locus of valid auxiliary values */ + + if (nsoln == 0) { + xflags |= RSPL_WILLCLIP; /* No valid locus, so we expect to have to clip */ +#ifdef DEBUG_RLUT + printf("inv_clut_aux: no valid locus, expect clip\n"); +#endif + /* Make sure that the auxiliar value is initialized, */ + /* even though it won't have any effect. */ + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + pp[0].p[e] = 0.5; + } + } + + } else { /* Got a valid locus */ + + /* Convert the locuses from input' -> input space */ + for (e = 0; e < p->clutTable->di; e++) { + co tc; + /* (Is speed more important than precision ?) */ + if (p->auxm[e] != 0) { + tc.p[0] = min[e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + min[e] = tc.v[0]; + tc.p[0] = max[e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + max[e] = tc.v[0]; + } + } + + if (auxr != NULL) { /* Report the locus range */ + int ee = 0; + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + auxr[ee++] = min[e]; + auxr[ee++] = max[e]; + } + } + } + + if (auxt != NULL) { /* overiding auxiliary target */ + int ee = 0; + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + double iv = auxt[ee++]; + if (iv < min[e]) + iv = min[e]; + else if (iv > max[e]) + iv = max[e]; + pp[0].p[e] = iv; + } + } + DBR(("inv_clut_aux: aux %f from auxt[] %f\n",pp[0].p[3],auxt[0])) + } else if (p->ink.k_rule == icxKvalue) { + /* Implement the auxiliary inking rule */ + /* Target auxiliary values are provided in out[] K value */ + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + double iv = out[e]; /* out[] holds aux target value */ + if (iv < min[e]) + iv = min[e]; + else if (iv > max[e]) + iv = max[e]; + pp[0].p[e] = iv; + } + } + DBR(("inv_clut_aux: aux %f from out[0] K target %f min %f max %f\n",pp[0].p[3],out[3],min[3],max[3])) + } else if (p->ink.k_rule == icxKlocus) { + /* Set target auxliary input values from values in out[] and locus */ + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + double ii, iv; + ii = out[e]; /* Input ink locus */ + iv = min[e] + ii * (max[e] - min[e]); /* Output ink from locus */ + if (iv < min[e]) + iv = min[e]; + else if (iv > max[e]) + iv = max[e]; + pp[0].p[e] = iv; + } + } + DBR(("inv_clut_aux: aux %f from out[0] locus %f min %f max %f\n",pp[0].p[3],out[3],min[3],max[3])) + } else { /* p->ink.k_rule == icxKluma5 || icxKluma5k || icxKl5l || icxKl5lk */ + /* Auxiliaries are driven by a rule and the output values */ + double rv, L; + + /* If we've got a mergeclut, then the PCS' is the same as the */ + /* effective PCS, and we need to convert to native PCS */ + if (p->mergeclut) { + p->mergeclut = 0; /* Hack to be able to use inv_out_abs() */ + icxLuLut_inv_out_abs(p, tin, in); + p->mergeclut = 1; + + } else { + /* Convert native PCS' to native PCS values */ + p->output(p, tin, in); + } + + /* Figure out Luminance number */ + if (p->natos == icSigXYZData) { + icmXYZ2Lab(&icmD50, tin, tin); + } else if (p->natos != icSigLabData) { /* Hmm. that's unexpected */ + error("Assert: xlut K locus, unexpected native pcs of 0x%x\n",p->natos); + } + L = 0.01 * tin[0]; + DBR(("inv_clut_aux: aux from Luminance, raw L = %f\n",L)); + + /* Normalise L to its possible range from min to max */ + L = (L - p->Lmin)/(p->Lmax - p->Lmin); + DBR(("inv_clut_aux: Normalize L = %f\n",L)); + + /* Convert L to curve value */ + rv = icxKcurve(L, &p->ink.c); + DBR(("inv_clut_aux: icxKurve lookup returns = %f\n",rv)); + + if (p->ink.k_rule == icxKluma5) { /* Curve is locus value */ + + /* Set target black as K fraction within locus */ + + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + pp[0].p[e] = min[e] + rv * (max[e] - min[e]); + } + } + DBR(("inv_clut_aux: aux %f from locus %f min %f max %f\n",pp[0].p[3],rv,min[3],max[3])) + + } else if (p->ink.k_rule == icxKluma5k) { /* Curve is K value */ + + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + double iv = rv; + if (iv < min[e]) /* Clip to locus */ + iv = min[e]; + else if (iv > max[e]) + iv = max[e]; + pp[0].p[e] = iv; + } + } + DBR(("inv_clut_aux: aux %f from out[0] K target %f min %f max %f\n",pp[0].p[3],rv,min[3],max[3])) + + } else { /* icxKl5l || icxKl5lk */ + /* Create second curve, and use input locus to */ + /* blend between */ + + double rv2; /* Upper limit */ + + /* Convert L to max curve value */ + rv2 = icxKcurve(L, &p->ink.x); + + if (rv2 < rv) { /* Ooops - better swap. */ + double tt; + tt = rv; + rv = rv2; + rv2 = tt; + } + + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + if (p->ink.k_rule == icxKl5l) { + double ii; + ii = out[e]; /* Input K locus */ + if (ii < 0.0) + ii = 0.0; + else if (ii > 1.0) + ii = 1.0; + ii = (1.0 - ii) * rv + ii * rv2;/* Blend between locus rule curves */ + /* Out ink from output locus */ + pp[0].p[e] = min[e] + ii * (max[e] - min[e]); + } else { + double iv; + iv = out[e]; /* Input K level */ + if (iv < rv) /* Constrain to curves */ + iv = rv; + else if (iv > rv2) + iv = rv2; + pp[0].p[e] = iv; + } + } + } + DBR(("inv_clut_aux: aux %f from 2 curves\n",pp[0].p[3])) + } + } + + /* Convert to input/dev aux target to input'/dev' space for rspl inversion */ + for (e = 0; e < p->clutTable->di; e++) { + double tv, bv = 0.0, bd = 1e6; + co tc; + if (p->auxm[e] != 0) { + tv = pp[0].p[e]; + /* Clip to overall locus range (belt and braces) */ + if (tv < min[e]) + tv = min[e]; + if (tv > max[e]) + tv = max[e]; + tc.p[0] = tv; + p->inputTable[e]->interp(p->inputTable[e], &tc); + pp[0].p[e] = tc.v[0]; + } + } + + xflags |= RSPL_EXACTAUX; /* Since we confine aux to locus */ + +#ifdef DEBUG_RLUT + printf("inv_clut_aux computed aux values "); + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) + printf("%d: %f ",e,pp[0].p[e]); + } + printf("\n"); +#endif /* DEBUG_RLUT */ + } + + if (clipd != NULL) { /* Copy pp.v[] to compute clip DE */ + for (f = 0; f < fdi; f++) + tin[f] = pp[0].v[f]; + } + + /* Find reverse solution with target auxiliaries */ + /* We choose the closest aux at or above the target */ + /* to try and avoid glitches near black due to */ + /* possible forked black locuses. */ + nsoln = p->clutTable->rev_interp( + p->clutTable, /* rspl object */ + RSPL_MAXAUX | flags | xflags, /* Combine all the flags */ + MAX_INVSOLN, /* Maxumum solutions to return */ + p->auxm, /* Auxiliary input chanel mask */ + cdir, /* Clip vector direction and length */ + pp); /* Input target and output solutions */ + /* returned solutions in pp[0..retval-1].p[] */ + + } else { + DBR(("inv_clut_aux needs no aux value\n")) + + if (clipd != NULL) { /* Copy pp.v[] to compute clip DE */ + for (f = 0; f < fdi; f++) + tin[f] = pp[0].v[f]; + } + + /* Color spaces don't need auxiliaries to choose from solution locus */ + nsoln = p->clutTable->rev_interp( + p->clutTable, /* rspl object */ + flags, /* No extra flags */ + MAX_INVSOLN, /* Maxumum solutions to return */ + NULL, /* No auxiliary input targets */ + cdir, /* Clip vector direction and length */ + pp); /* Input target and output solutions */ + /* returned solutions in pp[0..retval-1].p[] */ + } + if (nsoln & RSPL_DIDCLIP) + crv = 1; /* Clipped on PCS inverse lookup */ + + if (crv && clipd != NULL) { + + /* Compute the clip DE */ + for (cdist = 0.0, f = 0; f < fdi; f++) { + double tt; + tt = pp[0].v[f] - tin[f]; + cdist += tt * tt; + } + cdist = sqrt(cdist); + } + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + DBR(("inv_clut_aux got %d rev_interp solutions, clipflag = %d\n",nsoln,crv)) + + /* If we clipped and we should clip in CAM Jab space, compute reverse */ + /* clip solution using our additional CAM space. */ + /* (Note that we don't support vector clip in CAM space at the moment) */ + if (crv != 0 && p->camclip && p->nearclip) { + co cpp; /* Alternate CAM space solution */ + double bf; /* Blend factor */ + + DBR(("inv_clut_aux got clip, compute CAM clip\n")) + + if (nsoln != 1) { /* This would be unexpected */ + error("Unexpected failure to return 1 solution on clip for input to output table"); + } + + if (p->cclutTable == NULL) { /* we haven't created this yet, so do so */ + if (icxLuLut_init_clut_camclip(p)) + error("Creating CAM rspl for camclip failed"); + } + + /* Setup for reverse lookup */ + DBR(("inv_clut_aux cam clip PCS in %f %f %f\n",in[0],in[1],in[2])) + + /* Convert from PCS' to (XYZ) PCS */ + ((icmLuLut *)p->absxyzlu)->output((icmLuLut *)p->absxyzlu, tin, in); + DBR(("inv_clut_aux cam clip PCS' -> PCS %f %f %f\n",tin[0],tin[1],tin[2])) + + ((icmLuLut *)p->absxyzlu)->out_abs((icmLuLut *)p->absxyzlu, tin, tin); + DBR(("inv_clut_aux cam clip abs XYZ PCS %f %f %f\n",tin[0],tin[1],tin[2])) + + p->cam->XYZ_to_cam(p->cam, tin, tin); + DBR(("inv_clut_aux cam clip PCS after XYZtoCAM %f %f %f\n",tin[0],tin[1],tin[2])) + + for (f = 0; f < fdi; f++) /* Transfer CAM targ */ + cpp.v[f] = tin[f]; + + /* Make sure that the auxiliar value is initialized, */ + /* even though it shouldn't have any effect, since should clipp. */ + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + cpp.p[e] = 0.5; + } + } + + if (p->clutTable->di > fdi) { /* ie. CMYK->Lab, there will be ambiguity */ + + nsoln = p->cclutTable->rev_interp( + p->cclutTable, /* rspl object */ + flags | xflags | RSPL_WILLCLIP, /* Combine all the flags + clip ?? */ + 1, /* Maximum solutions to return */ + p->auxm, /* Auxiliary input chanel mask */ + cdir, /* Clip vector direction and length */ + &cpp); /* Input target and output solutions */ + + } else { + nsoln = p->cclutTable->rev_interp( + p->cclutTable, /* rspl object */ + flags | RSPL_WILLCLIP, /* Because we know it will clip ?? */ + 1, /* Maximum solutions to return */ + NULL, /* No auxiliary input targets */ + cdir, /* Clip vector direction and length */ + &cpp); /* Input target and output solutions */ + } + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln != 1) { /* This would be unexpected */ + error("Unexpected failure to return 1 solution on CAM clip for input to output table"); + } + + /* Compute the CAM clip distances */ + for (cdist = 0.0, f = 0; f < fdi; f++) { + double tt; + tt = cpp.v[f] - tin[f]; + cdist += tt * tt; + } + cdist = sqrt(cdist); + + /* Use magic number to set blend distance, and compute a blend factor. */ + /* Blend over 1 delta E, otherwise the Lab & CAM02 divergence can result */ + /* in reversals. */ + bf = cdist/CAMCLIPTRANS; /* 0.0 for PCS result, 1.0 for CAM result */ + if (bf > 1.0) + bf = 1.0; +#ifdef USECAMCLIPSPLINE + bf = bf * bf * (3.0 - 2.0 * bf); /* Convert to spline blend */ +#endif + DBR(("cdist %f, spline blend %f\n",cdist,bf)) + + /* Blend between solution values for PCS and CAM clip result. */ + /* We're hoping that the solutions are close, and expect them to be */ + /* that way when we're close to the gamut surface (since the cell */ + /* vertex values should be exact, irrespective of which space they're */ + /* in), but weird things could happen away from the surface. Weird */ + /* things can happen anyway with "clip to nearest", since this is not */ + /* guaranteed to be a smooth function, depending on the gamut surface */ + /* geometry, without taking some precaution such as clipping to a */ + /* convex hull "wrapper" to create a clip vector, which we're not */ + /* current doing. */ + DBR(("Clip blend between device:\n")) + DBR(("Lab: %f %f %f and\n",pp[0].p[0], pp[0].p[1], pp[0].p[2])) + DBR(("Jab: %f %f %f\n",cpp.p[0], cpp.p[1], cpp.p[2])) + + for (e = 0; e < p->clutTable->di; e++) { + out[e] = (1.0 - bf) * pp[0].p[e] + bf * cpp.p[e]; + } + + /* Not CAM clip case */ + } else { + + if (nsoln == 1) { /* Exactly one solution */ + i = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + double in_v[MXDO]; + p->output(p, in_v, pp[0].v); /* Get ICC inverse input values */ + p->out_abs(p, in_v, in_v); + error("Unexpected failure to find reverse solution for input to output table for value %f %f %f (ICC input %f %f %f)",pp[0].v[0],pp[0].v[1],pp[0].v[2], in_v[0], in_v[1], in_v[2]); + return 2; + } else { /* Multiple solutions */ + /* Use a simple minded resolution - choose the one closest to the center */ + double bdist = 1e300; + int bsoln = 0; + DBR(("got multiple reverse solutions\n")); + for (i = 0; i < nsoln; i++) { + double ss; + + for (ss = 0.0, e = 0; e < p->clutTable->di; e++) { + double tt; + tt = pp[i].p[e] - p->licent[e]; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = i; + } + } + } +//printf("~1 chose %d\n",bsoln); + i = bsoln; + } + for (e = 0; e < p->clutTable->di; e++) { + /* Save solution as atractor for next one, on the basis */ + /* that it might have better continuity given pesudo-hilbert inversion path. */ + p->licent[e] = out[e] = pp[i].p[e]; /* Solution */ + } + } + + /* Sanitise auxiliary locus range and auxiliary value return */ + if (auxr != NULL || auxv != NULL) { + int ee = 0; + for (e = 0; e < p->clutTable->di; e++) { + double v = out[e]; /* Solution */ + if (p->auxm[e] != 0) { + if (auxr != NULL) { /* Make sure locus encloses actual value */ + if (auxr[2 * ee] > v) + auxr[2 * ee] = v; + if (auxr[2 * ee + 1] < v) + auxr[2 * ee + 1] = v; + } + if (auxv != NULL) { + auxv[ee] = v; + } + ee++; + } + } + } + +#ifdef CHECK_ILIMIT /* Do sanity checks on meeting ink limit */ +if (p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0) { + double sum = icxLimitD(p, out); + if (sum > 0.0) + printf("xlut assert%s: icxLuLut_inv_clut returned outside limits by %f > tlimit %f\n",crv ? " (clip)" : "", sum, p->ink.tlimit); +} +#endif + + if (clipd != NULL) { + *clipd = cdist; + DBR(("inv_clut_aux returning clip DE %f\n",cdist)) + } + + DBR(("inv_clut_aux returning %f %f %f %f\n",out[0],out[1],out[2],out[3])) + return crv; +} + +/* Do output'->input' lookup, simple version */ +/* Note than out[] will carry inking value if icxKrule is icxKvalue of icxKlocus */ +/* and that the icxKrule value will be in the input (NOT input') space. */ +/* Note that the ink limit will be computed after converting input' to input */ +int icxLuLut_inv_clut(icxLuLut *p, double *out, double *in) { + return icxLuLut_inv_clut_aux(p, out, NULL, NULL, NULL, NULL, in); +} + +/* Given the proposed auxiliary input values in in[di], */ +/* and the target output' (ie. PCS') values in out[fdi], */ +/* return the auxiliary input (NOT input' space) values as a proportion of their */ +/* possible locus in locus[di]. */ +/* This is generally used on a source CMYK profile to convey the black intent */ +/* to destination CMYK profile. */ +int icxLuLut_clut_aux_locus(icxLuLut *p, double *locus, double *out, double *in) { + co pp[1]; /* Room for all the solutions found */ + int nsoln; /* Number of solutions found */ + int e,f; + + if (p->clutTable->di > p->clutTable->fdi) { /* ie. CMYK->Lab, there will be ambiguity */ + double min[MXDI], max[MXDI]; /* Auxiliary locus range */ + + /* Setup for auxiliary locus lookup */ + for (f = 0; f < p->clutTable->fdi; f++) { + pp[0].v[f] = out[f]; /* Target output' (i.e. PCS) value */ + } + + /* Compute auxiliary locus */ + nsoln = p->clutTable->rev_locus( + p->clutTable, /* rspl object */ + p->auxm, /* Auxiliary mask */ + pp, /* Input target and output solutions */ + min, max); /* Returned locus of valid auxiliary values */ + + if (nsoln == 0) { + for (e = 0; e < p->clutTable->di; e++) + locus[e] = 0.0; /* Return some safe values */ + } else { /* Got a valid locus */ + + /* Convert the locus from input' -> input space */ + for (e = 0; e < p->clutTable->di; e++) { + co tc; + /* (Is speed more important than precision ?) */ + if (p->auxm[e] != 0) { + tc.p[0] = min[e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + min[e] = tc.v[0]; + tc.p[0] = max[e]; + p->revinputTable[e]->interp(p->revinputTable[e], &tc); + max[e] = tc.v[0]; + } + } + + /* Figure out the proportion of the locus */ + for (e = 0; e < p->clutTable->di; e++) { + if (p->auxm[e] != 0) { + double iv = in[e]; + if (iv <= min[e]) + locus[e] = 0.0; + else if (iv >= max[e]) + locus[e] = 1.0; + else { + double lpl = max[e] - min[e]; /* Locus path length */ + if (lpl > 1e-6) + locus[e] = (iv - min[e])/lpl; + else + locus[e] = 0.0; + } + } + } + } + } else { + /* There should be no auxiliaries */ + for (e = 0; e < p->clutTable->di; e++) + locus[e] = 0.0; /* Return some safe values */ + } + return 0; +} + +/* Do input' -> input inverse lookup */ +int icxLuLut_inv_input(icxLuLut *p, double *out, double *in) { +#ifdef NEVER + return ((icmLuLut *)p->plu)->inv_input((icmLuLut *)p->plu, out, in); +#else + int rv = 0; + int i,j; + int nsoln; /* Number of solutions found */ + co pp[MAX_INVSOLN]; /* Room for all the solutions found */ + double cdir; + + DBR(("inv_input got DEV' %f %f %f %f\n",in[0],in[1],in[2],in[3])) + + for (i = 0; i < p->inputChan; i++) { + pp[0].p[0] = p->inputClipc[i]; + pp[0].v[0] = in[i]; + cdir = p->inputClipc[i] - in[i]; /* Clip towards output range */ + + nsoln = p->inputTable[i]->rev_interp ( + p->inputTable[i], /* this */ + RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */ + MAX_INVSOLN, /* Maximum number of solutions allowed for */ + NULL, /* No auxiliary input targets */ + &cdir, /* Clip vector direction and length */ + pp); /* Input and output values */ + + if (nsoln & RSPL_DIDCLIP) + rv = 1; + + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln == 1) { /* Exactly one solution */ + j = 0; + } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */ + error("Unexpected failure to find reverse solution for input table"); + return 2; + } else { /* Multiple solutions */ + /* Use a simple minded resolution - choose the one closest to the center */ + double bdist = 1e300; + int bsoln = 0; + /* Don't expect this - 1D luts are meant to be monotonic */ + warning("1D lut inversion got %d reverse solutions\n",nsoln); + warning("solution 0 = %f\n",pp[0].p[0]); + warning("solution 1 = %f\n",pp[1].p[0]); + for (j = 0; j < nsoln; j++) { + double tt; + tt = pp[i].p[0] - p->inputClipc[i]; + tt *= tt; + if (tt < bdist) { /* Better solution */ + bdist = tt; + bsoln = j; + } + } + j = bsoln; + } + out[i] = pp[j].p[0]; + } + + DBR(("inv_input returning DEV %f %f %f %f\n",out[0],out[1],out[2],out[3])) + return rv; +#endif /* NEVER */ +} + +/* Possible inverse matrix lookup */ +/* (Will do nothing if input is device space) */ +int icxLuLut_inv_matrix(icxLuLut *p, double *out, double *in) { + int rv = 0; + rv |= ((icmLuLut *)p->plu)->inv_matrix((icmLuLut *)p->plu, out, in); + return rv; +} + +/* Inverse input absolute intent conversion */ +/* (Will do nothing if input is device space) */ +int icxLuLut_inv_in_abs(icxLuLut *p, double *out, double *in) { + int rv = 0; + rv |= ((icmLuLut *)p->plu)->inv_in_abs((icmLuLut *)p->plu, out, in); + + if (p->ins == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + } + + return rv; +} + +/* Overall inverse lookup */ +/* Note that all auxiliary values are in input (NOT input') space */ +static int +icxLuLut_inv_lookup( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values/input auxiliary values */ +double *in /* Vector of input values */ +) { + icxLuLut *p = (icxLuLut *)pp; + int rv = 0; + int i; + double temp[MAX_CHAN]; + + DBOL(("xiccilu: input = %s\n", icmPdv(p->outputChan, in))); + if (p->mergeclut == 0) { /* Do this if it's not merger with clut */ + rv |= p->inv_out_abs (p, temp, in); + DBOL(("xiccilu: after inv abs = %s\n", icmPdv(p->outputChan, temp))); + rv |= p->inv_output (p, temp, temp); + DBOL(("xiccilu: after inv out = %s\n", icmPdv(p->outputChan, temp))); + } else { + for (i = 0; i < p->outputChan; i++) + temp[i] = in[i]; + } + DBOL(("xiccilu: aux targ = %s\n", icmPdv(p->inputChan,out))); + rv |= p->inv_clut (p, out, temp); + DBOL(("xiccilu: after inv clut = %s\n", icmPdv(p->inputChan,out))); + rv |= p->inv_input (p, out, out); + DBOL(("xiccilu: after inv input = %s\n", icmPdv(p->inputChan,out))); + rv |= p->inv_matrix (p, out, out); + DBOL(("xiccilu: after inv matrix = %s\n", icmPdv(p->inputChan,out))); + rv |= p->inv_in_abs (p, out, out); + DBOL(("xiccilu: after inv abs = %s\n", icmPdv(p->inputChan,out))); + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a nominated output PCS (ie. Absolute, Jab etc.), convert it in the bwd */ +/* direction into a relative XYZ or Lab PCS value */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuLut_bwd_outpcs_relpcs( +icxLuBase *pp, +icColorSpaceSignature os, /* Output space, XYZ or Lab */ +double *out, double *in) { + icxLuLut *p = (icxLuLut *)pp; + + if (p->outs == icxSigJabData) { + DBS(("bwd_outpcs_relpcs: Jab in = %s\n", icmPdv(3, in))); + p->cam->cam_to_XYZ(p->cam, out, in); + DBS(("bwd_outpcs_relpcs: abs XYZ = %s\n", icmPdv(3, out))); + /* Hack to prevent CAM02 weirdness being amplified by */ + /* per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + DBS(("bwd_outpcs_relpcs: after clipping -Y %s\n",icmPdv(p->outputChan, out))); + } + } else { + DBS(("bwd_outpcs_relpcs: abs PCS in = %s\n", icmPdv(3, out))); + icmCpy3(out, in); + } + + ((icmLuLut *)p->plu)->inv_out_abs((icmLuLut *)p->plu, out, out); + DBS(("bwd_outpcs_relpcs: rel PCS = %s\n", icmPdv(3, out))); + + if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmLab2XYZ(&icmD50, out, out); + DBS(("bwd_outpcs_relpcs: rel XYZ = %s\n", icmPdv(3, out))); + } else if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmXYZ2Lab(&icmD50, out, out); + DBS(("bwd_outpcs_relpcs: rel Lab = %s\n", icmPdv(3, out))); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Return LuLut information */ +static void icxLuLut_get_info( + icxLuLut *p, /* this */ + icmLut **lutp, /* Pointer to icc lut type return value */ + icmXYZNumber *pcswhtp, /* Pointer to profile PCS white point return value */ + icmXYZNumber *whitep, /* Pointer to profile absolute white point return value */ + icmXYZNumber *blackp /* Pointer to profile absolute black point return value */ +) { + ((icmLuLut *)p->plu)->get_info((icmLuLut *)p->plu, lutp, pcswhtp, whitep, blackp); +} + +/* Return the underlying Lut matrix */ +static void +icxLuLut_get_matrix ( + icxLuLut *p, + double m[3][3] +) { + ((icmLuLut *)p->plu)->get_matrix((icmLuLut *)p->plu, m); +} + +static void +icxLuLut_free( +icxLuBase *pp +) { + icxLuLut *p = (icxLuLut *)pp; + int i; + + for (i = 0; i < p->inputChan; i++) { + if (p->inputTable[i] != NULL) + p->inputTable[i]->del(p->inputTable[i]); + if (p->revinputTable[i] != NULL) + p->revinputTable[i]->del(p->revinputTable[i]); + } + + if (p->clutTable != NULL) + p->clutTable->del(p->clutTable); + + if (p->cclutTable != NULL) + p->cclutTable->del(p->cclutTable); + + for (i = 0; i < p->outputChan; i++) { + if (p->outputTable[i] != NULL) + p->outputTable[i]->del(p->outputTable[i]); + } + + if (p->plu != NULL) + p->plu->del(p->plu); + + if (p->cam != NULL) + p->cam->del(p->cam); + + if (p->absxyzlu != NULL) + p->absxyzlu->del(p->absxyzlu); + + free(p); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +static gamut *icxLuLutGamut(icxLuBase *plu, double detail); + +/* Do the basic icxLuLut creation and initialisation */ +static icxLuLut * +alloc_icxLuLut( + xicc *xicp, + icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ + int flags /* clip, merge flags */ +) { + icxLuLut *p; /* Object being created */ + icmLuLut *luluto = (icmLuLut *)plu; /* Lookup Lut type object */ + + if ((p = (icxLuLut *) calloc(1,sizeof(icxLuLut))) == NULL) + return NULL; + + p->pp = xicp; + p->plu = plu; + p->del = icxLuLut_free; + p->lutspaces = icxLutSpaces; + p->spaces = icxLuSpaces; + p->get_native_ranges = icxLu_get_native_ranges; + p->get_ranges = icxLu_get_ranges; + p->efv_wh_bk_points = icxLuEfv_wh_bk_points; + p->get_gamut = icxLuLutGamut; + p->fwd_relpcs_outpcs = icxLuLut_fwd_relpcs_outpcs; + p->bwd_outpcs_relpcs = icxLuLut_bwd_outpcs_relpcs; + p->nearclip = 0; /* Set flag defaults */ + p->mergeclut = 0; + p->noisluts = 0; + p->noipluts = 0; + p->nooluts = 0; + p->intsep = 0; + + p->lookup = icxLuLut_lookup; + p->in_abs = icxLuLut_in_abs; + p->matrix = icxLuLut_matrix; + p->input = icxLuLut_input; + p->clut = icxLuLut_clut; + p->clut_aux = icxLuLut_clut_aux; + p->output = icxLuLut_output; + p->out_abs = icxLuLut_out_abs; + + p->inv_lookup = icxLuLut_inv_lookup; + p->inv_in_abs = icxLuLut_inv_in_abs; + p->inv_matrix = icxLuLut_inv_matrix; + p->inv_input = icxLuLut_inv_input; + p->inv_clut = icxLuLut_inv_clut; + p->inv_clut_aux = icxLuLut_inv_clut_aux; + p->inv_output = icxLuLut_inv_output; + p->inv_out_abs = icxLuLut_inv_out_abs; + + p->clut_locus = icxLuLut_clut_aux_locus; + + p->get_info = icxLuLut_get_info; + p->get_matrix = icxLuLut_get_matrix; + + /* Setup all the rspl analogs of the icc Lut */ + /* NOTE: We assume that none of this relies on the flag settings, */ + /* since they will be set on our return. */ + + /* Get details of underlying, native icc color space */ + p->plu->lutspaces(p->plu, &p->natis, NULL, &p->natos, NULL, &p->natpcs); + + /* Get other details of conversion */ + p->plu->spaces(p->plu, NULL, &p->inputChan, NULL, &p->outputChan, NULL, NULL, NULL, NULL, NULL); + + /* Remember flags */ + p->flags = flags; + + /* Sanity check */ + if (p->inputChan > MXDI) { + sprintf(p->pp->err,"xicc can only handle input channels of %d or less",MXDI); + p->inputChan = MXDI; /* Avoid going outside array bounds */ + p->pp->errc = 1; + p->del((icxLuBase *)p); + return NULL; + } + if (p->outputChan > MXDO) { + sprintf(p->pp->err,"xicc can only handle output channels of %d or less",MXDO); + p->outputChan = MXDO; /* Avoid going outside array bounds */ + p->pp->errc = 1; + p->del((icxLuBase *)p); + return NULL; + } + + /* Get pointer to icmLut */ + luluto->get_info(luluto, &p->lut, NULL, NULL, NULL); + + return p; +} + +/* Initialise the clut ink limiting and black */ +/* generation information. */ +/* return 0 or error code */ +static int +setup_ink_icxLuLut( +icxLuLut *p, /* Object being initialised */ +icxInk *ink, /* inking details (NULL for default) */ +int setLminmax /* Figure the L locus for inking rule */ +) { + int devchan = p->func == icmFwd ? p->inputChan : p->outputChan; + + if (ink) { + p->ink = *ink; /* Copy the structure */ + } else { + p->ink.tlimit = 3.0; /* default ink limit of 300% */ + p->ink.klimit = -1.0; /* default no black limit */ + p->ink.KonlyLmin = 0; /* default don't use K only black as Locus Lmin */ + p->ink.k_rule = icxKluma5; /* default K generation rule */ + p->ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */ + p->ink.c.Kskew = ICXINKDEFSKEW; /* Default shape skew */ + p->ink.c.Kstle = 0.0; /* Min K at white end */ + p->ink.c.Kstpo = 0.0; /* Start of transition is at white */ + p->ink.c.Kenle = 1.0; /* Max K at black end */ + p->ink.c.Kenpo = 1.0; /* End transition at black */ + p->ink.c.Kshap = 1.0; /* Linear transition */ + } + + /* Normalise total and black ink limits */ + if (p->ink.tlimit <= 1e-4 || p->ink.tlimit >= (double)devchan) + p->ink.tlimit = -1.0; /* Turn ink limit off if not effective */ + if (devchan < 4 || p->ink.klimit < 0.0 || p->ink.klimit >= 1.0) + p->ink.klimit = -1.0; /* Turn black limit off if not effective */ + + /* Set the ink limit information for any reverse interpolation. */ + /* Calling this will clear the reverse interpolaton cache. */ + p->clutTable->rev_set_limit( + p->clutTable, /* this */ + p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL, + /* Optional input space limit() function. */ + /* Function should evaluate in[0..di-1], and return */ + /* number that is not to exceed 0.0. NULL if not used. */ + (void *)p, /* Context passed to limit() */ + 0.0 /* Value that limit() is not to exceed */ + ); + + /* Duplicate in the CAM clip rspl if it exists */ + if (p->cclutTable != NULL) { + p->cclutTable->rev_set_limit( + p->cclutTable, /* this */ + p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL, + /* Optional input space limit() function. */ + /* Function should evaluate in[0..di-1], and return */ + /* number that is not to exceed 0.0. NULL if not used. */ + (void *)p, /* Context passed to limit() */ + 0.0 /* Value that limit() is not to exceed */ + ); + } + + /* Figure Lmin and Lmax for icxKluma5 curve basis */ + if (setLminmax + && p->clutTable->di > p->clutTable->fdi) { /* If K generation makes sense */ + double wh[3], bk[3], kk[3]; + int mergeclut; /* Save/restore mergeclut value */ + + /* Get white/black in effective xlu PCS space */ + p->efv_wh_bk_points((icxLuBase *)p, wh, bk, kk); + + /* Convert from effective PCS (ie. Jab) to native XYZ or Lab PCS */ + mergeclut = p->mergeclut; /* Hack to be able to use inv_out_abs() */ + p->mergeclut = 0; /* if mergeclut is active. */ + icxLuLut_inv_out_abs(p, wh, wh); + icxLuLut_inv_out_abs(p, bk, bk); + icxLuLut_inv_out_abs(p, kk, kk); + p->mergeclut = mergeclut; /* Restore */ + + /* Convert to Lab PCS */ + if (p->natos == icSigXYZData) { /* Always do K rule in L space */ + icmXYZ2Lab(&icmD50, wh, wh); + icmXYZ2Lab(&icmD50, bk, bk); + icmXYZ2Lab(&icmD50, kk, kk); + } + p->Lmax = 0.01 * wh[0]; + if (p->ink.KonlyLmin != 0) + p->Lmin = 0.01 * kk[0]; + else + p->Lmin = 0.01 * bk[0]; + } else { + + /* Some sane defaults */ + p->Lmax = 1.0; + p->Lmin = 0.0; + } + + return 0; +} + + +/* Initialise the clut clipping information, ink limiting */ +/* and auxiliary parameter settings for all the rspl. */ +/* return 0 or error code */ +static int +setup_clip_icxLuLut( +icxLuLut *p /* Object being initialised */ +) { + double tmin[MXDIDO], tmax[MXDIDO]; + int i; + + /* Setup for inversion of multi-dim clut */ + + p->kch = -1; /* Not known yet */ + + /* Set auxiliaries */ + for (i = 0; i < p->inputChan; i++) + p->auxm[i] = 0; + + if (p->inputChan > p->outputChan) { + switch(p->natis) { + case icSigCmykData: + /* Should fix icm/xicc to remember K channel */ + p->auxm[3] = 1; /* K is the auxiliary channel */ + break; + default: + if (p->kch >= 0) /* It's been discovered */ + p->auxm[p->kch] = 1; + else { + p->pp->errc = 2; + sprintf(p->pp->err,"Unknown colorspace %s when setting auxliaries", + icm2str(icmColorSpaceSignature, p->natis)); + return p->pp->errc; + } + break; + } + } + +// p->auxlinf = NULL; /* Input space auxiliary linearisation function - not implemented */ +// p->auxlinf_ctx = NULL; /* Opaque context for auxliliary linearisation function */ + + /* Aproximate center of clut input gamut - used for */ + /* resolving multiple reverse solutions. */ + p->clutTable->get_in_range(p->clutTable, tmin, tmax); + for (i = 0; i < p->clutTable->di; i++) { + p->licent[i] = p->icent[i] = (tmin[i] + tmax[i])/2.0; + } + + /* Compute clip setup information relating to clut output gamut. */ + if (p->nearclip != 0 /* Near clip requested */ + || p->inputChan == 1) { /* or vector clip won't work */ + p->clip.nearclip = 1; + + } else { /* Vector clip */ + /* !!!! NOTE NOTE NOTE !!!! */ + /* We should re-write this to avoid calling p->clutTable->rev_interp(), */ + /* since this sets up all the rev acceleration tables for two calls, */ + /* if this lut is being setup from scattered data, and never used */ + /* for rev lookup */ + icColorSpaceSignature clutos = p->natos; + + fprintf(stderr, "!!!!! setup_clip_icxLuLut with vector clip - possibly unnecessary rev setup !!!!\n"); + p->clip.nearclip = 0; + p->clip.LabLike = 0; + p->clip.fdi = p->clutTable->fdi; + + switch(clutos) { + case icxSigJabData: + case icSigLabData: { + co pp; /* Room for all the solutions found */ + int nsoln; /* Number of solutions found */ + double cdir[MXDO]; /* Clip vector direction and length */ + + p->clip.LabLike = 1; + + /* Find high clip target */ + for (i = 0; i < p->inputChan; i++) + pp.p[i] = 0.0; /* Set aux values */ + pp.v[0] = 105.0; pp.v[1] = pp.v[2] = 0.0; /* PCS Target value */ + cdir[0] = cdir[1] = cdir[2] = 0.0; /* Clip Target */ + + p->inv_output(p, pp.v, pp.v); /* Compensate for output curve */ + p->inv_output(p, cdir, cdir); + + cdir[0] -= pp.v[0]; /* Clip vector */ + cdir[1] -= pp.v[1]; + cdir[2] -= pp.v[2]; + + /* PCS -> Device with clipping */ + nsoln = p->clutTable->rev_interp( + p->clutTable, /* rspl object */ + 0, /* No hint flags - might be in gamut, might vector clip */ + 1, /* Maxumum solutions to return */ + p->auxm, /* Auxiliary input targets */ + cdir, /* Clip vector direction and length */ + &pp); /* Input target and output solutions */ + /* returned solutions in pp[0..retval-1].p[] */ + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + + if (nsoln != 1) { + p->pp->errc = 2; + sprintf(p->pp->err,"Failed to find high clip target for Lab space"); + return p->pp->errc; + } + + p->clip.ocent[0] = pp.v[0] - 0.001; /* Got main target */ + p->clip.ocent[1] = pp.v[1]; + p->clip.ocent[2] = pp.v[2]; + + /* Find low clip target */ + pp.v[0] = -5.0; pp.v[1] = pp.v[2] = 0.0; /* PCS Target value */ + cdir[0] = 100.0; cdir[1] = cdir[2] = 0.0; /* Clip Target */ + + p->inv_output(p, pp.v, pp.v); /* Compensate for output curve */ + p->inv_output(p, cdir, cdir); + cdir[0] -= pp.v[0]; /* Clip vector */ + cdir[1] -= pp.v[1]; + cdir[2] -= pp.v[2]; + + /* PCS -> Device with clipping */ + nsoln = p->clutTable->rev_interp( + p->clutTable, /* rspl object */ + RSPL_WILLCLIP, /* Since there was no locus, we expect to have to clip */ + 1, /* Maxumum solutions to return */ + NULL, /* No auxiliary input targets */ + cdir, /* Clip vector direction and length */ + &pp); /* Input target and output solutions */ + /* returned solutions in pp[0..retval-1].p[] */ + nsoln &= RSPL_NOSOLNS; /* Get number of solutions */ + if (nsoln != 1) { + p->pp->errc = 2; + sprintf(p->pp->err,"Failed to find low clip target for Lab space"); + return p->pp->errc; + } + + p->clip.ocentv[0] = pp.v[0] + 0.001 - p->clip.ocent[0]; /* Raw line vector */ + p->clip.ocentv[1] = pp.v[1] - p->clip.ocent[1]; + p->clip.ocentv[2] = pp.v[2] - p->clip.ocent[2]; + + /* Compute vectors length */ + for (p->clip.ocentl = 0.0, i = 0; i < 3; i++) + p->clip.ocentl += p->clip.ocentv[i] * p->clip.ocentv[i]; + p->clip.ocentl = sqrt(p->clip.ocentl); + if (p->clip.ocentl <= 1e-8) + p->clip.ocentl = 0.0; + + break; + } + case icSigXYZData: + // ~~~~~~1 need to add this. + + default: + /* Do a crude approximation, that may not work. */ + p->clutTable->get_out_range(p->clutTable, tmin, tmax); + for (i = 0; i < p->clutTable->fdi; i++) { + p->clip.ocent[i] = (tmin[i] + tmax[i])/2.0; + } + p->clip.ocentl = 0.0; + break; + } + } + return 0; +} + +/* Function to pass to rspl to set secondary input/output transfer functions */ +static void +icxLuLut_inout_func( + void *pp, /* icxLuLut */ + double *out, /* output value */ + double *in /* inut value */ +) { + icxLuLut *p = (icxLuLut *)pp; /* this */ + icmLuLut *luluto = (icmLuLut *)p->plu; /* Get icmLuLut object */ + double tin[MAX_CHAN]; + double tout[MAX_CHAN]; + int i; + + if (p->iol_out == 0) { /* fwd input */ +#ifdef INK_LIMIT_TEST + tout[p->iol_ch] = in[0]; +#else + for (i = 0; i < p->inputChan; i++) + tin[i] = 0.0; + tin[p->iol_ch] = in[0]; + luluto->input(luluto, tout, tin); +#endif + } else if (p->iol_out == 1) { /* fwd output */ + for (i = 0; i < p->outputChan; i++) + tin[i] = 0.0; + tin[p->iol_ch] = in[0]; + luluto->output(luluto, tout, tin); + } else { /* bwd input */ +#ifdef INK_LIMIT_TEST + tout[p->iol_ch] = in[0]; +#else + for (i = 0; i < p->inputChan; i++) + tin[i] = 0.0; + tin[p->iol_ch] = in[0]; + luluto->inv_input(luluto, tout, tin); + /* This won't be valid if matrix is used or there is a PCS conversion !!! */ + /* Shouldn't be a problem because this is only used for inverting dev->PCS ? */ + luluto->inv_in_abs(luluto, tout, tout); +#endif + } + out[0] = tout[p->iol_ch]; +} + +/* Function to pass to rspl to set clut up, when mergeclut is set */ +static void +icxLuLut_clut_merge_func( + void *pp, /* icxLuLut */ + double *out, /* output value */ + double *in /* input value */ +) { + icxLuLut *p = (icxLuLut *)pp; /* this */ + icmLuLut *luluto = (icmLuLut *)p->plu; /* Get icmLuLut object */ + + luluto->clut(luluto, out, in); + luluto->output(luluto, out, out); + luluto->out_abs(luluto, out, out); + + if (p->outs == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + } +} + +/* Implimenation of Lut create from icc. */ +/* Note that xicc_get_luobj() will have set the pcsor & */ +/* intent to consistent values if Jab and/or icxAppearance */ +/* has been requested. */ +/* It will also have created the underlying icm lookup object */ +/* that is used to create and implement the icx one. The icm */ +/* will be used to translate from native to effective PCS, unless */ +/* the effective PCS is Jab, in which case the icm will be set to */ +/* have an effective PCS of XYZ. Since native<->effecive PCS conversion */ +/* is done at the to/from_abs() stage, none of it affects the individual */ +/* conversion steps, which will all talk the native PCS (unless merged). */ +static icxLuBase * +new_icxLuLut( +xicc *xicp, +int flags, /* clip, merge flags */ +icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ +icmLookupFunc func, /* Functionality requested */ +icRenderingIntent intent, /* Rendering intent */ +icColorSpaceSignature pcsor, /* PCS override (0 = def) */ +icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */ +icxInk *ink /* inking details (NULL for default) */ +) { + icxLuLut *p; /* Object being created */ + icmLuLut *luluto = (icmLuLut *)plu; /* Lookup Lut type object */ + + int i; + + /* Do basic creation and initialisation */ + if ((p = alloc_icxLuLut(xicp, plu, flags)) == NULL) + return NULL; + + p->func = func; + + /* Set LuLut "use" specific creation flags: */ + if (flags & ICX_CLIP_NEAREST) + p->nearclip = 1; + + if (flags & ICX_MERGE_CLUT) + p->mergeclut = 1; + + if (flags & ICX_FAST_SETUP) + p->fastsetup = 1; + + /* We're only implementing this under specific conditions. */ + if (flags & ICX_CAM_CLIP + && func == icmFwd + && !(p->mergeclut != 0 && pcsor == icxSigJabData)) /* Don't need camclip if merged Jab */ + p->camclip = 1; + + if (flags & ICX_INT_SEPARATE) { +fprintf(stderr,"~1 Internal optimised 4D separations not yet implemented!\n"); + p->intsep = 1; + } + + /* Init the CAM model if it will be used */ + if (pcsor == icxSigJabData || p->camclip) { + if (vc != NULL) /* One has been provided */ + p->vc = *vc; /* Copy the structure */ + else + xicc_enum_viewcond(xicp, &p->vc, -1, NULL, 0, NULL); /* Use a default */ + p->cam = new_icxcam(cam_default); + p->cam->set_view(p->cam, p->vc.Ev, p->vc.Wxyz, p->vc.La, p->vc.Yb, p->vc.Lv, p->vc.Yf, + p->vc.Fxyz, XICC_USE_HK); + } else { + p->cam = NULL; + } + + /* Remember the effective intent */ + p->intent = intent; + + /* Get the effective spaces of underlying icm */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, NULL, NULL, &p->pcs, NULL); + + /* Override with pcsor */ + /* We assume that any profile that has a CIE color as a "device" color */ + /* intends it to stay that way, and not be overridden. */ + if (pcsor == icxSigJabData) { + p->pcs = pcsor; + + if (xicp->pp->header->deviceClass == icSigAbstractClass) { + p->ins = pcsor; + p->outs = pcsor; + + } else if (xicp->pp->header->deviceClass != icSigLinkClass) { + if (func == icmBwd || func == icmGamut || func == icmPreview) + p->ins = pcsor; + if (func == icmFwd || func == icmPreview) + p->outs = pcsor; + } + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + /* If we have a merged clut, reflect this in the icx native PCS range. */ + /* Merging merges output processing (irrespective of whether we are using */ + /* the forward or backward cluts) */ + if (p->mergeclut != 0) { + int i; + for (i = 0; i < p->outputChan; i++) { + p->noutmin[i] = p->outmin[i]; + p->noutmax[i] = p->outmax[i]; + } + } + + /* ------------------------------- */ + /* Create rspl based input lookups */ + for (i = 0; i < p->inputChan; i++) { + if ((p->inputTable[i] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of input table rspl failed"); + p->del((icxLuBase *)p); + return NULL; + } + p->iol_out = 0; /* Input lookup */ + p->iol_ch = i; /* Chanel */ + p->inputTable[i]->set_rspl(p->inputTable[i], RSPL_NOFLAGS, + (void *)p, icxLuLut_inout_func, + &p->ninmin[i], &p->ninmax[i], (int *)&p->lut->inputEnt, &p->ninmin[i], &p->ninmax[i]); + } + + /* Setup center clip target for inversion */ + for (i = 0; i < p->inputChan; i++) { + p->inputClipc[i] = (p->ninmin[i] + p->ninmax[i])/2.0; + } + + /* Create rspl based reverse input lookups used in ink limit and locus range functions. */ + for (i = 0; i < p->inputChan; i++) { + int gres = p->inputTable[i]->g.mres; /* Same res as curve being inverted */ + if (gres < 256) + gres = 256; + if ((p->revinputTable[i] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of reverse input table rspl failed"); + p->del((icxLuBase *)p); + return NULL; + } + p->iol_out = 2; /* Input lookup */ + p->iol_ch = i; /* Chanel */ + p->revinputTable[i]->set_rspl(p->revinputTable[i], RSPL_NOFLAGS, + (void *)p, icxLuLut_inout_func, + &p->ninmin[i], &p->ninmax[i], &gres, &p->ninmin[i], &p->ninmax[i]); + } + + /* ------------------------------- */ + { + int gres[MXDI]; + + for (i = 0; i < p->inputChan; i++) + gres[i] = p->lut->clutPoints; + + /* Create rspl based multi-dim table */ + if ((p->clutTable = new_rspl((p->fastsetup ? RSPL_FASTREVSETUP : RSPL_NOFLAGS) + | (flags & ICX_VERBOSE ? RSPL_VERBOSE : RSPL_NOFLAGS), + p->inputChan, p->outputChan)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of clut table rspl failed"); + p->del((icxLuBase *)p); + return NULL; + } + + if (p->mergeclut == 0) { /* Do this if it's not merged with clut, */ + p->clutTable->set_rspl(p->clutTable, RSPL_NOFLAGS, + (void *)luluto, (void (*)(void *, double *, double *))luluto->clut, + p->ninmin, p->ninmax, gres, p->noutmin, p->noutmax); + + } else { /* If mergeclut */ + p->clutTable->set_rspl(p->clutTable, RSPL_NOFLAGS, + (void *)p, icxLuLut_clut_merge_func, + p->ninmin, p->ninmax, gres, p->noutmin, p->noutmax); + + } + + /* clut clipping is setup separately */ + } + + /* ------------------------------- */ + /* Create rspl based output lookups */ + for (i = 0; i < p->outputChan; i++) { + if ((p->outputTable[i] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of output table rspl failed"); + p->del((icxLuBase *)p); + return NULL; + } + p->iol_out = 1; /* Output lookup */ + p->iol_ch = i; /* Chanel */ + p->outputTable[i]->set_rspl(p->outputTable[i], RSPL_NOFLAGS, + (void *)p, icxLuLut_inout_func, + &p->noutmin[i], &p->noutmax[i], (int *)&p->lut->outputEnt, &p->noutmin[i], &p->noutmax[i]); + } + + /* Setup center clip target for inversion */ + for (i = 0; i < p->outputChan; i++) { + p->outputClipc[i] = (p->noutmin[i] + p->noutmax[i])/2.0; + } + + /* ------------------------------- */ + + /* Setup all the clipping, ink limiting and auxiliary stuff, */ + /* in case a reverse call is used. Only do this if we know */ + /* the reverse stuff isn't going to fail due to channel limits. */ + if (p->clutTable->within_restrictedsize(p->clutTable)) { + + if (setup_ink_icxLuLut(p, ink, 1) != 0) { + p->del((icxLuBase *)p); + return NULL; + } + + if (setup_clip_icxLuLut(p) != 0) { + p->del((icxLuBase *)p); + return NULL; + } + } + + return (icxLuBase *)p; +} + + +/* Function to pass to rspl to set clut up, when camclip is going to be used. */ +/* We use the temporary icm fwd absolute xyz lookup, then convert to CAM Jab. */ +static void +icxLuLut_clut_camclip_func( + void *pp, /* icxLuLut */ + double *out, /* output value */ + double *in /* inut value */ +) { + icxLuLut *p = (icxLuLut *)pp; /* this */ + icmLuLut *luluto = (icmLuLut *)p->absxyzlu; + + luluto->clut(luluto, out, in); + luluto->output(luluto, out, out); + luluto->out_abs(luluto, out, out); + p->cam->XYZ_to_cam(p->cam, out, out); +} + +/* Initialise the additional CAM space clut rspl, used to compute */ +/* reverse lookup CAM clipping results when the camclip flag is set. */ +/* return error code. */ +static int +icxLuLut_init_clut_camclip( +icxLuLut *p) { + int e, gres[MXDI]; + + /* Setup so clut contains transform to CAM Jab */ + /* (camclip is only used in fwd or invfwd direction lookup) */ + double cmin[3], cmax[3]; + cmin[0] = 0.0; cmax[0] = 100.0; /* Nominal Jab output ranges */ + cmin[1] = -128.0; cmax[1] = 128.0; + cmin[2] = -128.0; cmax[2] = 128.0; + + /* Get icm lookup we need for seting up and using CAM icx clut */ + if ((p->absxyzlu = p->pp->pp->get_luobj(p->pp->pp, icmFwd, icAbsoluteColorimetric, + icSigXYZData, icmLuOrdNorm)) == NULL) { + p->pp->errc = p->pp->pp->errc; /* Copy error to xicc */ + strcpy(p->pp->err, p->pp->pp->err); + return p->pp->errc; + } + + /* Create CAM rspl based multi-dim table */ + if ((p->cclutTable = new_rspl((p->fastsetup ? RSPL_FASTREVSETUP : RSPL_NOFLAGS) + | (p->flags & ICX_VERBOSE ? RSPL_VERBOSE : RSPL_NOFLAGS), + p->inputChan, p->outputChan)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of clut table rspl failed"); + return p->pp->errc; + } + + for (e = 0; e < p->inputChan; e++) + gres[e] = p->lut->clutPoints; + + /* Setup our special CAM space rspl */ + p->cclutTable->set_rspl(p->cclutTable, RSPL_NOFLAGS, (void *)p, + icxLuLut_clut_camclip_func, + p->ninmin, p->ninmax, gres, cmin, cmax); + + /* Duplicate the ink limit information for any reverse interpolation. */ + p->cclutTable->rev_set_limit( + p->cclutTable, /* this */ + p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL, + /* Optional input space limit() function. */ + /* Function should evaluate in[0..di-1], and return */ + /* number that is not to exceed 0.0. NULL if not used. */ + (void *)p, /* Context passed to limit() */ + 0.0 /* Value that limit() is not to exceed */ + ); + return 0; +} + +/* ========================================================== */ +/* xicc creation code */ +/* ========================================================== */ + +/* Callback for computing delta E squared for two output (PCS) */ +/* values, passed as a callback to xfit */ +static double xfit_to_de2(void *cntx, double *in1, double *in2) { + icxLuLut *p = (icxLuLut *)cntx; + double rv; + + if (p->pcs == icSigLabData) { +#ifdef USE_CIE94_DE + rv = icmCIE94sq(in1, in2); +#else + rv = icmLabDEsq(in1, in2); +#endif + } else { + double lab1[3], lab2[3]; + icmXYZ2Lab(&icmD50, lab1, in1); + icmXYZ2Lab(&icmD50, lab2, in2); +#ifdef USE_CIE94_DE + rv = icmCIE94sq(lab1, lab2); +#else + rv = icmLabDEsq(lab1, lab2); +#endif + } + return rv; +} + +/* Same as above plus partial derivatives */ +static double xfit_to_dde2(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) { + icxLuLut *p = (icxLuLut *)cntx; + double rv; + + if (p->pcs == icSigLabData) { + int j,k; + double tdout[2][3]; +#ifdef USE_CIE94_DE + rv = icxdCIE94sq(tdout, in1, in2); +#else + rv = icxdLabDEsq(tdout, in1, in2); +#endif + for (k = 0; k < 2; k++) { + for (j = 0; j < 3; j++) + dout[k][j] = tdout[k][j]; + } + } else { + double lab1[3], lab2[3]; + double dout12[2][3][3]; + double tdout[2][3]; + int i,j,k; + + icxdXYZ2Lab(&icmD50, lab1, dout12[0], in1); + icxdXYZ2Lab(&icmD50, lab2, dout12[1], in2); +#ifdef USE_CIE94_DE + rv = icxdCIE94sq(tdout, lab1, lab2); +#else + rv = icxdLabDEsq(tdout, lab1, lab2); +#endif + /* Compute partial derivative (is this correct ??) */ + for (k = 0; k < 2; k++) { + for (j = 0; j < 3; j++) { + dout[k][j] = 0.0; + for (i = 0; i < 3; i++) { + dout[k][j] += tdout[k][i] * dout12[k][i][j]; + } + } + } + } + return rv; +} + +#ifdef NEVER +/* Check partial derivative function within xfit_to_dde2() */ + +static double _xfit_to_dde2(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) { + icxLuLut *pp = (icxLuLut *)cntx; + int k, i; + double rv, drv; + double trv; + + rv = xfit_to_de2(cntx, in1, in2); + drv = xfit_to_dde2(cntx, dout, in1, in2); + + if (fabs(rv - drv) > 1e-6) + printf("######## DDE2: RV MISMATCH is %f should be %f ########\n",rv,drv); + + /* Check each parameter delta */ + for (k = 0; k < 2; k++) { + for (i = 0; i < 3; i++) { + double *in; + double del; + + if (k == 0) + in = in1; + else + in = in2; + + in[i] += 1e-9; + trv = xfit_to_de2(cntx, in1, in2); + in[i] -= 1e-9; + + /* Check that del is correct */ + del = (trv - rv)/1e-9; + if (fabs(dout[k][i] - del) > 0.04) { + printf("######## DDE2: EXCESSIVE at in[%d][%d] is %f should be %f ########\n",k,i,dout[k][i],del); + } + } + } + return rv; +} + +#define xfit_to_dde2 _xfit_to_dde2 + +#endif + +/* Context for rspl setting input and output curves */ +typedef struct { + int iix; + int oix; + xfit *xf; /* Optimisation structure */ +} curvectx; + +/* Function to pass to rspl to set input and output */ +/* transfer function for xicc lookup function */ +static void +set_linfunc( + void *cc, /* curvectx structure */ + double *out, /* Device output value */ + double *in /* Device input value */ +) { + curvectx *c = (curvectx *)cc; /* this */ + xfit *p = c->xf; + + if (c->iix >= 0) { /* Input curve */ + *out = p->incurve(p, *in, c->iix); + } else if (c->oix >= 0) { /* Output curve */ + *out = p->outcurve(p, *in, c->oix); + } +} + +/* Function to pass to rspl to set inverse input transfer function, */ +/* used for ink limiting calculation. */ +static void +icxLuLut_invinput_func( + void *cc, /* curvectx structure */ + double *out, /* Device output value */ + double *in /* Device input value */ +) { + curvectx *c = (curvectx *)cc; /* this */ + xfit *p = c->xf; + + *out = p->invincurve(p, *in, c->iix); +} + + +/* Functions to pass to icc settables() to setup icc A2B Lut: */ + +/* Input table */ +static void set_input(void *cntx, double *out, double *in) { + icxLuLut *p = (icxLuLut *)cntx; + + if (p->noisluts != 0 && p->noipluts != 0) { /* Input table must be linear */ + int i; + for (i = 0; i < p->inputChan; i++) + out[i] = in[i]; + } else { + if (p->input(p, out, in) > 1) + error ("%d, %s",p->pp->errc,p->pp->err); + } +} + +/* clut */ +static void set_clut(void *cntx, double *out, double *in) { + icxLuLut *p = (icxLuLut *)cntx; + int f; + + if (p->clut(p, out, in) > 1) + error ("%d, %s",p->pp->errc,p->pp->err); + + /* Convert from efective pcs to natural pcs */ + if (p->pcs != p->plu->icp->header->pcs) { + if (p->pcs == icSigLabData) + icmLab2XYZ(&icmD50, out, out); + else + icmXYZ2Lab(&icmD50, out, out); + } +} + +/* output */ +static void set_output(void *cntx, double *out, double *in) { + icxLuLut *p = (icxLuLut *)cntx; + + if (p->nooluts != 0) { /* Output table must be linear */ + int i; + for (i = 0; i < p->outputChan; i++) + out[i] = in[i]; + } else { + if (p->output(p, out, in) > 1) + error ("%d, %s",p->pp->errc,p->pp->err); + } +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Routine to figure out a suitable black point for CMYK */ + +/* Structure to hold optimisation information */ +typedef struct { + icxLuLut *p; /* Object being created */ + double toAbs[3][3]; /* To abs from aprox relative */ + double p1[3]; /* white pivot point in abs Lab */ + double p2[3]; /* Point on vector towards black */ + double toll; /* Tollerance of black direction */ +} bfinds; + +/* Optimise device values to minimise L, while remaining */ +/* within the ink limit, and staying in line between p1 (white) and p2 (black dir) */ +static double bfindfunc(void *adata, double pv[]) { + bfinds *b = (bfinds *)adata; + double rv = 0.0; + double tt[3], Lab[3]; + co bcc; + double lr, ta, tb, terr; /* L ratio, target a, target b, target error */ + double ovr; + +//printf("~1 bfindfunc got %f %f %f %f\n", pv[0], pv[1], pv[2], pv[3]); + /* See if over ink limit or outside device range */ + ovr = icxLimit(b->p, pv); /* > 0.0 if outside device gamut or ink limit */ + if (ovr < 0.0) + ovr = 0.0; +//printf("~1 bfindfunc got ovr %f\n", ovr); + + /* Compute the absolute Lab value: */ + b->p->input(b->p, bcc.p, pv); /* Through input tables */ + b->p->clutTable->interp(b->p->clutTable, &bcc); /* Through clut */ + b->p->output(b->p, bcc.v, bcc.v); /* Through the output tables */ + + if (b->p->pcs != icSigXYZData) /* Convert PCS to XYZ */ + icmLab2XYZ(&icmD50, bcc.v, bcc.v); + + /* Convert from relative to Absolute colorimetric */ + icmMulBy3x3(tt, b->toAbs, bcc.v); + icmXYZ2Lab(&icmD50, Lab, tt); /* Convert to Lab */ + +#ifdef DEBUG +printf("~1 p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]); +printf("~1 device value %f %f %f %f, Lab = %f %f %f\n",pv[0],pv[1],pv[2],pv[3],Lab[0],Lab[1],Lab[2]); +#endif + + /* Primary aim is to minimise L value */ + rv = Lab[0]; + + /* See how out of line from p1 to p2 we are */ + lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */ + ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */ + tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */ + + terr = (ta - Lab[1]) * (ta - Lab[1]) + + (tb - Lab[2]) * (tb - Lab[2]); + + if (terr < b->toll) /* Tollerance error doesn't count until it's over tollerance */ + terr = 0.0; + +#ifdef DEBUG +printf("~1 target error %f\n",terr); +#endif + rv += XICC_BLACK_FIND_ABERR_WEIGHT * terr; /* Make ab match more important than min. L */ + +#ifdef DEBUG +printf("~1 out of range error %f\n",ovr); +#endif + rv += 200 * ovr; + +#ifdef DEBUG +printf("~1 black find tc ret %f\n",rv); +#endif + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Create icxLuLut and underlying fwd Lut from scattered data */ +/* The scattered data is assumed to map Device -> native PCS */ +/* NOTE:- in theory once this icxLuLut is setup, it can be */ +/* called to translate color values. In practice I suspect */ +/* that the icxLuLut hasn't been setup completely enough to allows this. */ +/* Might be easier to close it and re-open it ? */ +static icxLuBase * +set_icxLuLut( +xicc *xicp, +icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ +icmLookupFunc func, /* Functionality requested */ +icRenderingIntent intent, /* Rendering intent */ +int flags, /* white/black point flags etc. */ +int nodp, /* Number of points */ +int nodpbw, /* Number of points to look for white & black patches in */ +cow *ipoints, /* Array of input points (Lab or XYZ normalized to 1.0) */ +icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */ +double dispLuminance, /* > 0.0 if display luminance value and is known */ +double wpscale, /* > 0.0 if white point is to be scaled */ +double smooth, /* RSPL smoothing factor, -ve if raw */ +double avgdev, /* reading Average Deviation as a prop. of the input range */ +icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */ +icxInk *ink, /* inking details (NULL for default) */ +int quality /* Quality metric, 0..3 */ +) { + icxLuLut *p; /* Object being created */ + icc *icco = xicp->pp; /* Underlying icc object */ + int luflags = 0; /* icxLuLut alloc clip, merge flags */ + int pcsy; /* Effective PCS L or Y chanel index */ + double pcsymax; /* Effective PCS L or Y maximum value */ + icmHeader *h = icco->header; /* Pointer to icc header */ + int maxchan; /* max(inputChan, outputChan) */ + int rsplflags = RSPL_NOFLAGS; /* Flags for scattered data rspl */ + int e, f, i, j; + double dwhite[MXDI], dblack[MXDI]; /* Device white and black values */ + double wp[3]; /* Absolute White point in XYZ */ + double bp[3]; /* Absolute Black point in XYZ */ + double dgwhite[MXDI]; /* Device space gamut boundary white (ie. RGB 1,1,1) */ + double oavgdev[MXDO]; /* Average output value deviation */ + int gres[MXDI]; /* RSPL/CLUT resolution */ + xfit *xf = NULL; /* Curve fitting class instance */ + + if (flags & ICX_VERBOSE) + rsplflags |= RSPL_VERBOSE; + +// if (flags & ICX_2PASSSMTH) +// rsplflags |= RSPL_2PASSSMTH; /* Smooth data using Gaussian */ + +// if (flags & ICX_EXTRA_FIT) +// rsplflags |= RSPL_EXTRAFIT2; /* Try extra hard to fit data */ + + luflags = flags; /* Transfer straight though ? */ + + /* Do basic creation and initialisation */ + if ((p = alloc_icxLuLut(xicp, plu, luflags)) == NULL) + return NULL; + + p->func = func; + + /* Set LuLut "use" specific creation flags: */ + if (flags & ICX_CLIP_NEAREST) + p->nearclip = 1; + + /* Set LuLut "create" specific flags: */ + if (flags & ICX_NO_IN_SHP_LUTS) + p->noisluts = 1; + + if (flags & ICX_NO_IN_POS_LUTS) + p->noipluts = 1; + + if (flags & ICX_NO_OUT_LUTS) + p->nooluts = 1; + + /* Get the effective spaces of underlying icm, and set icx the same */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, &p->intent, NULL, &p->pcs, NULL); + + /* For set_icx the effective pcs has to be the same as the native pcs */ + + if (p->pcs == icSigXYZData) { + pcsy = 1; /* Y of XYZ */ + pcsymax = 1.0; + } else { + pcsy = 0; /* L or Lab */ + pcsymax = 100.0; + } + + maxchan = p->inputChan > p->outputChan ? p->inputChan : p->outputChan; + + /* Translate overall average deviation into output channel deviation */ + /* (This is for rspl scattered data fitting smoothness adjustment) */ + /* (This could do with more tuning) */ + + /* For some unknown reason XYZ seems masively under-smoothed, so bump it up */ + if (p->pcs == icSigXYZData) { + oavgdev[0] = 1.0 * 0.60 * avgdev; + oavgdev[1] = 1.0 * 1.00 * avgdev; + oavgdev[2] = 1.0 * 0.60 * avgdev; + } else if (p->pcs == icSigLabData) { + oavgdev[0] = 1.00 * avgdev; + oavgdev[1] = 0.60 * avgdev; + oavgdev[2] = 0.60 * avgdev; + } else { /* Hmmm */ + for (f = 0; f < p->outputChan; f++) + oavgdev[f] = avgdev; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* ??? Does this ever happen with set_icxLuLut() ??? */ + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + /* ------------------------------- */ + + if (flags & ICX_VERBOSE) + printf("Estimating white point\n"); + + icmXYZ2Ary(wp, icmD50); /* Set a default value - D50 */ + icmXYZ2Ary(bp, icmBlack); /* Set a default value - absolute black */ + + { + /* Figure out as best we can the device white and black points */ + + /* Compute device white and black points as if */ + /* we are doing an Output or Display device */ + { + switch (h->colorSpace) { + + case icSigCmykData: + for (e = 0; e < p->inputChan; e++) { + dwhite[e] = 0.0; + dblack[e] = 1.0; + } + break; + case icSigCmyData: + for (e = 0; e < p->inputChan; e++) { + dwhite[e] = 0.0; + dblack[e] = 1.0; + } + break; + case icSigRgbData: + for (e = 0; e < p->inputChan; e++) { + dwhite[e] = 1.0; + dblack[e] = 0.0; + } + break; + + case icSigGrayData: { /* Could be additive or subtractive */ + double maxY, minY; /* Y min and max */ + double maxd, mind; /* Corresponding device values */ + + maxY = -1e8, minY = 1e8; + + /* Figure out if it's additive or subtractive */ + for (i = 0; i < nodpbw; i++) { + double Y; + if (p->pcs != icSigXYZData) { /* Convert white point to XYZ */ + double xyz[3]; + icmLab2XYZ(&icmD50, xyz, ipoints[i].v); + Y = xyz[1]; + } else { + Y = ipoints[i].v[1]; + } + + if (Y > maxY) { + maxY = Y; + maxd = ipoints[i].p[0]; + } + if (Y < minY) { + minY = Y; + mind = ipoints[i].p[0]; + } + } + if (maxd < mind) { /* Subtractive */ + for (e = 0; e < p->inputChan; e++) { + dwhite[e] = 0.0; + dblack[e] = 1.0; + } + } else { /* Additive */ + for (e = 0; e < p->inputChan; e++) { + dwhite[e] = 1.0; + dblack[e] = 0.0; + } + } + break; + } + default: + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuLut: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + } + /* dwhite is what we want for dgwhite[], used for XFIT_OUT_WP_REL_US */ + for (e = 0; e < p->inputChan; e++) + dgwhite[e] = dwhite[e]; + + /* If this is actuall an input device, lookup wp & bp */ + /* and override dwhite & dblack */ + if (h->deviceClass == icSigInputClass) { + double wpy = -1e60, bpy = 1e60; + int wix = -1, bix = -1; + /* We assume that the input target is well behaved, */ + /* and that it includes a white and black point patch, */ + /* and that they have the extreme L/Y values */ + + /* + NOTE that this may not be the best approach ! + It may be better to average the chromaticity + of all the neutral seeming patches, since + the whitest patch may have (for instance) + a blue tint. + */ + + /* Discover the white and black patches */ + for (i = 0; i < nodpbw; i++) { + double labv[3], yv; + + /* Create D50 Lab to allow some chromatic sensitivity */ + /* in picking the white point */ + if (p->pcs == icSigXYZData) + icmXYZ2Lab(&icmD50, labv, ipoints[i].v); + else + icmCpy3(labv,ipoints[i].v); + +#ifdef NEVER + /* Choose largest Y or L* */ + if (ipoints[i].v[pcsy] > wpy) { + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dwhite[e] = ipoints[i].p[e]; + wpy = ipoints[i].v[pcsy]; + wix = i; + } +#else + + /* Tilt things towards D50 neutral white patches */ + yv = labv[0] - 0.3 * sqrt(labv[1] * labv[1] + labv[2] * labv[2]); +//printf("~1 patch %d Lab = %s, yv = %f\n",i+1,icmPdv(3,labv),yv); + if (yv > wpy) { +//printf("~1 best so far\n"); + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dwhite[e] = ipoints[i].p[e]; + wpy = yv; + wix = i; + } +#endif + if (ipoints[i].v[pcsy] < bpy) { + bp[0] = ipoints[i].v[0]; + bp[1] = ipoints[i].v[1]; + bp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dblack[e] = ipoints[i].p[e]; + bpy = ipoints[i].v[pcsy]; + bix = i; + } + } + /* Make sure values are XYZ */ + if (p->pcs != icSigXYZData) { + icmLab2XYZ(&icmD50, wp, wp); + icmLab2XYZ(&icmD50, bp, bp); + } + + if (flags & ICX_VERBOSE) { + printf("Picked white patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + wix+1, icmPdv(p->inputChan, dwhite), icmPdv(3, wp), icmPLab(wp)); + printf("Picked black patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + bix+1, icmPdv(p->inputChan, dblack), icmPdv(3, bp), icmPLab(bp)); + } + + /* else Output or Display device */ + } else { + /* We assume that the output target is well behaved, */ + /* and that it includes a white point patch. */ + int nw = 0; + + wp[0] = wp[1] = wp[2] = 0.0; + + switch (h->colorSpace) { + + case icSigCmykData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] < 0.001 + && ipoints[i].p[1] < 0.001 + && ipoints[i].p[2] < 0.001 + && ipoints[i].p[3] < 0.001) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + case icSigCmyData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] < 0.001 + && ipoints[i].p[1] < 0.001 + && ipoints[i].p[2] < 0.001) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + case icSigRgbData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] > 0.999 + && ipoints[i].p[1] > 0.999 + && ipoints[i].p[2] > 0.999) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + + case icSigGrayData: { /* Could be additive or subtractive */ + double minwp[3], maxwp[3]; + int nminwp = 0, nmaxwp = 0; + + minwp[0] = minwp[1] = minwp[2] = 0.0; + maxwp[0] = maxwp[1] = maxwp[2] = 0.0; + + /* Look for both */ + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] < 0.001) + minwp[0] += ipoints[i].v[0]; + minwp[1] += ipoints[i].v[1]; + minwp[2] += ipoints[i].v[2]; { + nminwp++; + } + if (ipoints[i].p[0] > 0.999) + maxwp[0] += ipoints[i].v[0]; + maxwp[1] += ipoints[i].v[1]; + maxwp[2] += ipoints[i].v[2]; { + nmaxwp++; + } + } + if (nminwp > 0) { /* Subtractive */ + wp[0] = minwp[0]; + wp[1] = minwp[1]; + wp[2] = minwp[2]; + nw = nminwp; + if (minwp[pcsy]/nminwp < (0.5 * pcsymax)) + nw = 0; /* Looks like a mistake */ + } + if (nmaxwp > 0 /* Additive */ + && (nminwp == 0 || maxwp[pcsy]/nmaxwp > minwp[pcsy]/nminwp)) { + wp[0] = maxwp[0]; + wp[1] = maxwp[1]; + wp[2] = maxwp[2]; + nw = nmaxwp; + if (maxwp[pcsy]/nmaxwp < (0.5 * pcsymax)) + nw = 0; /* Looks like a mistake */ + } + break; + } + + default: + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuLut: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + + if (nw == 0) { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuLut: can't handle test points without a white patch"); + p->del((icxLuBase *)p); + return NULL; + } + wp[0] /= (double)nw; + wp[1] /= (double)nw; + wp[2] /= (double)nw; + if (p->pcs != icSigXYZData) /* Convert white point to XYZ */ + icmLab2XYZ(&icmD50, wp, wp); + } + + if (flags & ICX_VERBOSE) { + printf("Approximate White point XYZ = %s, Lab = %s\n", icmPdv(3,wp),icmPLab(wp)); + } + } + + if (h->colorSpace == icSigGrayData) { /* Don't use device or PCS curves for monochrome */ + p->noisluts = p->noipluts = p->nooluts = 1; + } + + if ((flags & ICX_VERBOSE) && (p->noisluts == 0 || p->noipluts == 0 || p->nooluts == 0)) + printf("Creating optimised per channel curves\n"); + + /* Set the target CLUT grid resolution so in/out curves can be optimised for it */ + for (e = 0; e < p->inputChan; e++) + gres[e] = p->lut->clutPoints; + + /* Setup and then create xfit object that does most of the work */ + { + int xfflags = 0; /* xfit flags */ + double in_min[MXDI]; /* Input value scaling minimum */ + double in_max[MXDI]; /* Input value scaling maximum */ + double out_min[MXDO]; /* Output value scaling minimum */ + double out_max[MXDO]; /* Output value scaling maximum */ + int iluord, sluord, oluord; + int iord[MXDI]; /* Input curve orders */ + int sord[MXDI]; /* Input sub-grid curve orders */ + int oord[MXDO]; /* Output curve orders */ + double shp_smooth[MXDI];/* Smoothing factors for each curve, nom = 1.0 */ + double out_smooth[MXDO]; + + optcomb tcomb = oc_ipo; /* Create all by default */ + + if ((xf = CAT2(new_, xfit)()) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of xfit object failed"); + p->del((icxLuBase *)p); + return NULL; + } + + /* Setup for optimising run */ + if (p->noisluts) + tcomb &= ~oc_i; + + if (p->noipluts) + tcomb &= ~oc_p; + + if (p->nooluts) + tcomb &= ~oc_o; + + if (flags & ICX_VERBOSE) + xfflags |= XFIT_VERB; + + if (flags & ICX_SET_WHITE) { + + xfflags |= XFIT_OUT_WP_REL; + if ((flags & ICX_SET_WHITE_C) == ICX_SET_WHITE_C) { + xfflags |= XFIT_OUT_WP_REL_C; + } + else if ((flags & ICX_SET_WHITE_US) == ICX_SET_WHITE_US) + xfflags |= XFIT_OUT_WP_REL_US; + + if (p->pcs != icSigXYZData) + xfflags |= XFIT_OUT_LAB; + } + + /* If asked, clip the absolute white point to have Y <= 1.0 ? */ + if (flags & ICX_CLIP_WB) + xfflags |= XFIT_CLIP_WP; + + /* With current B2A code, make sure a & b curves */ + /* pass through zero. */ + if (p->pcs == icSigLabData) { + xfflags |= XFIT_OUT_ZERO; + } + + /* Let xfit create the clut */ + xfflags |= XFIT_MAKE_CLUT; + + /* Set the curve orders for input (device) and output (PCS) */ + if (quality >= 3) { /* Ultra high */ + iluord = 25; + sluord = 4; + oluord = 25; + } else if (quality == 2) { /* High */ + iluord = 20; + sluord = 3; + oluord = 20; + } else if (quality == 1) { /* Medium */ + iluord = 17; + sluord = 2; + oluord = 17; + } else { /* Low */ + iluord = 10; + sluord = 1; + oluord = 10; + } + for (e = 0; e < p->inputChan; e++) { + iord[e] = iluord; + sord[e] = sluord; + in_min[e] = p->inmin[e]; + in_max[e] = p->inmax[e]; + shp_smooth[e] = SHP_SMOOTH; + } + + for (f = 0; f < p->outputChan; f++) { + oord[f] = oluord; + out_min[f] = p->outmin[f]; + out_max[f] = p->outmax[f]; + + /* Hack to prevent a convex L curve pushing */ + /* the clut L values above the maximum value */ + /* that can be represented, causing clipping. */ + /* Do this by making sure that the L curve pivots */ + /* through 100.0 to 100.0 */ + if (f == 0 && p->pcs == icSigLabData) { + if (out_min[f] < 0.0001 && out_max[f] > 100.0) { + out_max[f] = 100.0; + } + } + out_smooth[f] = OUT_SMOOTH1; + if (f != 0 && p->pcs == icSigLabData) /* a* & b* */ + out_smooth[f] = OUT_SMOOTH2; + } + +//printf("~1 wp before xfit %f %f %f\n", wp[0], wp[1], wp[2]); + /* Create input, sub and output per channel curves (if configured), */ + /* adjust for white point to make relative (if configured), */ + /* and create clut rspl using xfit class. */ + /* The true white point for the returned curves and rspl is returned. */ + if (xf->fit(xf, xfflags, p->inputChan, p->outputChan, + rsplflags, wp, dwhite, wpscale, dgwhite, + ipoints, nodp, skm, in_min, in_max, gres, out_min, out_max, + smooth, oavgdev, iord, sord, oord, shp_smooth, out_smooth, tcomb, + (void *)p, xfit_to_de2, xfit_to_dde2) != 0) { + p->pp->errc = 2; + sprintf(p->pp->err,"xfit fitting failed"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } +//printf("~1 wp after xfit %f %f %f\n", wp[0], wp[1], wp[2]); + + /* - - - - - - - - - - - - - - - */ + /* Set the xicc input curve rspl */ + for (e = 0; e < p->inputChan; e++) { + curvectx cx; + + cx.xf = xf; + cx.oix = -1; + cx.iix = e; + + if ((p->inputTable[e] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of input table rspl failed"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + + p->inputTable[e]->set_rspl(p->inputTable[e], RSPL_NOFLAGS, + (void *)&cx, set_linfunc, + &p->ninmin[e], &p->ninmax[e], + (int *)&p->lut->inputEnt, + &p->ninmin[e], &p->ninmax[e]); + } + + /* - - - - - - - - - - - - - - - */ + /* Set the xicc output curve rspl */ + for (f = 0; f < p->outputChan; f++) { + curvectx cx; + + cx.xf = xf; + cx.iix = -1; + cx.oix = f; + + if ((p->outputTable[f] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of output table rspl failed"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + + p->outputTable[f]->set_rspl(p->outputTable[f], RSPL_NOFLAGS, + (void *)&cx, set_linfunc, + &p->noutmin[f], &p->noutmax[f], + (int *)&p->lut->outputEnt, + &p->noutmin[f], &p->noutmax[f]); + + } + } + + if (flags & ICX_VERBOSE) + printf("Creating fast inverse input lookups\n"); + + /* Create rspl based reverse input lookups used in ink limit function. */ + for (e = 0; e < p->inputChan; e++) { + int res = 256; + curvectx cx; + + cx.xf = xf; + cx.oix = -1; + cx.iix = e; + + if ((p->revinputTable[e] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + p->pp->errc = 2; + sprintf(p->pp->err,"Creation of reverse input table rspl failed"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + p->revinputTable[e]->set_rspl(p->revinputTable[e], RSPL_NOFLAGS, + (void *)&cx, icxLuLut_invinput_func, + &p->ninmin[e], &p->ninmax[e], &res, &p->ninmin[e], &p->ninmax[e]); + } + + + /* ------------------------------- */ + /* Set clut lookup table from xfit */ + p->clutTable = xf->clut; + xf->clut = NULL; + + /* Setup all the clipping, ink limiting and auxiliary stuff, */ + /* in case a reverse call is used. Need to avoid relying on inking */ + /* stuff that makes use of the white/black points, since they haven't */ + /* been set up properly yet. */ + if (setup_ink_icxLuLut(p, ink, 0) != 0) { + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + + /* Deal with finalizing white/black points */ + { + + if ((flags & ICX_SET_WHITE) && (flags & ICX_VERBOSE)) { + printf("White point XYZ = %s, Lab = %s\n", icmPdv(3,wp),icmPLab(wp)); + } + + /* Lookup the black point */ + { /* Black Point Tag: */ + co bcc; + + if (flags & ICX_VERBOSE) + printf("Find black point\n"); + + /* !!! Hmm. For CMY and RGB we are simply using the device */ + /* combination values as the black point. In reality we might */ + /* want to have the option of using a neutral black point, */ + /* just like CMYK ?? */ + + /* For CMYK devices, we choose a black that has minumum L within */ + /* the ink limits, and if XICC_NEUTRAL_CMYK_BLACK it will in the direction */ + /* that has the same chromaticity as the white point, else choose the same */ + /* Lab vector direction as K, with the minimum L value. */ + /* (Note this is duplicated in xicc.c icxLu_comp_bk_point() !!!) */ + if (h->deviceClass != icSigInputClass + && h->colorSpace == icSigCmykData) { + bfinds bfs; /* Callback context */ + double sr[MXDO]; /* search radius */ + double tt[MXDO]; /* Temporary */ + double rs0[MXDO], rs1[MXDO]; /* Random start candidates */ + int trial; + double brv; + int kch = 3; + + /* Setup callback function context */ + bfs.p = p; + bfs.toll = XICC_BLACK_POINT_TOLL; + + /* !!! we should use an accessor funcion of xfit !!! */ + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + bfs.toAbs[i][j] = xf->toAbs[i][j]; + } + } + + /* Lookup abs Lab value of white point */ + icmXYZ2Lab(&icmD50, bfs.p1, wp); + +#ifdef XICC_NEUTRAL_CMYK_BLACK + icmScale3(tt, wp, 0.02); /* Scale white XYZ towards 0,0,0 */ + icmXYZ2Lab(&icmD50, bfs.p2, tt); /* Convert black direction to Lab */ + if (flags & ICX_VERBOSE) + printf("Neutral black direction (Lab) = %f %f %f\n",bfs.p2[0], bfs.p2[1], bfs.p2[2]); + +#else /* K direction black */ + /* Now figure abs Lab value of K only, as the direction */ + /* to use for the rich black. */ + for (e = 0; e < p->inputChan; e++) + bcc.p[e] = 0.0; + if (p->ink.klimit < 0.0) + bcc.p[kch] = 1.0; + else + bcc.p[kch] = p->ink.klimit; /* K value */ + + p->input(p, bcc.p, bcc.p); /* Through input tables */ + p->clutTable->interp(p->clutTable, &bcc); /* Through clut */ + p->output(p, bcc.v, bcc.v); /* Through the output tables */ + + if (p->pcs != icSigXYZData) /* Convert PCS to XYZ */ + icmLab2XYZ(&icmD50, bcc.v, bcc.v); + + /* Convert from relative to Absolute colorimetric */ + icmMulBy3x3(tt, xf->toAbs, bcc.v); + icmXYZ2Lab(&icmD50, bfs.p2, tt); /* Convert K only black point to Lab */ + + if (flags & ICX_VERBOSE) + printf("K only black direction (Lab) = %f %f %f\n",bfs.p2[0], bfs.p2[1], bfs.p2[2]); +#endif + /* Start with the K only as the current best value */ + brv = bfindfunc((void *)&bfs, bcc.p); +//printf("~1 initial brv for K only = %f\n",brv); + + /* Set the random start 0 location as 000K */ + /* and the random start 1 location as CMY0 */ + { + double tv; + + for (e = 0; e < p->inputChan; e++) + rs0[e] = 0.0; + if (p->ink.klimit < 0.0) + rs0[kch] = 1.0; + else + rs0[kch] = p->ink.klimit; /* K value */ + + if (p->ink.tlimit < 0.0) + tv = 1.0; + else + tv = p->ink.tlimit/(p->inputChan - 1.0); + for (e = 0; e < p->inputChan; e++) + rs1[e] = tv; + rs1[kch] = 0.0; /* K value */ + } + + /* Find the device black point using optimization */ + /* Do several trials to avoid local minima. */ + rand32(0x12345678); /* Make trial values deterministic */ + for (trial = 0; trial < 200; trial++) { + double rv; /* Temporary */ + + /* Start first trial at 000K */ + if (trial == 0) { + for (j = 0; j < p->inputChan; j++) { + tt[j] = rs0[j]; + sr[j] = 0.1; + } + + } else { + /* Base is random between 000K and CMY0: */ + if (trial < 100) { + rv = d_rand(0.0, 1.0); + for (j = 0; j < p->inputChan; j++) { + tt[j] = rv * rs0[j] + (1.0 - rv) * rs1[j]; + sr[j] = 0.1; + } + /* Base on current best */ + } else { + for (j = 0; j < p->inputChan; j++) { + tt[j] = bcc.p[j]; + sr[j] = 0.1; + } + } + + /* Then add random start offset */ + for (rv = 0.0, j = 0; j < p->inputChan; j++) { + tt[j] += d_rand(-0.5, 0.5); + if (tt[j] < 0.0) + tt[j] = 0.0; + else if (tt[j] > 1.0) + tt[j] = 1.0; + } + } + + /* Clip to be within ink limit */ + icxDoLimit(p, tt, tt); + + if (powell(&rv, p->inputChan, tt, sr, 0.000001, 1000, + bfindfunc, (void *)&bfs, NULL, NULL) == 0) { +//printf("~1 trial %d, rv %f bp %f %f %f %f\n",trial,rv,tt[0],tt[1],tt[2],tt[3]); + if (rv < brv) { +//printf("~1 new best\n"); + brv = rv; + for (j = 0; j < p->inputChan; j++) + bcc.p[j] = tt[j]; + } + } + } + if (brv > 1000.0) + error ("set_icxLuLut: Black point powell failed"); + + /* Make sure resulting device values are strictly in range */ + for (j = 0; j < p->inputChan; j++) { + if (bcc.p[j] < 0.0) + bcc.p[j] = 0.0; + else if (bcc.p[j] > 1.0) + bcc.p[j] = 1.0; + } + /* Now have device black in bcc.p[] */ +//printf("~1 Black point is CMYK %f %f %f %f\n", bcc.p[0], bcc.p[1], bcc.p[2], bcc.p[3]); + + /* Else not a CMYK output device, */ + /* use the previously determined device black value. */ + } else { + for (e = 0; e < p->inputChan; e++) + bcc.p[e] = dblack[e]; + } + + /* Lookup the PCS for the device black: */ + p->input(p, bcc.p, bcc.p); /* Through input tables */ + p->clutTable->interp(p->clutTable, &bcc); /* Through clut */ + p->output(p, bcc.v, bcc.v); /* Through the output tables */ + + if (p->pcs != icSigXYZData) /* Convert PCS to XYZ */ + icmLab2XYZ(&icmD50, bcc.v, bcc.v); + + /* Convert from relative to Absolute colorimetric */ + icmMulBy3x3(bp, xf->toAbs, bcc.v); + + /* Got XYZ black point in bp[] */ + if (flags & ICX_VERBOSE) { + printf("Black point XYZ = %s, Lab = %s\n", icmPdv(3,bp),icmPLab(bp)); + } + + /* Some ICC sw gets upset if the bp is at all -ve. */ + /* Clip it if this is the case */ + /* (Or we could get xfit to rescale the rspl instead ??) */ + if ((flags & ICX_CLIP_WB) + && (bp[0] < 0.0 || bp[1] < 0.0 || bp[2] < 0.0)) { + if (bp[0] < 0.0) + bp[0] = 0.0; + if (bp[1] < 0.0) + bp[1] = 0.0; + if (bp[2] < 0.0) + bp[2] = 0.0; + if (flags & ICX_VERBOSE) { + printf("Black point clipped to XYZ = %s, Lab = %s\n",icmPdv(3,bp),icmPLab(bp)); + } + } + } + + /* If this is a display, adjust the white point to be */ + /* exactly Y = 1.0, and compensate the dispLuminance */ + /* and black point accordingly. The Lut is already set to */ + /* assume device white maps to perfect PCS white. */ + if (h->deviceClass == icSigDisplayClass) { + double scale = 1.0/wp[1]; + int i; + + dispLuminance /= scale; + + for (i = 0; i < 3; i++) { + wp[i] *= scale; + bp[i] *= scale; + } + } + + if (h->deviceClass == icSigDisplayClass + && dispLuminance > 0.0) { + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigLuminanceTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_luminance: couldn't find luminance tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"luminance: tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = 0.0; + wo->data[0].Y = dispLuminance * wp[1]; + wo->data[0].Z = 0.0; + + if (flags & ICX_VERBOSE) + printf("Display Luminance = %f\n", wo->data[0].Y); + } + + /* Write white and black points */ + if (flags & ICX_SET_WHITE) { /* White Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaWhitePointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find white tag"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: white tag has wrong type"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = wp[0]; + wo->data[0].Y = wp[1]; + wo->data[0].Z = wp[2]; + } + if (flags & ICX_SET_BLACK) { /* Black Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaBlackPointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find black tag"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: black tag has wrong type"); + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = bp[0]; + wo->data[0].Y = bp[1]; + wo->data[0].Z = bp[2]; + } + if ((flags & ICX_SET_WHITE) || (flags & ICX_SET_BLACK)) { + /* Make sure ICC white/black point lookup notices the new white and black points */ + p->plu->init_wh_bk(p->plu); + } + + /* Setup the clut clipping, ink limiting and auxiliary stuff again */ + /* since re_set_rspl will have invalidated */ + if (setup_ink_icxLuLut(p, ink, 1) != 0) { + xf->del(xf); + p->del((icxLuBase *)p); + return NULL; + } + } + + /* Done with xfit now */ + xf->del(xf); + xf = NULL; + + if (setup_clip_icxLuLut(p) != 0) { + p->del((icxLuBase *)p); + return NULL; + } + + /* ------------------------------- */ + +//Debugging clipping of clut +//printf("~1 xlut.c: noutmin = %f %f %f\n", p->noutmin[0], p->noutmin[1], p->noutmin[2]); +//printf("~1 xlut.c: noutmax = %f %f %f\n", p->noutmax[0], p->noutmax[1], p->noutmax[2]); +//printf("~1 xlut.c: outmin = %f %f %f\n", p->outmin[0], p->outmin[1], p->outmin[2]); +//printf("~1 xlut.c: outmax = %f %f %f\n", p->outmax[0], p->outmax[1], p->outmax[2]); + + /* Do a specific test for out of Lab encoding range RGB primaries */ + /* (A more general check seems to get false positives - why ??) */ + if (h->pcs == icSigLabData + && ( h->deviceClass == icSigDisplayClass + || h->deviceClass == icSigOutputClass) + && h->colorSpace == icSigRgbData) { + double dev[3] = { 0.0, 0.0, 0.0 }; + double pcs[3]; + double clip = 0; + + for (i = 0; i < 3; i++) { + dev[i] = 1.0; + + if (p->clut(p, pcs, dev) > 1) + error ("%d, %s",p->pp->errc,p->pp->err); + + /* Convert from efective pcs to natural pcs */ + if (p->pcs != icSigLabData) + icmXYZ2Lab(&icmD50, pcs, pcs); + + if (pcs[1] < -128.0 || pcs[1] > 128.0 + || pcs[2] < -128.0 || pcs[2] > 128.0) { + warning("\n *** %s primary value can't be encoded in L*a*b* PCS (%f %f %f)", + i == 0 ? "Red" : i == 1 ? "Green" : "Blue", pcs[0],pcs[1],pcs[2]); + clip = 1; + } + dev[i] = 0.0; + } + if (clip) + a1logw(g_log," *** Try switching to XYZ PCS ***\n"); + } + + /* Use our rspl's to set the icc Lut AtoB table values. */ + /* Use helper function to do the hard work. */ + if (p->lut->set_tables(p->lut, ICM_CLUT_SET_EXACT, (void *)p, + h->colorSpace, /* Input color space */ + h->pcs, /* Output color space */ + set_input, /* Input transfer function, Dev->Dev' */ + NULL, NULL, /* Use default Maximum range of Dev' values */ + set_clut, /* Dev' -> PCS' transfer function */ + NULL, NULL, /* Use default Maximum range of PCS' values */ + set_output /* Linear output transform PCS'->PCS */ + ) != 0) + error("Setting 16 bit %s->%s Lut failed: %d, %s", + icm2str(icmColorSpaceSignature, h->colorSpace), + icm2str(icmColorSpaceSignature, h->pcs), + p->pp->pp->errc,p->pp->pp->err); + +#ifdef WARN_CLUT_CLIPPING + if (p->pp->pp->warnc) { + warning("Values clipped in setting A2B LUT!"); + if (p->pp->pp->warnc == 2 && h->pcs == icSigLabData) { + a1logw(g_log,"*** Try switching to XYZ PCS ***\n"); + } + } +#endif /* WARN_CLUT_CLIPPING */ + + /* Init a CAM model in case it will be used (ie. in profile with camclip flag) */ + if (vc != NULL) /* One has been provided */ + p->vc = *vc; /* Copy the structure */ + else + xicc_enum_viewcond(xicp, &p->vc, -1, NULL, 0, NULL); /* Use a default */ + p->cam = new_icxcam(cam_default); + p->cam->set_view(p->cam, p->vc.Ev, p->vc.Wxyz, p->vc.La, p->vc.Yb, p->vc.Lv, p->vc.Yf, + p->vc.Fxyz, XICC_USE_HK); + + if (flags & ICX_VERBOSE) + printf("Done A to B table creation\n"); + + return (icxLuBase *)p; +} + +/* ========================================================== */ +/* Gamut boundary code. */ +/* ========================================================== */ + + +/* Context for creating gamut boundary points fro, xicc */ +typedef struct { + gamut *g; /* Gamut being created */ + icxLuLut *x; /* xLut we are working from */ + icxLuBase *flu; /* Forward xlookup */ + double in[MAX_CHAN]; /* Device input values */ +} lutgamctx; + +/* Function to hand to zbrent to find a clut input' value at the ink limit */ +/* Returns value < 0.0 when within gamut, > 0.0 when out of gamut */ +static double icxLimitFind(void *fdata, double tp) { + int i; + double in[MAX_CHAN]; + lutgamctx *p = (lutgamctx *)fdata; + double tt; + + for (i = 0; i < p->x->inputChan; i++) { + in[i] = tp * p->in[i]; /* Scale given input value */ + } + + tt = icxLimitD(p->x, in); + + return tt; +} + +/* Function to pass to rspl to create gamut boundary from */ +/* forward xLut transform grid points. */ +static void +lutfwdgam_func( + void *pp, /* lutgamctx structure */ + double *out, /* output' value at clut grid point (ie. PCS' value) */ + double *in /* input' value at clut grid point (ie. device' value) */ +) { + lutgamctx *p = (lutgamctx *)pp; + double pcso[3]; /* PCS output value */ + + /* Figure if we are over the ink limit. */ + if ( (p->x->ink.tlimit >= 0.0 || p->x->ink.klimit >= 0.0) + && icxLimitD(p->x, in) > 0.0) { + int i; + double sf; + + /* We are, so use the bracket search to discover a scale */ + /* for the clut input' value that will put us on the ink limit. */ + + for (i = 0; i < p->x->inputChan; i++) + p->in[i] = in[i]; + + if (zbrent(&sf, 0.0, 1.0, 1e-4, icxLimitFind, pp) != 0) { + return; /* Give up */ + } + + /* Compute ink limit value */ + for (i = 0; i < p->x->inputChan; i++) + p->in[i] = sf * in[i]; + + /* Compute the clut output for this clut input */ + p->x->clut(p->x, pcso, p->in); + p->x->output(p->x, pcso, pcso); + p->x->out_abs(p->x, pcso, pcso); + } else { /* No ink limiting */ + /* Convert the clut PCS' values to PCS output values */ + p->x->output(p->x, pcso, out); + p->x->out_abs(p->x, pcso, pcso); + } + + /* Expand the gamut surface with this point */ + p->g->expand(p->g, pcso); + + /* Leave out[] unchanged */ +} + + +/* Function to pass to rspl to create gamut boundary from */ +/* backwards Lut transform. This is called for every node in the */ +/* B2A grid. */ +static void +lutbwdgam_func( + void *pp, /* lutgamctx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + lutgamctx *p = (lutgamctx *)pp; + double devo[MAX_CHAN]; /* Device output value */ + double pcso[3]; /* PCS output value */ + + /* Convert the clut values to device output values */ + p->x->output(p->x, devo, out); /* (Device never uses out_abs()) */ + + /* Convert from device values to PCS values */ + p->flu->lookup(p->flu, pcso, devo); + + /* Expand the gamut surface with this point */ + p->g->expand(p->g, pcso); + + /* Leave out[] unchanged */ +} + +/* Given an xicc lookup object, return a gamut object. */ +/* Note that the PCS must be Lab or Jab */ +/* An icxLuLut type must be icmFwd or icmBwd, */ +/* and for icmFwd, the ink limit (if supplied) will be applied. */ +/* Return NULL on error, check errc+err for reason */ +static gamut *icxLuLutGamut( +icxLuBase *plu, /* this */ +double detail /* gamut detail level, 0.0 = def */ +) { + xicc *p = plu->pp; /* parent xicc */ + icxLuLut *luluto = (icxLuLut *)plu; /* Lookup xLut type object */ + icColorSpaceSignature ins, pcs, outs; + icmLookupFunc func; + icRenderingIntent intent; + double white[3], black[3], kblack[3]; + int inn, outn; + gamut *gam; + + /* get some details */ + plu->spaces(plu, &ins, &inn, &outs, &outn, NULL, &intent, &func, &pcs); + + if (func != icmFwd && func != icmBwd) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface for anything other than Device <-> PCS is not supported."); + return NULL; + } + + if (pcs != icSigLabData && pcs != icxSigJabData) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface PCS of other than Lab or Jab is not supported."); + return NULL; + } + + if (func == icmFwd) { + lutgamctx cx; + + cx.g = gam = new_gamut(detail, pcs == icxSigJabData, 0); + cx.x = luluto; + + /* Scan through grid. */ + /* (Note this can give problems for a strange input space - ie. Lab */ + /* and a low grid resolution - ie. 2) */ + luluto->clutTable->scan_rspl( + luluto->clutTable, /* this */ + RSPL_NOFLAGS, /* Combination of flags */ + (void *)&cx, /* Opaque function context */ + lutfwdgam_func /* Function to set from */ + ); + + /* Make sure the white and point goes in too, if it isn't in the grid */ + plu->efv_wh_bk_points(plu, white, NULL, NULL); + gam->expand(gam, white); + + if (detail == 0.0) + detail = 10.0; + + /* If the gamut is more than cursary, add some more detail surface points */ + if (detail < 20.0 || luluto->clutTable->g.mres < 4) { + int res; + DCOUNT(co, MAX_CHAN, inn, 0, 0, 2); + + res = (int)(500.0/detail); /* Establish an appropriate sampling density */ + if (res < 10) + res = 10; + + /* Itterate over all the faces in the device space */ + DC_INIT(co); + while(!DC_DONE(co)) { /* Count through the corners of hyper cube */ + int e, m1, m2; + double in[MAX_CHAN]; + double out[3]; + + for (e = 0; e < inn; e++) { /* Base value */ + in[e] = (double)co[e]; /* Base value */ + in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e]) + + luluto->inmin[e]; + } + + /* Figure if we are over the ink limit. */ + if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0) + && icxLimit(luluto, in) > 0.0) { + DC_INC(co); + continue; /* Skip points over limit */ + } + + /* Scan only device surface */ + for (m1 = 0; m1 < inn; m1++) { /* Choose first coord to scan */ + if (co[m1] != 0) + continue; /* Not at lower corner */ + for (m2 = m1 + 1; m2 < inn; m2++) { /* Choose second coord to scan */ + int x, y; + + if (co[m2] != 0) + continue; /* Not at lower corner */ + + for (x = 0; x < res; x++) { /* step over surface */ + in[m1] = x/(res - 1.0); + in[m1] = in[m1] * (luluto->inmax[m1] - luluto->inmin[m1]) + + luluto->inmin[m1]; + for (y = 0; y < res; y++) { + in[m2] = y/(res - 1.0); + in[m2] = in[m2] * (luluto->inmax[m2] - luluto->inmin[m2]) + + luluto->inmin[m2]; + + /* Figure if we are over the ink limit. */ + if ( (luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0) + && icxLimit(luluto, in) > 0.0) { + continue; /* Skip points over limit */ + } + + luluto->lookup((icxLuBase *)luluto, out, in); + gam->expand(gam, out); + } + } + } + } + /* Increment index within block */ + DC_INC(co); + } + } + + /* Now set the cusp points by itterating through colorant 0 & 100% combinations */ + /* If we know what sort of space it is: */ + if (ins == icSigRgbData || ins == icSigCmyData || ins == icSigCmykData) { + DCOUNT(co, 3, 3, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[MAX_CHAN]; + double out[3]; + + if (!(co[0] == 0 && co[1] == 0 && co[2] == 0) + && !(co[0] == 1 && co[1] == 1 && co[2] == 1)) { /* Skip white and black */ + for (e = 0; e < 3; e++) + in[e] = (double)co[e]; + in[e] = 0; /* K */ + + /* Always use the device->PCS conversion */ + if (luluto->lookup((icxLuBase *)luluto, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->setcusps(gam, 3, out); + } + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + + /* Do all ink combinations and hope we can sort it out */ + /* (This may not be smart, since bodgy cusp values will cause gamut mapping to fail...) */ + } else if (ins != icSigXYZData + && ins != icSigLabData + && ins != icSigLuvData + && ins != icSigYxyData) { + DCOUNT(co, MAX_CHAN, inn, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[MAX_CHAN]; + double out[3]; + + for (e = 0; e < inn; e++) { + in[e] = (double)co[e]; + in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e]) + + luluto->inmin[e]; + } + + /* Figure if we are over the ink limit. */ + if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0) + && icxLimit(luluto, in) > 0.0) { + DC_INC(co); + continue; /* Skip points over limit */ + } + + luluto->lookup((icxLuBase *)luluto, out, in); + gam->setcusps(gam, 1, out); + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + } + + } else { /* Must be icmBwd */ + lutgamctx cx; + + /* Get an appropriate device to PCS conversion for the fwd conversion */ + /* we use after bwd conversion in lutbwdgam_func() */ + switch (intent) { + /* If it is relative */ + case icmDefaultIntent: /* Shouldn't happen */ + case icPerceptual: + case icRelativeColorimetric: + case icSaturation: + intent = icRelativeColorimetric; /* Choose relative */ + break; + /* If it is absolute */ + case icAbsoluteColorimetric: + case icxAppearance: + case icxAbsAppearance: + break; /* Leave unchanged */ + default: + break; + } + if ((cx.flu = p->get_luobj(p, ICX_CLIP_NEAREST , icmFwd, intent, pcs, icmLuOrdNorm, + &plu->vc, NULL)) == NULL) { + return NULL; /* oops */ + } + + cx.g = gam = new_gamut(detail, pcs == icxSigJabData, 0); + + cx.x = luluto; + + luluto->clutTable->scan_rspl( + luluto->clutTable, /* this */ + RSPL_NOFLAGS, /* Combination of flags */ + (void *)&cx, /* Opaque function context */ + lutbwdgam_func /* Function to set from */ + ); + + /* Now set the cusp points by using the fwd conversion and */ + /* itterating through colorant 0 & 100% combinations. */ + /* If we know what sort of space it is: */ + if (outs == icSigRgbData || outs == icSigCmyData || outs == icSigCmykData) { + DCOUNT(co, 3, 3, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[MAX_CHAN]; + double out[3]; + + if (!(co[0] == 0 && co[1] == 0 && co[2] == 0) + && !(co[0] == 1 && co[1] == 1 && co[2] == 1)) { /* Skip white and black */ + for (e = 0; e < 3; e++) + in[e] = (double)co[e]; + in[e] = 0; /* K */ + + /* Always use the device->PCS conversion */ + if (cx.flu->lookup((icxLuBase *)cx.flu, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->setcusps(gam, 3, out); + } + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + + /* Do all ink combinations and hope we can sort it out */ + /* (This may not be smart, since bodgy cusp values will cause gamut mapping to fail...) */ + } else if (ins != icSigXYZData + && ins != icSigLabData + && ins != icSigLuvData + && ins != icSigYxyData) { + DCOUNT(co, MAX_CHAN, inn, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[MAX_CHAN]; + double out[3]; + + for (e = 0; e < inn; e++) { + in[e] = (double)co[e]; + in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e]) + + luluto->inmin[e]; + } + + /* Figure if we are over the ink limit. */ + if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0) + && icxLimit(luluto, in) > 0.0) { + DC_INC(co); + continue; /* Skip points over limit */ + } + + cx.flu->lookup((icxLuBase *)cx.flu, out, in); + gam->setcusps(gam, 1, out); + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + } + cx.flu->del(cx.flu); /* Done with the fwd conversion */ + } + + /* Set the white and black points */ + plu->efv_wh_bk_points(plu, white, black, kblack); + gam->setwb(gam, white, black, kblack); + +//printf("~1 icxLuLutGamut: set black %f %f %f and kblack %f %f %f\n", black[0], black[1], black[2], kblack[0], kblack[1], kblack[2]); + +#ifdef NEVER /* Not sure if this is a good idea ?? */ + /* Since we know this is a profile, force the space and gamut points to be the same */ + gam->getwb(gam, NULL, NULL, white, black, kblack); /* Get the actual gamut white and black points */ + gam->setwb(gam, white, black, kblack); /* Put it back as colorspace one */ +#endif + + return gam; +} + +/* ----------------------------------------------- */ +#ifdef DEBUG +#undef DEBUG /* Limit extent to this file */ +#endif + + + + + + diff --git a/xicc/xlutfix.c b/xicc/xlutfix.c new file mode 100644 index 0000000..6497688 --- /dev/null +++ b/xicc/xlutfix.c @@ -0,0 +1,1306 @@ +/* + * International Color Consortium color transform expanded support + * Set Lut table values and do auxiliary chanel interpolation continuity fixups. + * + * Author: Graeme W. Gill + * Date: 17/12/00 + * Version: 1.00 + * + * Copyright 2000 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This module provides additional xicc functionality + * for CMYK lut based profiles. + * + * This is essentially a test of one approach to fixing + * auxiliary parameter induced interpolation errors. + */ + +/* + * TTBD: + * + * Remove this code when the optimised separation code is working. + * + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + * + */ + +/* Description: + + In all the clut based color systems, there are various + stages where the multi-dimenional profile functions are + resampled from one respresentation to another. As in all + sampling, aliasing may be an issue. The standard + methods for dealing with aliasing involve band limiting and + over-sampling. In dealing with color, anything other than + point sampling is often too slow to consider, meaning that + over sampling, or on-the-fly filtering is impractical. + + Band limiting the function to be sampled is therefore the + most practical approach, but there are still sever tradoffs. + For accurately representing the sampled characteristics + of a device, a high resolution grid, with band limited + sample points is desirable. 3 or 4 dimension grids however, + quickly consume memory, and generaly show an exponential + decline in access and manipulation speed with grid resolution. + To maintain accuracy therefore, the minimum grid resolution, + and the minimum level of filtering is often employed. + + The routines in this file are to deal with an aditional + subtlety when dealing with devices that have extra + degrees of freedom (ie. CMYK devices). In theory, the + aditional degrees of freedom can be set abitrarily, and + are often chosen to follow a "rule", designed to acheive + a goal such as minimising the amount of black used + in the highlights of bitonal devices (to minimise + "black dot" visibility), or to maximise black usage + for minimum ink costs, to resduce grey axis sensitivity + to the CMY values, to reduce the total ink loading, + or to pass through the inking values of a similar + input colorspace. In Argyll, these extra degrees of + freedom are refered to as auxiliary chanels. + + Because the final representation of the color correction + transform is often a multi-dimensional interpolation lookup + table (clut), there is an aditional hidden requirement for + any auxiliary input chanels, and that is that there be + a reasonable degree of interpolation continuity between + the sampled grid points. If this continuity requirement + is not met, then the accuracy of the interpolation within + each grid cell can be wildly inacurate, even though the + accuracy of the grid points themselves is good. + + For instance, if we have two grid points of a Lab->CMYK + interpolation grid: + + 1) 50 0 0 -> .0 .0 .0 .3 -> 50 0 0 + 2) 50 0 10 -> .2 .2 .4 .0 -> 50 0 10 + + Now if an input PCS value of 50 0 5 is used to + lookup the device values that should be used, a typical + interpolation might return: + + 50 0 5 -> .1 .1 .2 .15 -> 40 -5 6 + + This is a small change in PCS space, but bevcause the + two device points are at opposite extremes of the possible + auxliary locus for each point, the device values are + far appart in device space. The accuracy of the + device space interpolation is therefore not guaranteed + to be accurate, and might in this case, mean that + the device actually reproduces an unexpectedly inaccurate PCS value. + Even worse, at the gamut boundaries the locus shrinks to zero, + and particularly in the dark end of the gamut, there + may be a multitude of different Device values that overlap + at the gamut boundary, causing abrupt or even chaotic + device values at spacings well above the sampling spacing + of the interpolation grid being created. + + An additional challenge is that the locus of valid auxiliary + values may be discontinuous, (typically bifurcated), particularly + when an ink limit is imposed - the limit often removing a segment + of the auxiliary locus from the gamut. So ideally, a contiguous + auxiliary region needs to be mapped out, and any holes + patched over or removed from the gamut in a way that + doesn't introduce discontinuities. + + In Argyll, we want to maintain the freedom to set arbitrary + auxiliary rules, yet we need to avoid the gross loss of + accuracy abrupt transitions in auxiliary values can cause. + + The approach I've taken here involves a number of steps. The + first step sets up the clut in the usual maner, but records + various internal values for each point. In the second step, these + grid values are examined to locate cells which are "at risk" of + auxiliary interpolation errors. In the third step, the grid points + around the "at risk" cells have their auxiliary target values + adjusted to new target values, by using a simple smoothing filter + to reduce abrupt transitions. In the fourth step, new device values + are searched for, that have the same target PCS for the grid point, + but the smoothed auxiliary value. In cases where there is no scope + for meeting the new auxiliary target, because it is already at one + extreme of its possible locus for the target PCS, a tradoff is then + made between reduced target PCS accuracy, and an improved auxiliary + accuracy. In the final step, the new grid values replace the old + in the ICC. + +*/ + +#include "icc.h" +#include "numlib.h" +#include "xicc.h" + +/* NOTE:- that we only implement support for CMYK output here !!! */ + +#define CHECK_FUNCS /* Sanity check the callback functions */ +#define DO_STATS +#undef SAVE_TRACE /* Save the values returned by the clut callback function */ +#undef USE_TRACE /* Use the trace file instead of the clut callback function */ + +#define TRACENAME "D:/usr/argyll/xicc/xlutfix.xxx" /* So it will work in the debugger */ + +#define MAX_PASSES 7 +#define MAX_FILTERS 20 +#define THRESH 0.55 /* Fix Threshold, ratio of mean to maximum PCS point */ +#define MINTHRESH 2.0 /* Set minimum interp error threshold. Don't fix if below this */ +#define AUXWHT 3.0 /* Auxiliary tradeoff weight and increment */ +#undef WIDEFILTER /* Alter 4x4 neighborhood */ + +/* ========================================================== */ + +/* Return maximum difference */ +static double maxdiffn(int n, double *in1, double *in2) { + double tt, rv = 0.0; + int i; + for (i = 0; i < n; i++) { + if ((tt = fabs(in1[i] - in2[i])) > rv) + rv = tt; + } + return rv; +} + +/* Return absolute difference */ +static double absdiffn(int n, double *in1, double *in2) { + double tt, rv = 0.0; + int i; + for (i = 0; i < n; i++) { + tt = in1[i] - in2[i]; + rv += tt * tt; + } + return sqrt(rv); +} + +/* ========================================================== */ +/* Callback functions used by icc set_tables */ +/* ========================================================== */ + +/* Context for set_tables callbacks */ + +typedef struct { + int dofix; + void *cbctx; + void (*infunc)(void *cbctx, double *out, double *in); + void (*clutfunc)(void *cbctx, double *out, double *aux, double *auxr, double *pcs, double *in); + void (*clutpcsfunc)(void *cbctx, double *out, double *auxv, double *pcs); + void (*clutipcsfunc)(void *cbctx, double *pcs, double *olimit, double *auxv, double *in); + void (*outfunc)(void *cbctx, double *out, double *in); + + float *g; /* Base of grid */ + int res; /* Grid resolution */ + int fn; /* Number of floats in grid */ + int n; /* Number of entries in grid */ + int fesz; /* Entry size in floats */ + int fci[MXDI]; /* float increment for each input dimension into latice */ + int cmin[MXDI]; /* Fixup area bounding box minimum */ + int cmax[MXDI]; /* Fixup area bounding box maximum +1 */ + /* One float for flags */ + int din; /* Number of input (ie. grid) dimensions */ + int daux; /* Number of auxiliary dimensions */ + int dout; /* Number of output dimensions */ + int oauxr; /* Offset to start of aux range entries */ + int oauxv; /* Offset to start of aux value entries */ + int oauxvv; /* Offset to start of aux new value entries */ + int opcs; /* Offset to start of PCS value entries */ + int oout; /* Offset to start of output value entries */ + int nhi; /* Number of corners in an input grid cube */ + int *fhi; /* nhi grid cube corner offsets in floats */ + + /* Minimiser info */ + double m_auxw; /* Auxiliary error weighting factor (ie. 5 - 100) */ + double m_auxv[MAX_CHAN];/* Auxiliary target value */ + double m_pcs[3]; /* PCS target value */ + +#if defined(SAVE_TRACE) || defined(USE_TRACE) + FILE *tf; +#endif +} xifs; + +/* Macros to access flag values */ +#define XLF_FLAGV(fp) (*((unsigned int *)(fp))) +#define XLF_TOFIX 0x0001 /* Grid point to be fixed flag */ +#define XLF_UPDATE 0x0002 /* Grid point to be updated flag */ +#define XLF_HARDER 0x0004 /* Compromise PCS to improve result */ + +/* Functions to pass to icc settables() to setup icc Lut */ +/* Input table. input -> input' space. */ +static void xif_set_input(void *cntx, double *out, double *in) { + xifs *p = (xifs *)cntx; + + p->infunc(p->cbctx, out, in); +} + + +/* clut, input' -> output' space */ +static void xif_set_clut(void *cntx, double *out, double *in) { + xifs *p = (xifs *)cntx; + + if (p->dofix == 0) { /* No fixups */ + p->clutfunc(p->cbctx, out, NULL, NULL, NULL, in); + + } else if (p->dofix == 1) { /* First pass */ + int e, f; + float *fp, *ep; + double pcs[MAX_CHAN], auxv[MAX_CHAN], auxr[MAX_CHAN * 2]; + + /* the icclib set_tables() supplies us the grid indexes */ + /* as integer in the double locations at in[-e-1] */ + +#if defined(USE_TRACE) + if (fread(pcs, sizeof(double), 3, p->tf) != 3 + || fread(auxr, sizeof(double), 2 * p->daux, p->tf) != (2 * p->daux) + || fread(auxv, sizeof(double), p->daux, p->tf) != p->daux + || fread(out, sizeof(double), p->dout, p->tf) != p->dout) { + fprintf(stderr,"mark_cells: read of trace failed\n"); + exit(-1); + } +#else /* !USE_TRACE */ + p->clutfunc(p->cbctx, out, auxv, auxr, pcs, in); + +#if defined(SAVE_TRACE) + if (fwrite(pcs, sizeof(double), 3, p->tf) != 3 + || fwrite(auxr, sizeof(double), 2 * p->daux, p->tf) != (2 * p->daux) + || fwrite(auxv, sizeof(double), p->daux, p->tf) != p->daux + || fwrite(out, sizeof(double), p->dout, p->tf) != p->dout) { + fprintf(stderr,"mark_cells: write of trace failed\n"); + exit(-1); + } +#endif /* SAVE_TRACE */ +#endif /* !USE_TRACE */ + + /* Figure grid pointer to grid entry */ + for (fp = p->g, e = 0; e < p->din; e++) + fp += *((int *)&in[-e-1]) * p->fci[e]; + + XLF_FLAGV(fp) = 0; /* Clear flags */ + + ep = fp + p->opcs; + for (f = 0; f < 3; f++) /* Save PCS values */ + ep[f] = (float)pcs[f]; + + ep = fp + p->oauxr; + for (f = 0; f < (2 * p->daux); f++) /* Save auxiliary range values */ + ep[f] = (float)auxr[f]; + + ep = fp + p->oauxv; + for (f = 0; f < p->daux; f++) /* Save auxiliary values */ + ep[f] = (float)auxv[f]; + + ep = fp + p->oout; + for (f = 0; f < p->dout; f++) /* Save the output values */ + ep[f] = (float)out[f]; + + } else { /* Second pass */ + int e, f; + float *fp, *ep; + + /* Figure grid pointer to grid entry */ + for (fp = p->g, e = 0; e < p->din; e++) + fp += *((int *)&in[-e-1]) * p->fci[e]; + + ep = fp + p->oout; + for (f = 0; f < p->dout; f++) /* Return the fixed output values */ + out[f] = (double)ep[f]; + } +} + +/* output output' -> output space */ +static void xif_set_output(void *cntx, double *out, double *in) { + xifs *p = (xifs *)cntx; + + p->outfunc(p->cbctx, out, in); +} + +static int mark_cells(xifs *p); +static int filter_grid(xifs *p, int tharder); +static void fix_grid(xifs *p, double auxw); +static int comp_pcs(xifs *p, double auxw, double *auxrv, double *auxv, double *pcs, double *dev); + +/* Helper function to setup the three tables contents, and the underlying icc. */ +/* Only useful if there are auxiliary device output chanels to be set, */ +/* as this takes care of auxiliary interpolation continuity fixups. */ +int icxLut_set_tables_auxfix( +icmLut *p, /* Pointer to icmLut object */ +void *cbctx, /* Opaque callback context pointer value */ +icColorSpaceSignature insig, /* Input color space */ +icColorSpaceSignature outsig, /* Output color space */ +void (*infunc)(void *cbctx, double *out, double *in), + /* Input transfer function, inspace->inspace' (NULL = default) */ +double *inmin, double *inmax, /* Maximum range of inspace' values */ + /* (NULL = default) */ +void (*clutfunc)(void *cbctx, double *out, double *aux, double *auxr, double *pcs, double *in), + /* inspace' -> outspace' transfer function, also */ + /* return the target PCS and the (packed) auxiliary locus range, */ + /* as [min0, max0, min1, max1...], the auxiliary chosen. */ +void (*clutpcsfunc)(void *cbctx, double *out, double *aux, double *pcs), + /* PCS + aux_target -> outspace' transfer function */ +void (*clutipcsfunc)(void *cbctx, double *pcs, double *olimit, double *auxv, double *in), + /* outspace' -> PCS + auxv check function */ +double *clutmin, double *clutmax, /* Maximum range of outspace' values */ + /* (NULL = default) */ +void (*outfunc)(void *cbctx, double *out, double *in) + /* Output transfer function, outspace'->outspace (NULL = deflt) */ +) { + int rv, g, e, jj, kk; + double auxw; /* Auxiliary weight factor */ + xifs xcs; /* Our context structure */ + + /* Simply pass this on to the icc set_table() */ + xcs.dofix = 0; /* Assume we won't attempt fix */ + xcs.cbctx = cbctx; + xcs.infunc = infunc; + xcs.clutfunc = clutfunc; + xcs.clutpcsfunc = clutpcsfunc; + xcs.clutipcsfunc = clutipcsfunc; + xcs.outfunc = outfunc; + + if (outsig != icSigCmykData) { /* Don'y know how/if to fix this */ + rv = p->set_tables(p, + ICM_CLUT_SET_APXLS, + (void *)&xcs, + insig, outsig, + xif_set_input, + inmin, inmax, + xif_set_clut, + clutmin, clutmax, + xif_set_output); + + return rv; + } + +#ifdef CHECK_FUNCS + if (insig == icSigLabData) { + double in[3], out[MAX_CHAN]; + double aux[1], auxr[2], pcs[3]; + double out_check[MAX_CHAN]; + double apcs[3], pcs_check[3]; + + /* Pick a sample input value */ + in[0] = 50.0; in[1] = 0.0; in[2] = 0.0; + + /* Test the in->out function */ + clutfunc(cbctx, out, aux, auxr, pcs, in); + +printf("~1 %f %f %f -> pcs %f %f %f,\n auxr %f - %f, auxv %f, dev %f %f %f %f\n", + in[0], in[1], in[2], pcs[0], pcs[1], pcs[2], auxr[0], auxr[1], aux[0], + out[0], out[1], out[2], out[3]); + + /* Check that we get the same result for the pcs function */ + clutpcsfunc(cbctx, out_check, aux, pcs); + + if (maxdiffn(p->outputChan, out, out_check) > 1e-6) { + fprintf(stderr,"set_tables_auxfix: pcsfunc check failed\n"); +printf("~1 is %f %f %f %f, should be %f %f %f %f\n", +out_check[0], out_check[1], out_check[2], out_check[3], +out[0], out[1], out[2], out[3]); + } +printf("~1 PCS version gives %f %f %f %f\n", +out_check[0], out_check[1], out_check[2], out_check[3]); + + /* Checkout the reverse function */ + clutipcsfunc(cbctx, apcs, NULL, NULL, out); /* Device -> clipped PCS */ + +printf("~1 clipped PCS = %f %f %f\n", apcs[0], apcs[1], apcs[2]); + clutpcsfunc(cbctx, out_check, aux, apcs); /* clipped PCS -> Device */ + clutipcsfunc(cbctx, pcs_check, NULL, NULL, out_check); /* Device -> PCS */ +printf("~1 check PCS = %f %f %f\n", pcs_check[0], pcs_check[1], pcs_check[2]); + + if (maxdiffn(3, apcs, pcs_check) > 1e-5) { + fprintf(stderr,"set_tables_auxfix: ipcsfunc check failed\n"); + printf("~1 is %f %f %f, should be %f %f %f\n", + pcs_check[0], pcs_check[1], pcs_check[2], + pcs[0], pcs[1], pcs[2]); + } + + } else { + fprintf(stderr,"Sanity check of %s not implemented!\n", + icm2str(icmColorSpaceSignature,insig)); + } +#endif /* CHECK_FUNCS */ + + /* Allocate an array to hold all the results */ + xcs.res = p->clutPoints; + xcs.din = p->inputChan; + xcs.dout = p->outputChan; + xcs.daux = xcs.dout - 3; /* Number of auxiliary values */ + xcs.fesz = 1 + 3 + xcs.dout + 4 * xcs.daux; /* Entry size in floats */ + + /* Compute total number of entries, and offsets in each dimension */ + xcs.n = xcs.res; + xcs.fci[0] = xcs.fesz; + for (e = 1; e < xcs.din; e++) { + xcs.n *= xcs.res; + xcs.fci[e] = xcs.fci[e-1] * xcs.res; + } + xcs.fn = xcs.n * xcs.fesz; + +printf("~1 fci = %d %d %d\n", +xcs.fci[0], xcs.fci[1], xcs.fci[2]); + + /* Setup offset list to grid cube corners */ + xcs.nhi = 1 << xcs.din; + if ((xcs.fhi = (int *)malloc(sizeof(int) * xcs.nhi)) == NULL) { + sprintf(p->icp->err,"icxLut_set_tables: malloc() failed"); + return p->icp->errc = 2; + } + for (g = 0; g < xcs.nhi; g++) { + xcs.fhi[g] = 0; + for (e = 0; e < xcs.din; e++) { + if (g & (1 << e)) + xcs.fhi[g] += xcs.fci[e]; + } + + } +printf("~1 nhi = %dd\n",xcs.nhi); + + /* Offsets into each entry */ + xcs.opcs = 1; /* Allow 1 flag float */ + xcs.oout = xcs.opcs + 3; /* dpcs floats */ + xcs.oauxr = xcs.oout + xcs.dout; /* dout floats */ + xcs.oauxv = xcs.oauxr + xcs.daux * 2; /* 2 daux floats */ + xcs.oauxvv = xcs.oauxv + xcs.daux; /* daux floats */ + /* daux floats */ + +printf("~1 res %d, entry size = %d floats, total floats needed = %d\n",xcs.res,xcs.fesz,xcs.fn); + +printf("~1 opcs = %d, oout = %d, oauxr = %d, oauxv = %d\n", + xcs.opcs, xcs.oout, xcs.oauxr, xcs.oauxv); + + /* Allocate the grid */ + if ((xcs.g = (float *)malloc(sizeof(float) * xcs.fn)) == NULL) { + sprintf(p->icp->err,"icxLut_set_tables: malloc() failed"); + return p->icp->errc = 2; + } + +#if defined(SAVE_TRACE) || defined(USE_TRACE) + { + char *tname = TRACENAME; + +#if defined(SAVE_TRACE) + if ((xcs.tf = fopen(tname,"w")) == NULL) { +#else + if ((xcs.tf = fopen(tname,"r")) == NULL) { +#endif + fprintf(stderr,"mark_cells: Can't open file '%s'\n",tname); + exit(-1); + } +#if defined(O_BINARY) +#if defined(SAVE_TRACE) + xcs.tf = freopen(tname,"wb",xcs.tf); +#else + xcs.tf = freopen(tname,"rb",xcs.tf); +#endif +#endif + } +#endif /* SAVE_TRACE || USE_TRACE */ + +#ifdef NEVER +// ~9 check function +{ + int rv; + double auxv[1], rauxv[1]; + double pcs[3], rpcs[3]; + double dev[4]; + + auxv[0] = 0.5; + pcs[0] = 60.0; + pcs[1] = 0.0; + pcs[2] = 0.0; + + dev[0] = 0.5; + dev[1] = 0.1; + dev[2] = 0.1; + dev[3] = 0.1; + + rv = comp_pcs(&xcs, 20.0, NULL, auxv, pcs, dev); + + printf("~9 comp_pcs returned %d, device %f %f %f %f\n",rv, dev[0], dev[1], dev[2], dev[3]); + + xcs.clutipcsfunc(xcs.cbctx, rpcs, NULL, rauxv, dev); + + printf("~9 comp_pcs pcs %f %f %f, aux %f\n", rpcs[0], rpcs[1], rpcs[2], rauxv[0]); + + return 0; +} +#endif + +printf("~1 doing the first pass\n"); + /* First pass */ + xcs.dofix = 1; + rv = p->set_tables(p, + ICM_CLUT_SET_APXLS, + (void *)&xcs, + insig, outsig, + xif_set_input, + inmin, inmax, + xif_set_clut, + clutmin, clutmax, + xif_set_output); + +#if defined(SAVE_TRACE) || defined(USE_TRACE) + fclose(xcs.tf); +#endif /* SAVE_TRACE || USE_TRACE */ + + if (rv != 0) { + free(xcs.fhi); + free(xcs.g); + return rv; + } + +printf("~1 doing the fixups\n"); + + /* Try three passes */ + for(jj = 0, auxw = AUXWHT; jj < MAX_PASSES; jj++, auxw += AUXWHT) { + int lrv; + + /* Figure out which cells need fixing */ + rv = mark_cells(&xcs); +printf("~1 cells that need fixing = %d\n", rv); + + if (rv == 0) + break; + + /* Filter the grid values that need fixing */ +printf("~1 about to filter grid points\n"); + for (kk = 0, lrv = 0, rv = 1; kk < MAX_FILTERS && rv > 0 && rv != lrv; kk++) { + lrv = rv; + rv = filter_grid(&xcs, 1); + } + + if (rv == 0) + break; + + /* Lookup device values for grid points with changed auxiliary targets */ +printf("~1 about to fix grid points\n"); + fix_grid(&xcs, auxw); + }; + +rv = mark_cells(&xcs); +printf("~1 faulty cells remaining = %d\n", rv); + +printf("~1 updatding the icc\n"); + /* Second pass */ + xcs.dofix = 2; + rv = p->set_tables(p, + ICM_CLUT_SET_APXLS, + (void *)&xcs, + insig, outsig, + xif_set_input, + inmin, inmax, + xif_set_clut, + clutmin, clutmax, + xif_set_output); + + free(xcs.fhi); + free(xcs.g); + +printf("~1 done\n"); + + return rv; +} + + + + +/* ----------------------------------------- */ +/* Mark cells that need fixing */ +/* Return number of cells that need fixing */ +static int mark_cells(xifs *p) { + int e, f; + int coa[MAX_CHAN], ce; /* grid counter */ + int tcount = 0; +#ifdef DO_STATS + double aerr = 0.0; + double merr = 0.0; + double ccount = 0.0; +#endif + + /* Get ready to track fixup area bounding box */ + for (e = 0; e < p->din; e++) { + p->cmin[e] = 99999; + p->cmax[e] = -1; + } + + /* Init the grid counter */ + for (ce = 0; ce < p->din; ce++) + coa[ce] = 0; + ce = 0; + + /* Itterate throught the PCS clut grid cells */ + while (ce < p->din) { + int j, m; + float *gp; /* Grid pointer */ + float *ep, *fp; /* Temporary grid pointers */ + double wpcsd; /* Worst case PCS distance */ + double apcs[3]; /* Average PCS value */ + double aout[MAX_CHAN]; /* Average output value */ + double check[3]; /* Check PCS */ + double ier; /* Interpolation error */ + int markcell = 0; /* Mark the cell */ + + /* Compute base of cell pointer */ + gp = p->g; /* Grid pointer */ + for (e = 0; e < p->din; e++) + gp += coa[e] * p->fci[e]; + + /* - - - - - - - - - - - - - - - - - */ + /* Full surrounding Cell calculation */ + + /* Init averaging accumulators */ + for (j = 0; j < 3; j++) + apcs[j] = 0.0; + for (f = 0; f < p->dout; f++) + aout[f] = 0.0; + + /* For each corner of the PCS grid based at the current point, */ + /* average the PCS and Device values */ + for (m = 0; m < p->nhi; m++) { + double pcs[3]; + double dev[MAX_CHAN]; + + fp = gp + p->fhi[m]; + +//ep = fp + p->opcs; +//printf("Input PCS %f %f %f\n", ep[0], ep[1], ep[2]); + + ep = fp + p->oout; /* base of device values */ + for (f = 0; f < p->dout; f++) { + double v = (double)ep[f]; + dev[f] = v; + aout[f] += v; + } + + /* Device to clipped PCS */ + p->clutipcsfunc(p->cbctx, pcs, NULL, NULL, dev); + + for (j = 0; j < 3; j++) + apcs[j] += pcs[j]; + +//printf("Corner PCS %f %f %f -> ", pcs[0], pcs[1], pcs[2]); +//printf("%f %f %f %f\n", dev[0], dev[1], dev[2], dev[3]); + } + + for (j = 0; j < 3; j++) + apcs[j] /= (double)p->nhi; + + for (f = 0; f < p->dout; f++) + aout[f] /= (double)p->nhi; + + /* Compute worst case distance of PCS corners to average PCS */ + wpcsd = 0.0; + for (m = 0; m < p->nhi; m++) { + double ss; + + fp = gp + p->fhi[m] + p->opcs; + for (ss = 0.0, j = 0; j < 3; j++) { + double tt = (double)fp[j] - apcs[j]; + ss += tt * tt; + } + ss = sqrt(ss); + if (ss > wpcsd) + wpcsd = ss; + } + wpcsd *= THRESH; /* Set threshold as proportion of most distant corner */ + if (wpcsd < MINTHRESH) /* Set a minimum threshold */ + wpcsd = MINTHRESH; + +//printf("Average PCS %f %f %f, Average Device %f %f %f %f\n", +//apcs[0], apcs[1], apcs[2], aout[0], aout[1], aout[2], aout[3]); + + /* Average Device to PCS */ + p->clutipcsfunc(p->cbctx, check, NULL, NULL, aout); + +//printf("Check PCS %f %f %f\n", +//check[0], check[1], check[2]); + + /* Compute error in PCS vs. Device interpolation */ + ier = absdiffn(3, apcs, check); + +//printf("Average PCS %f %f %f, Check PCS %f %f %f, error %f\n", +//apcs[0], apcs[1], apcs[2], check[0], check[1], check[2], ier); + +#ifdef DO_STATS + aerr += ier; + ccount++; + if (ier > merr) + merr = ier; +#endif /* STATS */ + + if (ier > wpcsd) { + markcell = 1; +printf("~1 ier = %f, wpcsd = %f, Dev = %f %f %f %f\n", ier, wpcsd, aout[0], aout[1], aout[2], aout[3]); + } + + /* - - - - - - - - - - - - - */ + /* Point pair calculations */ + + if (markcell == 0) { /* Don't bother testing if already marked */ + + /* Get the base point values */ + ep = gp + p->oout; /* base of device values (assumes fhi[0] == 0) */ + + for (f = 0; f < p->dout; f++) + aout[f] = (double)ep[f]; + + p->clutipcsfunc(p->cbctx, apcs, NULL, NULL, aout); + + /* For each other corner of the PCS grid based at the */ + /* current point, compute the interpolation error */ + for (m = 1; m < p->nhi; m++) { + double pcs[3]; /* Average PCS */ + double dpcs[3]; /* PCS of averaged device */ + double dev[MAX_CHAN]; + + fp = gp + p->fhi[m]; /* Other point point */ + ep = fp + p->oout; /* base of device values */ + + for (f = 0; f < p->dout; f++) + dev[f] = (double)ep[f]; + + /* Device to clipped PCS */ + p->clutipcsfunc(p->cbctx, pcs, NULL, NULL, dev); + + for (j = 0; j < 3; j++) + pcs[j] = 0.5 * (pcs[j] + apcs[j]); /* PCS averaged value */ + + for (f = 0; f < p->dout; f++) + dev[f] = 0.5 * (aout[f] + dev[f]); /* Average device */ + + /* Average Device to PCS */ + p->clutipcsfunc(p->cbctx, dpcs, NULL, NULL, dev); + + wpcsd = THRESH * absdiffn(3, pcs, apcs); /* Threshold value */ + if (wpcsd < MINTHRESH) /* Set a minimum threshold */ + wpcsd = MINTHRESH; + + /* Compute error in PCS vs. Device interpolation */ + ier = absdiffn(3, pcs, dpcs); + +#ifdef DO_STATS + aerr += ier; + ccount++; + if (ier > merr) + merr = ier; +#endif /* STATS */ + if (ier > wpcsd) { /* Over threshold */ + markcell = 1; +printf("~1 ier = %f, wpcsd = %f, Dev = %f %f %f %f\n", ier, wpcsd, aout[0], aout[1], aout[2], aout[3]); + } + } + } + + if (markcell) { + +#ifndef WIDEFILTER + /* Mark the whole cube around this base point */ + tcount++; + /* Grid points that make up cell */ + for (m = 0; m < p->nhi; m++) { + float *fp = gp + p->fhi[m]; + XLF_FLAGV(fp) |= XLF_TOFIX; + } + for (e = 0; e < p->din; e++) { + if (coa[e] < p->cmin[e]) + p->cmin[e] = coa[e]; + if ((coa[e]+2) > p->cmax[e]) + p->cmax[e] = coa[e] + 2; + } +#else + int fo[MAX_CHAN], fe; /* region counter */ + + /* Mark the whole cube around this base point */ + tcount++; + /* Grid points one row beyond cell */ + for (fe = 0; fe < p->din; fe++) + fo[fe] = -1; /* Init the neighborhood counter */ + fe = 0; + + /* For each corner of the filter region */ + while (fe < p->din) { + float *fp = gp; + + for (e = 0; e < p->din; e++) { + int oo = fo[e] + coa[e]; + if (oo < 0 || oo >= p->res) + break; + if (oo < p->cmin[e]) + p->cmin[e] = oo; + if ((oo+1) > p->cmax[e]) + p->cmax[e] = oo + 1; + fp += fo[e] * p->fci[e]; + } + + if (e >= p->din) { /* Within the grid */ + XLF_FLAGV(fp) |= XLF_TOFIX; + } + + /* Increment the counter */ + for (fe = 0; fe < p->din; fe++) { + if (++fo[fe] < 3) + break; /* No carry */ + fo[fe] = -1; + } + } +#endif /* WIDEFILTER */ + } + + /* - - - - - - - - - - - - - - */ + + + /* Increment the main grid counter */ + for (ce = 0; ce < p->din; ce++) { + if (++coa[ce] < (p->res-1)) + break; /* No carry */ + coa[ce] = 0; + } + } + +#ifdef DO_STATS + aerr /= ccount; + + printf("~1Average interpolation error %f, maximum %f\n",aerr, merr); + printf("~1Number outside corner radius = %f%%\n",(double)tcount * 100.0/ccount); + printf("~1Bounding box is %d - %d, %d - %d, %d - %d\n", + p->cmin[0], p->cmax[0], p->cmin[1], p->cmax[1], p->cmin[2], p->cmax[2]); +#endif /* STATS */ + + return tcount; +} + + +/* ----------------------------------------- */ +/* Do one filter pass of grid aux values that need fixing */ +/* If tharder is set, don't clamp new aux targets, but signal trading PCS for aux. */ +/* Return the number of grid points that have a new aux target */ +static int filter_grid(xifs *p, int tharder) { + int e, f; + int coa[MAX_CHAN], ce; /* grid counter */ + int tcount = 0; + + /* Init the grid counter */ + for (ce = 0; ce < p->din; ce++) + coa[ce] = p->cmin[ce]; + ce = 0; + + /* Itterate throught the PCS clut grid cells, */ + /* computing new auxiliary values */ + while (ce < p->din) { + float *gp, *fp; /* Grid pointer */ + int fo[MAX_CHAN], fe; /* filter counter */ + double faux[MAX_CHAN]; /* Filtered auxiliary value */ + double nfv; /* Number of values in filter value */ + + /* Compute base of cell pointer */ + gp = p->g; /* Grid pointer */ + for (e = 0; e < p->din; e++) + gp += coa[e] * p->fci[e]; + + if ((XLF_FLAGV(gp) & XLF_TOFIX) != 0) { + + for (f = 0; f < p->daux; f++) + faux[f] = 0.0; + nfv = 0.0; + + /* Init the neighborhood counter */ + for (fe = 0; fe < p->din; fe++) + fo[fe] = -1; + fe = 0; + + /* For each corner of the filter region, */ + /* compute a filter kernel value */ + while (fe < p->din) { + + fp = gp + p->oauxv; + for (e = 0; e < p->din; e++) { + int oo = coa[e] + fo[e]; + if (oo < 0 || oo >= p->res) + break; + fp += fo[e] * p->fci[e]; + } + + if (e >= p->din) { /* Add this neighborhood value */ + for (f = 0; f < p->daux; f++) + faux[f] += fp[f]; + nfv++; + } + + /* Increment the counter */ + for (fe = 0; fe < p->din; fe++) { + if (++fo[fe] < 2) + break; /* No carry */ + fo[fe] = -1; + } + } + + /* Compute average value, and save it */ + fp = gp + p->oauxvv; + for (f = 0; f < p->daux; f++) + fp[f] = (float)(faux[f] / nfv); + + } + + /* Increment the counter */ + for (ce = 0; ce < p->din; ce++) { + if (++coa[ce] < p->cmax[ce]) + break; /* No carry */ + coa[ce] = p->cmin[ce]; + } + } + + /* Clip the new values to the valid range, and put them into place */ + + /* Init the grid counter */ + for (ce = 0; ce < p->din; ce++) + coa[ce] = p->cmin[ce]; + ce = 0; + + /* Itterate throught the PCS clut grid cells, */ + /* computing new auxiliary values */ + while (ce < p->din) { + float *gp, *dp, *sp, *rp; /* Grid pointer */ + + /* Compute base of cell pointer */ + gp = p->g; /* Grid pointer */ + for (e = 0; e < p->din; e++) + gp += coa[e] * p->fci[e]; + + if ((XLF_FLAGV(gp) & XLF_TOFIX) != 0) { + int ud = 0; /* Update point flag */ + int th = 0; /* Try harder flag */ + sp = gp + p->oauxvv; /* Source */ + dp = gp + p->oauxv; /* Destination */ + rp = gp + p->oauxr; /* Range */ + for (f = 0; f < p->daux; f++) { + double v, ov; + v = sp[f]; + if (v < rp[2 * f]) { /* Limit new aux to valid locus range */ + if (tharder) + th = 1; + else + v = rp[2 * f]; + } + if (v > rp[2 * f + 1]) { + if (tharder) + th = 1; + else + v = rp[2 * f + 1]; + } + ov = dp[f]; /* Old aux value */ + if (fabs(ov - v) > 0.001) { + dp[f] = (float)v; + ud = 1; /* Worth updating it */ + } + } + + if (ud != 0) { + XLF_FLAGV(gp) |= XLF_UPDATE; + if (th != 0) + XLF_FLAGV(gp) |= XLF_HARDER; + } + if (XLF_FLAGV(gp) & XLF_UPDATE) + tcount++; + } + + /* Increment the counter */ + for (ce = 0; ce < p->din; ce++) { + if (++coa[ce] < p->cmax[ce]) + break; /* No carry */ + coa[ce] = p->cmin[ce]; + } + } +#ifdef DO_STATS + printf("~1 totol no. cells that will change = %d\n",tcount); +#endif + + return tcount; +} + +/* ----------------------------------------- */ +/* Update the grid values given the new auxiliary targets. */ +/* Reset the TOFIX flags. */ +static void fix_grid( +xifs *p, +double auxw /* Compromise PCS factor */ +) { + int e, f; + int coa[MAX_CHAN], ce; /* grid counter */ + + /* Init the grid counter */ + for (ce = 0; ce < p->din; ce++) + coa[ce] = p->cmin[ce]; + ce = 0; + + /* Itterate throught the PCS clut grid cells, */ + /* computing new auxiliary values */ + while (ce < p->din) { + float *gp, *ep; /* Grid pointer */ + + /* Compute base of cell pointer */ + gp = p->g; /* Grid pointer */ + for (e = 0; e < p->din; e++) + gp += coa[e] * p->fci[e]; + + if ((XLF_FLAGV(gp) & XLF_TOFIX) != 0) { + if ((XLF_FLAGV(gp) & XLF_UPDATE) != 0) { + double out[MAX_CHAN], auxv[MAX_CHAN], pcs[MAX_CHAN]; + double auxrv[MAX_CHAN]; + + ep = gp + p->opcs; + for (f = 0; f < 3; f++) /* Get PCS values */ + pcs[f] = (double)ep[f]; + + ep = gp + p->oauxv; + for (f = 0; f < p->daux; f++) /* Get auxiliary values */ + auxv[f] = (double)ep[f]; + + ep = gp + p->oout; + + if ((XLF_FLAGV(gp) & XLF_HARDER) != 0) { + /* Use "try harder" PCS->devicve lookup */ + + /* Set starting value */ + for (f = 0; f < p->dout; f++) + out[f] = (double)ep[f]; + + if (comp_pcs(p, auxw, auxrv, auxv, pcs, out) == 0) { + for (f = 0; f < p->dout; f++) /* Save the new output values */ + ep[f] = (float)out[f]; + } +#ifndef NEVER + else { + printf("~9 comp_pcs failed!\n"); + } +#endif + + /* Save actual auxiliary values */ + ep = gp + p->oauxv; + for (f = 0; f < p->daux; f++) + ep[f] = (float)auxrv[f]; + + } else { + + /* Lookup output value with different auxiliary target */ + p->clutpcsfunc(p->cbctx, out, auxv, pcs); + + for (f = 0; f < p->dout; f++) /* Save the new output values */ + ep[f] = (float)out[f]; + + /* assume auxiliary target will have been met */ + } + } + + XLF_FLAGV(gp) &= ~(XLF_TOFIX | XLF_UPDATE | XLF_HARDER); + } + + /* Increment the counter */ + for (ce = 0; ce < p->din; ce++) { + if (++coa[ce] < p->cmax[ce]) + break; /* No carry */ + coa[ce] = p->cmin[ce]; + } + } +} + +/* ----------------------------------------- */ + +/* minimizer callback function */ +static double minfunc( /* Return function value */ +void *fdata, /* Opaque data pointer */ +double *tp /* Device input value */ +) { + xifs *p = (xifs *)fdata; + double pcs[3]; + double auxv[MAX_CHAN]; + double olimit; + double fval; + double tval; + int e, f; + +#define GWHT 1000.0 + + /* Check if the device input values are outside (assumed) device range 0.0 - 1.0 */ + for (tval = 0.0, f = 0; f < p->dout; f++) { + if (tp[f] < 0.0) { + if (tp[0] > tval) + tval = tp[0]; + tp[f] = 0.0; + } else if (tp[f] > 1.0) { + if ((tp[0] - 1.0) > tval) + tval = (tp[0] - 1.0); + tp[f] = 1.0; + } + } + + /* Figure PCS, auxiliary error, and amount over ink limit */ + p->clutipcsfunc(p->cbctx, pcs, &olimit, auxv, tp); + + if (olimit > tval) + tval = olimit; + + fval = GWHT * tval; /* Largest value that exceeds device/ink range */ + + /* Figure auxiliary error */ + for (tval = 0.0, e = 0; e < p->daux; e++) { + double tt; + tt = auxv[e] - p->m_auxv[e]; + tval += tt * tt; + } + + fval += p->m_auxw * sqrt(tval); + + /* Figure PCS error */ + for (tval = 0.0, e = 0; e < 3; e++) { + double tt; + tt = pcs[e] - p->m_pcs[e]; + tval += tt * tt; + } + + fval += sqrt(tval); + +//printf("~9 minfunc returning error %f on %f %f %f %f\n", fval, tp[0], tp[1], tp[2], tp[3]); + return fval; +} + +/* Given a PCS target, and auxiliary target, and a current */ +/* device value, return a device value that is a better */ +/* tradeoff between the PCS target and the auxiliary target. */ +/* return non-zero on error */ +static int comp_pcs( +xifs *p, /* Aux fix structure */ +double auxw, /* Auxiliary error weighting factor (ie. 5 - 100) */ +double *auxrv, /* If not NULL, return actual auxiliary acheived */ +double *auxv, /* Auxiliary target value */ +double *pcs, /* PCS target value */ +double *dev /* Device starting value, return value */ +) { + int i; + double rv; + double ss[MAX_CHAN]; + double check[3]; /* Check PCS */ +#ifdef NEVER /* Diagnostic info */ +double start[3]; /* Starting PCS */ +double auxst[1]; /* Starting aux */ +double auxch[1]; /* Auxiliary check value */ +p->clutipcsfunc(p->cbctx, start, NULL, auxst, dev); +#endif + + p->m_auxw = auxw; + + for (i = 0; i < p->daux; i++) + p->m_auxv[i] = auxv[i]; + + for (i = 0; i < 3; i++) + p->m_pcs[i] = pcs[i]; + + /* Set initial search size */ + for (i = 0; i < p->dout; i++) + ss[i] = 0.3; + + if (powell(&rv, p->dout, dev, ss, 0.001, 1000, minfunc, p, NULL, NULL) != 0) { + return 1; + } + + /* Sanitise device values */ + for (i = 0; i < p->dout; i++) { + double v = dev[i]; + if (v < 0.0) + v = 0.0; + else if (v > 1.0) + v = 1.0; + dev[i] = v; + } + + if (auxrv != NULL) { /* Return actual auxiliary */ + p->clutipcsfunc(p->cbctx, check, NULL, auxrv, dev); + } + +#ifdef NEVER /* Diagnostic info */ +p->clutipcsfunc(p->cbctx, check, NULL, auxch, dev); +printf("~9 comp_pcs target aux %f PCS %f %f %f\n", auxv[0], pcs[0], pcs[1], pcs[2]); +printf("~9 returning error %f on %f %f %f %f\n", rv, dev[0], dev[1], dev[2], dev[3]); +printf("~9 PCS start = %f %f %f (%f)\n",start[0], start[1], start[2], + absdiffn(3, start, pcs)); +printf("~9 PCS finish = %f %f %f (%f)\n",check[0], check[1], check[2], + absdiffn(3, check, pcs)); +printf("~9 PCS delta = %f, aux delta = %f\n", absdiffn(3, start, check), + fabs(auxst[0] - auxch[0])); +#endif /* NEVER */ + + return 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xmatrix.c b/xicc/xmatrix.c new file mode 100644 index 0000000..a194314 --- /dev/null +++ b/xicc/xmatrix.c @@ -0,0 +1,2022 @@ +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000, 2003 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * This module provides the expands icclib functionality + * for matrix profiles. + * This file is #included in xicc.c, to keep its functions private. + */ + +/* + * TTBD: + * + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + * + * Should allow for offset in curves - this will greatly improve + * profile quality on non-calibrated displays. See spectro/dispcal.c + * spectro/moncurve.c. Use conjgrad() instead of powell() to speed things up. + * Note that if curves have scale, the scale will have to be + * normalized back to zero by scaling the matrix before storing + * the result in the ICC profile. + * + */ + +#define USE_CIE94_DE /* Use CIE94 delta E measure when creating fit */ + +/* Weights in shaper parameters, to minimise unconstrained "wiggles" */ +#define MXNORDERS 30 /* Maximum shaper harmonic orders to use */ +#define XSHAPE_MAG 1.0 /* Overall shaper parameter magnitide */ + +#define XSHAPE_OFFG 0.1 /* Input offset weights when ord 0 is gamma */ +#define XSHAPE_OFFS 1.0 /* Input offset weights when ord 0 is shaper */ +#define XSHAPE_HW01 0.002 /* 0 & 1 harmonic weights */ +#define XSHAPE_HBREAK 4 /* Harmonic that has HWBR */ +#define XSHAPE_HWBR 0.8 /* Base weight of harmonics HBREAK up */ +#define XSHAPE_HWINC 0.5 /* Increase in weight for each harmonic above HWBR */ + +#define XSHAPE_GAMTHR 0.01 /* Input threshold for linear slope below gamma power */ + +#undef DEBUG /* Extra printfs */ +#undef DEBUG_PLOT /* Plot curves */ + +/* ========================================================= */ +/* Forward and Backward Matrix type conversion */ +/* Return 0 on success, 1 if clipping occured, 2 on other error */ + +/* Individual components of Fwd conversion: */ +static int +icxLuMatrixFwd_curve ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->fwd_curve((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixFwd_matrix ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->fwd_matrix((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixFwd_abs ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + rv |= ((icmLuMatrix *)p->plu)->fwd_abs((icmLuMatrix *)p->plu, out, in); + + if (p->pcs == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + } + return rv; +} + + +/* Overall Fwd conversion */ +static int +icxLuMatrixFwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + icxLuMatrix *p = (icxLuMatrix *)pp; + rv |= icxLuMatrixFwd_curve(p, out, in); + rv |= icxLuMatrixFwd_matrix(p, out, out); + rv |= icxLuMatrixFwd_abs(p, out, out); + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a relative XYZ or Lab PCS value, convert in the fwd direction into */ +/* the nominated output PCS (ie. Absolute, Jab etc.) */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMatrix_fwd_relpcs_outpcs( +icxLuBase *pp, +icColorSpaceSignature is, /* Input space, XYZ or Lab */ +double *out, double *in) { + icxLuMatrix *p = (icxLuMatrix *)pp; + + if (is == icSigLabData && p->natpcs == icSigXYZData) { + icmLab2XYZ(&icmD50, out, in); + icxLuMatrixFwd_abs(p, out, out); + } else if (is == icSigXYZData && p->natpcs == icSigLabData) { + icmXYZ2Lab(&icmD50, out, in); + icxLuMatrixFwd_abs(p, out, out); + } else { + icxLuMatrixFwd_abs(p, out, in); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - */ +/* Individual components of Bwd conversion: */ + +static int +icxLuMatrixBwd_abs ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + + if (p->pcs == icxSigJabData) { + p->cam->cam_to_XYZ(p->cam, out, in); + /* Hack to prevent CAM02 weirdness being amplified by */ + /* any later per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + } + rv |= ((icmLuMatrix *)p->plu)->bwd_abs((icmLuMatrix *)p->plu, out, out); + } else { + rv |= ((icmLuMatrix *)p->plu)->bwd_abs((icmLuMatrix *)p->plu, out, in); + } + return rv; +} + +static int +icxLuMatrixBwd_matrix ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->bwd_matrix((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixBwd_curve ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->bwd_curve((icmLuMatrix *)p->plu, out, in); +} + +/* Overall Bwd conversion */ +static int +icxLuMatrixBwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + icxLuMatrix *p = (icxLuMatrix *)pp; + rv |= icxLuMatrixBwd_abs(p, out, in); + rv |= icxLuMatrixBwd_matrix(p, out, out); + rv |= icxLuMatrixBwd_curve(p, out, out); + return rv; +} + +static void +icxLuMatrix_free( +icxLuBase *p +) { + p->plu->del(p->plu); + if (p->cam != NULL) + p->cam->del(p->cam); + free(p); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a nominated output PCS (ie. Absolute, Jab etc.), convert it in the bwd */ +/* direction into a relative XYZ or Lab PCS value */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMatrix_bwd_outpcs_relpcs( +icxLuBase *pp, +icColorSpaceSignature os, /* Output space, XYZ or Lab */ +double *out, double *in) { + icxLuMatrix *p = (icxLuMatrix *)pp; + + icxLuMatrixFwd_abs(p, out, in); + if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmLab2XYZ(&icmD50, out, out); + } else if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmXYZ2Lab(&icmD50, out, out); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +static gamut *icxLuMatrixGamut(icxLuBase *plu, double detail); + +/* Do the basic icxLuMatrix creation and initialisation */ +static icxLuMatrix * +alloc_icxLuMatrix( + xicc *xicp, + icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ + int dir, /* 0 = fwd, 1 = bwd */ + int flags /* clip, merge flags */ +) { + icxLuMatrix *p; + + if ((p = (icxLuMatrix *) calloc(1,sizeof(icxLuMatrix))) == NULL) + return NULL; + + p->pp = xicp; + p->plu = plu; + p->del = icxLuMatrix_free; + p->lutspaces = icxLutSpaces; + p->spaces = icxLuSpaces; + p->get_native_ranges = icxLu_get_native_ranges; + p->get_ranges = icxLu_get_ranges; + p->efv_wh_bk_points = icxLuEfv_wh_bk_points; + p->get_gamut = icxLuMatrixGamut; + p->fwd_relpcs_outpcs = icxLuMatrix_fwd_relpcs_outpcs; + p->bwd_outpcs_relpcs = icxLuMatrix_bwd_outpcs_relpcs; + + p->nearclip = 0; /* Set flag defaults */ + p->mergeclut = 0; + p->noisluts = 0; + p->noipluts = 0; + p->nooluts = 0; + + p->intsep = 0; + + p->fwd_lookup = icxLuMatrixFwd_lookup; + p->fwd_curve = icxLuMatrixFwd_curve; + p->fwd_matrix = icxLuMatrixFwd_matrix; + p->fwd_abs = icxLuMatrixFwd_abs; + p->bwd_lookup = icxLuMatrixBwd_lookup; + p->bwd_abs = icxLuMatrixBwd_abs; + p->bwd_matrix = icxLuMatrixBwd_matrix; + p->bwd_curve = icxLuMatrixBwd_curve; + + if (dir) { /* Bwd */ + p->lookup = icxLuMatrixBwd_lookup; + p->inv_lookup = icxLuMatrixFwd_lookup; + } else { + p->lookup = icxLuMatrixFwd_lookup; + p->inv_lookup = icxLuMatrixBwd_lookup; + } + + /* There are no matrix specific flags */ + p->flags = flags; + + /* Get details of internal, native color space */ + p->plu->lutspaces(p->plu, &p->natis, NULL, &p->natos, NULL, &p->natpcs); + + /* Get other details of conversion */ + p->plu->spaces(p->plu, NULL, &p->inputChan, NULL, &p->outputChan, NULL, NULL, NULL, NULL, NULL); + + return p; +} + +/* We setup valid fwd and bwd component conversions, */ +/* but setup only the asked for overal conversion. */ +static icxLuBase * +new_icxLuMatrix( +xicc *xicp, +int flags, /* clip, merge flags */ +icmLuBase *plu, /* Pointer to Lu we are expanding */ +icmLookupFunc func, /* Functionality requested */ +icRenderingIntent intent, /* Rendering intent */ +icColorSpaceSignature pcsor, /* PCS override (0 = def) */ +icxViewCond *vc, /* Viewing Condition (NULL if pcsor is not CIECAM) */ +int dir /* 0 = fwd, 1 = bwd */ +) { + icxLuMatrix *p; + + /* Do basic creation and initialisation */ + if ((p = alloc_icxLuMatrix(xicp, plu, dir, flags)) == NULL) + return NULL; + + p->func = func; + + /* Init the CAM model */ + if (pcsor == icxSigJabData) { + if (vc != NULL) /* One has been provided */ + p->vc = *vc; /* Copy the structure */ + else + xicc_enum_viewcond(xicp, &p->vc, -1, NULL, 0, NULL); /* Use a default */ + p->cam = new_icxcam(cam_default); + p->cam->set_view(p->cam, p->vc.Ev, p->vc.Wxyz, p->vc.La, p->vc.Yb, p->vc.Lv, p->vc.Yf, + p->vc.Fxyz, XICC_USE_HK); + } else { + p->cam = NULL; + } + + /* Remember the effective intent */ + p->intent = intent; + + /* Get the effective spaces */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, NULL, NULL, &p->pcs, NULL); + + /* Override with pcsor */ + if (pcsor == icxSigJabData) { + p->pcs = pcsor; + if (func == icmBwd || func == icmGamut || func == icmPreview) + p->ins = pcsor; + if (func == icmFwd || func == icmPreview) + p->outs = pcsor; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + return (icxLuBase *)p; +} + +/* ========================================================== */ +/* xicc creation code */ +/* ========================================================== */ + +/* Context for figuring input curves */ +typedef struct { + rspl *r; /* Device -> PCS rspl */ + int linear; /* Flag */ + double nmin, nmax; /* PCS End points to linearise */ + double min, max; /* device End points to linearise */ +} mxinctx; + +#define NPARMS (9 + 6 + 3 * MXNORDERS) + +/* Context for optimising matrix */ +typedef struct { + int verb; /* Verbose */ + int optdim; /* Optimisation dimensions */ + int isLinear; /* NZ if no curves, fixed Gamma = 1.0 */ + int isGamma; /* NZ if gamma + matrix, else shaper */ + int isShTRC; /* NZ if shared TRC */ + int shape0gam; /* NZ if zero'th order shaper should be gamma function */ + int norders; /* Number of shaper orders */ + int clipbw; /* Prevent white > 1 and -ve black */ + int clipprims; /* Prevent primaries going -ve */ + double smooth; /* Shaper smoothing factor (nominal = 1.0) */ + double dscale; /* Scale device values */ + double v[NPARMS]; /* Holder for parameters */ + double sa[NPARMS]; /* Initial search area */ + /* Rest are matrix : */ + /* 0 1 2 R X */ + /* 3 4 5 * G = Y */ + /* 6 7 8 B Z */ + /* For Gamma: */ + /* 9, 10, 11 are gamma */ + /* Else for shaper only: */ + /* 9, 10, 11 are Input Offset */ + /* 12, 13, 14 are Output Offset */ + /* 15, 16, 17 are Gamma or 0th harmonics */ + /* 18, 19, 20 are 1st harmonics */ + /* 21, 22, 23 are 2nd harmonics */ + /* 24, 25, 26 etc. */ + /* For isShTRC there is only one set of offsets & harmonics */ + icmXYZNumber wp; /* Assumed white point for Lab conversion */ + cow *points; /* List of test points as dev->Lab */ + int nodp; /* Number of data points */ +} mxopt; + +/* Per chanel function being optimised */ +static void mxmfunc1(mxopt *p, int j, double *v, double *out, double *in) { + double vv, g; + int ps = 3; /* Parameter spacing */ + + vv = *in * p->dscale; + + if (p->isShTRC) { + j = 0; + ps = 1; /* Only one channel */ + } + + if (p->isLinear) { /* No per channel curve */ + *out = vv; + return; + } + + if (p->isGamma) { /* Pure Gamma */ + /* Apply gamma */ + g = v[9 + j]; + if (g <= 0.0) + vv = 1.0; + else { + if (vv >= 0.0) { + vv = pow(vv, g); + } else { + vv = -pow(-vv, g); + } + } + } else { /* Add extra shaper parameters */ + int ord; + + if (p->shape0gam) { + + /* Apply input offset */ + g = v[9 + j]; /* Offset value */ + + if (g >= 1.0) { + vv = 1.0; + } else { + vv = g + ((1.0 - g) * vv); + } + + /* Apply gamma as order 0 */ + g = v[9 + 2 * ps + j]; + if (g <= 0.0) + vv = 1.0; + else { + /* Power with straight line at small values */ + if (vv >= XSHAPE_GAMTHR) { + vv = pow(vv, g); + } else { + double slope, oth; + + oth = pow(XSHAPE_GAMTHR, g); /* Output at input threshold */ + slope = g * pow(XSHAPE_GAMTHR, g - 1.0); /* Slope at input threshold */ + vv = oth + (vv - XSHAPE_GAMTHR) * slope; /* Straight line */ + } + } + } + + /* Process all the shaper orders from high to low. */ + /* [These shapers were inspired by a Graphics Gem idea */ + /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ + /* Gain Functions, pp 401). */ + /* They have the nice properties that they are smooth, and */ + /* can't be non-monotonic. The control parameter has been */ + /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ + /* so that the search space is less non-linear. ] */ + if (p->shape0gam) + ord = 1; + else + ord = 0; + for (; ord < p->norders; ord++) + { + int nsec; /* Number of sections */ + double sec; /* Section */ + g = v[9 + 2 * ps + ord * ps + j]; /* Parameter */ + + nsec = ord + 1; /* Increase sections for each order */ + + vv *= (double)nsec; + + sec = floor(vv); + if (((int)sec) & 1) + g = -g; /* Alternate action in each section */ + vv -= sec; + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + vv += sec; + vv /= (double)nsec; + } + + /* (For extrapolation it helps to pin 0 & 1) */ + if (p->shape0gam) { + /* Apply output offset */ + g = v[9 + 1 * ps + j]; /* Offset value */ + + if (g >= 1.0) { + vv = 1.0; + } else if (g > 0.0) { + vv = g + ((1.0 - g) * vv); + } + } + } + + *out = vv; +} + +/* Function being optimised */ +static void mxmfunc(mxopt *p, double *v, double *xyz, double *in) { + int j; + double rgb[3]; + + /* Apply per channel processing */ + for (j = 0; j < 3; j++) + mxmfunc1(p, j, v, &rgb[j], &in[j]); + + /* Apply matrix */ + xyz[0] = v[0] * rgb[0] + v[1] * rgb[1] + v[2] * rgb[2]; + xyz[1] = v[3] * rgb[0] + v[4] * rgb[1] + v[5] * rgb[2]; + xyz[2] = v[6] * rgb[0] + v[7] * rgb[1] + v[8] * rgb[2]; + +} + +/* return the sum of the squares of the current shaper parameters */ +static double xshapmag( +mxopt *p, /* Base of optimisation structure */ +double *v /* Pointer to parameters */ +) { + double tt, w, tparam = 0.0; + int f, g; + + if (p->isGamma) { /* Pure Gamma only */ + return 0.0; + } + + if (p->isShTRC) { + /* Input offset value */ + if (p->shape0gam) + w = XSHAPE_OFFG; + else + w = XSHAPE_OFFS; + + tt = v[9]; + tt *= tt; + tparam += w * tt; + + /* Output offset value */ + tt = v[10]; + tt *= tt; + tparam += w * tt; + + /* Shaper values */ + for (f = 0; f < p->norders; f++) { + tt = v[11 + f]; + if (f == 0 && p->shape0gam) + tt -= 1.0; /* default is linear */ + tt *= tt; + /* Weigh to suppress ripples */ + if (f <= 1) { /* Use XSHAPE_HW01 */ + w = XSHAPE_HW01; + } else if (f <= XSHAPE_HBREAK) { /* Blend from XSHAPE_HW01 to XSHAPE_HWBR * smooth */ + double bl = (f - 1.0)/(XSHAPE_HBREAK - 1.0); + w = (1.0 - bl) * XSHAPE_HW01 + bl * XSHAPE_HWBR * p->smooth; + } else { /* Use XSHAPE_HWBR * smooth */ + w = XSHAPE_HWBR + (f-XSHAPE_HBREAK) * XSHAPE_HWINC; + w *= p->smooth; + } + tparam += w * tt; + } + return XSHAPE_MAG * tparam; + } + + /* Input ffset value */ + if (p->shape0gam) + w = XSHAPE_OFFG; + else + w = XSHAPE_OFFS; + + for (g = 0; g < 3; g++) { + tt = v[9 + g]; + tt *= tt; + tparam += w * tt; + } + /* Output ffset value */ + for (g = 0; g < 3; g++) { + tt = v[12 + g]; + tt *= tt; + tparam += w * tt; + } + /* Shaper values */ + for (f = 0; f < p->norders; f++) { + /* Weigh to suppress ripples */ + if (f <= 1) { + w = XSHAPE_HW01; + } else if (f <= XSHAPE_HBREAK) { + double bl = (f - 1.0)/(XSHAPE_HBREAK - 1.0); + w = (1.0 - bl) * XSHAPE_HW01 + bl * XSHAPE_HWBR * p->smooth; + } else { + w = XSHAPE_HWBR + (f-XSHAPE_HBREAK) * XSHAPE_HWINC * p->smooth; + w *= p->smooth; + } + for (g = 0; g < 3; g++) { + tt = v[15 + 3 * f + g]; + if (f == 0 && p->shape0gam) + tt -= 1.0; /* default is linear */ + tt *= tt; + tparam += w * tt; + } + } + return XSHAPE_MAG * tparam/3.0; +} + +/* Matrix optimisation function handed to powell() */ +static double mxoptfunc(void *edata, double *v) { + mxopt *p = (mxopt *)edata; + double err = 0.0, rv = 0.0, smv; + double xyz[3], lab[3]; + int i; + + for (i = 0; i < p->nodp; i++) { + + /* Apply our function */ +//printf("%f %f %f -> %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], xyz[0], xyz[1], xyz[2]); + mxmfunc(p, v, xyz, p->points[i].p); + + /* Convert to Lab */ + icmXYZ2Lab(&p->wp, lab, xyz); +//printf("%f %f %f -> %f %f %f, target %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], lab[0], lab[1], lab[2], p->points[i].v[0], p->points[i].v[1], p->points[i].v[2]); + + /* Accumulate total delta E squared */ +#ifdef USE_CIE94_DE + rv += p->points[i].w * icmCIE94sq(lab, p->points[i].v); +#else + rv += p->points[i].w * icmLabDEsq(lab, p->points[i].v); +#endif + } + + /* Normalise error to be an average delta E squared */ + rv /= (double)p->nodp; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + smv = xshapmag(p, v); + rv += smv; + + /* Penalize if we have white > 1 or -ve black */ + if (p->clipbw) { + double tp[3]; + + tp[0] = tp[1] = tp[2] = 1.0; + mxmfunc(p, v, xyz, tp); + if ((xyz[1] - 1.0) > err) + err = xyz[1] - 1.0; + + tp[0] = tp[1] = tp[2] = 0.0; + mxmfunc(p, v, xyz, tp); + for (i = 0; i < 3; i++) { + if (-xyz[i] > err) + err = -v[i]; + } + } + + /* Penalize if we have -ve primaries */ + if (p->clipprims) { + for (i = 0; i < 9; i++) { + if (-v[i] > err) + err = -v[i]; + } + } + rv += err * 1000.0; + +#ifdef DEBUG +printf("~9(%f)mxoptfunc returning %f\n",smv,rv); +#endif + + return rv; +} + +/* Matrix progress function handed to powell() */ +static void mxprogfunc(void *pdata, int perc) { + mxopt *p = (mxopt *)pdata; + + if (p->verb) { + printf("%c% 3d%%",cr_char,perc); + if (perc == 100) + printf("\n"); + fflush(stdout); + } +} + + +/* Given a correction matrix, transform the matrix values */ +static void mxtransform(mxopt *os, double mat[3][3]) { + double vec[3]; + + vec[0] = os->v[0]; vec[1] = os->v[3]; vec[2] = os->v[6]; + icmMulBy3x3(vec, mat, vec); + os->v[0] = vec[0]; os->v[3] = vec[1]; os->v[6] = vec[2]; + + vec[0] = os->v[1]; vec[1] = os->v[4]; vec[2] = os->v[7]; + icmMulBy3x3(vec, mat, vec); + os->v[1] = vec[0]; os->v[4] = vec[1]; os->v[7] = vec[2]; + + vec[0] = os->v[2]; vec[1] = os->v[5]; vec[2] = os->v[8]; + icmMulBy3x3(vec, mat, vec); + os->v[2] = vec[0]; os->v[5] = vec[1]; os->v[8] = vec[2]; +} + + +/* Setup and then return the optimized matrix fit in the mxopt structure. */ +/* Return 0 on sucess, error code on failure. */ +static int +createMatrix( +char *err, /* Return error message */ +mxopt *os, /* Optimisation information */ +int verb, /* NZ if verbose */ +int nodp, /* Number of points */ +cow *ipoints, /* Array of input points in XYZ space */ +int isLab, /* nz if data points are Lab */ +int quality, /* Quality metric, 0..3 (-1 == 2 orders only) */ +int isLinear, /* NZ if pure linear, gamma = 1.0 */ +int isGamma, /* NZ if gamma rather than shaper */ +int isShTRC, /* NZ if shared TRCs */ +int shape0gam, /* NZ if zero'th order shaper should be gamma function */ +int clipbw, /* Prevent white > 1 and -ve black */ +int clipprims, /* Prevent primaries going -ve */ +double smooth, /* Smoothing factor (nominal 1.0) */ +double scale /* Scale device values */ +) { + double nweight = 1.0; /* Amount to weight neutral patches (make a parameter ?) */ + int inputChan = 3; /* Must be RGB like */ + int outputChan = 3; /* Must be the PCS */ + int rsplflags = 0; /* Flags for scattered data rspl */ + int e, f, i, j; + int maxits = 200; /* Optimisation stop params */ + double stopon = 0.01; /* Absolute delta E change to stop on */ + cow *points; /* Lab copy of ipoints */ + double rerr; + +#ifdef DEBUG_PLOT + #define XRES 100 + double xx[XRES]; + double y1[XRES]; +#endif /* DEBUG_PLOT */ + + if (verb) + rsplflags |= RSPL_VERBOSE; + + /* Allocate the array passed to fit_rspl() */ + if ((points = (cow *)malloc(sizeof(cow) * nodp)) == NULL) { + if (err != NULL) + sprintf(err,"Allocation of scattered coordinate array failed"); + return 2; + } + + /* Setup for optimising run */ + if (verb != 0) + os->verb = verb; + else + os->verb = 0; + os->points = points; + os->nodp = nodp; + os->isShTRC = 0; + os->shape0gam = shape0gam; + os->smooth = smooth; + os->clipbw = clipbw; + os->clipprims = clipprims; + os->dscale = scale; + + /* Set quality/effort factors */ + if (quality >= 3) { /* Ultra high */ + os->norders = 20; + maxits = 10000; + stopon = 5e-7; + } else if (quality == 2) { /* High */ + os->norders = 12; + maxits = 5000; + stopon = 5e-6; + } else if (quality == 1) { /* Medium */ + os->norders = 8; + maxits = 2000; + stopon = 5e-5; + } else if (quality == 0) { /* Low */ + os->norders = 4; + maxits = 1000; + stopon = 5e-4; + } else { /* Ultra Low */ + os->norders = 2; + maxits = 1000; + stopon = 5e-4; + } + if (os->norders > MXNORDERS) + os->norders = MXNORDERS; + + /* Setup points ready for optimisation and do an initial Lab conversion */ + for (i = 0; i < nodp; i++) { + for (e = 0; e < inputChan; e++) + points[i].p[e] = ipoints[i].p[e]; + + for (f = 0; f < outputChan; f++) + points[i].v[f] = ipoints[i].v[f]; + + points[i].w = ipoints[i].w; + } + + /* Pick a white point for the real Lab conversion */ + { + double wp[3]; + double wpy = -1e60; + int wix = -1; + + /* We assume that the input target is well behaved, */ + /* and that it includes a white point patch, */ + /* and that it has an extreme L value */ + + for (i = 0; i < nodp; i++) { + double yv; + + /* Tilt things towards D50 neutral white patches */ + yv = points[i].v[0] - 0.3 * sqrt(points[i].v[1] * points[i].v[1] + + points[i].v[2] * points[i].v[2]); + if (yv > wpy) { + wpy = yv; + wix = i; + } + } +//printf("~1 picked point %d as white\n",wix); + icmLab2XYZ(&icmD50, wp, points[wix].v); + wp[0] /= wp[1]; + wp[2] /= wp[1]; + wp[1] = 1.0; + icmAry2XYZ(os->wp, wp); + + /* We'll use this wp for delta E calculation when creating the matrix */ +// if (os->verb) printf("Switching to L*a*b* white point %f %f %f\n",os->wp.X,os->wp.Y,os->wp.Z); + if (nweight < 1.0) /* Sanity */ + nweight = 1.0; + for (i = 0; i < nodp; i++) { + double lch[3]; + if (isLab) + icmLab2XYZ(&icmD50, points[i].v, points[i].v); + icmXYZ2Lab(&os->wp, points[i].v, points[i].v); + icmLab2LCh(lch, points[i].v); + /* Apply any neutral weighting */ + if (lch[1] < 10.0) { + double w = nweight; + if (lch[1] > 5.0) + w = 1.0 + (nweight - 1.0) * (10.0 - lch[1])/(10.0 - 5.0); + points[i].w = w; + } +//printf("~1 patch %d = Lab %f %f %f, C = %f w = %f\n",i,points[i].v[0], points[i].v[1], points[i].v[2], lch[1],points[i].w); + } + } + + /* Set initial matrix optimisation values */ + os->v[0] = 0.4; os->v[1] = 0.4; os->v[2] = 0.2; /* Matrix */ + os->v[3] = 0.2; os->v[4] = 0.8; os->v[5] = 0.1; + os->v[6] = 0.02; os->v[7] = 0.15; os->v[8] = 1.3; + + /* Do a first pass just setting the matrix values */ + os->isLinear = 1; + os->isGamma = 1; + os->optdim = 9; + os->v[9] = os->v[10] = os->v[11] = 1.0; /* Linear */ + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + } +#endif /* NEVER */ + + /* Now optimize again with shaper or gamma curves */ + if (!isLinear || isGamma) { + + /* Start from linear, which is what was assumed for the matrix fit, */ + /* and fit first with a single shared curve. */ + os->isShTRC = 1; + if (isGamma) { /* Just gamma curve */ + os->isLinear = 0; + os->isGamma = 1; + os->optdim = 10; + os->v[9] = 1.0; /* Linear */ + } else { /* Creating input curves */ + os->isLinear = 0; + os->isGamma = 0; + os->optdim = 9 + 2 + os->norders; /* Matrix, offset + orders */ + os->v[9] = 0.0; /* Input offset */ + os->v[10] = 0.0; /* Output offset */ + if (shape0gam) + os->v[11] = 1.0; /* Gamma */ + else + os->v[11] = 0.0; /* 0th Harmonic */ + for (i = 12; i < os->optdim; i++) + os->v[i] = 0.0; /* Higher orders */ + } + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix and single curve...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + if (isGamma) { /* Creating input curves */ + printf("Gamma = %f\n",os->v[9]); + } else { /* Creating input curves */ + printf("Input offset = %f\n",os->v[9]); + printf("Output offset = %f\n",os->v[10]); + for (j = 0; j < os->norders; j++) { + if (shape0gam && j == 0) + printf("gamma = %f\n", os->v[11 + j]); + else + printf("%d harmonics = %f\n",j, os->v[11 + j]); + } + } + } +#endif /* NEVER */ + + /* Now do the final optimisation with all curves */ + if (!isShTRC) { + os->isShTRC = 0; + if (isGamma) { /* Just gamma curves */ + os->isLinear = 0; + os->isGamma = 1; + os->optdim = 12; + os->v[9] = os->v[10] = os->v[11] = 1.0; /* Linear */ + } else { /* Creating input curves */ + os->isLinear = 0; + os->isGamma = 0; + os->optdim = 9 + 6 + 3 * os->norders; /* Matrix, offset + orders */ + os->v[9] = os->v[10] = os->v[11] = 0.0; /* Input offset */ + os->v[12] = os->v[13] = os->v[14] = 0.0; /* Output offset */ + if (shape0gam) + os->v[15] = os->v[16] = os->v[17] = 1.0; /* Gamma */ + else + os->v[15] = os->v[16] = os->v[17] = 0.0; /* 0th Harmonic */ + for (i = 18; i < os->optdim; i++) + os->v[i] = 0.0; /* Higher orders */ + } + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix and curves...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + } + } + if (os->clipprims) { /* Clip -ve primaries */ + for (i = 0; i < 9; i++) { + if (os->v[i] < 0.0) + os->v[i] = 0.0; + } + } + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + if (!isLinear) { /* Creating input curves */ + if (isGamma) { /* Creating input curves */ + if (isShTRC) + printf("Gamma = %f\n",os->v[9]); + else + printf("Gamma = %f %f %f\n",os->v[9], os->v[10], os->v[11]); + } else { /* Creating input curves */ + if (isShTRC) { + printf("Input offset = %f\n",os->v[9]); + printf("Output offset = %f\n",os->v[10]); + } else { + printf("Input offset = %f %f %f\n",os->v[9], os->v[10], os->v[11]); + printf("Output offset = %f %f %f\n",os->v[12], os->v[13], os->v[14]); + } + for (j = 0; j < os->norders; j++) { + if (isShTRC) { + if (shape0gam && j == 0) + printf("gamma = %f\n", os->v[11 + j]); + else + printf("%d harmonics = %f\n",j, os->v[11 + j]); + } else { + if (shape0gam && j == 0) + printf("%d gamma = %f %f %f\n",j, os->v[15 + j * 3], + os->v[16 + j * 3], os->v[17 + j * 3]); + else + printf("%d harmonics = %f %f %f\n",j, os->v[15 + j * 3], + os->v[16 + j * 3], os->v[17 + j * 3]); + } + } + } + } + } +#endif /* NEVER */ +#ifdef NEVER /* Check DE of fit */ + { + double xyz[3], txyz[3]; + + for (i = 0; i < nodp; i++) { + + mxmfunc(os, os->v, xyz, ipoints[i].p); + + if (isLab) + icmLab2XYZ(&icmD50, txyz, ipoints[i].v); + else + icmCpy3(txyz, ipoints[i].v); + + printf("~1 point %d DE %f\n", i, icmXYZLabDE(&icmD50, txyz, xyz)); + } + } +#endif + + /* Free the coordinate lists */ + free(points); + + return 0; +} + +/* Apply a chromatic transform to the matrix to force the given */ +/* xyz value (typically white) to be exact */ +static void icxMM_force_exact(icxMatrixModel *p, double *targ, double *rgb) { + mxopt *os = (mxopt *)p->imp; + double txyz[3], axyz[3]; /* Target & actual xyz */ + icmXYZNumber _tp, _ap; + double cmat[3][3]; /* Model transform matrix */ + + if (p->isLab) + icmLab2XYZ(&icmD50, txyz, targ); + else + icmCpy3(txyz, targ); + + mxmfunc(os, os->v, axyz, rgb); + + icmAry2XYZ(_ap, axyz); + icmAry2XYZ(_tp, txyz); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _tp, _ap, cmat); + + /* Apply correction to fine tune matrix. */ + mxtransform(os, cmat); +} + +static void icxMM_lookup(icxMatrixModel *p, double *out, double *in) { + mxopt *os = (mxopt *)p->imp; + + mxmfunc(os, os->v, out, in); + + if (p->isLab) + icmXYZ2Lab(&icmD50, out, out); +} + +static void icxMM_del(icxMatrixModel *p) { + free(p->imp); + free(p); +} + +/* Create a matrix model of a set of points, and return an object to lookup */ +/* points from the model. Return NULL on error. */ +icxMatrixModel *new_MatrixModel( +int verb, /* NZ if verbose */ +int nodp, /* Number of points */ +cow *ipoints, /* Array of input points in XYZ space */ +int isLab, /* nz if data points are Lab */ +int quality, /* Quality metric, 0..3 (-1 == 2 orders only) */ +int isLinear, /* NZ if pure linear, gamma = 1.0 */ +int isGamma, /* NZ if gamma rather than shaper */ +int isShTRC, /* NZ if shared TRCs */ +int shape0gam, /* NZ if zero'th order shaper should be gamma function */ +int clipbw, /* Prevent white > 1 and -ve black */ +int clipprims, /* Prevent primaries going -ve */ +double smooth, /* Smoothing factor (nominal 1.0) */ +double scale /* Scale device values */ +) { + icxMatrixModel *p; + + if ((p = (icxMatrixModel *) calloc(1,sizeof(icxMatrixModel))) == NULL) + return NULL; + + p->force = icxMM_force_exact; + p->lookup = icxMM_lookup; + p->del = icxMM_del; + + if ((p->imp = (void *) calloc(1,sizeof(mxopt))) == NULL) { + free(p); + return NULL; + } + + if (createMatrix(NULL, (mxopt *)p->imp, verb, nodp, ipoints, isLab, quality, + isLinear, isGamma, isShTRC, shape0gam, + clipbw, clipprims, smooth, scale) != 0) { + free(p->imp); + free(p); + return NULL; + } + + p->isLab = isLab; + + return p; +} + +/* Create icxLuMatrix and undelying tone reproduction curves and */ +/* colorant tags, initialised from the icc, and then overwritten */ +/* by a conversion created from the supplied scattered data points. */ + +/* The scattered data is assumed to map Device -> native PCS (ie. dir = Fwd) */ +/* NOTE:- in theory once this icxLuMatrix is setup, it can be */ +/* called to translate color values. In practice I suspect */ +/* that the icxLuMatrix hasn't been setup completely enough to allows this. */ +/* Might be easier to close it and re-open it ? */ +static icxLuBase * +set_icxLuMatrix( +xicc *xicp, +icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ +int flags, /* white/black point flags */ +int nodp, /* Number of points */ +int nodpbw, /* Number of points to look for white & black patches in */ +cow *ipoints, /* Array of input points in XYZ space */ +icxMatrixModel *skm, /* Optional skeleton model (not used here) */ +double dispLuminance, /* > 0.0 if display luminance value and is known */ +double wpscale, /* > 0.0 if input white point is to be scaled */ +int quality, /* Quality metric, 0..3 */ +double smooth /* Curve smoothing, nominally 1.0 */ +) { + icxLuMatrix *p; /* Object being created */ + icc *icco = xicp->pp; /* Underlying icc object */ + icmLuMatrix *pmlu = (icmLuMatrix *)plu; /* icc matrix lookup object */ + int luflags = 0; /* icxLuMatrix alloc clip, merge flags */ + int isLinear = 0; /* NZ if pure linear, gamma = 1.0 */ + int isGamma = 0; /* NZ if gamma rather than shaper */ + int isShTRC = 0; /* NZ if shared TRCs */ + int inputChan = 3; /* Must be RGB like */ + int outputChan = 3; /* Must be the PCS */ + icmHeader *h = icco->header; /* Pointer to icc header */ + int rsplflags = 0; /* Flags for scattered data rspl */ + int e, f, i, j; + int maxits = 200; /* Optimisation stop params */ + double stopon = 0.01; /* Absolute delta E change to stop on */ + mxopt os; /* Optimisation information */ + double rerr; + /* If ICX_SET_WHITE | ICX_SET_BLACK: */ + double wp[3]; /* Absolute White point in XYZ */ + double bp[3]; /* Absolute Black point in XYZ */ + double dw[MXDI]; /* Device white value to adjust to be D50 */ + double db[MXDI]; /* Device balck value */ + double dgw[3]; /* Device space gamut boundary white for ICX_SET_WHITE_US */ + double fromAbs[3][3]; /* From abs to relative */ + double toAbs[3][3]; /* To abs from relative */ + cow *rpoints = NULL; /* Aprox. relative in->output values */ + +#ifdef DEBUG_PLOT + #define XRES 100 + double xx[XRES]; + double y1[XRES]; +#endif /* DEBUG_PLOT */ + + if (flags & ICX_VERBOSE) + rsplflags |= RSPL_VERBOSE; + + luflags = flags; /* Transfer straight though ? */ + + /* Check out some things about the profile */ + { + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + + if (wor == wog) { + if (wog != wob) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: TRC sharing is inconsistent"); + return NULL; + } + isShTRC = 1; + } + if (wor->flag != wog->flag || wog->flag != wob->flag) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: TRC type is inconsistent"); + return NULL; + } + if (wor->flag == icmCurveGamma) { + isGamma = 1; + } + + if (flags & ICX_NO_IN_SHP_LUTS) { + isLinear = 1; + } + } + + /* Do basic icxLu creation and initialisation */ + if ((p = alloc_icxLuMatrix(xicp, plu, 0, luflags)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: malloc failed"); + return NULL; + } + + p->func = icmFwd; /* Assumed by caller */ + + /* Get the effective spaces of underlying icm, and set icx the same */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, &p->intent, NULL, &p->pcs, NULL); + + /* For set_icx the effective pcs has to be the same as the native pcs */ + + /* Sanity check for matrix */ + if (p->pcs != icSigXYZData) { + p->pp->errc = 1; + sprintf(p->pp->err,"Can't create matrix profile with PCS of Lab !"); + p->del((icxLuBase *)p); + return NULL; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + /* ------------------------------- */ + + /* Choose a white and black point */ + if (flags & (ICX_SET_WHITE | ICX_SET_BLACK)) { + + if (flags & ICX_VERBOSE) + printf("Find white & black points\n"); + + /* Compute device white and black points as if */ + /* we are doing an Output or Display device */ + { + switch (h->colorSpace) { + + case icSigCmyData: + for (e = 0; e < p->inputChan; e++) { + dw[e] = 0.0; + db[e] = 1.0; + } + break; + case icSigRgbData: + for (e = 0; e < p->inputChan; e++) { + dw[e] = 1.0; + db[e] = 0.0; + } + break; + + default: { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + } + } + + /* dw is what we want for dgw[], used for XFIT_OUT_WP_REL_US */ + for (e = 0; e < p->inputChan; e++) + dgw[e] = dw[e]; + + /* If this is actuall an input device, lookup wp & bp */ + /* and override dwhite & dblack */ + if (h->deviceClass == icSigInputClass) { + double wpy = -1e60, bpy = 1e60; + int wix = -1, bix = -1; + + /* We assume that the input target is well behaved, */ + /* and that it includes a white and black point patch, */ + /* and that they have the extreme L/Y values */ + + /* + NOTE that this may not be the best approach ! + It may be better to average the chromaticity + of all the neutral seeming patches, since + the whitest patch may have (for instance) + a blue tint. + */ + + /* Discover the white and black patches */ + for (i = 0; i < nodpbw; i++) { + double labv[3], yv; + + /* Create D50 Lab to allow some chromatic sensitivity */ + /* in picking the white point */ + icmXYZ2Lab(&icmD50, labv, ipoints[i].v); + +#ifdef NEVER + /* Choose Y */ + if (ipoints[i].v[1] > wpy) { + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dw[e] = ipoints[i].p[e]; + wpy = ipoints[i].v[1]; + wix = i; + } +#else + + /* Tilt things towards D50 neutral white patches */ + yv = labv[0] - 0.3 * sqrt(labv[1] * labv[1] + labv[2] * labv[2]); + if (yv > wpy) { + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dw[e] = ipoints[i].p[e]; + wpy = yv; + wix = i; + } +#endif + if (ipoints[i].v[1] < bpy) { + bp[0] = ipoints[i].v[0]; + bp[1] = ipoints[i].v[1]; + bp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + db[e] = ipoints[i].p[e]; + bpy = ipoints[i].v[1]; + bix = i; + } + } + if (flags & ICX_VERBOSE) { + printf("Picked white patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + wix+1, icmPdv(p->inputChan, dw), icmPdv(3, wp), icmPLab(wp)); + printf("Picked black patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + bix+1, icmPdv(p->inputChan, db), icmPdv(3, bp), icmPLab(bp)); + } + + } else { + /* We assume that the display target is well behaved, */ + /* and that it includes a white point patch. */ + int nw = 0; + + wp[0] = wp[1] = wp[2] = 0.0; + + switch (h->colorSpace) { + + case icSigCmyData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] < 0.001 + && ipoints[i].p[1] < 0.001 + && ipoints[i].p[2] < 0.001) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + case icSigRgbData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] > 0.999 + && ipoints[i].p[1] > 0.999 + && ipoints[i].p[2] > 0.999) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + + default: + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + + if (nw == 0) { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle test points without a white patch"); + p->del((icxLuBase *)p); + return NULL; + } + wp[0] /= (double)nw; + wp[1] /= (double)nw; + wp[2] /= (double)nw; + + if (flags & ICX_VERBOSE) { + printf("Initial white point = %f %f %f\n",wp[0],wp[1],wp[2]); + } + + /* Need to lookup bp[] before we set the tag */ + } + + /* Create some abs<->rel chromatic conversions */ + { + icmXYZNumber _wp; + icmAry2XYZ(_wp, wp); + + /* Absolute->Aprox. Relative Adaptation matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + + /* Aproximate relative to absolute conversion matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + } + + } else { + icmSetUnity3x3(fromAbs); + icmSetUnity3x3(toAbs); + } + + /* Create copy of input points with output converted to white relative */ + if ((rpoints = (cow *)malloc(nodp * sizeof(cow))) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: malloc failed"); + p->del((icxLuBase *)p); + return NULL; + } + for (i = 0; i < nodp; i++) { + rpoints[i].w = ipoints[i].w; + for (e = 0; e < inputChan; e++) + rpoints[i].p[e] = ipoints[i].p[e]; + for (f = 0; f < outputChan; f++) + rpoints[i].v[f] = ipoints[i].v[f]; + + /* abs out -> aprox. rel out */ + icmMulBy3x3(rpoints[i].v, fromAbs, rpoints[i].v); + } + + /* ------------------------------- */ + + /* (Use a gamma curve as 0th order shape) */ + if ((p->pp->errc = createMatrix(p->pp->err, &os, flags & ICX_VERBOSE ? 1 : 0, + nodp, rpoints, 0, quality, + isLinear, isGamma, isShTRC, 1, + flags & ICX_CLIP_WB ? 1 : 0, + flags & ICX_CLIP_PRIMS ? 1 : 0, + smooth, 1.0)) != 0) { + free(rpoints); + p->del((icxLuBase *)p); + return NULL; + } + free(rpoints); rpoints = NULL; + + /* The overall device to absolute conversion is now what we want */ + /* (as dictated by the points, weighting and best fit), */ + /* but we need to adjust the device to relative conversion */ + /* to make device white map exactly to D50, without touching */ + /* the overall absolute behaviour. */ + if (p->flags & ICX_SET_WHITE) { + double aw[3]; /* aprox rel. white */ + icmXYZNumber _wp; /* Uncorrected dw maps to _wp */ + double cmat[3][3]; /* Model correction matrix */ + + if (flags & ICX_VERBOSE) + printf("Doing White point fine tune:\n"); + + + /* See what the aprox. relative white point has turned out to be, */ + /* by looking up the device white in the current conversion */ + mxmfunc(&os, os.v, aw, dw); + + if (flags & ICX_VERBOSE) { + printf("Before fine tune, rel WP = XYZ %s, Lab %s\n", icmPdv(3,aw), icmPLab(aw)); + } + + /* Matrix needed to correct aprox white to target D50 */ + icmAry2XYZ(_wp, aw); /* Aprox relative target white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, cmat); /* Correction */ + + /* Compute the current absolute white point */ + icmMulBy3x3(wp, toAbs, aw); + + /* Apply correction to fine tune matrix. */ + mxtransform(&os, cmat); + + /* Fix relative conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + + if (flags & ICX_VERBOSE) { + double tw[3]; + mxmfunc(&os, os.v, tw, dw); /* Lookup white again */ + printf("After fine tune, rel WP = XYZ %s, Lab %s\n", icmPdv(3, tw), icmPLab(tw)); + printf(" abs WP = XYZ %s, Lab %s\n", icmPdv(3, wp), icmPLab(wp)); + } + } + + /* Create default wpscale */ + if (wpscale < 0.0) { + wpscale = 1.0; + } else { + if (flags & ICX_VERBOSE) { + printf("White manual point scale %f\n", wpscale); + } + } + + /* If we are going to auto scale the WP to avoid clipping */ + /* values above the WP: (not important for matrix profiles ?) */ + if ((p->flags & ICX_SET_WHITE_US) == ICX_SET_WHITE_US) { + double tw[3], bw[3]; + icmXYZNumber _wp; + double uswpscale = 1.0; + double mxd, mxY; + double ndw[3]; + + /* See what device space gamut boundary white (ie. 1,1,1) maps to */ + mxmfunc(&os, os.v, tw, dgw); + icmMulBy3x3(tw, toAbs, tw); /* Convert to absolute */ + + mxY = tw[1]; + icmCpy3(bw, tw); +//printf("~1 1,1,1 Y = %f\n",tw[1]); + + /* See what the device white point value scaled to 1 produces */ + mxd = -1.0; + for (e = 0; e < inputChan; e++) { + if (dw[e] > mxd) + mxd = dw[e]; + } + for (e = 0; e < inputChan; e++) + ndw[e] = dw[e]/mxd; + + mxmfunc(&os, os.v, tw, ndw); + icmMulBy3x3(tw, toAbs, tw); /* Convert to absolute */ + +//printf("~1 ndw = %f %f %f Y = %f\n",ndw[0],ndw[1],ndw[2],tw[1]); + if (tw[1] > mxY) { + mxY = tw[1]; + icmCpy3(bw, tw); + } + + /* Compute WP scale factor needed to fit mxY */ + if (mxY > wp[1]) { + uswpscale = mxY/wp[1]; + wpscale *= uswpscale; + if (flags & ICX_VERBOSE) { + printf("Dev boundary white XYZ %s, scale WP by %f, total WP scale %f\n", + icmPdv(3, bw), uswpscale, wpscale); + } + } + } + + /* If the scaled WP would have Y > 1.0, clip it to 1.0 */ + if (p->flags & ICX_CLIP_WB) { + + if ((wp[1] * wpscale) > 1.0) { + wpscale = 1.0/wp[1]; /* Make wp Y = 1.0 */ + if (flags & ICX_VERBOSE) { + printf("WP Y would ve > 1.0. scale by %f to clip it\n",wpscale); + } + } + } + + /* Apply our total wp scale factor */ + if (wpscale != 1.0) { + icmXYZNumber _wp; + double cmat[3][3]; /* Model correction matrix */ + + /* Create inverse scaling matrix for relative rspl data */ + icmSetUnity3x3(cmat); + icmScale3x3(cmat, cmat, 1.0/wpscale); + + /* Inverse scale the matrix */ + mxtransform(&os, cmat); + + /* Scale the WP */ + icmScale3(wp, wp, wpscale); + + /* Fix absolute conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + } + + /* Look up the actual black point */ + if (p->flags & ICX_SET_BLACK) { + + /* Look black point up in dev->rel model */ + mxmfunc(&os, os.v, bp, db); + + /* Convert from relative to Absolute colorimetric */ + icmMulBy3x3(bp, toAbs, bp); + + + /* Got XYZ black point in bp[] */ + if (flags & ICX_VERBOSE) { + printf("Black point XYZ = %s, Lab = %s\n", icmPdv(3,bp),icmPLab(bp)); + } + + if (flags & ICX_CLIP_WB) { + if (bp[0] < 0.0 || bp[1] < 0.0 || bp[1] < 0.0) { + if (bp[0] < 0.0) + bp[0] = 0.0; + if (bp[1] < 0.0) + bp[1] = 0.0; + if (bp[2] < 0.0) + bp[2] = 0.0; + if (flags & ICX_VERBOSE) + printf("Black point clipped to XYZ = %s, Lab = %s\n",icmPdv(3,bp),icmPLab(bp)); + } + } + } + + if (flags & (ICX_SET_WHITE | ICX_SET_BLACK)) { + + /* If this is a display, adjust the absolute white point to be */ + /* exactly Y = 1.0, and compensate the matrix, dispLuminance */ + /* and black point accordingly. */ + if (h->deviceClass == icSigDisplayClass) { + double cmat[3][3]; /* Model correction matrix */ + double scale = 1.0/wp[1]; + + if (flags & ICX_VERBOSE) + printf("Scaling White Point by %f to make Y = 1.0\n", scale); + + /* Scale the WP & BP*/ + icmScale3(wp, wp, scale); + icmScale3(bp, bp, scale); + + /* Inverse scale the luminance */ + dispLuminance /= scale; + } + + /* Absolute luminance tag */ + if (flags & ICX_WRITE_WBL + && h->deviceClass == icSigDisplayClass + && dispLuminance > 0.0) { + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigLuminanceTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_luminance: couldn't find luminance tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"luminance: tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = 0.0; + wo->data[0].Y = dispLuminance; + wo->data[0].Z = 0.0; + + if (flags & ICX_VERBOSE) + printf("Display Luminance = %f\n", wo->data[0].Y); + } + + /* Write white and black tags */ + if ((flags & ICX_WRITE_WBL) + && (flags & ICX_SET_WHITE)) { /* White Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaWhitePointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find white tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: white tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = wp[0]; + wo->data[0].Y = wp[1]; + wo->data[0].Z = wp[2]; + + if (flags & ICX_VERBOSE) + printf("White point XYZ = %f %f %f\n",wp[0],wp[1],wp[2]); + } + if ((flags & ICX_WRITE_WBL) + && (flags & ICX_SET_BLACK)) { /* Black Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaBlackPointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find black tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: black tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = bp[0]; + wo->data[0].Y = bp[1]; + wo->data[0].Z = bp[2]; + + if (flags & ICX_VERBOSE) + printf("Black point XYZ = %f %f %f\n",bp[0],bp[1],bp[2]); + } + + // ~~99 + if (flags & ICX_CLIP_PRIMS) { + for (i = 0; i < 9; i++) { + if (os.v[i] < 0.0) + os.v[i] = 0.0; + } + } + } + + if (flags & ICX_VERBOSE) + printf("Done gamma/shaper and matrix creation\n"); + + /* Write the gamma/shaper and matrix to the icc memory structures */ + if (!isGamma) { /* Creating input curves */ + unsigned int ui; + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + + for (ui = 0; ui < wor->size; ui++) { + double in, rgb[3]; + + for (j = 0; j < 3; j++) { + + in = (double)ui / (wor->size - 1.0); + + mxmfunc1(&os, j, os.v, &rgb[j], &in); + + if (rgb[j] < 0.0) + rgb[j] = 0.0; + else if (rgb[j] > 1.0) + rgb[j] = 1.0; + } + wor->data[ui] = rgb[0]; /* Curve values 0.0 - 1.0 */ + if (!isShTRC) { + wog->data[ui] = rgb[1]; + wob->data[ui] = rgb[2]; + } + } +#ifdef DEBUG_PLOT + /* Display the result fit */ + for (j = 0; j < 3; j++) { + for (i = 0; i < XRES; i++) { + double x, y; + xx[i] = x = i/(double)(XRES-1); + mxmfunc1(&os, j, os.v, &y, &x); + if (y < 0.0) + y = 0.0; + else if (y > 1.0) + y = 1.0; + y1[i] = y; + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* DEBUG_PLOT */ + + + } else { /* Gamma */ + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + wor->data[0] = os.v[9]; /* Gamma values */ + if (!isShTRC) { + wog->data[0] = os.v[10]; + wob->data[0] = os.v[11]; + } + } + + /* Matrix values */ + { + icmXYZArray *wor, *wog, *wob; + wor = pmlu->redColrnt; + wog = pmlu->greenColrnt; + wob = pmlu->blueColrnt; + wor->data[0].X = os.v[0]; wor->data[0].Y = os.v[3]; wor->data[0].Z = os.v[6]; + wog->data[0].X = os.v[1]; wog->data[0].Y = os.v[4]; wog->data[0].Z = os.v[7]; + wob->data[0].X = os.v[2]; wob->data[0].Y = os.v[5]; wob->data[0].Z = os.v[8]; + + /* Load into pmlu matrix and inverse ??? */ + } + + if (flags & ICX_VERBOSE) + printf("Profile done\n"); + + return (icxLuBase *)p; +} + +/* ========================================================= */ + +/* Given an xicc lookup object, returm a gamut object. */ +/* Note that the PCS must be Lab or Jab */ +/* Return NULL on error, check errc+err for reason */ +static gamut *icxLuMatrixGamut( +icxLuBase *plu, /* this */ +double detail /* gamut detail level, 0.0 = def */ +) { + xicc *p = plu->pp; /* parent xicc */ + icxLuMatrix *lumat = (icxLuMatrix *)plu; /* Lookup xMatrix type object */ + icColorSpaceSignature pcs; + icmLookupFunc func; + double white[3], black[3], kblack[3]; + gamut *gam; + int res; /* Sample point resolution */ + int i, e; + + if (detail == 0.0) + detail = 10.0; + + /* get some details */ + plu->spaces(plu, NULL, NULL, NULL, NULL, NULL, NULL, &func, &pcs); + + if (func != icmFwd && func != icmBwd) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface for anything other than Device <-> PCS is not supported."); + return NULL; + } + + if (pcs != icSigLabData && pcs != icxSigJabData) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface PCS of other than Lab or Jab is not supported."); + return NULL; + } + + gam = new_gamut(detail, pcs == icxSigJabData, 0); + + /* Explore the gamut by itterating through */ + /* it with sample points in device space. */ + + res = (int)(600.0/detail); /* Establish an appropriate sampling density */ + + if (res < 40) + res = 40; + + /* Since matrix profiles can't be non-monotonic, */ + /* just itterate through the surface colors. */ + for (i = 0; i < 3; i++) { + int co[3]; + int ep[3]; + int co_e = 0; + + for (e = 0; e < 3; e++) { + co[e] = 0; + ep[e] = res; + } + ep[i] = 2; + + while (co_e < 3) { + double in[3]; + double out[3]; + + for (e = 0; e < 3; e++) /* Convert count to input value */ + in[e] = co[e]/(ep[e]-1.0); + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + + gam->expand(gam, out); + + /* Increment the counter */ + for (co_e = 0; co_e < 3; co_e++) { + co[co_e]++; + if (co[co_e] < ep[co_e]) + break; /* No carry */ + co[co_e] = 0; + } + } + } + +#ifdef NEVER + /* Try it twice */ + for (i = 0; i < 3; i++) { + int co[3]; + int ep[3]; + int co_e = 0; + + for (e = 0; e < 3; e++) { + co[e] = 0; + ep[e] = res; + } + ep[i] = 2; + + while (co_e < 3) { + double in[3]; + double out[3]; + + for (e = 0; e < 3; e++) /* Convert count to input value */ + in[e] = co[e]/(ep[e]-1.0); + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + + gam->expand(gam, out); + + /* Increment the counter */ + for (co_e = 0; co_e < 3; co_e++) { + co[co_e]++; + if (co[co_e] < ep[co_e]) + break; /* No carry */ + co[co_e] = 0; + } + } + } +#endif + +#ifdef NEVER // (doesn't seem to make much difference) + /* run along the primary ridges in more detail too */ + /* just itterate through the surface colors. */ + for (i = 0; i < 3; i++) { + int j; + double in[3]; + double out[3]; + + res *= 4; + + for (j = 0; j < res; j++) { + double vv = i/(res-1.0); + + in[0] = in[1] = in[2] = vv; + in[i] = 0.0; + + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->expand(gam, out); + + in[0] = in[1] = in[2] = 0.0; + in[i] = vv; + + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->expand(gam, out); + } + } +#endif + + /* Put the white and black points in the gamut */ + plu->efv_wh_bk_points(plu, white, black, kblack); + gam->setwb(gam, white, black, kblack); + + /* set the cusp points by itterating through the 0 & 100% colorant combinations */ + { + DCOUNT(co, 3, 3, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[3]; + double out[3]; + + if (!(co[0] == 0 && co[1] == 0 && co[2] == 0) + && !(co[0] == 1 && co[1] == 1 && co[2] == 1)) { /* Skip white and black */ + for (e = 0; e < 3; e++) + in[e] = (double)co[e]; + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->setcusps(gam, 3, out); + } + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + } + +#ifdef NEVER /* Not sure if this is a good idea ?? */ + gam->getwb(gam, NULL, NULL, white, black); /* Get the actual gamut white and black points */ + gam->setwb(gam, white, black); /* Put it back as colorspace one */ +#endif + + return gam; +} + +#ifdef DEBUG +#undef DEBUG +#endif diff --git a/xicc/xmono.c b/xicc/xmono.c new file mode 100644 index 0000000..f0ee04f --- /dev/null +++ b/xicc/xmono.c @@ -0,0 +1,324 @@ +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * This module provides the expands icclib functionality + * for monochrome profiles. + * This file is #included in xicc.c, to keep its functions private. + */ + +/* + * TTBD: + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + * + */ + +/* ============================================================= */ +/* Forward and Backward Monochrome type conversion */ +/* Return 0 on success, 1 if clipping occured, 2 on other error */ + +/* - - - - - - - - - - - - - - - - - - - - - */ +/* Individual components of Fwd conversion: */ +static int +icxLuMonoFwd_curve ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMono *)p->plu)->fwd_curve((icmLuMono *)p->plu, out, in); +} + +static int +icxLuMonoFwd_map ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMono *)p->plu)->fwd_map((icmLuMono *)p->plu, out, in); +} + +static int +icxLuMonoFwd_abs ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + rv |= ((icmLuMono *)p->plu)->fwd_abs((icmLuMono *)p->plu, out, in); + + if (p->pcs == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + } + return rv; +} + + +/* Overall Fwd conversion routine */ +static int +icxLuMonoFwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + icxLuMono *p = (icxLuMono *)pp; + rv |= icxLuMonoFwd_curve(p, out, in); + rv |= icxLuMonoFwd_map(p, out, out); + rv |= icxLuMonoFwd_abs(p, out, out); + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a relative XYZ or Lab PCS value, convert in the fwd direction into */ +/* the nominated output PCS (ie. Absolute, Jab etc.) */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMono_fwd_relpcs_outpcs( +icxLuBase *pp, +icColorSpaceSignature is, /* Input space, XYZ or Lab */ +double *out, double *in) { + icxLuMono *p = (icxLuMono *)pp; + + icmLab2XYZ(&icmD50, out, in); + if (is == icSigLabData && p->natpcs == icSigXYZData) { + icxLuMonoFwd_abs(p, out, out); + } else if (is == icSigXYZData && p->natpcs == icSigLabData) { + icxLuMonoFwd_abs(p, out, out); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - */ +/* Individual components of Bwd conversion: */ + +static int +icxLuMonoBwd_abs ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + + if (p->pcs == icxSigJabData) { + p->cam->cam_to_XYZ(p->cam, out, in); + rv |= ((icmLuMono *)p->plu)->bwd_abs((icmLuMono *)p->plu, out, out); + /* Hack to prevent CAM02 weirdness being amplified by */ + /* any later per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + } + } else { + rv |= ((icmLuMono *)p->plu)->bwd_abs((icmLuMono *)p->plu, out, in); + } + return rv; +} + +static int +icxLuMonoBwd_map ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMono *)p->plu)->bwd_map((icmLuMono *)p->plu, out, in); +} + +static int +icxLuMonoBwd_curve ( +icxLuMono *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMono *)p->plu)->bwd_curve((icmLuMono *)p->plu, out, in); +} + +/* Overall Bwd conversion routine */ +static int +icxLuMonoBwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + double temp[3]; + int rv = 0; + icxLuMono *p = (icxLuMono *)pp; + rv |= icxLuMonoBwd_abs(p, temp, in); + rv |= icxLuMonoBwd_map(p, out, temp); + rv |= icxLuMonoBwd_curve(p, out, out); + return rv; +} + +static void +icxLuMono_free( +icxLuBase *p +) { + p->plu->del(p->plu); + if (p->cam != NULL) + p->cam->del(p->cam); + free(p); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a nominated output PCS (ie. Absolute, Jab etc.), convert it in the bwd */ +/* direction into a relative XYZ or Lab PCS value */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMono_bwd_outpcs_relpcs( +icxLuBase *pp, +icColorSpaceSignature os, /* Output space, XYZ or Lab */ +double *out, double *in) { + icxLuMono *p = (icxLuMono *)pp; + + if (os == icSigXYZData && p->natpcs == icSigLabData) { + icxLuMonoFwd_abs(p, out, in); + icmLab2XYZ(&icmD50, out, out); + } else if (os == icSigXYZData && p->natpcs == icSigLabData) { + icxLuMonoFwd_abs(p, out, in); + icmXYZ2Lab(&icmD50, out, out); + } else { + icxLuMonoFwd_abs(p, out, in); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +static gamut *icxLuMonoGamut(icxLuBase *plu, double detail); + +static icxLuBase * +new_icxLuMono( +xicc *xicp, +int flags, /* clip, merge flags */ +icmLuBase *plu, /* Pointer to Lu we are expanding */ +icmLookupFunc func, /* Functionality requested */ +icRenderingIntent intent, /* Rendering intent */ +icColorSpaceSignature pcsor, /* PCS override (0 = def) */ +icxViewCond *vc, /* Viewing Condition (NULL if pcsor is not CIECAM) */ +int dir /* 0 = fwd, 1 = bwd */ +) { + icxLuMono *p; + + /* Do the basic icxLuMono creation and initialisation */ + + if ((p = (icxLuMono *) calloc(1,sizeof(icxLuMono))) == NULL) + return NULL; + + p->pp = xicp; + p->plu = plu; + p->del = icxLuMono_free; + p->lutspaces = icxLutSpaces; + p->spaces = icxLuSpaces; + p->get_native_ranges = icxLu_get_native_ranges; + p->get_ranges = icxLu_get_ranges; + p->efv_wh_bk_points = icxLuEfv_wh_bk_points; + p->get_gamut = icxLuMonoGamut; + p->fwd_relpcs_outpcs = icxLuMono_fwd_relpcs_outpcs; + p->bwd_outpcs_relpcs = icxLuMono_bwd_outpcs_relpcs; + p->nearclip = 0; /* Set flag defaults */ + p->mergeclut = 0; + p->noisluts = 0; + p->noipluts = 0; + p->nooluts = 0; + p->intsep = 0; + + p->fwd_lookup = icxLuMonoFwd_lookup; + p->fwd_curve = icxLuMonoFwd_curve; + p->fwd_map = icxLuMonoFwd_map; + p->fwd_abs = icxLuMonoFwd_abs; + p->bwd_lookup = icxLuMonoBwd_lookup; + p->bwd_abs = icxLuMonoFwd_abs; + p->bwd_map = icxLuMonoFwd_map; + p->bwd_curve = icxLuMonoFwd_curve; + if (dir) { + p->lookup = icxLuMonoBwd_lookup; + p->inv_lookup = icxLuMonoFwd_lookup; + } else { + p->lookup = icxLuMonoFwd_lookup; + p->inv_lookup = icxLuMonoBwd_lookup; + } + + /* There are no mono specific flags */ + p->flags = flags; + p->func = func; + + /* Get details of internal, native color space */ + plu->lutspaces(p->plu, &p->natis, NULL, &p->natos, NULL, &p->natpcs); + + /* Get other details of conversion */ + p->plu->spaces(p->plu, NULL, &p->inputChan, NULL, &p->outputChan, NULL, NULL, NULL, NULL, NULL); + + /* Init the CAM model */ + if (pcsor == icxSigJabData) { + p->vc = *vc; /* Copy the structure */ + p->cam = new_icxcam(cam_default); + p->cam->set_view(p->cam, vc->Ev, vc->Wxyz, vc->La, vc->Yb, vc->Lv, vc->Yf, vc->Fxyz, + XICC_USE_HK); + } else + p->cam = NULL; + + /* Remember the effective intent */ + p->intent = intent; + + /* Get the effective spaces */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, NULL, NULL, &p->pcs, NULL); + + /* Override with pcsor */ + if (pcsor == icxSigJabData) { + p->pcs = pcsor; + if (func == icmBwd || func == icmGamut || func == icmPreview) + p->ins = pcsor; + if (func == icmFwd || func == icmPreview) + p->outs = pcsor; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + return (icxLuBase *)p; +} + +/* ============================================================= */ + +/* Given an xicc lookup object, returm a gamut object. */ +/* Note that the PCS must be Lab or Jab */ +/* Return NULL on error, check errc+err for reason */ +static gamut *icxLuMonoGamut( +icxLuBase *plu, /* this */ +double detail /* gamut detail level, 0.0 = def */ +) { + gamut *xgam; + xicc *p = plu->pp; /* parent xicc */ + + p->errc = 1; + sprintf(p->err,"Creating Mono gamut surface not supported yet."); + plu->del(plu); + xgam = NULL; + + return xgam; +} + diff --git a/xicc/xspect.c b/xicc/xspect.c new file mode 100644 index 0000000..a372d60 --- /dev/null +++ b/xicc/xspect.c @@ -0,0 +1,4714 @@ + +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 21/6/01 + * Version: 1.00 + * + * Copyright 2000 - 2006 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * This module supports converting spectral samples + * into CIE XYZ (1.0) or D50 Lab tristimulous values. + */ + +/* + * TTBD: + * + * If needed by ISO 13655-1009: + * fwa_convert() function takes two illuminants: + * first one is the measurement illumination to correct to, + * the second is the assumed illumination spectrum for XYZ conversion. + * so we compute the spectral reflectance as if the instrument had + * one sort of practical illuminant (and taking into account FWA), + * and then convert to D50 equivalent. + * Need to bypass FWA if inst illum == simulated inst. illum. + * Need to modify tools to allow optional param to -f which is + * the simulated instrument illum, then make -i have -M0, -M1, -M2 options. + * + * [Does this make any sense though ? That is what's happening + * for a standard A illuminant instrument emitting D50 XYZ values, + * but doesn't represent actually viewing under a (say) M2 illuminant. + * But is M0 actual A illuminant, or notional D50 measured by an A illuminant ?] + */ + +#include <stdlib.h> +#include <sys/types.h> +#include <time.h> +#include <string.h> +#include <math.h> +#ifndef SALONEINSTLIB +# include "numlib.h" +# include "cgats.h" +# include "plot.h" /* For debugging */ +#else +# include "numsup.h" +#endif +#include "xspect.h" + +#define CLAMP_XYZ /* [def] Clamp XYZ to be >= 0.0 */ + +#ifndef SALONEINSTLIB + +#undef STOCKFWA /* [und] Use table shape else compute from flat line estimate*/ + +#undef DEBUG /* [und] Extra printouts + debugging messages */ +#undef DOPLOT /* [und] Plot FWA setup */ +#undef DOPLOT_ALL_FWA /* [und] Plot all FWA corrected conversions */ +#undef WRITE_FWA1_STIM /* [und] Write file "fwa1_stip.sp" when FWA is setup */ + +#endif /* !SALONEINSTLIB */ + +#ifndef CLAMP_XYZ +# pragma message("###### CLAMP_XYZ is not defined ######") +#endif + +#if defined(DEBUG) || defined(DOPLOT) || defined(DOPLOT_ALL_FWA) || defined(WRITE_FWA1_STIM) +# pragma message("###### xspect debugging is on ######") +#endif + +#ifdef DEBUG +# define DBG(xx) a1logd(g_log, 0, xx ) +# define DBGA g_log, 0 /* First argument to DBGF() */ +# define DBGF(xx) a1logd xx +#else +# define DBG(xx) +# define DBGF(xx) +#endif + +/* ======================================================== */ +#if defined(__APPLE__) && defined(__POWERPC__) + +/* Workaround for a PPC gcc 3.3 optimiser bug... */ +/* It seems to cause a segmentation fault instead of */ +/* converting an integer loop index into a float, */ +/* when there are sufficient variables in play. */ +static int gcc_bug_fix(int i) { + static int nn; + nn += i; + return nn; +} +#endif /* APPLE */ + +/* ======================================================== */ +/* Define various standard spectra */ + +/* ------------------ */ +/* Illuminant spectra */ + +/* Dummy "no illuminant" illuminant spectra used to signal an emmission */ +/* or equal energy 'E' illuminant */ +static xspect il_none = { + 54, 300.0, 830.0, /* 54 bands from 300 to 830 in 10nm steps */ + 1.0, /* Scale factor */ + { + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 + } +}; + + +/* CIE 15.2-1986 Table 1.1 */ +/* Part 1: CIE Standard Illuminant A relative spectral power distribution */ +/* This is a 2848K tungsten filament lamp (Acording to the old temperature scale) */ +/* and 2856 according to the newer temerature scale. */ +static xspect il_A = { + 107, 300.0, 830.0, /* 107 bands from 300 to 830 nm in 5nm steps */ + 100.0, /* Arbitrary scale factor */ + { + 0.930483, 1.128210, 1.357690, 1.622190, 1.925080, + 2.269800, 2.659810, 3.098610, 3.589680, 4.136480, + 4.742380, 5.410700, 6.144620, 6.947200, 7.821350, + 8.769800, 9.795100, 10.899600, 12.085300, 13.354300, + 14.708000, 16.148000, 17.675300, 19.290700, 20.995000, + 22.788300, 24.670900, 26.642500, 28.702700, 30.850800, + 33.085900, 35.406800, 37.812100, 40.300200, 42.869300, + 45.517400, 48.242300, 51.041800, 53.913200, 56.853900, + 59.861100, 62.932000, 66.063500, 69.252500, 72.495900, + 75.790300, 79.132600, 82.519300, 85.947000, 89.412400, + 92.912000, 96.442300, 100.000000, 103.582000, 107.184000, + 110.803000, 114.436000, 118.080000, 121.731000, 125.386000, + 129.043000, 132.697000, 136.346000, 139.988000, 143.618000, + 147.235000, 150.836000, 154.418000, 157.979000, 161.516000, + 165.028000, 168.510000, 171.963000, 175.383000, 178.769000, + 182.118000, 185.429000, 188.701000, 191.931000, 195.118000, + 198.261000, 201.359000, 204.409000, 207.411000, 210.365000, + 213.268000, 216.120000, 218.920000, 221.667000, 224.361000, + 227.000000, 229.585000, 232.115000, 234.589000, 237.008000, + 239.370000, 241.675000, 243.924000, 246.116000, 248.251000, + 250.329000, 252.350000, 254.314000, 256.221000, 258.071000, + 259.865000, 261.602000 + } +}; + +/* CIE 15.2-1986 Table 1.1 */ +/* Part 1: CIE Standard Illuminant C relative spectral power distribution */ +/* This is a CIE Illuminant A combined with a filter to simulate daylight. */ +static xspect il_C = { + 93, 320.0, 780.0, /* 107 bands from 300 to 830 nm in 5nm steps */ + 100.0, /* Arbitrary factor */ + { + 0.01, 0.20, 0.40, 1.55, 2.70, 4.85, 7.00, 9.95, 12.90, 17.20, + 21.40, 27.50, 33.00, 39.92, 47.40, 55.17, 63.30, 71.81, 80.60, 89.53, + 98.10, 105.80, 112.40, 117.75, 121.50, 123.45, 124.00, 123.60, 123.10, 123.30, + 123.80, 124.09, 123.90, 122.92, 120.70, 116.90, 112.10, 106.98, 102.30, 98.81, + 96.90, 96.78, 98.00, 99.94, 102.10, 103.95, 105.20, 105.67, 105.30, 104.11, + 102.30, 100.15, 97.80, 95.43, 93.20, 91.22, 89.70, 88.83, 88.40, 88.19, + 88.10, 88.06, 88.00, 87.86, 87.80, 87.99, 88.20, 88.20, 87.90, 87.22, + 86.30, 85.30, 84.00, 82.21, 80.20, 78.24, 76.30, 74.36, 72.40, 70.40, + 68.30, 66.30, 64.40, 62.80, 61.50, 60.20, 59.20, 58.50, 58.10, 58.00, + 58.20, 58.50, 59.10 + } +}; + +/* D50 illuminant spectra */ +static xspect il_D50 = { + 107, 300.0, 830.0, /* 107 bands from 300 to 830 nm in 5nm steps */ + 100.0, /* Arbitrary factor */ + { + 0.02, 1.03, 2.05, 4.91, 7.78, 11.26, 14.75, 16.35, 17.95, 19.48, + 21.01, 22.48, 23.94, 25.45, 26.96, 25.72, 24.49, 27.18, 29.87, 39.59, + 49.31, 52.91, 56.51, 58.27, 60.03, 58.93, 57.82, 66.32, 74.82, 81.04, + 87.25, 88.93, 90.61, 90.99, 91.37, 93.24, 95.11, 93.54, 91.96, 93.84, + 95.72, 96.17, 96.61, 96.87, 97.13, 99.61, 102.10, 101.43, 100.75, 101.54, + 102.32, 101.16, 100.00, 98.87, 97.74, 98.33, 98.92, 96.21, 93.50, 95.59, + 97.69, 98.48, 99.27, 99.16, 99.04, 97.38, 95.72, 97.29, 98.86, 97.26, + 95.67, 96.93, 98.19, 100.60, 103.00, 101.07, 99.13, 93.26, 87.38, 89.49, + 91.60, 92.25, 92.89, 84.87, 76.85, 81.68, 86.51, 89.55, 92.58, 85.40, + 78.23, 67.96, 57.69, 70.31, 82.92, 80.60, 78.27, 78.91, 79.55, 76.48, + 73.40, 68.66, 63.92, 67.35, 70.78, 72.61, 74.44 + } +}; + +/* D50M2 illuminant spectra, UV filtered */ +/* Computed from il_D50 */ +static xspect il_D50M2 = { + 0, 0.0, 0.0, + 0.0 +}; + + +/* CIE 15.2-1986 Table 1.1 */ +/* Part 2: CIE Standard Illuminant D65 relative spectral power distribution */ +static xspect il_D65 = { + 107, 300.0, 830.0, /* 107 bands from 300 to 830 nm in 5nm steps */ + 100.0, /* Arbitrary factor */ + { + 0.03410, 1.66430, 3.29450, 11.76520, 20.23600, + 28.64470, 37.05350, 38.50110, 39.94880, 42.43020, + 44.91170, 45.77500, 46.63830, 49.36370, 52.08910, + 51.03230, 49.97550, 52.31180, 54.64820, 68.70150, + 82.75490, 87.12040, 91.48600, 92.45890, 93.43180, + 90.05700, 86.68230, 95.77360, 104.86500, 110.93600, + 117.00800, 117.41000, 117.81200, 116.33600, 114.86100, + 115.39200, 115.92300, 112.36700, 108.81100, 109.08200, + 109.35400, 108.57800, 107.80200, 106.29600, 104.79000, + 106.23900, 107.68900, 106.04700, 104.40500, 104.22500, + 104.04600, 102.02300, 100.00000, 98.16710, 96.33420, + 96.06110, 95.78800, 92.23680, 88.68560, 89.34590, + 90.00620, 89.80260, 89.59910, 88.64890, 87.69870, + 85.49360, 83.28860, 83.49390, 83.69920, 81.86300, + 80.02680, 80.12070, 80.21460, 81.24620, 82.27780, + 80.28100, 78.28420, 74.00270, 69.72130, 70.66520, + 71.60910, 72.97900, 74.34900, 67.97650, 61.60400, + 65.74480, 69.88560, 72.48630, 75.08700, 69.33980, + 63.59270, 55.00540, 46.41820, 56.61180, 66.80540, + 65.09410, 63.38280, 63.84340, 64.30400, 61.87790, + 59.45190, 55.70540, 51.95900, 54.69980, 57.44060, + 58.87650, 60.31250 + } +}; + +#ifndef SALONEINSTLIB +/* General temperature Daylight spectra (Using CIE 1960 u,v CCT) */ +/* Fill in the given xspect with the specified daylight illuminant */ +/* Return nz if temperature is out of range */ +static int daylight_il(xspect *sp, double ct) { + static double s0[107] = { + 0.04, 3.02, 6.00, 17.80, 29.60, 42.45, 55.30, 56.30, 57.30, 59.55, + 61.80, 61.65, 61.50, 65.15, 68.80, 66.10, 63.40, 64.60, 65.80, 80.30, + 94.80, 99.80, 104.80, 105.35, 105.90, 101.35, 96.80, 105.35, 113.90, 119.75, + 125.60, 125.55, 125.50, 123.40, 121.30, 121.30, 121.30, 117.40, 113.50, 113.30, + 113.10, 111.95, 110.80, 108.65, 106.50, 107.65, 108.80, 107.05, 105.30, 104.85, + 104.40, 102.20, 100.00, 98.00, 96.00, 95.55, 95.10, 92.10, 89.10, 89.80, + 90.50, 90.40, 90.30, 89.35, 88.40, 86.20, 84.00, 84.55, 85.10, 83.50, + 81.90, 82.25, 82.60, 83.75, 84.90, 83.10, 81.30, 76.60, 71.90, 73.10, + 74.30, 75.35, 76.40, 69.85, 63.30, 67.50, 71.70, 74.35, 77.00, 71.10, + 65.20, 56.45, 47.70, 58.15, 68.60, 66.80, 65.00, 65.50, 66.00, 63.50, + 61.00, 57.15, 53.30, 56.10, 58.90, 60.40, 61.90 + }; + static double s1[107] = { + 0.02, 2.26, 4.50, 13.45, 22.40, 32.20, 42.00, 41.30, 40.60, 41.10, + 41.60, 39.80, 38.00, 40.20, 42.40, 40.45, 38.50, 36.75, 35.00, 39.20, + 43.40, 44.85, 46.30, 45.10, 43.90, 40.50, 37.10, 36.90, 36.70, 36.30, + 35.90, 34.25, 32.60, 30.25, 27.90, 26.10, 24.30, 22.20, 20.10, 18.15, + 16.20, 14.70, 13.20, 10.90, 8.60, 7.35, 6.10, 5.15, 4.20, 3.05, + 1.90, 0.95, 0.00, -0.80, -1.60, -2.55, -3.50, -3.50, -3.50, -4.65, + -5.80, -6.50, -7.20, -7.90, -8.60, -9.05, -9.50, -10.20, -10.90, -10.80, + -10.70, -11.35, -12.00, -13.00, -14.00, -13.80, -13.60, -12.80, -12.00, -12.65, + -13.30, -13.10, -12.90, -11.75, -10.60, -11.10, -11.60, -11.90, -12.20, -11.20, + -10.20, -9.00, -7.80, -9.50, -11.20, -10.80, -10.40, -10.50, -10.60, -10.15, + -9.70, -9.00, -8.30, -8.80, -9.30, -9.55, -9.80 + }; + static double s2[107] = { + 0.00, 1.00, 2.00, 3.00, 4.00, 6.25, 8.50, 8.15, 7.80, 7.25, + 6.70, 6.00, 5.30, 5.70, 6.10, 4.55, 3.00, 2.10, 1.20, 0.05, + -1.10, -0.80, -0.50, -0.60, -0.70, -0.95, -1.20, -1.90, -2.60, -2.75, + -2.90, -2.85, -2.80, -2.70, -2.60, -2.60, -2.60, -2.20, -1.80, -1.65, + -1.50, -1.40, -1.30, -1.25, -1.20, -1.10, -1.00, -0.75, -0.50, -0.40, + -0.30, -0.15, 0.00, 0.10, 0.20, 0.35, 0.50, 1.30, 2.10, 2.65, + 3.20, 3.65, 4.10, 4.40, 4.70, 4.90, 5.10, 5.90, 6.70, 7.00, + 7.30, 7.95, 8.60, 9.20, 9.80, 10.00, 10.20, 9.25, 8.30, 8.95, + 9.60, 9.05, 8.50, 7.75, 7.00, 7.30, 7.60, 7.80, 8.00, 7.35, + 6.70, 5.95, 5.20, 6.30, 7.40, 7.10, 6.80, 6.90, 7.00, 6.70, + 6.40, 5.95, 5.50, 5.80, 6.10, 6.30, 6.50 + }; + int i; + double xd, yd; + double m1, m2; + + if (ct < 1000.0 || ct > 35000.0) /* Actually, accuracy is guaranteed from only 4000 - 25000 */ + return 1; + + /* Compute chromaticity coordinates */ + if (ct < 7000.0) { + xd = -4.6070e9/(ct * ct * ct) + 2.9678e6/(ct * ct) + 0.09911e3/ct + 0.244063; + } else { + xd = -2.0064e9/(ct * ct * ct) + 1.9018e6/(ct * ct) + 0.24748e3/ct + 0.237040; + } + yd = -3.000 * xd * xd + 2.870 * xd - 0.275; + + /* Compute m factors */ + m1 = (-1.3515 - 1.7703 * xd + 5.9114 * yd)/(0.0241 + 0.2562 * xd - 0.7341 * yd); + m2 = (0.0300 - 31.4424 * xd + 30.0717 * yd)/(0.0241 + 0.2562 * xd - 0.7341 * yd); + + /* Compute spectral values */ + for (i = 0; i < 107; i++) { + sp->spec[i] = s0[i] + m1 * s1[i] + m2 * s2[i]; + } + sp->spec_n = 107; + sp->spec_wl_short = 300.0; + sp->spec_wl_long = 830; + sp->norm = 100.0; /* Arbitrary */ + + return 0; +} + +/* General temperature Planckian (black body) spectra */ +/* Fill in the given xspect with the specified Planckian illuminant */ +/* Return nz if temperature is out of range */ +static int planckian_il(xspect *sp, double ct) { + int i; + double wl, norm; + + if (ct < 1.0 || ct > 1e6) /* set some arbitrary limits */ + return 1; + + /* Set out targets */ +// sp->spec_n = 107; /* 5nm */ + sp->spec_n = 531; /* 1nm */ + sp->spec_wl_short = 300.0; + sp->spec_wl_long = 830; + + /* Compute spectral values using Plank's radiation law: */ + /* Normalise numbers by energy at 560 nm */ + wl = 1e-9 * 560; + norm = 0.01 * (3.74183e-16 * pow(wl, -5.0)) / (exp(1.4388e-2 / (wl * ct)) - 1.0); + for (i = 0; i < sp->spec_n; i++) { + wl = 1e-9 * XSPECT_XWL(sp, i); /* Wavelength in meters */ + sp->spec[i] = (3.74183e-16 * pow(wl, -5.0)) / (exp(1.4388e-2 / (wl * ct)) - 1.0); + sp->spec[i] /= norm; + + } + sp->norm = 100.0; /* Arbitrary */ + + return 0; +} + +/* CIE F5 */ +/* Fluorescent, Standard, 6350K, CRI 72 */ +static xspect il_F5 = { + 107, 300.0, 830.0, /* 109 bands from 300 to 830 nm in 5nm steps */ + 20.0, /* Arbitrary scale factor */ + { +/* 300 */ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +/* 340 */ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +/* 380 */ 1.87, 2.35, 2.92, 3.45, 5.10, 18.91, 6.00, 6.11, 6.85, 7.58, +/* 430 */ 8.31, 40.76, 16.06, 10.32, 10.91, 11.40, 11.83, 12.17, 12.40, 12.54, +/* 480 */ 12.58, 12.52, 12.47, 12.20, 11.89, 11.61, 11.33, 11.10, 10.96, 10.97, +/* 530 */ 11.16, 11.54, 12.12, 27.78, 17.73, 14.47, 15.20, 15.77, 16.10, 18.54, +/* 580 */ 19.50, 15.39, 14.64, 13.72, 12.69, 11.57, 10.45, 9.35, 8.29, 7.32, +/* 630 */ 6.41, 5.63, 4.90, 4.26, 3.72, 3.25, 2.83, 2.49, 2.19, 1.93, +/* 680 */ 1.71, 1.52, 1.48, 1.26, 1.13, 1.05, 0.96, 0.85, 0.78, 0.72, +/* 730 */ 0.68, 0.67, 0.65, 0.61, 0.62, 0.59, 0.62, 0.64, 0.55, 0.47, +/* 780 */ 0.40, +/* 785 */ 0.0, 0.0, 0.0, 0.0, 0.0, +/* 810 */ 0.0, 0.0, 0.0, 0.0, 0.0 + } +}; + + +/* CIE F8 */ +/* Fluorescent, Wide band 5000K, CRI 95 */ +static xspect il_F8 = { + 107, 300.0, 830.0, /* 109 bands from 300 to 830 nm in 5nm steps */ + 20.0, /* Arbitrary scale factor */ + { +/* 300 */ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +/* 340 */ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +/* 380 */ 1.21, 1.5, 1.81, 2.13, 3.17, 13.08, 3.83, 3.45, 3.86, 4.42, +/* 430 */ 5.09, 34.10, 12.42, 7.68, 8.6, 9.46, 10.24, 10.84, 11.33, 11.71, +/* 480 */ 11.98, 12.17, 12.28, 12.32, 12.35, 12.44, 12.55, 12.68, 12.77, 12.72, +/* 530 */ 12.60, 12.43, 12.22, 28.96, 16.51, 11.79, 11.76, 11.77, 11.84, 14.61, +/* 580 */ 16.11, 12.34, 12.53, 12.72, 12.92, 13.12, 13.34, 13.61, 13.87, 14.07, +/* 630 */ 14.20, 14.16, 14.13, 14.34, 14.50, 14.46, 14.00, 12.58, 10.99, 9.98, +/* 680 */ 9.22, 8.62, 8.07, 7.39, 6.71, 6.16, 5.63, 5.03, 4.46, 4.02, +/* 730 */ 3.66, 3.36, 3.09, 2.85, 2.65, 2.51, 2.37, 2.15, 1.89, 1.61, +/* 780 */ 1.32, +/* 785 */ 0.0, 0.0, 0.0, 0.0, 0.0, +/* 810 */ 0.0, 0.0, 0.0, 0.0, 0.0 + } +}; + + + +/* CIE F10 */ +/* Fluorescent, Narrow band 5000K, CRI 81 */ +static xspect il_F10 = { + 107, 300.0, 830.0, /* 109 bands from 300 to 830 nm in 5nm steps */ + 20.0, /* Arbitrary scale factor */ + { + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 1.11, 0.80, 0.62, 0.57, 1.48, 12.16, 2.12, 2.70, 3.74, 5.14, + 6.75, 34.39, 14.86, 10.40, 10.76, 10.67, 10.11, 9.27, 8.29, 7.29, + 7.91, 16.64, 16.73, 10.44, 5.94, 3.34, 2.35, 1.88, 1.59, 1.47, + 1.80, 5.71, 40.98, 73.69, 33.61, 8.24, 3.38, 2.47, 2.14, 4.86, + 11.45, 14.79, 12.16, 8.97, 6.53, 8.31, 44.12, 34.55, 12.09, 12.15, + 10.52, 4.43, 1.95, 2.19, 3.19, 2.77, 2.29, 2.00, 1.52, 1.35, + 1.47, 1.79, 1.74, 1.02, 1.14, 3.32, 4.49, 2.05, 0.49, 0.24, + 0.21, 0.21, 0.24, 0.24, 0.21, 0.17, 0.21, 0.22, 0.17, 0.12, + 0.09, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0 + } +}; + +/* Spectrocam Xenon Lamp */ +static xspect il_Spectrocam = { + 95, 325.0, 795.0, /* 95 bands from 325 to 795 nm in 5nm steps */ + 1.0, /* Arbitrary scale factor */ + { + 0.220794, 0.240550, 0.281212, 0.363042, 0.493282, + 0.582279, 0.657489, 0.715563, 0.797559, 0.916343, + 1.066625, 1.228461, 1.298467, 1.373143, 1.457366, + 1.496117, 1.509290, 1.573544, 1.596359, 1.495740, + 1.477898, 1.521371, 1.479780, 1.453196, 1.532119, + 1.548128, 1.503433, 1.428481, 1.357290, 1.354425, + 1.317263, 1.237048, 1.169737, 1.109248, 1.085784, + 1.080186, 1.104001, 1.131713, 1.161153, 1.158589, + 1.148998, 1.123934, 1.077395, 1.017907, 1.026532, + 1.045921, 1.083780, 1.081868, 1.048489, 1.021549, + 0.993572, 0.956559, 0.942657, 0.952544, 0.957087, + 0.958472, 0.945666, 0.923988, 0.890418, 0.852044, + 0.812935, 0.792055, 0.791141, 0.825459, 0.829230, + 0.818171, 0.851752, 0.913113, 1.038844, 1.116913, + 1.164211, 1.133376, 1.109062, 1.129427, 1.086885, + 0.991213, 0.924226, 0.875499, 0.894231, 0.922219, + 0.960372, 0.896142, 0.819477, 0.879305, 0.912777, + 0.908489, 0.775942, 0.598118, 0.532988, 0.484102, + 0.465986, 0.414848, 0.346473, 0.324622, 0.309978 + } +}; + +#endif /* !SALONEINSTLIB */ + +/* Apply ISO 13655:2009 UV filter to the given spectrum. */ +/* The filter is applied point by point. */ +static void uv_filter(xspect *dst, xspect *src) { + int i; + + XSPECT_COPY_INFO(dst, src); + for (i = 0; i < src->spec_n; i++) { + double wl = XSPECT_XWL(src, i); + double ff = 1.0; + + if (wl <= 395.0) { + ff = 0.0; + } else if (wl < 425.0) { + ff = (wl - 395.0)/(425.0 - 395.0); + ff = ff * ff * (3.0 - 2.0 * ff); /* Cubic spline */ + } + dst->spec[i] = ff * src->spec[i]; + } +} + +/* Fill in an xpsect with a standard illuminant spectrum */ +/* return 0 on sucecss, nz if not matched */ +int standardIlluminant( +xspect *sp, /* Xspect to fill in */ +icxIllumeType ilType, /* Type of illuminant */ +double temp /* Optional temperature in degrees kelvin, for Dtemp and Ptemp */ +) { + switch (ilType) { + case icxIT_none: + return 1; + case icxIT_custom: + return 1; + case icxIT_A: + *sp = il_A; /* Struct copy */ + return 0; + case icxIT_C: + *sp = il_C; /* " */ + return 0; + case icxIT_default: + case icxIT_D50: + *sp = il_D50; /* etc */ + return 0; + case icxIT_D50M2: + if (il_D50M2.spec_n == 0) + uv_filter(&il_D50M2, &il_D50); + *sp = il_D50M2; + return 0; + case icxIT_D65: + *sp = il_D65; + return 0; + case icxIT_E: + *sp = il_none; + return 0; +#ifndef SALONEINSTLIB + case icxIT_F5: + *sp = il_F5; + return 0; + case icxIT_F8: + *sp = il_F8; + return 0; + case icxIT_F10: + *sp = il_F10; + return 0; + case icxIT_Spectrocam: + *sp = il_Spectrocam; + return 0; + case icxIT_Dtemp: + return daylight_il(sp, temp); + case icxIT_Ptemp: + return planckian_il(sp, temp); +#endif + } + return 1; +} + +/* ------------- */ + +/* Spectral locus poligon cache */ +typedef struct { + int n; /* Number of spectral vertexes, 0 if uninit */ + double xmin, xmax, ymin, ymax; /* Boundint box */ + double tx[3], ty[3]; /* Fast inner triangle test, RGB */ + double be[3][3]; /* baricentric equations of triangle */ +// double eed[3]; /* Distance of triangle points to 0.3, 0.3 */ + double x[XSPECT_MAX_BANDS]; /* x value of vertex */ + double y[XSPECT_MAX_BANDS]; /* y value of vertex */ +} xslpoly; + +/* ------------- */ +/* Observer Data and locus poligon cache */ + +/* Standard CIE 1931 2 degree */ +static xspect ob_CIE_1931_2[3] = { + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000129900000, 0.000145847000, 0.000163802100, 0.000184003700, 0.000206690200, + 0.000232100000, 0.000260728000, 0.000293075000, 0.000329388000, 0.000369914000, + 0.000414900000, 0.000464158700, 0.000518986000, 0.000581854000, 0.000655234700, + 0.000741600000, 0.000845029600, 0.000964526800, 0.001094949000, 0.001231154000, + 0.001368000000, 0.001502050000, 0.001642328000, 0.001802382000, 0.001995757000, + 0.002236000000, 0.002535385000, 0.002892603000, 0.003300829000, 0.003753236000, + 0.004243000000, 0.004762389000, 0.005330048000, 0.005978712000, 0.006741117000, + 0.007650000000, 0.008751373000, 0.010028880000, 0.011421700000, 0.012869010000, + 0.014310000000, 0.015704430000, 0.017147440000, 0.018781220000, 0.020748010000, + 0.023190000000, 0.026207360000, 0.029782480000, 0.033880920000, 0.038468240000, + 0.043510000000, 0.048995600000, 0.055022600000, 0.061718800000, 0.069212000000, + 0.077630000000, 0.086958110000, 0.097176720000, 0.108406300000, 0.120767200000, + 0.134380000000, 0.149358200000, 0.165395700000, 0.181983100000, 0.198611000000, + 0.214770000000, 0.230186800000, 0.244879700000, 0.258777300000, 0.271807900000, + 0.283900000000, 0.294943800000, 0.304896500000, 0.313787300000, 0.321645400000, + 0.328500000000, 0.334351300000, 0.339210100000, 0.343121300000, 0.346129600000, + 0.348280000000, 0.349599900000, 0.350147400000, 0.350013000000, 0.349287000000, + 0.348060000000, 0.346373300000, 0.344262400000, 0.341808800000, 0.339094100000, + 0.336200000000, 0.333197700000, 0.330041100000, 0.326635700000, 0.322886800000, + 0.318700000000, 0.314025100000, 0.308884000000, 0.303290400000, 0.297257900000, + 0.290800000000, 0.283970100000, 0.276721400000, 0.268917800000, 0.260422700000, + 0.251100000000, 0.240847500000, 0.229851200000, 0.218407200000, 0.206811500000, + 0.195360000000, 0.184213600000, 0.173327300000, 0.162688100000, 0.152283300000, + 0.142100000000, 0.132178600000, 0.122569600000, 0.113275200000, 0.104297900000, + 0.095640000000, 0.087299550000, 0.079308040000, 0.071717760000, 0.064580990000, + 0.057950010000, 0.051862110000, 0.046281520000, 0.041150880000, 0.036412830000, + 0.032010000000, 0.027917200000, 0.024144400000, 0.020687000000, 0.017540400000, + 0.014700000000, 0.012161790000, 0.009919960000, 0.007967240000, 0.006296346000, + 0.004900000000, 0.003777173000, 0.002945320000, 0.002424880000, 0.002236293000, + 0.002400000000, 0.002925520000, 0.003836560000, 0.005174840000, 0.006982080000, + 0.009300000000, 0.012149490000, 0.015535880000, 0.019477520000, 0.023992770000, + 0.029100000000, 0.034814850000, 0.041120160000, 0.047985040000, 0.055378610000, + 0.063270000000, 0.071635010000, 0.080462240000, 0.089739960000, 0.099456450000, + 0.109600000000, 0.120167400000, 0.131114500000, 0.142367900000, 0.153854200000, + 0.165500000000, 0.177257100000, 0.189140000000, 0.201169400000, 0.213365800000, + 0.225749900000, 0.238320900000, 0.251066800000, 0.263992200000, 0.277101700000, + 0.290400000000, 0.303891200000, 0.317572600000, 0.331438400000, 0.345482800000, + 0.359700000000, 0.374083900000, 0.388639600000, 0.403378400000, 0.418311500000, + 0.433449900000, 0.448795300000, 0.464336000000, 0.480064000000, 0.495971300000, + 0.512050100000, 0.528295900000, 0.544691600000, 0.561209400000, 0.577821500000, + 0.594500000000, 0.611220900000, 0.627975800000, 0.644760200000, 0.661569700000, + 0.678400000000, 0.695239200000, 0.712058600000, 0.728828400000, 0.745518800000, + 0.762100000000, 0.778543200000, 0.794825600000, 0.810926400000, 0.826824800000, + 0.842500000000, 0.857932500000, 0.873081600000, 0.887894400000, 0.902318100000, + 0.916300000000, 0.929799500000, 0.942798400000, 0.955277600000, 0.967217900000, + 0.978600000000, 0.989385600000, 0.999548800000, 1.009089200000, 1.018006400000, + 1.026300000000, 1.033982700000, 1.040986000000, 1.047188000000, 1.052466700000, + 1.056700000000, 1.059794400000, 1.061799200000, 1.062806800000, 1.062909600000, + 1.062200000000, 1.060735200000, 1.058443600000, 1.055224400000, 1.050976800000, + 1.045600000000, 1.039036900000, 1.031360800000, 1.022666200000, 1.013047700000, + 1.002600000000, 0.991367500000, 0.979331400000, 0.966491600000, 0.952847900000, + 0.938400000000, 0.923194000000, 0.907244000000, 0.890502000000, 0.872920000000, + 0.854449900000, 0.835084000000, 0.814946000000, 0.794186000000, 0.772954000000, + 0.751400000000, 0.729583600000, 0.707588800000, 0.685602200000, 0.663810400000, + 0.642400000000, 0.621514900000, 0.601113800000, 0.581105200000, 0.561397700000, + 0.541900000000, 0.522599500000, 0.503546400000, 0.484743600000, 0.466193900000, + 0.447900000000, 0.429861300000, 0.412098000000, 0.394644000000, 0.377533300000, + 0.360800000000, 0.344456300000, 0.328516800000, 0.313019200000, 0.298001100000, + 0.283500000000, 0.269544800000, 0.256118400000, 0.243189600000, 0.230727200000, + 0.218700000000, 0.207097100000, 0.195923200000, 0.185170800000, 0.174832300000, + 0.164900000000, 0.155366700000, 0.146230000000, 0.137490000000, 0.129146700000, + 0.121200000000, 0.113639700000, 0.106465000000, 0.099690440000, 0.093330610000, + 0.087400000000, 0.081900960000, 0.076804280000, 0.072077120000, 0.067686640000, + 0.063600000000, 0.059806850000, 0.056282160000, 0.052971040000, 0.049818610000, + 0.046770000000, 0.043784050000, 0.040875360000, 0.038072640000, 0.035404610000, + 0.032900000000, 0.030564190000, 0.028380560000, 0.026344840000, 0.024452750000, + 0.022700000000, 0.021084290000, 0.019599880000, 0.018237320000, 0.016987170000, + 0.015840000000, 0.014790640000, 0.013831320000, 0.012948680000, 0.012129200000, + 0.011359160000, 0.010629350000, 0.009938846000, 0.009288422000, 0.008678854000, + 0.008110916000, 0.007582388000, 0.007088746000, 0.006627313000, 0.006195408000, + 0.005790346000, 0.005409826000, 0.005052583000, 0.004717512000, 0.004403507000, + 0.004109457000, 0.003833913000, 0.003575748000, 0.003334342000, 0.003109075000, + 0.002899327000, 0.002704348000, 0.002523020000, 0.002354168000, 0.002196616000, + 0.002049190000, 0.001910960000, 0.001781438000, 0.001660110000, 0.001546459000, + 0.001439971000, 0.001340042000, 0.001246275000, 0.001158471000, 0.001076430000, + 0.000999949300, 0.000928735800, 0.000862433200, 0.000800750300, 0.000743396000, + 0.000690078600, 0.000640515600, 0.000594502100, 0.000551864600, 0.000512429000, + 0.000476021300, 0.000442453600, 0.000411511700, 0.000382981400, 0.000356649100, + 0.000332301100, 0.000309758600, 0.000288887100, 0.000269539400, 0.000251568200, + 0.000234826100, 0.000219171000, 0.000204525800, 0.000190840500, 0.000178065400, + 0.000166150500, 0.000155023600, 0.000144621900, 0.000134909800, 0.000125852000, + 0.000117413000, 0.000109551500, 0.000102224500, 0.000095394450, 0.000089023900, + 0.000083075270, 0.000077512690, 0.000072313040, 0.000067457780, 0.000062928440, + 0.000058706520, 0.000054770280, 0.000051099180, 0.000047676540, 0.000044485670, + 0.000041509940, 0.000038733240, 0.000036142030, 0.000033723520, 0.000031464870, + 0.000029353260, 0.000027375730, 0.000025524330, 0.000023793760, 0.000022178700, + 0.000020673830, 0.000019272260, 0.000017966400, 0.000016749910, 0.000015616480, + 0.000014559770, 0.000013573870, 0.000012654360, 0.000011797230, 0.000010998440, + 0.000010253980, 0.000009559646, 0.000008912044, 0.000008308358, 0.000007745769, + 0.000007221456, 0.000006732475, 0.000006276423, 0.000005851304, 0.000005455118, + 0.000005085868, 0.000004741466, 0.000004420236, 0.000004120783, 0.000003841716, + 0.000003581652, 0.000003339127, 0.000003112949, 0.000002902121, 0.000002705645, + 0.000002522525, 0.000002351726, 0.000002192415, 0.000002043902, 0.000001905497, + 0.000001776509, 0.000001656215, 0.000001544022, 0.000001439440, 0.000001341977, + 0.000001251141 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000003917000, 0.000004393581, 0.000004929604, 0.000005532136, 0.000006208245, + 0.000006965000, 0.000007813219, 0.000008767336, 0.000009839844, 0.000011043230, + 0.000012390000, 0.000013886410, 0.000015557280, 0.000017442960, 0.000019583750, + 0.000022020000, 0.000024839650, 0.000028041260, 0.000031531040, 0.000035215210, + 0.000039000000, 0.000042826400, 0.000046914600, 0.000051589600, 0.000057176400, + 0.000064000000, 0.000072344210, 0.000082212240, 0.000093508160, 0.000106136100, + 0.000120000000, 0.000134984000, 0.000151492000, 0.000170208000, 0.000191816000, + 0.000217000000, 0.000246906700, 0.000281240000, 0.000318520000, 0.000357266700, + 0.000396000000, 0.000433714700, 0.000473024000, 0.000517876000, 0.000572218700, + 0.000640000000, 0.000724560000, 0.000825500000, 0.000941160000, 0.001069880000, + 0.001210000000, 0.001362091000, 0.001530752000, 0.001720368000, 0.001935323000, + 0.002180000000, 0.002454800000, 0.002764000000, 0.003117800000, 0.003526400000, + 0.004000000000, 0.004546240000, 0.005159320000, 0.005829280000, 0.006546160000, + 0.007300000000, 0.008086507000, 0.008908720000, 0.009767680000, 0.010664430000, + 0.011600000000, 0.012573170000, 0.013582720000, 0.014629680000, 0.015715090000, + 0.016840000000, 0.018007360000, 0.019214480000, 0.020453920000, 0.021718240000, + 0.023000000000, 0.024294610000, 0.025610240000, 0.026958570000, 0.028351250000, + 0.029800000000, 0.031310830000, 0.032883680000, 0.034521120000, 0.036225710000, + 0.038000000000, 0.039846670000, 0.041768000000, 0.043766000000, 0.045842670000, + 0.048000000000, 0.050243680000, 0.052573040000, 0.054980560000, 0.057458720000, + 0.060000000000, 0.062601970000, 0.065277520000, 0.068042080000, 0.070911090000, + 0.073900000000, 0.077016000000, 0.080266400000, 0.083666800000, 0.087232800000, + 0.090980000000, 0.094917550000, 0.099045840000, 0.103367400000, 0.107884600000, + 0.112600000000, 0.117532000000, 0.122674400000, 0.127992800000, 0.133452800000, + 0.139020000000, 0.144676400000, 0.150469300000, 0.156461900000, 0.162717700000, + 0.169300000000, 0.176243100000, 0.183558100000, 0.191273500000, 0.199418000000, + 0.208020000000, 0.217119900000, 0.226734500000, 0.236857100000, 0.247481200000, + 0.258600000000, 0.270184900000, 0.282293900000, 0.295050500000, 0.308578000000, + 0.323000000000, 0.338402100000, 0.354685800000, 0.371698600000, 0.389287500000, + 0.407300000000, 0.425629900000, 0.444309600000, 0.463394400000, 0.482939500000, + 0.503000000000, 0.523569300000, 0.544512000000, 0.565690000000, 0.586965300000, + 0.608200000000, 0.629345600000, 0.650306800000, 0.670875200000, 0.690842400000, + 0.710000000000, 0.728185200000, 0.745463600000, 0.761969400000, 0.777836800000, + 0.793200000000, 0.808110400000, 0.822496200000, 0.836306800000, 0.849491600000, + 0.862000000000, 0.873810800000, 0.884962400000, 0.895493600000, 0.905443200000, + 0.914850100000, 0.923734800000, 0.932092400000, 0.939922600000, 0.947225200000, + 0.954000000000, 0.960256100000, 0.966007400000, 0.971260600000, 0.976022500000, + 0.980300000000, 0.984092400000, 0.987418200000, 0.990312800000, 0.992811600000, + 0.994950100000, 0.996710800000, 0.998098300000, 0.999112000000, 0.999748200000, + 1.000000000000, 0.999856700000, 0.999304600000, 0.998325500000, 0.996898700000, + 0.995000000000, 0.992600500000, 0.989742600000, 0.986444400000, 0.982724100000, + 0.978600000000, 0.974083700000, 0.969171200000, 0.963856800000, 0.958134900000, + 0.952000000000, 0.945450400000, 0.938499200000, 0.931162800000, 0.923457600000, + 0.915400000000, 0.907006400000, 0.898277200000, 0.889204800000, 0.879781600000, + 0.870000000000, 0.859861300000, 0.849392000000, 0.838622000000, 0.827581300000, + 0.816300000000, 0.804794700000, 0.793082000000, 0.781192000000, 0.769154700000, + 0.757000000000, 0.744754100000, 0.732422400000, 0.720003600000, 0.707496500000, + 0.694900000000, 0.682219200000, 0.669471600000, 0.656674400000, 0.643844800000, + 0.631000000000, 0.618155500000, 0.605314400000, 0.592475600000, 0.579637900000, + 0.566800000000, 0.553961100000, 0.541137200000, 0.528352800000, 0.515632300000, + 0.503000000000, 0.490468800000, 0.478030400000, 0.465677600000, 0.453403200000, + 0.441200000000, 0.429080000000, 0.417036000000, 0.405032000000, 0.393032000000, + 0.381000000000, 0.368918400000, 0.356827200000, 0.344776800000, 0.332817600000, + 0.321000000000, 0.309338100000, 0.297850400000, 0.286593600000, 0.275624500000, + 0.265000000000, 0.254763200000, 0.244889600000, 0.235334400000, 0.226052800000, + 0.217000000000, 0.208161600000, 0.199548800000, 0.191155200000, 0.182974400000, + 0.175000000000, 0.167223500000, 0.159646400000, 0.152277600000, 0.145125900000, + 0.138200000000, 0.131500300000, 0.125024800000, 0.118779200000, 0.112769100000, + 0.107000000000, 0.101476200000, 0.096188640000, 0.091122960000, 0.086264850000, + 0.081600000000, 0.077120640000, 0.072825520000, 0.068710080000, 0.064769760000, + 0.061000000000, 0.057396210000, 0.053955040000, 0.050673760000, 0.047549650000, + 0.044580000000, 0.041758720000, 0.039084960000, 0.036563840000, 0.034200480000, + 0.032000000000, 0.029962610000, 0.028076640000, 0.026329360000, 0.024708050000, + 0.023200000000, 0.021800770000, 0.020501120000, 0.019281080000, 0.018120690000, + 0.017000000000, 0.015903790000, 0.014837180000, 0.013810680000, 0.012834780000, + 0.011920000000, 0.011068310000, 0.010273390000, 0.009533311000, 0.008846157000, + 0.008210000000, 0.007623781000, 0.007085424000, 0.006591476000, 0.006138485000, + 0.005723000000, 0.005343059000, 0.004995796000, 0.004676404000, 0.004380075000, + 0.004102000000, 0.003838453000, 0.003589099000, 0.003354219000, 0.003134093000, + 0.002929000000, 0.002738139000, 0.002559876000, 0.002393244000, 0.002237275000, + 0.002091000000, 0.001953587000, 0.001824580000, 0.001703580000, 0.001590187000, + 0.001484000000, 0.001384496000, 0.001291268000, 0.001204092000, 0.001122744000, + 0.001047000000, 0.000976589600, 0.000911108800, 0.000850133200, 0.000793238400, + 0.000740000000, 0.000690082700, 0.000643310000, 0.000599496000, 0.000558454700, + 0.000520000000, 0.000483913600, 0.000450052800, 0.000418345200, 0.000388718400, + 0.000361100000, 0.000335383500, 0.000311440400, 0.000289165600, 0.000268453900, + 0.000249200000, 0.000231301900, 0.000214685600, 0.000199288400, 0.000185047500, + 0.000171900000, 0.000159778100, 0.000148604400, 0.000138301600, 0.000128792500, + 0.000120000000, 0.000111859500, 0.000104322400, 0.000097335600, 0.000090845870, + 0.000084800000, 0.000079146670, 0.000073858000, 0.000068916000, 0.000064302670, + 0.000060000000, 0.000055981870, 0.000052225600, 0.000048718400, 0.000045447470, + 0.000042400000, 0.000039561040, 0.000036915120, 0.000034448680, 0.000032148160, + 0.000030000000, 0.000027991250, 0.000026113560, 0.000024360240, 0.000022724610, + 0.000021200000, 0.000019778550, 0.000018452850, 0.000017216870, 0.000016064590, + 0.000014990000, 0.000013987280, 0.000013051550, 0.000012178180, 0.000011362540, + 0.000010600000, 0.000009885877, 0.000009217304, 0.000008592362, 0.000008009133, + 0.000007465700, 0.000006959567, 0.000006487995, 0.000006048699, 0.000005639396, + 0.000005257800, 0.000004901771, 0.000004569720, 0.000004260194, 0.000003971739, + 0.000003702900, 0.000003452163, 0.000003218302, 0.000003000300, 0.000002797139, + 0.000002607800, 0.000002431220, 0.000002266531, 0.000002113013, 0.000001969943, + 0.000001836600, 0.000001712230, 0.000001596228, 0.000001488090, 0.000001387314, + 0.000001293400, 0.000001205820, 0.000001124143, 0.000001048009, 0.000000977058, + 0.000000910930, 0.000000849251, 0.000000791721, 0.000000738090, 0.000000688110, + 0.000000641530, 0.000000598090, 0.000000557575, 0.000000519808, 0.000000484612, + 0.000000451810 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000606100000, 0.000680879200, 0.000765145600, 0.000860012400, 0.000966592800, + 0.001086000000, 0.001220586000, 0.001372729000, 0.001543579000, 0.001734286000, + 0.001946000000, 0.002177777000, 0.002435809000, 0.002731953000, 0.003078064000, + 0.003486000000, 0.003975227000, 0.004540880000, 0.005158320000, 0.005802907000, + 0.006450001000, 0.007083216000, 0.007745488000, 0.008501152000, 0.009414544000, + 0.010549990000, 0.011965800000, 0.013655870000, 0.015588050000, 0.017730150000, + 0.020050010000, 0.022511360000, 0.025202880000, 0.028279720000, 0.031897040000, + 0.036210000000, 0.041437710000, 0.047503720000, 0.054119880000, 0.060998030000, + 0.067850010000, 0.074486320000, 0.081361560000, 0.089153640000, 0.098540480000, + 0.110200000000, 0.124613300000, 0.141701700000, 0.161303500000, 0.183256800000, + 0.207400000000, 0.233692100000, 0.262611400000, 0.294774600000, 0.330798500000, + 0.371300000000, 0.416209100000, 0.465464200000, 0.519694800000, 0.579530300000, + 0.645600000000, 0.718483800000, 0.796713300000, 0.877845900000, 0.959439000000, + 1.039050100000, 1.115367300000, 1.188497100000, 1.258123300000, 1.323929600000, + 1.385600000000, 1.442635200000, 1.494803500000, 1.542190300000, 1.584880700000, + 1.622960000000, 1.656404800000, 1.685295900000, 1.709874500000, 1.730382100000, + 1.747060000000, 1.760044600000, 1.769623300000, 1.776263700000, 1.780433400000, + 1.782600000000, 1.782968200000, 1.781699800000, 1.779198200000, 1.775867100000, + 1.772110000000, 1.768258900000, 1.764039000000, 1.758943800000, 1.752466300000, + 1.744100000000, 1.733559500000, 1.720858100000, 1.705936900000, 1.688737200000, + 1.669200000000, 1.647528700000, 1.623412700000, 1.596022300000, 1.564528000000, + 1.528100000000, 1.486111400000, 1.439521500000, 1.389879900000, 1.338736200000, + 1.287640000000, 1.237422300000, 1.187824300000, 1.138761100000, 1.090148000000, + 1.041900000000, 0.994197600000, 0.947347300000, 0.901453100000, 0.856619300000, + 0.812950100000, 0.770517300000, 0.729444800000, 0.689913600000, 0.652104900000, + 0.616200000000, 0.582328600000, 0.550416200000, 0.520337600000, 0.491967300000, + 0.465180000000, 0.439924600000, 0.416183600000, 0.393882200000, 0.372945900000, + 0.353300000000, 0.334857800000, 0.317552100000, 0.301337500000, 0.286168600000, + 0.272000000000, 0.258817100000, 0.246483800000, 0.234771800000, 0.223453300000, + 0.212300000000, 0.201169200000, 0.190119600000, 0.179225400000, 0.168560800000, + 0.158200000000, 0.148138300000, 0.138375800000, 0.128994200000, 0.120075100000, + 0.111700000000, 0.103904800000, 0.096667480000, 0.089982720000, 0.083845310000, + 0.078249990000, 0.073208990000, 0.068678160000, 0.064567840000, 0.060788350000, + 0.057250010000, 0.053904350000, 0.050746640000, 0.047752760000, 0.044898590000, + 0.042160000000, 0.039507280000, 0.036935640000, 0.034458360000, 0.032088720000, + 0.029840000000, 0.027711810000, 0.025694440000, 0.023787160000, 0.021989250000, + 0.020300000000, 0.018718050000, 0.017240360000, 0.015863640000, 0.014584610000, + 0.013400000000, 0.012307230000, 0.011301880000, 0.010377920000, 0.009529306000, + 0.008749999000, 0.008035200000, 0.007381600000, 0.006785400000, 0.006242800000, + 0.005749999000, 0.005303600000, 0.004899800000, 0.004534200000, 0.004202400000, + 0.003900000000, 0.003623200000, 0.003370600000, 0.003141400000, 0.002934800000, + 0.002749999000, 0.002585200000, 0.002438600000, 0.002309400000, 0.002196800000, + 0.002100000000, 0.002017733000, 0.001948200000, 0.001889800000, 0.001840933000, + 0.001800000000, 0.001766267000, 0.001737800000, 0.001711200000, 0.001683067000, + 0.001650001000, 0.001610133000, 0.001564400000, 0.001513600000, 0.001458533000, + 0.001400000000, 0.001336667000, 0.001270000000, 0.001205000000, 0.001146667000, + 0.001100000000, 0.001068800000, 0.001049400000, 0.001035600000, 0.001021200000, + 0.001000000000, 0.000968640000, 0.000929920000, 0.000886880000, 0.000842560000, + 0.000800000000, 0.000760960000, 0.000723680000, 0.000685920000, 0.000645440000, + 0.000600000000, 0.000547866700, 0.000491600000, 0.000435400000, 0.000383466700, + 0.000340000000, 0.000307253300, 0.000283160000, 0.000265440000, 0.000251813300, + 0.000240000000, 0.000229546700, 0.000220640000, 0.000211960000, 0.000202186700, + 0.000190000000, 0.000174213300, 0.000155640000, 0.000135960000, 0.000116853300, + 0.000100000000, 0.000086133330, 0.000074600000, 0.000065000000, 0.000056933330, + 0.000049999990, 0.000044160000, 0.000039480000, 0.000035720000, 0.000032640000, + 0.000030000000, 0.000027653330, 0.000025560000, 0.000023640000, 0.000021813330, + 0.000020000000, 0.000018133330, 0.000016200000, 0.000014200000, 0.000012133330, + 0.000010000000, 0.000007733333, 0.000005400000, 0.000003200000, 0.000001333333, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000 + } + } +}; + +static xslpoly poly_CIE_1931_2 = { 0 }; + +/* Standard CIE 1964 10 degree */ +static xspect ob_CIE_1964_10[3] = { + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000000122200, 0.000000185138, 0.000000278830, 0.000000417470, 0.000000621330, + 0.000000919270, 0.000001351980, 0.000001976540, 0.000002872500, 0.000004149500, + 0.000005958600, 0.000008505600, 0.000012068600, 0.000017022600, 0.000023868000, + 0.000033266000, 0.000046087000, 0.000063472000, 0.000086892000, 0.000118246000, + 0.000159952000, 0.000215080000, 0.000287490000, 0.000381990000, 0.000504550000, + 0.000662440000, 0.000864500000, 0.001121500000, 0.001446160000, 0.001853590000, + 0.002361600000, 0.002990600000, 0.003764500000, 0.004710200000, 0.005858100000, + 0.007242300000, 0.008899600000, 0.010870900000, 0.013198900000, 0.015929200000, + 0.019109700000, 0.022788000000, 0.027011000000, 0.031829000000, 0.037278000000, + 0.043400000000, 0.050223000000, 0.057764000000, 0.066038000000, 0.075033000000, + 0.084736000000, 0.095041000000, 0.105836000000, 0.117066000000, 0.128682000000, + 0.140638000000, 0.152893000000, 0.165416000000, 0.178191000000, 0.191214000000, + 0.204492000000, 0.217650000000, 0.230267000000, 0.242311000000, 0.253793000000, + 0.264737000000, 0.275195000000, 0.285301000000, 0.295143000000, 0.304869000000, + 0.314679000000, 0.324355000000, 0.333570000000, 0.342243000000, 0.350312000000, + 0.357719000000, 0.364482000000, 0.370493000000, 0.375727000000, 0.380158000000, + 0.383734000000, 0.386327000000, 0.387858000000, 0.388396000000, 0.387978000000, + 0.386726000000, 0.384696000000, 0.382006000000, 0.378709000000, 0.374915000000, + 0.370702000000, 0.366089000000, 0.361045000000, 0.355518000000, 0.349486000000, + 0.342957000000, 0.335893000000, 0.328284000000, 0.320150000000, 0.311475000000, + 0.302273000000, 0.292858000000, 0.283502000000, 0.274044000000, 0.264263000000, + 0.254085000000, 0.243392000000, 0.232187000000, 0.220488000000, 0.208198000000, + 0.195618000000, 0.183034000000, 0.170222000000, 0.157348000000, 0.144650000000, + 0.132349000000, 0.120584000000, 0.109456000000, 0.099042000000, 0.089388000000, + 0.080507000000, 0.072034000000, 0.063710000000, 0.055694000000, 0.048117000000, + 0.041072000000, 0.034642000000, 0.028896000000, 0.023876000000, 0.019628000000, + 0.016172000000, 0.013300000000, 0.010759000000, 0.008542000000, 0.006661000000, + 0.005132000000, 0.003982000000, 0.003239000000, 0.002934000000, 0.003114000000, + 0.003816000000, 0.005095000000, 0.006936000000, 0.009299000000, 0.012147000000, + 0.015444000000, 0.019156000000, 0.023250000000, 0.027690000000, 0.032444000000, + 0.037465000000, 0.042956000000, 0.049114000000, 0.055920000000, 0.063349000000, + 0.071358000000, 0.079901000000, 0.088909000000, 0.098293000000, 0.107949000000, + 0.117749000000, 0.127839000000, 0.138450000000, 0.149516000000, 0.161041000000, + 0.172953000000, 0.185209000000, 0.197755000000, 0.210538000000, 0.223460000000, + 0.236491000000, 0.249633000000, 0.262972000000, 0.276515000000, 0.290269000000, + 0.304213000000, 0.318361000000, 0.332705000000, 0.347232000000, 0.361926000000, + 0.376772000000, 0.391683000000, 0.406594000000, 0.421539000000, 0.436517000000, + 0.451584000000, 0.466782000000, 0.482147000000, 0.497738000000, 0.513606000000, + 0.529826000000, 0.546440000000, 0.563426000000, 0.580726000000, 0.598290000000, + 0.616053000000, 0.633948000000, 0.651901000000, 0.669824000000, 0.687632000000, + 0.705224000000, 0.722773000000, 0.740483000000, 0.758273000000, 0.776083000000, + 0.793832000000, 0.811436000000, 0.828822000000, 0.845879000000, 0.862525000000, + 0.878655000000, 0.894208000000, 0.909206000000, 0.923672000000, 0.937638000000, + 0.951162000000, 0.964283000000, 0.977068000000, 0.989590000000, 1.001910000000, + 1.014160000000, 1.026500000000, 1.038800000000, 1.051000000000, 1.062900000000, + 1.074300000000, 1.085200000000, 1.095200000000, 1.104200000000, 1.112000000000, + 1.118520000000, 1.123800000000, 1.128000000000, 1.131100000000, 1.133200000000, + 1.134300000000, 1.134300000000, 1.133300000000, 1.131200000000, 1.128100000000, + 1.123990000000, 1.118900000000, 1.112900000000, 1.105900000000, 1.098000000000, + 1.089100000000, 1.079200000000, 1.068400000000, 1.056700000000, 1.044000000000, + 1.030480000000, 1.016000000000, 1.000800000000, 0.984790000000, 0.968080000000, + 0.950740000000, 0.932800000000, 0.914340000000, 0.895390000000, 0.876030000000, + 0.856297000000, 0.836350000000, 0.816290000000, 0.796050000000, 0.775610000000, + 0.754930000000, 0.733990000000, 0.712780000000, 0.691290000000, 0.669520000000, + 0.647467000000, 0.625110000000, 0.602520000000, 0.579890000000, 0.557370000000, + 0.535110000000, 0.513240000000, 0.491860000000, 0.471080000000, 0.450960000000, + 0.431567000000, 0.412870000000, 0.394750000000, 0.377210000000, 0.360190000000, + 0.343690000000, 0.327690000000, 0.312170000000, 0.297110000000, 0.282500000000, + 0.268329000000, 0.254590000000, 0.241300000000, 0.228480000000, 0.216140000000, + 0.204300000000, 0.192950000000, 0.182110000000, 0.171770000000, 0.161920000000, + 0.152568000000, 0.143670000000, 0.135200000000, 0.127130000000, 0.119480000000, + 0.112210000000, 0.105310000000, 0.098786000000, 0.092610000000, 0.086773000000, + 0.081260600000, 0.076048000000, 0.071114000000, 0.066454000000, 0.062062000000, + 0.057930000000, 0.054050000000, 0.050412000000, 0.047006000000, 0.043823000000, + 0.040850800000, 0.038072000000, 0.035468000000, 0.033031000000, 0.030753000000, + 0.028623000000, 0.026635000000, 0.024781000000, 0.023052000000, 0.021441000000, + 0.019941300000, 0.018544000000, 0.017241000000, 0.016027000000, 0.014896000000, + 0.013842000000, 0.012862000000, 0.011949000000, 0.011100000000, 0.010311000000, + 0.009576880000, 0.008894000000, 0.008258100000, 0.007666400000, 0.007116300000, + 0.006605200000, 0.006130600000, 0.005690300000, 0.005281900000, 0.004903300000, + 0.004552630000, 0.004227500000, 0.003925800000, 0.003645700000, 0.003385900000, + 0.003144700000, 0.002920800000, 0.002713000000, 0.002520200000, 0.002341100000, + 0.002174960000, 0.002020600000, 0.001877300000, 0.001744100000, 0.001620500000, + 0.001505700000, 0.001399200000, 0.001300400000, 0.001208700000, 0.001123600000, + 0.001044760000, 0.000971560000, 0.000903600000, 0.000840480000, 0.000781870000, + 0.000727450000, 0.000676900000, 0.000629960000, 0.000586370000, 0.000545870000, + 0.000508258000, 0.000473300000, 0.000440800000, 0.000410580000, 0.000382490000, + 0.000356380000, 0.000332110000, 0.000309550000, 0.000288580000, 0.000269090000, + 0.000250969000, 0.000234130000, 0.000218470000, 0.000203910000, 0.000190350000, + 0.000177730000, 0.000165970000, 0.000155020000, 0.000144800000, 0.000135280000, + 0.000126390000, 0.000118100000, 0.000110370000, 0.000103150000, 0.000096427000, + 0.000090151000, 0.000084294000, 0.000078830000, 0.000073729000, 0.000068969000, + 0.000064525800, 0.000060376000, 0.000056500000, 0.000052880000, 0.000049498000, + 0.000046339000, 0.000043389000, 0.000040634000, 0.000038060000, 0.000035657000, + 0.000033411700, 0.000031315000, 0.000029355000, 0.000027524000, 0.000025811000, + 0.000024209000, 0.000022711000, 0.000021308000, 0.000019994000, 0.000018764000, + 0.000017611500, 0.000016532000, 0.000015521000, 0.000014574000, 0.000013686000, + 0.000012855000, 0.000012075000, 0.000011345000, 0.000010659000, 0.000010017000, + 0.000009413630, 0.000008847900, 0.000008317100, 0.000007819000, 0.000007351600, + 0.000006913000, 0.000006501500, 0.000006115300, 0.000005752900, 0.000005412700, + 0.000005093470, 0.000004793800, 0.000004512500, 0.000004248300, 0.000004000200, + 0.000003767100, 0.000003548000, 0.000003342100, 0.000003148500, 0.000002966500, + 0.000002795310, 0.000002634500, 0.000002483400, 0.000002341400, 0.000002207800, + 0.000002082000, 0.000001963600, 0.000001851900, 0.000001746500, 0.000001647100, + 0.000001553140 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000000013398, 0.000000020294, 0.000000030560, 0.000000045740, 0.000000068050, + 0.000000100650, 0.000000147980, 0.000000216270, 0.000000314200, 0.000000453700, + 0.000000651100, 0.000000928800, 0.000001317500, 0.000001857200, 0.000002602000, + 0.000003625000, 0.000005019000, 0.000006907000, 0.000009449000, 0.000012848000, + 0.000017364000, 0.000023327000, 0.000031150000, 0.000041350000, 0.000054560000, + 0.000071560000, 0.000093300000, 0.000120870000, 0.000155640000, 0.000199200000, + 0.000253400000, 0.000320200000, 0.000402400000, 0.000502300000, 0.000623200000, + 0.000768500000, 0.000941700000, 0.001147800000, 0.001390300000, 0.001674000000, + 0.002004400000, 0.002386000000, 0.002822000000, 0.003319000000, 0.003880000000, + 0.004509000000, 0.005209000000, 0.005985000000, 0.006833000000, 0.007757000000, + 0.008756000000, 0.009816000000, 0.010918000000, 0.012058000000, 0.013237000000, + 0.014456000000, 0.015717000000, 0.017025000000, 0.018399000000, 0.019848000000, + 0.021391000000, 0.022992000000, 0.024598000000, 0.026213000000, 0.027841000000, + 0.029497000000, 0.031195000000, 0.032927000000, 0.034738000000, 0.036654000000, + 0.038676000000, 0.040792000000, 0.042946000000, 0.045114000000, 0.047333000000, + 0.049602000000, 0.051934000000, 0.054337000000, 0.056822000000, 0.059399000000, + 0.062077000000, 0.064737000000, 0.067285000000, 0.069764000000, 0.072218000000, + 0.074704000000, 0.077272000000, 0.079979000000, 0.082874000000, 0.086000000000, + 0.089456000000, 0.092947000000, 0.096275000000, 0.099535000000, 0.102829000000, + 0.106256000000, 0.109901000000, 0.113835000000, 0.118167000000, 0.122932000000, + 0.128201000000, 0.133457000000, 0.138323000000, 0.143042000000, 0.147787000000, + 0.152761000000, 0.158102000000, 0.163941000000, 0.170362000000, 0.177425000000, + 0.185190000000, 0.193025000000, 0.200313000000, 0.207156000000, 0.213644000000, + 0.219940000000, 0.226170000000, 0.232467000000, 0.239025000000, 0.245997000000, + 0.253589000000, 0.261876000000, 0.270643000000, 0.279645000000, 0.288694000000, + 0.297665000000, 0.306469000000, 0.315035000000, 0.323335000000, 0.331366000000, + 0.339133000000, 0.347860000000, 0.358326000000, 0.370001000000, 0.382464000000, + 0.395379000000, 0.408482000000, 0.421588000000, 0.434619000000, 0.447601000000, + 0.460777000000, 0.474340000000, 0.488200000000, 0.502340000000, 0.516740000000, + 0.531360000000, 0.546190000000, 0.561180000000, 0.576290000000, 0.591500000000, + 0.606741000000, 0.622150000000, 0.637830000000, 0.653710000000, 0.669680000000, + 0.685660000000, 0.701550000000, 0.717230000000, 0.732570000000, 0.747460000000, + 0.761757000000, 0.775340000000, 0.788220000000, 0.800460000000, 0.812140000000, + 0.823330000000, 0.834120000000, 0.844600000000, 0.854870000000, 0.865040000000, + 0.875211000000, 0.885370000000, 0.895370000000, 0.905150000000, 0.914650000000, + 0.923810000000, 0.932550000000, 0.940810000000, 0.948520000000, 0.955600000000, + 0.961988000000, 0.967540000000, 0.972230000000, 0.976170000000, 0.979460000000, + 0.982200000000, 0.984520000000, 0.986520000000, 0.988320000000, 0.990020000000, + 0.991761000000, 0.993530000000, 0.995230000000, 0.996770000000, 0.998090000000, + 0.999110000000, 0.999770000000, 1.000000000000, 0.999710000000, 0.998850000000, + 0.997340000000, 0.995260000000, 0.992740000000, 0.989750000000, 0.986300000000, + 0.982380000000, 0.977980000000, 0.973110000000, 0.967740000000, 0.961890000000, + 0.955552000000, 0.948601000000, 0.940981000000, 0.932798000000, 0.924158000000, + 0.915175000000, 0.905954000000, 0.896608000000, 0.887249000000, 0.877986000000, + 0.868934000000, 0.860164000000, 0.851519000000, 0.842963000000, 0.834393000000, + 0.825623000000, 0.816764000000, 0.807544000000, 0.797947000000, 0.787893000000, + 0.777405000000, 0.766490000000, 0.755309000000, 0.743845000000, 0.732190000000, + 0.720353000000, 0.708281000000, 0.696055000000, 0.683621000000, 0.671048000000, + 0.658341000000, 0.645545000000, 0.632718000000, 0.619815000000, 0.606887000000, + 0.593878000000, 0.580781000000, 0.567653000000, 0.554490000000, 0.541228000000, + 0.527963000000, 0.514634000000, 0.501363000000, 0.488124000000, 0.474935000000, + 0.461834000000, 0.448823000000, 0.435917000000, 0.423153000000, 0.410526000000, + 0.398057000000, 0.385835000000, 0.373951000000, 0.362311000000, 0.350863000000, + 0.339554000000, 0.328309000000, 0.317118000000, 0.305936000000, 0.294737000000, + 0.283493000000, 0.272222000000, 0.260990000000, 0.249877000000, 0.238946000000, + 0.228254000000, 0.217853000000, 0.207780000000, 0.198072000000, 0.188748000000, + 0.179828000000, 0.171285000000, 0.163059000000, 0.155151000000, 0.147535000000, + 0.140211000000, 0.133170000000, 0.126400000000, 0.119892000000, 0.113640000000, + 0.107633000000, 0.101870000000, 0.096347000000, 0.091063000000, 0.086010000000, + 0.081187000000, 0.076583000000, 0.072198000000, 0.068024000000, 0.064052000000, + 0.060281000000, 0.056697000000, 0.053292000000, 0.050059000000, 0.046998000000, + 0.044096000000, 0.041345000000, 0.038750700000, 0.036297800000, 0.033983200000, + 0.031800400000, 0.029739500000, 0.027791800000, 0.025955100000, 0.024226300000, + 0.022601700000, 0.021077900000, 0.019650500000, 0.018315300000, 0.017068600000, + 0.015905100000, 0.014818300000, 0.013800800000, 0.012849500000, 0.011960700000, + 0.011130300000, 0.010355500000, 0.009633200000, 0.008959900000, 0.008332400000, + 0.007748800000, 0.007204600000, 0.006697500000, 0.006225100000, 0.005785000000, + 0.005375100000, 0.004994100000, 0.004639200000, 0.004309300000, 0.004002800000, + 0.003717740000, 0.003452620000, 0.003205830000, 0.002976230000, 0.002762810000, + 0.002564560000, 0.002380480000, 0.002209710000, 0.002051320000, 0.001904490000, + 0.001768470000, 0.001642360000, 0.001525350000, 0.001416720000, 0.001315950000, + 0.001222390000, 0.001135550000, 0.001054940000, 0.000980140000, 0.000910660000, + 0.000846190000, 0.000786290000, 0.000730680000, 0.000678990000, 0.000631010000, + 0.000586440000, 0.000545110000, 0.000506720000, 0.000471110000, 0.000438050000, + 0.000407410000, 0.000378962000, 0.000352543000, 0.000328001000, 0.000305208000, + 0.000284041000, 0.000264375000, 0.000246109000, 0.000229143000, 0.000213376000, + 0.000198730000, 0.000185115000, 0.000172454000, 0.000160678000, 0.000149730000, + 0.000139550000, 0.000130086000, 0.000121290000, 0.000113106000, 0.000105501000, + 0.000098428000, 0.000091853000, 0.000085738000, 0.000080048000, 0.000074751000, + 0.000069819000, 0.000065222000, 0.000060939000, 0.000056942000, 0.000053217000, + 0.000049737000, 0.000046491000, 0.000043464000, 0.000040635000, 0.000038000000, + 0.000035540500, 0.000033244800, 0.000031100600, 0.000029099000, 0.000027230700, + 0.000025486000, 0.000023856100, 0.000022333200, 0.000020910400, 0.000019580800, + 0.000018338400, 0.000017177700, 0.000016093400, 0.000015080000, 0.000014133600, + 0.000013249000, 0.000012422600, 0.000011649900, 0.000010927700, 0.000010251900, + 0.000009619600, 0.000009028100, 0.000008474000, 0.000007954800, 0.000007468600, + 0.000007012800, 0.000006585800, 0.000006185700, 0.000005810700, 0.000005459000, + 0.000005129800, 0.000004820600, 0.000004531200, 0.000004259100, 0.000004004200, + 0.000003764730, 0.000003539950, 0.000003329140, 0.000003131150, 0.000002945290, + 0.000002770810, 0.000002607050, 0.000002453290, 0.000002308940, 0.000002173380, + 0.000002046130, 0.000001926620, 0.000001814400, 0.000001708950, 0.000001609880, + 0.000001516770, 0.000001429210, 0.000001346860, 0.000001269450, 0.000001196620, + 0.000001128090, 0.000001063680, 0.000001003130, 0.000000946220, 0.000000892630, + 0.000000842160, 0.000000794640, 0.000000749780, 0.000000707440, 0.000000667480, + 0.000000629700 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000000535027, 0.000000810720, 0.000001221200, 0.000001828700, 0.000002722200, + 0.000004028300, 0.000005925700, 0.000008665100, 0.000012596000, 0.000018201000, + 0.000026143700, 0.000037330000, 0.000052987000, 0.000074764000, 0.000104870000, + 0.000146220000, 0.000202660000, 0.000279230000, 0.000382450000, 0.000520720000, + 0.000704776000, 0.000948230000, 0.001268200000, 0.001686100000, 0.002228500000, + 0.002927800000, 0.003823700000, 0.004964200000, 0.006406700000, 0.008219300000, + 0.010482200000, 0.013289000000, 0.016747000000, 0.020980000000, 0.026127000000, + 0.032344000000, 0.039802000000, 0.048691000000, 0.059210000000, 0.071576000000, + 0.086010900000, 0.102740000000, 0.122000000000, 0.144020000000, 0.168990000000, + 0.197120000000, 0.228570000000, 0.263470000000, 0.301900000000, 0.343870000000, + 0.389366000000, 0.437970000000, 0.489220000000, 0.542900000000, 0.598810000000, + 0.656760000000, 0.716580000000, 0.778120000000, 0.841310000000, 0.906110000000, + 0.972542000000, 1.038900000000, 1.103100000000, 1.165100000000, 1.224900000000, + 1.282500000000, 1.338200000000, 1.392600000000, 1.446100000000, 1.499400000000, + 1.553480000000, 1.607200000000, 1.658900000000, 1.708200000000, 1.754800000000, + 1.798500000000, 1.839200000000, 1.876600000000, 1.910500000000, 1.940800000000, + 1.967280000000, 1.989100000000, 2.005700000000, 2.017400000000, 2.024400000000, + 2.027300000000, 2.026400000000, 2.022300000000, 2.015300000000, 2.006000000000, + 1.994800000000, 1.981400000000, 1.965300000000, 1.946400000000, 1.924800000000, + 1.900700000000, 1.874100000000, 1.845100000000, 1.813900000000, 1.780600000000, + 1.745370000000, 1.709100000000, 1.672300000000, 1.634700000000, 1.595600000000, + 1.554900000000, 1.512200000000, 1.467300000000, 1.419900000000, 1.370000000000, + 1.317560000000, 1.262400000000, 1.205000000000, 1.146600000000, 1.088000000000, + 1.030200000000, 0.973830000000, 0.919430000000, 0.867460000000, 0.818280000000, + 0.772125000000, 0.728290000000, 0.686040000000, 0.645530000000, 0.606850000000, + 0.570060000000, 0.535220000000, 0.502340000000, 0.471400000000, 0.442390000000, + 0.415254000000, 0.390024000000, 0.366399000000, 0.344015000000, 0.322689000000, + 0.302356000000, 0.283036000000, 0.264816000000, 0.247848000000, 0.232318000000, + 0.218502000000, 0.205851000000, 0.193596000000, 0.181736000000, 0.170281000000, + 0.159249000000, 0.148673000000, 0.138609000000, 0.129096000000, 0.120215000000, + 0.112044000000, 0.104710000000, 0.098196000000, 0.092361000000, 0.087088000000, + 0.082248000000, 0.077744000000, 0.073456000000, 0.069268000000, 0.065060000000, + 0.060709000000, 0.056457000000, 0.052609000000, 0.049122000000, 0.045954000000, + 0.043050000000, 0.040368000000, 0.037839000000, 0.035384000000, 0.032949000000, + 0.030451000000, 0.028029000000, 0.025862000000, 0.023920000000, 0.022174000000, + 0.020584000000, 0.019127000000, 0.017740000000, 0.016403000000, 0.015064000000, + 0.013676000000, 0.012308000000, 0.011056000000, 0.009915000000, 0.008872000000, + 0.007918000000, 0.007030000000, 0.006223000000, 0.005453000000, 0.004714000000, + 0.003988000000, 0.003289000000, 0.002646000000, 0.002063000000, 0.001533000000, + 0.001091000000, 0.000711000000, 0.000407000000, 0.000184000000, 0.000047000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000 + } + } +}; + +static xslpoly poly_CIE_1964_10 = { 0 }; + +#ifndef SALONEINSTLIB +/* Standard CIE 1964 10 degree observer, */ +/* adjusted for compatibility with 2 degree observer. */ +/* This has a problem in that it will return -ve XYZ values !! */ +static xspect ob_CIE_1964_10c[3] = { + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000000, 0.000000, 0.000000, 0.000000, 0.000001, + 0.000001, 0.000001, 0.000002, 0.000003, 0.000004, + 0.000006, 0.000008, 0.000012, 0.000016, 0.000023, + 0.000032, 0.000044, 0.000061, 0.000083, 0.000114, + 0.000154, 0.000207, 0.000276, 0.000367, 0.000485, + 0.000636, 0.000830, 0.001077, 0.001389, 0.001780, + 0.002268, 0.002872, 0.003616, 0.004524, 0.005626, + 0.006956, 0.008547, 0.010440, 0.012675, 0.015297, + 0.018351, 0.021882, 0.025937, 0.030562, 0.035793, + 0.041670, 0.048218, 0.055455, 0.063395, 0.072026, + 0.081334, 0.091218, 0.101571, 0.112339, 0.123475, + 0.134934, 0.146676, 0.158671, 0.170901, 0.183362, + 0.196059, 0.208632, 0.220678, 0.232166, 0.243106, + 0.253521, 0.263459, 0.273052, 0.282379, 0.291584, + 0.300858, 0.309993, 0.318677, 0.326834, 0.334402, + 0.341323, 0.347615, 0.353171, 0.357967, 0.361976, + 0.365148, 0.367370, 0.368575, 0.368826, 0.368155, + 0.366675, 0.364435, 0.361545, 0.358052, 0.354057, + 0.349627, 0.344810, 0.339593, 0.333917, 0.327752, + 0.321095, 0.313902, 0.306154, 0.297863, 0.289008, + 0.279595, 0.269978, 0.260456, 0.250851, 0.240930, + 0.230604, 0.219745, 0.208345, 0.196419, 0.183863, + 0.170973, 0.158090, 0.145040, 0.131973, 0.119113, + 0.106660, 0.094739, 0.083435, 0.072806, 0.062881, + 0.053655, 0.044761, 0.035965, 0.027445, 0.019347, + 0.011775, 0.004818, -0.001448, -0.006980, -0.011730, + -0.015680, -0.019142, -0.022434, -0.025515, -0.028335, + -0.030850, -0.033009, -0.034770, -0.036095, -0.036941, + -0.037294, -0.037113, -0.036399, -0.035191, -0.033525, + -0.031433, -0.028948, -0.026100, -0.022920, -0.019440, + -0.015700, -0.011512, -0.006687, -0.001238, 0.004822, + 0.011455, 0.018627, 0.026279, 0.034335, 0.042701, + 0.051262, 0.060174, 0.069663, 0.079658, 0.090158, + 0.101084, 0.112386, 0.124002, 0.135873, 0.147891, + 0.160018, 0.172256, 0.184702, 0.197370, 0.210271, + 0.223389, 0.236747, 0.250340, 0.264164, 0.278208, + 0.292463, 0.306856, 0.321322, 0.335886, 0.350539, + 0.365327, 0.380281, 0.395429, 0.410820, 0.426495, + 0.442517, 0.458930, 0.475719, 0.492834, 0.510231, + 0.527852, 0.545635, 0.563512, 0.581404, 0.599231, + 0.616897, 0.634569, 0.652440, 0.670431, 0.688482, + 0.706512, 0.724440, 0.742190, 0.759656, 0.776754, + 0.793379, 0.809482, 0.825090, 0.840216, 0.854883, + 0.869139, 0.883013, 0.896564, 0.909854, 0.922934, + 0.935926, 0.948983, 0.961990, 0.974889, 0.987491, + 0.999611, 1.011241, 1.022004, 1.031804, 1.040446, + 1.047851, 1.054056, 1.059207, 1.063287, 1.066386, + 1.068504, 1.069546, 1.069604, 1.068584, 1.066579, + 1.063578, 1.059609, 1.054735, 1.048871, 1.042112, + 1.034363, 1.025625, 1.015993, 1.005466, 0.993951, + 0.981619, 0.968336, 0.954330, 0.939514, 0.923995, + 0.907841, 0.891082, 0.873794, 0.856007, 0.837798, + 0.819205, 0.800376, 0.781406, 0.762235, 0.742849, + 0.723211, 0.703308, 0.683132, 0.662676, 0.641942, + 0.620930, 0.599617, 0.578068, 0.556470, 0.534965, + 0.513699, 0.492797, 0.472355, 0.452479, 0.433228, + 0.414667, 0.396767, 0.379415, 0.362613, 0.346305, + 0.330490, 0.315149, 0.300263, 0.285813, 0.271789, + 0.258182, 0.244984, 0.232213, 0.219890, 0.208026, + 0.196640, 0.185723, 0.175297, 0.165350, 0.155874, + 0.146878, 0.138317, 0.130168, 0.122403, 0.115042, + 0.108045, 0.101405, 0.095126, 0.089181, 0.083563, + 0.078256, 0.073238, 0.068488, 0.064001, 0.059773, + 0.055794, 0.052058, 0.048555, 0.045275, 0.042210, + 0.039347, 0.036671, 0.034163, 0.031816, 0.029622, + 0.027571, 0.025656, 0.023870, 0.022205, 0.020653, + 0.019209, 0.017863, 0.016608, 0.015438, 0.014349, + 0.013334, 0.012390, 0.011510, 0.010693, 0.009933, + 0.009225, 0.008568, 0.007955, 0.007385, 0.006855, + 0.006363, 0.005906, 0.005481, 0.005088, 0.004723, + 0.004385, 0.004072, 0.003782, 0.003512, 0.003261, + 0.003029, 0.002813, 0.002613, 0.002428, 0.002255, + 0.002095, 0.001946, 0.001808, 0.001680, 0.001561, + 0.001450, 0.001348, 0.001253, 0.001164, 0.001082, + 0.001006, 0.000936, 0.000870, 0.000809, 0.000753, + 0.000701, 0.000652, 0.000607, 0.000565, 0.000526, + 0.000489, 0.000456, 0.000425, 0.000395, 0.000368, + 0.000343, 0.000320, 0.000298, 0.000278, 0.000259, + 0.000242, 0.000225, 0.000210, 0.000196, 0.000183, + 0.000171, 0.000160, 0.000149, 0.000139, 0.000130, + 0.000122, 0.000114, 0.000106, 0.000099, 0.000093, + 0.000087, 0.000081, 0.000076, 0.000071, 0.000066, + 0.000062, 0.000058, 0.000054, 0.000051, 0.000048, + 0.000045, 0.000042, 0.000039, 0.000037, 0.000034, + 0.000032, 0.000030, 0.000028, 0.000026, 0.000025, + 0.000023, 0.000022, 0.000021, 0.000019, 0.000018, + 0.000017, 0.000016, 0.000015, 0.000014, 0.000013, + 0.000012, 0.000012, 0.000011, 0.000010, 0.000010, + 0.000009, 0.000009, 0.000008, 0.000008, 0.000007, + 0.000007, 0.000006, 0.000006, 0.000006, 0.000005, + 0.000005, 0.000005, 0.000004, 0.000004, 0.000004, + 0.000004, 0.000003, 0.000003, 0.000003, 0.000003, + 0.000003, 0.000003, 0.000002, 0.000002, 0.000002, + 0.000002, 0.000002, 0.000002, 0.000002, 0.000002, + 0.000001 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000001, -0.000001, -0.000001, + -0.000002, -0.000003, -0.000004, -0.000005, -0.000007, + -0.000009, -0.000012, -0.000017, -0.000022, -0.000029, + -0.000039, -0.000051, -0.000066, -0.000086, -0.000111, + -0.000142, -0.000181, -0.000229, -0.000289, -0.000362, + -0.000452, -0.000560, -0.000690, -0.000845, -0.001028, + -0.001243, -0.001494, -0.001786, -0.002121, -0.002505, + -0.002940, -0.003430, -0.003976, -0.004584, -0.005251, + -0.005978, -0.006763, -0.007608, -0.008509, -0.009457, + -0.010445, -0.011465, -0.012506, -0.013548, -0.014581, + -0.015588, -0.016540, -0.017410, -0.018194, -0.018887, + -0.019474, -0.019954, -0.020356, -0.020652, -0.020844, + -0.020966, -0.020987, -0.020899, -0.020710, -0.020374, + -0.019885, -0.019227, -0.018381, -0.017329, -0.016059, + -0.014554, -0.012894, -0.011147, -0.009284, -0.007270, + -0.005073, -0.002659, 0.000006, 0.002956, 0.006209, + 0.009845, 0.013596, 0.017297, 0.021039, 0.024915, + 0.029010, 0.033403, 0.038157, 0.043368, 0.049064, + 0.055306, 0.061573, 0.067495, 0.073309, 0.079203, + 0.085372, 0.091959, 0.099098, 0.106881, 0.115363, + 0.124611, 0.134041, 0.143043, 0.151664, 0.159962, + 0.168055, 0.176039, 0.184021, 0.192166, 0.200605, + 0.209524, 0.219014, 0.228895, 0.238933, 0.248950, + 0.258827, 0.268480, 0.277842, 0.286890, 0.295622, + 0.304046, 0.313314, 0.324171, 0.336130, 0.348800, + 0.361865, 0.375076, 0.388254, 0.401320, 0.414291, + 0.427388, 0.440818, 0.454528, 0.468501, 0.482717, + 0.497136, 0.511747, 0.526497, 0.541348, 0.556275, + 0.571209, 0.586279, 0.601590, 0.617079, 0.632648, + 0.648223, 0.663715, 0.679010, 0.693991, 0.708553, + 0.722563, 0.735901, 0.748570, 0.760630, 0.772158, + 0.783222, 0.793907, 0.804300, 0.814498, 0.824603, + 0.834716, 0.844817, 0.854762, 0.864494, 0.873959, + 0.883100, 0.891844, 0.900135, 0.907907, 0.915088, + 0.921619, 0.927360, 0.932278, 0.936482, 0.940066, + 0.943128, 0.945794, 0.948157, 0.950335, 0.952425, + 0.954563, 0.956738, 0.958855, 0.960827, 0.962596, + 0.964083, 0.965229, 0.965966, 0.966206, 0.965899, + 0.964967, 0.963491, 0.961602, 0.959270, 0.956503, + 0.953289, 0.949616, 0.945492, 0.940884, 0.935810, + 0.930259, 0.924111, 0.917313, 0.909967, 0.902174, + 0.894043, 0.885675, 0.877179, 0.868664, 0.860233, + 0.852001, 0.844039, 0.836194, 0.828430, 0.820645, + 0.812656, 0.804568, 0.796113, 0.787273, 0.777966, + 0.768211, 0.758017, 0.747540, 0.736765, 0.725779, + 0.714594, 0.703154, 0.691541, 0.679699, 0.667698, + 0.655540, 0.643271, 0.630947, 0.618523, 0.606050, + 0.593471, 0.580782, 0.568037, 0.555234, 0.542308, + 0.529357, 0.516318, 0.503314, 0.490317, 0.477348, + 0.464445, 0.451610, 0.438859, 0.426230, 0.413718, + 0.401346, 0.389202, 0.377375, 0.365775, 0.354350, + 0.343050, 0.331804, 0.320601, 0.309399, 0.298173, + 0.286897, 0.275586, 0.264305, 0.253136, 0.242143, + 0.231384, 0.220911, 0.210763, 0.200977, 0.191575, + 0.182575, 0.173953, 0.165647, 0.157658, 0.149962, + 0.142556, 0.135433, 0.128580, 0.121988, 0.115652, + 0.109560, 0.103711, 0.098103, 0.092734, 0.087598, + 0.082694, 0.078011, 0.073550, 0.069303, 0.065262, + 0.061425, 0.057778, 0.054312, 0.051021, 0.047905, + 0.044950, 0.042148, 0.039506, 0.037007, 0.034649, + 0.032426, 0.030326, 0.028341, 0.026469, 0.024707, + 0.023051, 0.021498, 0.020042, 0.018681, 0.017410, + 0.016224, 0.015115, 0.014078, 0.013108, 0.012201, + 0.011354, 0.010564, 0.009827, 0.009140, 0.008500, + 0.007905, 0.007350, 0.006833, 0.006351, 0.005902, + 0.005484, 0.005095, 0.004733, 0.004397, 0.004084, + 0.003793, 0.003523, 0.003271, 0.003036, 0.002819, + 0.002616, 0.002429, 0.002254, 0.002093, 0.001943, + 0.001804, 0.001676, 0.001556, 0.001445, 0.001342, + 0.001247, 0.001158, 0.001076, 0.001000, 0.000929, + 0.000863, 0.000802, 0.000745, 0.000693, 0.000644, + 0.000598, 0.000556, 0.000517, 0.000481, 0.000447, + 0.000416, 0.000387, 0.000360, 0.000335, 0.000311, + 0.000290, 0.000270, 0.000251, 0.000234, 0.000218, + 0.000203, 0.000189, 0.000176, 0.000164, 0.000153, + 0.000142, 0.000133, 0.000124, 0.000115, 0.000108, + 0.000100, 0.000094, 0.000087, 0.000082, 0.000076, + 0.000071, 0.000066, 0.000062, 0.000058, 0.000054, + 0.000051, 0.000047, 0.000044, 0.000041, 0.000039, + 0.000036, 0.000034, 0.000032, 0.000030, 0.000028, + 0.000026, 0.000024, 0.000023, 0.000021, 0.000020, + 0.000019, 0.000018, 0.000016, 0.000015, 0.000014, + 0.000013, 0.000013, 0.000012, 0.000011, 0.000010, + 0.000010, 0.000009, 0.000009, 0.000008, 0.000008, + 0.000007, 0.000007, 0.000006, 0.000006, 0.000006, + 0.000005, 0.000005, 0.000005, 0.000004, 0.000004, + 0.000004, 0.000004, 0.000003, 0.000003, 0.000003, + 0.000003, 0.000003, 0.000002, 0.000002, 0.000002, + 0.000002, 0.000002, 0.000002, 0.000002, 0.000002, + 0.000002, 0.000001, 0.000001, 0.000001, 0.000001, + 0.000001, 0.000001, 0.000001, 0.000001, 0.000001, + 0.000001, 0.000001, 0.000001, 0.000001, 0.000001, + 0.000001 + } + }, + { + 471, 360.0, 830.0, /* 471 bands from 360 to 830 nm in 1nm steps */ + 1.0, /* Scale factor */ + { + 0.000000, 0.000001, 0.000001, 0.000002, 0.000002, + 0.000004, 0.000005, 0.000008, 0.000011, 0.000017, + 0.000024, 0.000034, 0.000048, 0.000068, 0.000095, + 0.000133, 0.000184, 0.000253, 0.000347, 0.000473, + 0.000640, 0.000861, 0.001151, 0.001530, 0.002023, + 0.002657, 0.003470, 0.004505, 0.005815, 0.007460, + 0.009514, 0.012061, 0.015200, 0.019042, 0.023714, + 0.029357, 0.036126, 0.044195, 0.053743, 0.064968, + 0.078071, 0.093257, 0.110741, 0.130730, 0.153398, + 0.178935, 0.207487, 0.239172, 0.274063, 0.312169, + 0.353478, 0.397611, 0.444150, 0.492897, 0.543671, + 0.596302, 0.650635, 0.706534, 0.763936, 0.822806, + 0.883165, 0.943463, 1.001808, 1.058162, 1.112525, + 1.164898, 1.215552, 1.265032, 1.313703, 1.362201, + 1.411414, 1.460308, 1.507373, 1.552264, 1.594709, + 1.634529, 1.671632, 1.705747, 1.736693, 1.764380, + 1.788610, 1.808612, 1.823873, 1.834684, 1.841230, + 1.844058, 1.843442, 1.839930, 1.833797, 1.825590, + 1.815677, 1.803767, 1.789396, 1.772476, 1.753107, + 1.731476, 1.707589, 1.681541, 1.653519, 1.623619, + 1.591997, 1.559431, 1.526359, 1.492551, 1.457384, + 1.420779, 1.382383, 1.342017, 1.299412, 1.254573, + 1.207456, 1.157853, 1.106181, 1.053574, 1.000759, + 0.948651, 0.897828, 0.848786, 0.801953, 0.757664, + 0.716144, 0.676765, 0.638856, 0.602538, 0.567881, + 0.534929, 0.503728, 0.474283, 0.446572, 0.420583, + 0.396265, 0.373716, 0.352709, 0.332886, 0.314056, + 0.296143, 0.279152, 0.263152, 0.248277, 0.234696, + 0.222673, 0.211713, 0.201107, 0.190856, 0.180969, + 0.171463, 0.162367, 0.153733, 0.145596, 0.138030, + 0.131104, 0.124934, 0.119502, 0.114675, 0.110342, + 0.106383, 0.102706, 0.099197, 0.095748, 0.092248, + 0.088582, 0.084959, 0.081651, 0.078623, 0.075842, + 0.073262, 0.070852, 0.068555, 0.066306, 0.064065, + 0.061762, 0.059523, 0.057502, 0.055668, 0.053992, + 0.052433, 0.050967, 0.049534, 0.048112, 0.046651, + 0.045104, 0.043532, 0.042023, 0.040577, 0.039188, + 0.037849, 0.036544, 0.035292, 0.034054, 0.032828, + 0.031603, 0.030388, 0.029205, 0.028058, 0.026937, + 0.025874, 0.024845, 0.023862, 0.022928, 0.022050, + 0.021230, 0.020427, 0.019595, 0.018738, 0.017856, + 0.016954, 0.016034, 0.015099, 0.014153, 0.013199, + 0.012241, 0.011277, 0.010301, 0.009319, 0.008334, + 0.007350, 0.006370, 0.005397, 0.004434, 0.003484, + 0.002547, 0.001621, 0.000702, -0.000208, -0.001107, + -0.001996, -0.002870, -0.003725, -0.004559, -0.005368, + -0.006147, -0.006898, -0.007619, -0.008311, -0.008971, + -0.009601, -0.010199, -0.010764, -0.011296, -0.011794, + -0.012259, -0.012690, -0.013085, -0.013445, -0.013769, + -0.014058, -0.014311, -0.014530, -0.014715, -0.014865, + -0.014982, -0.015064, -0.015114, -0.015131, -0.015117, + -0.015074, -0.015002, -0.014904, -0.014780, -0.014633, + -0.014462, -0.014272, -0.014059, -0.013828, -0.013579, + -0.013314, -0.013035, -0.012743, -0.012439, -0.012124, + -0.011801, -0.011467, -0.011122, -0.010769, -0.010411, + -0.010052, -0.009695, -0.009340, -0.008992, -0.008650, + -0.008318, -0.007995, -0.007679, -0.007371, -0.007069, + -0.006773, -0.006483, -0.006199, -0.005920, -0.005647, + -0.005378, -0.005116, -0.004859, -0.004609, -0.004366, + -0.004133, -0.003908, -0.003692, -0.003486, -0.003290, + -0.003104, -0.002926, -0.002757, -0.002595, -0.002441, + -0.002294, -0.002155, -0.002023, -0.001898, -0.001780, + -0.001668, -0.001562, -0.001462, -0.001367, -0.001277, + -0.001193, -0.001113, -0.001039, -0.000969, -0.000904, + -0.000843, -0.000786, -0.000732, -0.000682, -0.000635, + -0.000591, -0.000550, -0.000512, -0.000476, -0.000443, + -0.000412, -0.000383, -0.000356, -0.000331, -0.000308, + -0.000286, -0.000266, -0.000247, -0.000230, -0.000213, + -0.000198, -0.000184, -0.000171, -0.000159, -0.000147, + -0.000137, -0.000127, -0.000118, -0.000109, -0.000101, + -0.000094, -0.000087, -0.000081, -0.000075, -0.000070, + -0.000065, -0.000060, -0.000056, -0.000052, -0.000048, + -0.000045, -0.000042, -0.000039, -0.000036, -0.000033, + -0.000031, -0.000029, -0.000027, -0.000025, -0.000023, + -0.000022, -0.000020, -0.000019, -0.000017, -0.000016, + -0.000015, -0.000014, -0.000013, -0.000012, -0.000011, + -0.000010, -0.000010, -0.000009, -0.000008, -0.000008, + -0.000007, -0.000007, -0.000006, -0.000006, -0.000006, + -0.000005, -0.000005, -0.000004, -0.000004, -0.000004, + -0.000004, -0.000003, -0.000003, -0.000003, -0.000003, + -0.000003, -0.000002, -0.000002, -0.000002, -0.000002, + -0.000002, -0.000002, -0.000002, -0.000002, -0.000001, + -0.000001, -0.000001, -0.000001, -0.000001, -0.000001, + -0.000001, -0.000001, -0.000001, -0.000001, -0.000001, + -0.000001, -0.000001, -0.000001, -0.000001, -0.000001, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000, -0.000000, -0.000000, -0.000000, -0.000000, + -0.000000 + } + } +}; + +static xslpoly poly_CIE_1964_10c = { 0 }; + +/* Judd & Voss 1978 2 degree */ +static xspect ob_Judd_Voss_2[3] = { + { + 90, 380.0, 825.0, /* 90 bands from 380 to 825 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 2.689900e-003, 5.310500e-003, 1.078100e-002, 2.079200e-002, 3.798100e-002, + 6.315700e-002, 9.994100e-002, 1.582400e-001, 2.294800e-001, 2.810800e-001, + 3.109500e-001, 3.307200e-001, 3.333600e-001, 3.167200e-001, 2.888200e-001, + 2.596900e-001, 2.327600e-001, 2.099900e-001, 1.747600e-001, 1.328700e-001, + 9.194400e-002, 5.698500e-002, 3.173100e-002, 1.461300e-002, 4.849100e-003, + 2.321500e-003, 9.289900e-003, 2.927800e-002, 6.379100e-002, 1.108100e-001, + 1.669200e-001, 2.276800e-001, 2.926900e-001, 3.622500e-001, 4.363500e-001, + 5.151300e-001, 5.974800e-001, 6.812100e-001, 7.642500e-001, 8.439400e-001, + 9.163500e-001, 9.770300e-001, 1.023000e+000, 1.051300e+000, 1.055000e+000, + 1.036200e+000, 9.923900e-001, 9.286100e-001, 8.434600e-001, 7.398300e-001, + 6.328900e-001, 5.335100e-001, 4.406200e-001, 3.545300e-001, 2.786200e-001, + 2.148500e-001, 1.616100e-001, 1.182000e-001, 8.575300e-002, 6.307700e-002, + 4.583400e-002, 3.205700e-002, 2.218700e-002, 1.561200e-002, 1.109800e-002, + 7.923300e-003, 5.653100e-003, 4.003900e-003, 2.825300e-003, 1.994700e-003, + 1.399400e-003, 9.698000e-004, 6.684700e-004, 4.614100e-004, 3.207300e-004, + 2.257300e-004, 1.597300e-004, 1.127500e-004, 7.951300e-005, 5.608700e-005, + 3.954100e-005, 2.785200e-005, 1.959700e-005, 1.377000e-005, 9.670000e-006, + 6.791800e-006, 4.770600e-006, 3.355000e-006, 2.353400e-006, 1.637700e-006 + } + }, + { + 90, 380.0, 825.0, /* 90 bands from 380 to 825 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 2.000000e-004, 3.955600e-004, 8.000000e-004, 1.545700e-003, 2.800000e-003, + 4.656200e-003, 7.400000e-003, 1.177900e-002, 1.750000e-002, 2.267800e-002, + 2.730000e-002, 3.258400e-002, 3.790000e-002, 4.239100e-002, 4.680000e-002, + 5.212200e-002, 6.000000e-002, 7.294200e-002, 9.098000e-002, 1.128400e-001, + 1.390200e-001, 1.698700e-001, 2.080200e-001, 2.580800e-001, 3.230000e-001, + 4.054000e-001, 5.030000e-001, 6.081100e-001, 7.100000e-001, 7.951000e-001, + 8.620000e-001, 9.150500e-001, 9.540000e-001, 9.800400e-001, 9.949500e-001, + 1.000100e+000, 9.950000e-001, 9.787500e-001, 9.520000e-001, 9.155800e-001, + 8.700000e-001, 8.162300e-001, 7.570000e-001, 6.948300e-001, 6.310000e-001, + 5.665400e-001, 5.030000e-001, 4.417200e-001, 3.810000e-001, 3.205200e-001, + 2.650000e-001, 2.170200e-001, 1.750000e-001, 1.381200e-001, 1.070000e-001, + 8.165200e-002, 6.100000e-002, 4.432700e-002, 3.200000e-002, 2.345400e-002, + 1.700000e-002, 1.187200e-002, 8.210000e-003, 5.772300e-003, 4.102000e-003, + 2.929100e-003, 2.091000e-003, 1.482200e-003, 1.047000e-003, 7.401500e-004, + 5.200000e-004, 3.609300e-004, 2.492000e-004, 1.723100e-004, 1.200000e-004, + 8.462000e-005, 6.000000e-005, 4.244600e-005, 3.000000e-005, 2.121000e-005, + 1.498900e-005, 1.058400e-005, 7.465600e-006, 5.259200e-006, 3.702800e-006, + 2.607600e-006, 1.836500e-006, 1.295000e-006, 9.109200e-007, 6.356400e-007 + } + }, + { + 90, 380.0, 825.0, /* 90 bands from 380 to 825 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 1.226000e-002, 2.422200e-002, 4.925000e-002, 9.513500e-002, 1.740900e-001, + 2.901300e-001, 4.605300e-001, 7.316600e-001, 1.065800e+000, 1.314600e+000, + 1.467200e+000, 1.579600e+000, 1.616600e+000, 1.568200e+000, 1.471700e+000, + 1.374000e+000, 1.291700e+000, 1.235600e+000, 1.113800e+000, 9.422000e-001, + 7.559600e-001, 5.864000e-001, 4.466900e-001, 3.411600e-001, 2.643700e-001, + 2.059400e-001, 1.544500e-001, 1.091800e-001, 7.658500e-002, 5.622700e-002, + 4.136600e-002, 2.935300e-002, 2.004200e-002, 1.331200e-002, 8.782300e-003, + 5.857300e-003, 4.049300e-003, 2.921700e-003, 2.277100e-003, 1.970600e-003, + 1.806600e-003, 1.544900e-003, 1.234800e-003, 1.117700e-003, 9.056400e-004, + 6.946700e-004, 4.288500e-004, 3.181700e-004, 2.559800e-004, 1.567900e-004, + 9.769400e-005, 6.894400e-005, 5.116500e-005, 3.601600e-005, 2.423800e-005, + 1.691500e-005, 1.190600e-005, 8.148900e-006, 5.600600e-006, 3.954400e-006, + 2.791200e-006, 1.917600e-006, 1.313500e-006, 9.151900e-007, 6.476700e-007, + 4.635200e-007, 3.330400e-007, 2.382300e-007, 1.702600e-007, 1.220700e-007, + 8.710700e-008, 6.145500e-008, 4.316200e-008, 3.037900e-008, 2.155400e-008, + 1.549300e-008, 1.120400e-008, 8.087300e-009, 5.834000e-009, 4.211000e-009, + 3.038300e-009, 2.190700e-009, 1.577800e-009, 1.134800e-009, 8.156500e-010, + 5.862600e-010, 4.213800e-010, 3.031900e-010, 2.175300e-010, 1.547600e-010 + } + } +}; + +static xslpoly poly_Judd_Voss_2 = { 0 }; + + +/* Stiles & Burch 1955 2 degree, */ +/* rotated to align with 1931 XYZ space, */ +/* using Mark Shaw's matrix. */ +static xspect ob_Stiles_Burch_2[3] = { + { + 69, 390.0, 730.0, /* 69 bands from 390 to 730 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.005035, 0.012873, 0.025933, 0.054264, 0.093147, + 0.144597, 0.207609, 0.266538, 0.303933, 0.336185, + 0.356549, 0.364180, 0.328209, 0.286053, 0.262928, + 0.210562, 0.182549, 0.131014, 0.081974, 0.045980, + 0.020673, 0.008302, 0.004814, 0.008248, 0.024412, + 0.050113, 0.084255, 0.131255, 0.186757, 0.243224, + 0.298768, 0.359848, 0.428510, 0.500880, 0.571271, + 0.650846, 0.742250, 0.829040, 0.905369, 0.971275, + 1.024797, 1.060952, 1.071632, 1.054762, 1.012750, + 0.947501, 0.861487, 0.761200, 0.654122, 0.548338, + 0.450269, 0.361237, 0.281687, 0.213565, 0.158588, + 0.115934, 0.083874, 0.060355, 0.043191, 0.030661, + 0.021532, 0.014822, 0.010047, 0.006832, 0.004755, + 0.003363, 0.002330, 0.001623, 0.001136 + } + }, + { + 69, 390.0, 730.0, /* 69 bands from 390 to 730 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.000021, 0.000137, 0.000267, 0.000499, 0.000959, + -0.000352, -0.000535, -0.002306, -0.001139, 0.001507, + 0.007142, 0.012389, 0.022879, 0.037200, 0.054616, + 0.080087, 0.108008, 0.140411, 0.170719, 0.199791, + 0.240641, 0.297681, 0.367645, 0.455184, 0.546333, + 0.641762, 0.736259, 0.813393, 0.873858, 0.911828, + 0.931983, 0.954960, 0.971754, 0.970171, 0.950790, + 0.937240, 0.932444, 0.903026, 0.857070, 0.815886, + 0.769666, 0.712437, 0.651257, 0.588631, 0.523557, + 0.457801, 0.393963, 0.332964, 0.275541, 0.223722, + 0.179091, 0.140943, 0.108310, 0.081218, 0.059780, + 0.043398, 0.031238, 0.022406, 0.016003, 0.011340, + 0.007953, 0.005473, 0.003713, 0.002527, 0.001759, + 0.001244, 0.000863, 0.000602, 0.000422 + } + }, + { + 69, 390.0, 730.0, /* 69 bands from 390 to 730 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.023163, 0.059308, 0.118897, 0.250907, 0.433765, + 0.684409, 0.999185, 1.308097, 1.518706, 1.707302, + 1.840521, 1.906560, 1.756384, 1.578949, 1.505983, + 1.283002, 1.178612, 0.947751, 0.709500, 0.529689, + 0.398963, 0.310980, 0.240664, 0.188969, 0.145151, + 0.110796, 0.087421, 0.069953, 0.059951, 0.051960, + 0.042905, 0.037710, 0.033821, 0.028764, 0.023371, + 0.018909, 0.015103, 0.009656, 0.003595, -0.001221, + -0.005978, -0.010905, -0.014270, -0.016302, -0.018412, + -0.019889, -0.019510, -0.017854, -0.015815, -0.013632, + -0.011388, -0.009171, -0.007076, -0.005238, -0.003775, + -0.002692, -0.001946, -0.001447, -0.001074, -0.000766, + -0.000527, -0.000357, -0.000242, -0.000164, -0.000113, + -0.000077, -0.000051, -0.000034, -0.000023 + } + } +}; + +static xslpoly poly_Stiles_Burch_2 = { 0 }; + +/* Shaw & Fairchild 1997 2 degree observer. */ +/* From Mark Shaw's Masters thesis: */ +/* "Evaluating the 1931 CIE Color Matching Functions" */ +static xspect ob_Shaw_Fairchild_2[3] = { + { + 61, 400.0, 700.0, /* 61 bands from 400 to 700 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.050035085, 0.10599540, 0.17570524, 0.26369069, 0.34385256, + 0.36314044, 0.35022338, 0.35921696, 0.37057582, 0.37027683, + 0.31092719, 0.24467905, 0.21495057, 0.16408854, 0.15609086, + 0.10496585, 0.053550350, 0.016029866, -0.010473666, -0.020635411, + -0.020599591, -0.010774255, 0.013507015, 0.045305699, 0.082609321, + 0.13244251, 0.18966495, 0.24710489, 0.30272442, 0.36362744, + 0.43329425, 0.50816654, 0.57883305, 0.66085495, 0.75701632, + 0.84336950, 0.91608703, 0.98348582, 1.0386456, 1.0699974, + 1.0751974, 1.0492333, 1.0010173, 0.92955516, 0.83907524, + 0.74332961, 0.64179542, 0.53031723, 0.42814376, 0.34048130, + 0.26368241, 0.19677558, 0.14402031, 0.10472063, 0.076398767, + 0.054311075, 0.037000030, 0.026240015, 0.018750015, 0.012892199, + 0.0081198003 + } + }, + { + 61, 400.0, 700.0, /* 61 bands from 400 to 700 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.00073439190, 0.0010295739, 0.0011050375, -0.00057123313, -0.0015421159, + -0.0050492258, -0.0060441241, -0.0050340813, -0.00046015829, 0.0043453053, + 0.014594307, 0.028653705, 0.047841334, 0.078039315, 0.11339641, + 0.15326829, 0.18931877, 0.22596008, 0.26950734, 0.32894461, + 0.39924943, 0.48161678, 0.56603317, 0.65284913, 0.73864212, + 0.80870955, 0.86388621, 0.90168525, 0.92331427, 0.94508269, + 0.96035974, 0.96084837, 0.94474215, 0.93248079, 0.92686312, + 0.89683591, 0.85193527, 0.81446493, 0.77206051, 0.71732417, + 0.65749412, 0.59544590, 0.53033945, 0.46382662, 0.39929743, + 0.33905951, 0.28179144, 0.22775797, 0.18104592, 0.14195030, + 0.10887196, 0.081491712, 0.059933233, 0.043516174, 0.031399280, + 0.022402933, 0.015787610, 0.011178068, 0.0078482427, 0.0054361119, + 0.0036285556 + } + }, + { + 61, 400.0, 700.0, /* 61 bands from 400 to 700 nm in 5nm steps */ + 1.0, /* Scale factor */ + { + 0.19346810, 0.37355444, 0.62641781, 0.98559734, 1.3578634, + 1.5413908, 1.6258281, 1.7422823, 1.8184109, 1.7982693, + 1.6624945, 1.4917210, 1.3537111, 1.2543216, 1.1444894, + 0.94078221, 0.73058355, 0.55774101, 0.42026628, 0.31970216, + 0.24388223, 0.18951860, 0.14567319, 0.11603887, 0.094972125, + 0.077803903, 0.065288720, 0.055235267, 0.046945157, 0.039405440, + 0.033042398, 0.026944585, 0.021626844, 0.016807242, 0.011642648, + 0.0061489002, 0.00061480026, -0.0044002687, -0.0090514735, -0.013048603, + -0.016063598, -0.018035874, -0.019053770, -0.019163305, -0.018371066, + -0.016810570, -0.014805852, -0.012650736, -0.010513405, -0.0085481723, + -0.0067628649, -0.0051813692, -0.0038792081, -0.0028536093, -0.0020731313, + -0.0014924891, -0.0010704383, -0.00075273042, -0.00052400943, -0.00036054897, + -0.00025295701 + } + } +}; + +static xslpoly poly_Shaw_Fairchild_2 = { 0 }; + +#endif /* !SALONEINSTLIB */ + +/* Return pointers to three xpsects with a standard observer weighting curves */ +/* return 0 on sucecss, nz if not matched */ +int standardObserver( +xspect *sp[3], /* Return 3 pointers */ +icxObserverType obType /* Type of observer */ +) { + switch (obType) { + case icxOT_custom: + return 1; + case icxOT_none: + return 1; + case icxOT_default: + case icxOT_CIE_1931_2: + sp[0] = &ob_CIE_1931_2[0]; + sp[1] = &ob_CIE_1931_2[1]; + sp[2] = &ob_CIE_1931_2[2]; + return 0; + case icxOT_CIE_1964_10: + sp[0] = &ob_CIE_1964_10[0]; + sp[1] = &ob_CIE_1964_10[1]; + sp[2] = &ob_CIE_1964_10[2]; + return 0; +#ifndef SALONEINSTLIB + case icxOT_Stiles_Burch_2: + sp[0] = &ob_Stiles_Burch_2[0]; + sp[1] = &ob_Stiles_Burch_2[1]; + sp[2] = &ob_Stiles_Burch_2[2]; + return 0; + case icxOT_Judd_Voss_2: + sp[0] = &ob_Judd_Voss_2[0]; + sp[1] = &ob_Judd_Voss_2[1]; + sp[2] = &ob_Judd_Voss_2[2]; + return 0; + case icxOT_CIE_1964_10c: + sp[0] = &ob_CIE_1964_10c[0]; + sp[1] = &ob_CIE_1964_10c[1]; + sp[2] = &ob_CIE_1964_10c[2]; + return 0; + case icxOT_Shaw_Fairchild_2: + sp[0] = &ob_Shaw_Fairchild_2[0]; + sp[1] = &ob_Shaw_Fairchild_2[1]; + sp[2] = &ob_Shaw_Fairchild_2[2]; + return 0; +#endif /* !SALONEINSTLIB */ + default: + return 1; + } +} + +/* Return a string describing the standard observer */ +char *standardObserverDescription(icxObserverType obType) { + switch (obType) { + case icxOT_custom: + return "Custom observer"; + case icxOT_none: + return "No observer"; + case icxOT_default: + case icxOT_CIE_1931_2: + return "CIE 1931 2 degree observer"; + case icxOT_CIE_1964_10: + return "CIE 1964 10 degree observer"; +#ifndef SALONEINSTLIB + case icxOT_Stiles_Burch_2: + return "Stiles & Burch 1955 2 degree observer (aligned)"; + case icxOT_Judd_Voss_2: + return "Judd & Voss 1978 2 degree observer"; + case icxOT_CIE_1964_10c: + return "CIE 1964 10 degree observer (aligned)"; + case icxOT_Shaw_Fairchild_2: + return "Shaw & Fairchild 1997 2 degree observer"; +#endif /* !SALONEINSTLIB */ + } + return "Unknown observer"; +} + +/* Return a pointer to the spectral locus poligon */ +/* return NULL on failure. */ +static xslpoly *spectral_locus_poligon( +icxObserverType obType /* Type of observer */ +) { + switch (obType) { + case icxOT_custom: + return NULL; + case icxOT_none: + return NULL; + case icxOT_default: + case icxOT_CIE_1931_2: + return &poly_CIE_1931_2; + case icxOT_CIE_1964_10: + return &poly_CIE_1964_10; +#ifndef SALONEINSTLIB + case icxOT_Stiles_Burch_2: + return &poly_Stiles_Burch_2; + case icxOT_Judd_Voss_2: + return &poly_Judd_Voss_2; + case icxOT_CIE_1964_10c: + return &poly_CIE_1964_10c; + case icxOT_Shaw_Fairchild_2: + return &poly_Shaw_Fairchild_2; +#endif /* !SALONEINSTLIB */ + default: + return NULL; + } +} + + +#ifndef SALONEINSTLIB +/* ----------------------------------- */ +/* Standard refelective sample spectra */ + +/* Ra CRI Test Color Samples */ +static xspect CIE1995_TCS[] = { + + /* TCS01 7.5 R 6/4 Light greyish red */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.12, 0.14, 0.16, 0.19, 0.22, 0.24, 0.25, 0.26, 0.26, 0.25, + 0.25, 0.25, 0.24, 0.24, 0.24, 0.23, 0.23, 0.23, 0.23, 0.22, + 0.22, 0.22, 0.22, 0.21, 0.21, 0.21, 0.22, 0.22, 0.22, 0.23, + 0.23, 0.23, 0.23, 0.23, 0.23, 0.23, 0.24, 0.25, 0.25, 0.26, + 0.27, 0.28, 0.3, 0.32, 0.34, 0.37, 0.39, 0.41, 0.42, 0.44, + 0.44, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, + 0.45, 0.45, 0.45, 0.45, 0.46, 0.46, 0.46, 0.46, 0.46, 0.46, + 0.46, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, + 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, + 0.47, 0.47, 0.47, 0.46, 0.46 + } + }, + /* TCS02 5 Y6/4 Dark greyish yellow */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.05, 0.06, 0.06, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, + 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.13, 0.13, + 0.13, 0.13, 0.14, 0.14, 0.15, 0.16, 0.17, 0.19, 0.21, 0.23, + 0.24, 0.25, 0.26, 0.26, 0.27, 0.27, 0.27, 0.28, 0.28, 0.29, + 0.3, 0.31, 0.32, 0.33, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, + 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, + 0.33, 0.33, 0.33, 0.33, 0.33, 0.33, 0.33, 0.33, 0.33, 0.33, + 0.33, 0.33, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, + 0.32, 0.32, 0.32, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, + 0.31, 0.31, 0.31, 0.31, 0.31 + } + }, + /* TCS03 5 GY 6/8 Strong yellow green */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.06, 0.06, 0.06, 0.06, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, + 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.08, + 0.08, 0.08, 0.09, 0.09, 0.11, 0.13, 0.15, 0.17, 0.2, 0.22, + 0.24, 0.26, 0.28, 0.3, 0.34, 0.37, 0.39, 0.4, 0.4, 0.39, + 0.38, 0.37, 0.35, 0.33, 0.32, 0.3, 0.29, 0.27, 0.26, 0.26, + 0.25, 0.25, 0.24, 0.24, 0.23, 0.22, 0.22, 0.22, 0.22, 0.22, + 0.22, 0.22, 0.23, 0.24, 0.25, 0.27, 0.29, 0.31, 0.34, 0.37, + 0.39, 0.41, 0.43, 0.45, 0.46, 0.47, 0.48, 0.49, 0.49, 0.5, + 0.5, 0.5, 0.51, 0.51, 0.52, 0.52, 0.52, 0.53, 0.53, 0.54, + 0.54, 0.54, 0.55, 0.55, 0.56 + } + }, + /* TCS04 2.5 G 6/6 Moderate yellowish green */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.06, 0.06, 0.06, 0.07, 0.07, 0.08, 0.09, 0.11, 0.12, 0.12, + 0.12, 0.13, 0.13, 0.13, 0.14, 0.14, 0.14, 0.15, 0.16, 0.17, + 0.19, 0.21, 0.23, 0.25, 0.28, 0.31, 0.33, 0.35, 0.37, 0.38, + 0.39, 0.39, 0.4, 0.39, 0.39, 0.38, 0.37, 0.35, 0.34, 0.33, + 0.31, 0.3, 0.28, 0.26, 0.25, 0.23, 0.21, 0.2, 0.19, 0.18, + 0.17, 0.16, 0.16, 0.16, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, + 0.15, 0.15, 0.15, 0.15, 0.16, 0.16, 0.17, 0.17, 0.17, 0.17, + 0.17, 0.17, 0.17, 0.16, 0.16, 0.17, 0.17, 0.17, 0.18, 0.18, + 0.19, 0.19, 0.19, 0.19, 0.2, 0.2, 0.2, 0.21, 0.22, 0.23, + 0.23, 0.24, 0.25, 0.26, 0.27 + } + }, + /* TCS05 10 BG 6/4 Light bluish green */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.14, 0.19, 0.23, 0.27, 0.3, 0.31, 0.31, 0.31, 0.31, 0.32, + 0.32, 0.32, 0.33, 0.33, 0.33, 0.34, 0.35, 0.35, 0.36, 0.37, + 0.38, 0.39, 0.4, 0.41, 0.42, 0.42, 0.42, 0.42, 0.41, 0.41, + 0.4, 0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.32, + 0.31, 0.3, 0.28, 0.27, 0.26, 0.25, 0.23, 0.22, 0.21, 0.2, + 0.19, 0.19, 0.19, 0.18, 0.18, 0.18, 0.18, 0.18, 0.18, 0.18, + 0.18, 0.18, 0.18, 0.18, 0.19, 0.19, 0.19, 0.2, 0.2, 0.2, + 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.21, + 0.21, 0.21, 0.22, 0.22, 0.22, 0.22, 0.23, 0.23, 0.24, 0.24, + 0.25, 0.26, 0.27, 0.27, 0.28 + } + }, + /* TCS06 5 PB 6/8 Light blue */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.08, 0.08, 0.09, 0.11, 0.15, 0.2, 0.27, 0.34, 0.41, 0.46, + 0.49, 0.51, 0.52, 0.52, 0.53, 0.54, 0.54, 0.55, 0.56, 0.56, + 0.55, 0.55, 0.54, 0.53, 0.52, 0.5, 0.49, 0.47, 0.45, 0.43, + 0.41, 0.4, 0.38, 0.36, 0.34, 0.33, 0.31, 0.29, 0.28, 0.27, + 0.25, 0.24, 0.23, 0.23, 0.23, 0.22, 0.22, 0.22, 0.22, 0.22, + 0.22, 0.22, 0.22, 0.23, 0.23, 0.24, 0.24, 0.25, 0.26, 0.26, + 0.27, 0.27, 0.28, 0.28, 0.28, 0.29, 0.29, 0.3, 0.3, 0.31, + 0.33, 0.34, 0.35, 0.36, 0.38, 0.39, 0.4, 0.41, 0.43, 0.44, + 0.45, 0.46, 0.47, 0.48, 0.49, 0.49, 0.5, 0.51, 0.51, 0.52, + 0.52, 0.53, 0.53, 0.53, 0.54 + } + }, + /* TCS07 2.5 P 6/8 Y6/4 Light violet */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.15, 0.18, 0.22, 0.29, 0.38, 0.46, 0.52, 0.55, 0.55, 0.56, + 0.56, 0.56, 0.56, 0.56, 0.56, 0.55, 0.54, 0.54, 0.52, 0.51, + 0.49, 0.47, 0.45, 0.43, 0.41, 0.39, 0.36, 0.34, 0.32, 0.31, + 0.3, 0.29, 0.28, 0.27, 0.27, 0.26, 0.26, 0.26, 0.26, 0.26, + 0.26, 0.26, 0.26, 0.25, 0.25, 0.26, 0.27, 0.28, 0.3, 0.32, + 0.34, 0.36, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, + 0.45, 0.46, 0.46, 0.47, 0.47, 0.47, 0.47, 0.48, 0.48, 0.49, + 0.5, 0.5, 0.51, 0.52, 0.53, 0.53, 0.54, 0.55, 0.55, 0.56, + 0.57, 0.57, 0.58, 0.58, 0.58, 0.58, 0.59, 0.59, 0.59, 0.59, + 0.59, 0.59, 0.59, 0.59, 0.59 + } + }, + /* TCS08 10 P 6/8 Light reddish purple */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.08, 0.08, 0.08, 0.09, 0.1, 0.13, 0.17, 0.24, 0.32, 0.42, + 0.46, 0.48, 0.49, 0.49, 0.48, 0.47, 0.46, 0.45, 0.44, 0.43, + 0.41, 0.4, 0.38, 0.37, 0.35, 0.34, 0.33, 0.31, 0.3, 0.29, + 0.28, 0.28, 0.27, 0.26, 0.26, 0.25, 0.25, 0.25, 0.25, 0.26, + 0.26, 0.27, 0.27, 0.27, 0.28, 0.28, 0.3, 0.32, 0.35, 0.38, + 0.43, 0.48, 0.53, 0.57, 0.6, 0.63, 0.65, 0.66, 0.68, 0.69, + 0.69, 0.7, 0.71, 0.71, 0.71, 0.72, 0.72, 0.72, 0.72, 0.72, + 0.72, 0.72, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, + 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, 0.73, + 0.73, 0.73, 0.73, 0.73, 0.73 + } + }, + /* TCS09 4.5 R 4/13 Strong red */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.069, 0.072, 0.073, 0.070, 0.066, 0.062, 0.058, 0.055, 0.052, 0.052, + 0.051, 0.050, 0.050, 0.049, 0.048, 0.047, 0.046, 0.044, 0.042, 0.041, + 0.038, 0.035, 0.033, 0.031, 0.030, 0.029, 0.028, 0.028, 0.028, 0.029, + 0.030, 0.030, 0.031, 0.031, 0.032, 0.032, 0.033, 0.034, 0.035, 0.037, + 0.041, 0.044, 0.048, 0.052, 0.060, 0.076, 0.102, 0.136, 0.190, 0.256, + 0.336, 0.418, 0.505, 0.581, 0.641, 0.682, 0.717, 0.740, 0.758, 0.770, + 0.781, 0.790, 0.797, 0.803, 0.809, 0.814, 0.819, 0.824, 0.828, 0.830, + 0.831, 0.833, 0.835, 0.836, 0.836, 0.837, 0.838, 0.839, 0.839, 0.839, + 0.839, 0.839, 0.839, 0.839, 0.839, 0.839, 0.839, 0.839, 0.839, 0.839, + 0.838, 0.837, 0.837, 0.836, 0.836 + } + }, + /* TCS10 5 Y 8/10 Strong yellow */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.04, 0.04, 0.05, 0.05, 0.05, 0.05, 0.06, 0.06, 0.07, 0.07, + 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.08, 0.08, 0.08, 0.09, + 0.1, 0.1, 0.11, 0.13, 0.14, 0.16, 0.19, 0.22, 0.26, 0.31, + 0.37, 0.42, 0.47, 0.51, 0.55, 0.58, 0.61, 0.63, 0.65, 0.67, + 0.68, 0.69, 0.69, 0.7, 0.7, 0.7, 0.71, 0.71, 0.71, 0.71, + 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.72, 0.72, + 0.72, 0.72, 0.73, 0.73, 0.73, 0.74, 0.74, 0.74, 0.75, 0.75, + 0.75, 0.75, 0.75, 0.75, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, + 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, + 0.76, 0.76, 0.76, 0.76, 0.76 + } + }, + /* TCS11 4.5 G 5/8 Strong green */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.13, 0.13, 0.12, + 0.12, 0.11, 0.11, 0.11, 0.1, 0.1, 0.11, 0.11, 0.11, 0.12, + 0.12, 0.13, 0.15, 0.17, 0.19, 0.22, 0.25, 0.29, 0.33, 0.35, + 0.36, 0.35, 0.35, 0.33, 0.31, 0.29, 0.27, 0.25, 0.23, 0.21, + 0.19, 0.17, 0.15, 0.14, 0.13, 0.11, 0.11, 0.1, 0.1, 0.09, + 0.09, 0.09, 0.09, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, + 0.08, 0.08, 0.09, 0.09, 0.1, 0.11, 0.13, 0.14, 0.16, 0.18, + 0.2, 0.22, 0.24, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.31, + 0.32, 0.32, 0.33, 0.33, 0.34, 0.34, 0.35, 0.35, 0.36, 0.37, + 0.37, 0.38, 0.39, 0.4, 0.4 + } + }, + /* TCS12 3 PB 3/11 Strong blue */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.19, 0.18, 0.16, 0.14, 0.12, 0.1, 0.09, 0.08, 0.08, 0.07, + 0.06, 0.07, 0.08, 0.09, 0.12, 0.16, 0.21, 0.26, 0.3, 0.33, + 0.35, 0.35, 0.34, 0.33, 0.31, 0.28, 0.26, 0.23, 0.2, 0.18, + 0.15, 0.13, 0.11, 0.09, 0.08, 0.06, 0.05, 0.04, 0.04, 0.03, + 0.03, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, + 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, + 0.02, 0.02, 0.03, 0.03, 0.04, 0.04, 0.06, 0.07, 0.1, 0.13, + 0.17, 0.21, 0.26, 0.31, 0.35, 0.4, 0.45, 0.49, 0.52, 0.55, + 0.58, 0.6, 0.62, 0.63, 0.65, 0.66, 0.67, 0.67, 0.68, 0.69, + 0.69, 0.69, 0.7, 0.7, 0.7 + } + }, + /* TCS13 5 YR 8/4 Light yellowish pink */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.07, 0.08, 0.08, 0.09, 0.1, 0.13, 0.16, 0.21, 0.26, 0.31, + 0.34, 0.35, 0.36, 0.36, 0.36, 0.37, 0.37, 0.37, 0.37, 0.37, + 0.38, 0.38, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, + 0.46, 0.47, 0.47, 0.47, 0.47, 0.48, 0.48, 0.49, 0.51, 0.53, + 0.55, 0.58, 0.62, 0.65, 0.68, 0.7, 0.72, 0.73, 0.74, 0.74, + 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, + 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, + 0.75, 0.74, 0.74, 0.74, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, + 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, + 0.75, 0.75, 0.75, 0.75, 0.75 + } + }, + /* TCS14 5 GY 4/4 Moderate olive green */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, + 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.05, 0.05, 0.05, + 0.05, 0.05, 0.05, 0.05, 0.06, 0.06, 0.06, 0.07, 0.08, 0.08, + 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.15, 0.16, 0.15, + 0.15, 0.14, 0.13, 0.13, 0.12, 0.11, 0.11, 0.1, 0.1, 0.1, + 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.08, 0.08, 0.08, 0.08, + 0.09, 0.09, 0.09, 0.1, 0.1, 0.11, 0.12, 0.14, 0.15, 0.17, + 0.19, 0.21, 0.23, 0.24, 0.26, 0.28, 0.29, 0.31, 0.33, 0.34, + 0.35, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.42, 0.43, 0.43, + 0.44, 0.44, 0.45, 0.45, 0.45 + } + }, + /* TCS15 1 YR 6/4 Asian skin */ + { + 95, 360.0, 830.0, /* 95 bands from 360 to 830 nm in 5nm steps */ + 1.0, /* Scale factor */ + + { + 0, 0, 0, 0, 0.13, 0.14, 0.15, 0.15, 0.16, 0.16, + 0.16, 0.17, 0.17, 0.18, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, + 0.24, 0.24, 0.25, 0.25, 0.26, 0.26, 0.27, 0.28, 0.28, 0.29, + 0.3, 0.3, 0.3, 0.29, 0.28, 0.28, 0.27, 0.28, 0.28, 0.29, + 0.29, 0.29, 0.29, 0.28, 0.29, 0.31, 0.35, 0.4, 0.44, 0.47, + 0.49, 0.51, 0.52, 0.54, 0.54, 0.55, 0.56, 0.57, 0.57, 0.58, + 0.58, 0.59, 0.59, 0.59, 0.6, 0.6, 0.61, 0.61, 0.61, 0.61, + 0.62, 0.62, 0.62, 0.62, 0.62, 0.61, 0.61, 0.61, 0.61, 0.61, + 0.61, 0.61, 0.61, 0.61, 0.61, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + } + } +}; + + +/* -------------------------------- */ +/* Fluorescent Whitening Agent Data */ + +/* Generic stimulation/exitation spectrum, used in FWA. */ +/* This is also used to estimate the UV content of an illuminant, */ +/* by its FWA effect (illumread) */ +static xspect FWA1_stim = { + 14, 290.0, 420.0, /* 14 bands from 290 to 420 nm in 10nm steps */ + 1.0, /* Scale factor */ + { +/* 290 */ 0.000000, +/* 300 */ 0.075000, 0.158000, 0.228000, 0.318638, 0.393663, +/* 350 */ 0.460003, 0.524409, 0.550955, 0.540374, 0.497947, +/* 400 */ 0.412503, 0.265935, 0.000000 + } +}; + +/* !!! This is not normally used !!! */ +#ifdef STOCKFWA /* Use table shape as FWA basis, rather than estimating from spectrum. */ + +/* Generic emmission spectrum */ +static xspect FWA1_emit = { + 17, 390.0, 550.0, /* 17 bands from 390 to 550 nm in 10nm steps */ + 1.0, /* Scale factor */ + { +#ifdef NEVER +/* 390 */ 0.00000, +/* 400 */ 0.08989, 0.27831, 0.45278, 0.494, 0.496, +/* 450 */ 0.36872, 0.30495, 0.226, 0.1676, 0.1216, +/* 500 */ 0.08515, 0.06877, 0.04930, 0.0246, 0.0123, +/* 550 */ 0.00000 +#else + /* Hacked */ +/* 390 */ 0.00000, +/* 400 */ 0.01089, 0.15000, 0.20278, 0.374, 0.496, +/* 450 */ 0.38000, 0.27495, 0.186, 0.1376, 0.1016, +/* 500 */ 0.08515, 0.06877, 0.04930, 0.0246, 0.0123, +/* 550 */ 0.00000 +#endif + } +}; + +#endif /* STOCKFWA */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* save a set of spectrum to a CGATS file */ +/* type 0 = SPECT, 1 = CMF */ +/* Return NZ on error */ +int write_nxspect(char *fname, xspect *sp, int nspec, int type) { + char buf[100]; + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *ocg; /* output cgats structure */ + cgats_set_elem *setel; /* Array of set value elements */ + int i, j; + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + if (type != 0) + ocg->add_other(ocg, "CMF"); /* our special type is spectral color matching func */ + else + ocg->add_other(ocg, "SPECT"); /* our special type is spectral power or reflectance */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Spectral power/reflectance information",NULL); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll CMS", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + sprintf(buf,"%d", sp->spec_n); + ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL); + sprintf(buf,"%f", sp->spec_wl_short); + ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL); + sprintf(buf,"%f", sp->spec_wl_long); + ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL); + sprintf(buf,"%f", sp->norm); + ocg->add_kword(ocg, 0, "SPECTRAL_NORM",buf, NULL); + + /* Generate fields for spectral values */ + for (i = 0; i < sp->spec_n; i++) { + int nm; + + /* Compute nearest integer wavelength */ + nm = (int)(XSPECT_XWL(sp, i) + 0.5); + sprintf(buf,"SPEC_%03d",nm); + ocg->add_field(ocg, 0, buf, r_t); + } + + if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * sp->spec_n)) == NULL) { + ocg->del(ocg); + return 1; + } + + for (j = 0; j < nspec; j++) { + for (i = 0; i < sp[j].spec_n; i++) { + setel[i].d = sp[j].spec[i]; + } + ocg->add_setarr(ocg, 0, setel); + } + + if (ocg->write_name(ocg, fname)) { + DBGF((DBGA,"CGATS file write error : %s\n",ocg->err)); + return 1; + } + + free(setel); + ocg->del(ocg); /* Clean up */ + + return 0; +} + +/* restore a set of spectrum from a CGATS file. */ +/* Up to nspec will be restored starting at offset off.. */ +/* The number restored from the file will be written to *nret */ +/* type: 0 = any, mask: 1 = SPECT, 2 = CMF, 4 = ccss */ +/* Return NZ on error */ +/* (Would be nice to return an error message!) */ +int read_nxspect(xspect *sp, char *fname, int *nret, int off, int nspec, int type) { + cgats *icg; /* input cgats structure */ + char buf[100]; + int sflds[XSPECT_MAX_BANDS]; + int i, j, ii; + xspect proto; + + /* Open and look at the spectrum file */ + if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */ + DBG("new_cgats() failed"); + icg->del(icg); + return 1; + } + if (type == 0) { + icg->add_other(icg, ""); /* Allow any signature file */ + } else { + if (type & 1) + icg->add_other(icg, "SPECT"); /* Spectrum file */ + if (type & 2) + icg->add_other(icg, "CMF"); /* Color Matching Functions */ + if (type & 4) + icg->add_other(icg, "CCSS"); /* Color Correction Spectral Samples */ + } + + if (icg->read_name(icg, fname)) { + DBGF((DBGA,"CGATS file read error : %s\n",icg->err)); + icg->del(icg); + return 1; + } + + if (icg->ntables != 1) { + DBG("Input file doesn't contain exactly one table\n"); + icg->del(icg); + return 1; + } + + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0) { + DBG ("Input file doesn't contain keyword SPECTRAL_BANDS\n"); + icg->del(icg); + return 1; + } + proto.spec_n = atoi(icg->t[0].kdata[ii]); + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0) { + DBG("Input file doesn't contain keyword SPECTRAL_START_NM\n"); + icg->del(icg); + return 1; + } + proto.spec_wl_short = atof(icg->t[0].kdata[ii]); + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0) { + DBG("Input file doesn't contain keyword SPECTRAL_END_NM\n"); + icg->del(icg); + return 1; + } + proto.spec_wl_long = atof(icg->t[0].kdata[ii]); + + if ((ii = icg->find_kword(icg, 0, "SPECTRAL_NORM")) < 0) { + DBG("Input file doesn't contain keyword SPECTRAL_NORM - assuming 1.0\n"); + proto.norm = 1.0; + } else { + proto.norm = atof(icg->t[0].kdata[ii]); + } + + /* Find the fields for spectral values */ + for (i = 0; i < proto.spec_n; i++) { + int nm, fi; + + /* Compute nearest integer wavelength */ + nm = (int)(XSPECT_XWL(&proto, i) + 0.5); + sprintf(buf,"SPEC_%03d",nm); + + if ((fi = icg->find_field(icg, 0, buf)) < 0) { + DBGF((DBGA,"Input file doesn't contain field %s\n",buf)); + icg->del(icg); + return 1; + } + + if (icg->t[0].ftype[fi] != r_t) { + DBGF((DBGA,"Field %s in specrum is wrong type - should be a float\n",buf)); + icg->del(icg); + return 1; + } + sflds[i] = fi; + } + + /* Read all the spectra */ + for (j = off; j < nspec && j < icg->t[0].nsets; j++) { + + XSPECT_COPY_INFO(&sp[j], &proto); + + for (i = 0; i < proto.spec_n; i++) { + sp[j].spec[i] = *((double *)icg->t[0].fdata[j][sflds[i]]); + } + } + if (nret != NULL) + *nret = j - off; + + icg->del(icg); + + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* save a spectrum to a CGATS file */ +/* Return NZ on error */ +int write_xspect(char *fname, xspect *sp) { + return write_nxspect(fname, sp, 1, 0); +} + +/* restore a spectrum from a CGATS file */ +/* Return NZ on error */ +/* (Would be nice to return an error message!) */ +int read_xspect(xspect *sp, char *fname) { + int rv, nret; + + if ((rv = read_nxspect(sp, fname, &nret, 0, 1, 1)) != 0) + return rv; + if (nret != 1) { + DBG("Didn't read one spectra\n"); + return 1; + } + + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* save a set of 3 spectrum to a CGATS CMF file */ +/* Return NZ on error */ +int write_cmf(char *fname, xspect sp[3]) { + return write_nxspect(fname, sp, 3, 1); +} + +/* restore a spectrum from a CGATS file */ +/* Return NZ on error */ +/* (Would be nice to return an error message!) */ +int read_cmf(xspect sp[3], char *fname) { + int rv, nret; + + if ((rv = read_nxspect(sp, fname, &nret, 0, 3, 2)) != 0) + return rv; + if (nret != 3) { + DBG("Didn't read three spectra\n"); + return 1; + } + + return 0; +} + +/* ------------- */ +#endif /* !SALONEINSTLIB */ + + +/* Get a raw 3rd order polinomial interpolated spectrum value. */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +/* NOTE: Returned value isn't normalised by sp->norm */ +static int getval_raw_xspec_poly3(xspect *sp, double *rv, double xw) { + int i, rc = 1; + double spcing, f; +#ifdef NEVER + double w1, w2, w3; /* For Hermite curves */ +#endif + double y[4], yw; + double x[4]; + + if (xw < sp->spec_wl_short) { + xw = sp->spec_wl_short; + rc = 0; + } + + if (xw > sp->spec_wl_long) { + xw = sp->spec_wl_long; + rc = 0; + } + + /* Compute fraction 0.0 - 1.0 out of known spectrum */ + spcing = (sp->spec_wl_long - sp->spec_wl_short)/(sp->spec_n-1.0); + f = (xw - sp->spec_wl_short) / (sp->spec_wl_long - sp->spec_wl_short); + f *= (sp->spec_n - 1.0); + i = (int)floor(f); /* Base grid coordinate */ + + if (i < 0) /* Limit to valid cube base index range */ + i = 0; + else if (i > (sp->spec_n - 2)) + i = (sp->spec_n - 2); + + /* Setup the surrounding values */ + x[0] = sp->spec_wl_short + (i-1) * spcing; + if (i == 0) + y[0] = sp->spec[i]; + else + y[0] = sp->spec[i-1]; + x[1] = sp->spec_wl_short + i * spcing; + y[1] = sp->spec[i]; + x[2] = sp->spec_wl_short + (i+1) * spcing; + y[2] = sp->spec[i+1]; + x[3] = sp->spec_wl_short + (i+2) * spcing; + if ((i+2) < sp->spec_n) + y[3] = sp->spec[i+2]; + else + y[3] = sp->spec[i+1]; + +#ifndef NEVER + /* Compute interpolated value using Lagrange: */ + yw = y[0] * (xw-x[1]) * (xw-x[2]) * (xw-x[3])/((x[0]-x[1]) * (x[0]-x[2]) * (x[0]-x[3])) + + y[1] * (xw-x[0]) * (xw-x[2]) * (xw-x[3])/((x[1]-x[0]) * (x[1]-x[2]) * (x[1]-x[3])) + + y[2] * (xw-x[0]) * (xw-x[1]) * (xw-x[3])/((x[2]-x[0]) * (x[2]-x[1]) * (x[2]-x[3])) + + y[3] * (xw-x[0]) * (xw-x[1]) * (xw-x[2])/((x[3]-x[0]) * (x[3]-x[1]) * (x[3]-x[2])); +#else + /* Use Hermite curves */ + y[0] = 0.5 * (y[2] - y[0]); /* Convert to tangent */ + y[3] = 0.5 * (y[3] - y[1]); /* Not sure about the best weighting here ... */ + + w1 = f - (double)i; /* Interpolation weighting factor, 0.0 - 1.0 */ + w2 = w1 * w1; + w3 = w2 * w1; + yw = y[0] * (w3 - 2.0 * w2 + w1) + + y[1] * (2.0 * w3 - 3.0 * w2 + 1.0) + + y[2] * (-2.0 * w3 + 3.0 * w2) + + y[3] * (w3 - w2); +#endif + +#ifdef NEVER // ~~99 + /* Calibration issues or interpolation overshoot can give -ve values, */ + /* so protect against this. */ + /* On the other hand, not allowing -ve values wrecks black level */ + /* by not averaging out the noise. */ + if (yw < 0.0) + yw = 0.0; +#endif /* NEVER */ + + *rv = yw; + return rc; +} + +/* Get a raw linearly interpolated spectrum value. */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +/* NOTE: Returned value isn't normalised by sp->norm */ +static int getval_raw_xspec_lin(xspect *sp, double *rv, double wl) { + int i, rc = 1; + double f, w; + + if (wl < sp->spec_wl_short) { + wl = sp->spec_wl_short; + rc = 0; + } + + if (wl > sp->spec_wl_long) { + wl = sp->spec_wl_long; + rc = 0; + } + + /* Compute fraction 0.0 - 1.0 out of known spectrum */ + f = (wl - sp->spec_wl_short) / (sp->spec_wl_long - sp->spec_wl_short); + f *= (sp->spec_n - 1.0); + i = (int)floor(f); /* Base grid coordinate */ + + if (i < 0) /* Limit to valid cube base index range */ + i = 0; + else if (i > (sp->spec_n - 2)) + i = (sp->spec_n - 2); + + w = f - (double)i; /* Interpolation weighting factor */ + + /* Compute interpolated value */ + *rv = (1.0 - w) * sp->spec[i] + w * sp->spec[i+1]; + +#ifdef NEVER + /* Calibration issues or interpolation overshoot can give -ve values, */ + /* so protect against this. */ + /* On the other hand, not allowing -ve values wrecks black level */ + /* by not averaging out the noise. */ + if (*rv < 0.0) + *rv = 0.0; +#endif /* NEVER */ + + return rc; +} + +#ifdef NEVER /* Nearest neighbor resampler, for testing */ +/* Get a raw nearest-neighbor interpolated spectrum value. */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +/* NOTE: Returned value isn't normalised by sp->norm */ +static int getval_raw_xspec_nn(xspect *sp, double *rv, double wl) { + int i, rc = 1; + double f; + + if (wl < sp->spec_wl_short) { + wl = sp->spec_wl_short; + rc = 0; + } + + if (wl > sp->spec_wl_long) { + wl = sp->spec_wl_long; + rc = 0; + } + + /* Compute fraction 0.0 - 1.0 out of known spectrum */ + f = (wl - sp->spec_wl_short) / (sp->spec_wl_long - sp->spec_wl_short); + f *= (sp->spec_n - 1.0); + i = (int)floor(f + 0.5); /* Base grid coordinate */ + + if (i < 0) /* Limit to valid cube base index range */ + i = 0; + else if (i > (sp->spec_n - 1)) + i = (sp->spec_n - 1); + + /* Compute interpolated value */ + *rv = sp->spec[i]; + +#ifdef NEVER + /* Calibration issues or interpolation overshoot can give -ve values, */ + /* so protect against this. */ + /* On the other hand, not allowing -ve values wrecks black level */ + /* by not averaging out the noise. */ + if (*rv < 0.0) + *rv = 0.0; +#endif /* NEVER */ + + return rc; +} +#endif /* NEVER */ + +/* Call the appropriate interpolation routine */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +/* NOTE: Returned value isn't normalised by sp->norm */ +static int getval_raw_xspec(xspect *sp, double *rv, double wl) { + double spcg = (sp->spec_wl_long - sp->spec_wl_short)/(sp->spec_n-1.0); + + if (spcg < 5.01) { + return getval_raw_xspec_lin(sp, rv, wl); + } else { + return getval_raw_xspec_poly3(sp, rv, wl); + } +} + +/* Get a (normalised) linearly or poly interpolated spectrum value. */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +static int getval_xspec(xspect *sp, double *rv, double wl) { + int sv = getval_raw_xspec(sp, rv, wl); + *rv /= sp->norm; + return sv; +} + +/* Public function to get a spectrum value. */ +/* Return a spectrum value at the given wavelenth. It */ +/* may have been interpolated or extrapolated. */ +/* Returned value isn't normalised by sp->norm */ +double value_xspect(xspect *sp, double wl) { + double rv; + getval_raw_xspec(sp, &rv, wl); + return rv; +} + +/* Get a (normalised) linearly interpolated spectrum value. */ +/* Return NZ if value is valid, Z and last valid value */ +/* if outside the range */ +static int getval_lxspec(xspect *sp, double *rv, double wl) { + int sv = getval_raw_xspec_lin(sp, rv, wl); + *rv /= sp->norm; + return sv; +} + +/* De-noramlize and set normalisation factor to 1.0 */ +void xspect_denorm(xspect *sp) { + int i; + + for (i = 0; i < sp->spec_n; i++) { + sp->spec[i] /= sp->norm; + } + sp->norm = 1.0; +} + +#ifndef SALONEINSTLIB +/* Convert from one xspect type to another (targ type) */ +/* Linear or polinomial interpolation will be used as appropriate */ +/* (converted to targ norm too) */ +void xspect2xspect(xspect *dst, xspect *targ, xspect *src) { + xspect dd; + int i; + + dd.spec_n = targ->spec_n; + dd.spec_wl_short = targ->spec_wl_short; + dd.spec_wl_long = targ->spec_wl_long; + dd.norm = targ->norm; + + if (targ->spec_n != src->spec_n + || targ->spec_wl_short != src->spec_wl_short + || targ->spec_wl_long != src->spec_wl_long) { + for (i = 0; i < targ->spec_n; i++) { + double ww = XSPECT_XWL(targ, i); + getval_raw_xspec(src, &dd.spec[i], ww); + } + } else { + for (i = 0; i < targ->spec_n; i++) + dd.spec[i] = src->spec[i]; + } + if (targ->norm != src->norm) { + for (i = 0; i < targ->spec_n; i++) + dd.spec[i] *= targ->norm/src->norm; + } + *dst = dd; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Given an emission spectrum, set the UV output to the given level. */ +/* The shape of the UV is taken from FWA1_stim, and the level is */ +/* with respect to the Y of the input spectrum. */ +void xsp_setUV(xspect *out, xspect *in, double uvlevel) { + int i, xs, xe; + double ww, avg; + xspect cin; /* Copy of in */ + + cin = *in; + + /* Compute the average of the input spetrum */ + for (avg = 0.0, i = 0; i < cin.spec_n; i++) + avg += cin.spec[i]; + avg /= cin.spec_n; + + if (avg < 1e-5) /* Make it do something with 0.0 */ + avg = 1e-5; + + /* Copy and Extend the range */ + *out = cin; + i = (int)floor(XSPECT_XDIX(out, FWA1_stim.spec_wl_short)); + ww = XSPECT_XWL(out, i); + if (i < 0) + out->spec_n -= i; + out->spec_wl_short = ww; + + /* Copy from input and merge in the UV */ + for (i = 0; i < out->spec_n; i++) { + double inv, uvv, bl, nbl, outv; + + ww = XSPECT_XWL(out, i); + getval_raw_xspec_lin(&cin, &inv, ww); + getval_raw_xspec_lin(&FWA1_stim, &uvv, ww); + + /* Taper measured illum out */ + bl = (ww - FWA1_stim.spec_wl_short)/(FWA1_stim.spec_wl_long - FWA1_stim.spec_wl_short); + bl = bl < 0.0 ? 0.0 : (bl > 1.0 ? 1.0 : bl); + inv *= bl; + + /* Add/subtract UV in */ + outv = inv + uvv * uvlevel * avg;; + + /* Protect against creating negative output */ + if (outv >= out->spec[i]) + out->spec[i] = outv; + } +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Set Media White. This enables extracting and applying the */ +/* colorant reflectance value from/to the meadia. */ +// ~~99 this is confused. ->media is set from ->imedia in fwa setup. +// ~~99 what's going on here ? The API needs fixing. +static int xsp2cie_set_mw(xsp2cie *p, /* this */ +xspect *media /* Spectrum of plain media measured under that instrument */ +) { + p->media = *media; /* Take copy of media white */ + return 0; +} + +/* Extract the colorant reflectance value from the media. Takes FWA */ +/* into account if set. Media white or FWA must be set. */ +static int xsp2cie_extract(xsp2cie *p, /* this */ +xspect *out, /* Extracted colorant refl. spectrum */ +xspect *in /* Spectrum to be converted, normalised by norm */ +) { + int j; + + if (p->media.spec_n == 0) + return 1; + + if (p->media.spec_n != in->spec_n + || p->media.spec_wl_short != in->spec_wl_short + || p->media.spec_wl_long != in->spec_wl_long) + return 1; + + *out = *in; + + /* Divide out the media */ + for (j = 0; j < p->media.spec_n; j++) { + if (p->media.spec[j] < 0.01) + out->spec[j] = in->spec[j] / 0.01; + else + out->spec[j] = in->spec[j] / p->media.spec[j]; + } + + out->norm = in->norm / p->media.norm; + return 0; +} + + +/* Apply the colorant reflectance value from the media. Takes FWA */ +/* into account if set. Media white or FWA must be set. */ +static int xsp2cie_apply(xsp2cie *p, /* this */ +xspect *out, /* Applied refl. spectrum */ +xspect *in /* Colorant reflectance to be applied */ +) { + int j; + + if (p->media.spec_n == 0) + return 1; + + if (p->media.spec_n != in->spec_n + || p->media.spec_wl_short != in->spec_wl_short + || p->media.spec_wl_long != in->spec_wl_long) + return 1; + + *out = *in; + + /* Multiply in the media */ + for (j = 0; j < p->media.spec_n; j++) { + if (p->media.spec[j] < 0.01) + out->spec[j] = in->spec[j] * 0.01; + else + out->spec[j] = in->spec[j] * p->media.spec[j]; + } + + out->norm = in->norm * p->media.norm; + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +static void xsp2cie_fwa_convert(xsp2cie *p, double *out, xspect *in); +static void xsp2cie_fwa_sconvert(xsp2cie *p, xspect *sout, double *out, xspect *in); +static int xsp2cie_fwa_extract(xsp2cie *p, xspect *out, xspect *in); +static int xsp2cie_fwa_apply(xsp2cie *p, xspect *out, xspect *in); + +/* Set Fluorescent Whitening Agent compensation. */ +/* This attempts to compensate for the presense of */ +/* Fluorescent whitner in the media, under the possibly */ +/* different level of UV radiation of the illuminant being */ +/* simulated in the conversion from spectral absorbition */ +/* to CIE values, from the illuminant that the media was */ +/* measured under by the spectrometer. */ +/* Note that the media input spectrum normalisation value is used. */ +/* return nz if error */ + +/* + + Limitations of current FWA model: + + Scattering: The inking model assumes that the inks are purely + absorbtive. If instead they have a significant scattering + component, then the FWA effect will be over estimated, + as it will be assumed that more UV is reaching the substrate + and not being filtered by the colorant. + + Colorant UV transparency: The current model assumes that + the filtering behaviour of the ink can be extrapolated + from the blue reflectance. It could be that inks behave + quite differently, filtering more or less in UV than + they do in blue. Different inks might have different characteristics. + + Solution: A solution would be to add a colorant correction model, + that takes as input the colorant levels. To create the model, + illumread would be augmented to read (say) 50% colorant swatches + as well as white, and use the discrepancy between the non-corrected + FWA spectrum and the actual spectrum under the illuminant to + create the correction model. This could be fine tuned by doing + similar measurements of neutral patches. + */ + +/* + See page 248 of the Proceedings of the IS&T/SID + 11th Color Imaging Conference, November 2003: + "A Practical Approach to Measuring and Modelling Paper Fluorescense + for Improved Colorimetric Characterisation of Printing Processes" + ISBN: 0-89208-248-8 + for more information about the fwa compensation approach. + */ + +static int xsp2cie_set_fwa_imp(xsp2cie *p) { + double ww; + int i, j; + int flag; + double aw = 0.0, bw = 0.0; /* Points wavelength */ + double ar, br; /* Points reflection */ +#ifdef STOCKFWA /* Use table shape */ + double Em; /* Emmision multiplier */ +#endif +#ifdef DOPLOT + double xx[XSPECT_MAX_BANDS]; + double y1[XSPECT_MAX_BANDS]; + double y2[XSPECT_MAX_BANDS]; + double y3[XSPECT_MAX_BANDS]; + double y4[XSPECT_MAX_BANDS]; +#endif /* DOPLOT */ + +#ifdef WRITE_FWA1_STIM + write_xspect("fwa1_stip.sp", &FWA1_stim); +#endif + + DBG("set_fwa started\n"); + + p->bw = 1.0; /* Intergrate over 1nm bands */ + p->oillum = p->illuminant; /* Take copy of observer illuminant */ + xspect_denorm(&p->oillum); + if (p->tillum.spec_n == 0) { /* If not set by set_fwa(), use observer illuminant */ + p->tillum = p->oillum; /* as target/simulated instrument illuminant. */ + } + + /* Compute Y = 1 normalised instrument illuminant spectrum */ + { + double scale = 0.0; + double Iim; /* illuminant multiplier */ + + Iim = 0.0; + for (ww = p->observer[1].spec_wl_short; ww <= p->observer[1].spec_wl_long; ww += p->bw) { + double O, I; + getval_lxspec(&p->iillum, &I, ww); + getval_lxspec(&p->observer[1], &O, ww); + scale += O; /* Integrate Y observer values */ + Iim += O * I; + } + Iim /= scale; /* Scale Y observer to unity */ + + Iim = 1.0/Iim; /* Scale factor to make illuminant integral 1.0 */ + + for (j = 0; j < p->iillum.spec_n; j++) + p->iillum.spec[j] *= Iim; + DBGF((DBGA,"Instrument Illum normal multiplier Iim = %f\n",Iim)); + } + + /* Compute Y = 1 normalised target illuminant spectrum */ + { + double scale; + double Itm; /* illuminant multiplier */ + + scale = 0.0; + Itm = 0.0; + for (ww = p->observer[1].spec_wl_short; ww <= p->observer[1].spec_wl_long; ww += p->bw) { + double O, I; + getval_lxspec(&p->tillum, &I, ww); + getval_lxspec(&p->observer[1], &O, ww); + scale += O; /* Integrate Y observer values */ + Itm += O * I; + } + Itm /= scale; /* Scale Y observer to unity */ + Itm = 1.0/Itm; /* Scale factor to make illuminant integral 1.0 */ + + for (j = 0; j < p->tillum.spec_n; j++) + p->tillum.spec[j] *= Itm; + } + + /* Check if the instrument and target/simulated illuminant are the same. */ + /* If they are, FWA compensation can be bypassed. */ + /* (We check for an almost exact matcg on the assumption that these will */ + /* both be xspect presets) */ +#define DEQ(A, B) (fabs(A - B) < 1e-6) + p->insteqtarget = 0; + if (p->iillum.spec_n == p->tillum.spec_n + && DEQ(p->iillum.spec_wl_short, p->tillum.spec_wl_short) + && DEQ(p->iillum.spec_wl_long, p->tillum.spec_wl_long)) { + for (i = 0; i < p->iillum.spec_n; i++) { + if (!DEQ(p->tillum.spec[i], p->iillum.spec[i])) + break; + } + if (i >= p->iillum.spec_n) { + p->insteqtarget = 1; + DBGF((DBGA,"###### inst equals target illuminant #####\n")); + } + } +#undef DEQ + + /* Compute Y = 1 normalised observer illuminant spectrum */ + { + double scale; + double Itm; /* Target illuminant multiplier */ + + scale = 0.0; + Itm = 0.0; + for (ww = p->observer[1].spec_wl_short; ww <= p->observer[1].spec_wl_long; ww += p->bw) { + double O, I; + getval_lxspec(&p->oillum, &I, ww); + getval_lxspec(&p->observer[1], &O, ww); + scale += O; /* Integrate Y observer values */ + Itm += O * I; + } + Itm /= scale; /* Scale Y observer to unity */ + Itm = 1.0/Itm; /* Scale factor to make illuminant integral 1.0 */ + + for (j = 0; j < p->oillum.spec_n; j++) + p->oillum.spec[j] *= Itm; + } + + /* Estimate the amount of generic FWA in the media. */ + /* and also compute an estimated media minus FWA spectrum */ + /* by creating a target white line from the media spectrum */ + + /* This is quite good for "normal" media, which has a fairly */ + /* flat underlying (non FWA) response, but doesn't work so */ + /* well for meadia that rolls off at short wavelengths and uses */ + /* FWA to compensate for this. */ + + /* Find darkest point between 450 and 510nm */ + ar = 1e6; + for (ww = 450.0; ww <= 510.0; ww += p->bw) { + double rr; + getval_lxspec(&p->imedia, &rr, ww); + DBGF((DBGA,"media %f = %f\n",ww,rr)); + + if (rr < ar) { + aw = ww; + ar = rr; + } + } + + /* Find lightest point between A point+70 and 650 */ + br = -1.0; + for (ww = aw+70.0; ww <= 630.0; ww += p->bw) { + double rr; + getval_lxspec(&p->imedia, &rr, ww); + DBGF((DBGA,"media %f = %f\n",ww,rr)); + if (rr > br) { + bw = ww; + br = rr; + } + } + if (br < ar) + br = ar; /* Make flat rather than slope to the right */ + + DBGF((DBGA,"Cuttoff line params: A = %f %f, B = %f %f\n", aw, ar, bw, br)); + +#ifdef STOCKFWA /* Use table shape as FWA basis */ + + /* Compute an Em that explains the bump over the flat line */ + Em = 0.0; + for (ww = FWA1_emit.spec_wl_short; ww <= (FWA1_emit.spec_wl_long - 100.0); ww += p->bw) { + double Rl, rr; + + /* Compute value of line at this wavelength */ + Rl = (ww - aw)/(bw - aw) * (br - ar) + ar; + + getval_lxspec(&p->imedia, &rr, ww); /* Media at this point */ + + if (rr > Rl) { /* Media is over the line */ + double Ii; + double Eu; + double mm; + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised illuminant at this wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + getval_lxspec(&FWA1_emit, &Eu, ww); /* FWA emission at this wavelength */ + mm = ((rr - Rl) * Ii)/Eu; + if (mm > Em) { + DBGF((DBGA,"Update Em to %f at %fnm for target %f\n",mm,ww,rr-Rl)); + Em = mm; /* Greater multiplier to explain bump */ + } + } + } + DBGF((DBGA,"Em = %f\n",Em)); + + /* Setup spectrum to hold result over exected range */ + /* and base media reflectance */ + p->media = p->imedia; /* Take copy of media white */ + p->emits = p->imedia; /* Structure copy */ + xspect_denorm(&p->media); /* Set norm to 1.0 */ + xspect_denorm(&p->emits); + + /* Copy emission spectra that explains bump over line */ + /* plus estimated media without FWA spectrum. */ + for (i = 0; i < p->media.spec_n; i++) { + double Eu, Ii; + double Rm, Rmb; + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(i); +#endif + + ww = (p->media.spec_wl_long - p->media.spec_wl_short) + * ((double)i/(p->media.spec_n-1.0)) + p->media.spec_wl_short; + + getval_lxspec(&FWA1_emit, &Eu, ww); /* FWA emission at this wavelength */ + Eu *= Em; + p->emits.spec[i] = p->emits.norm * Eu; /* Remember FWA spectrum */ + + Rm = p->media.spec[i]/p->media.norm; /* Media at this point */ + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised illuminant at this wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + Rm *= Ii; /* Light reflected from media */ + + Rmb = Rm - Eu; /* Convert media to base media */ + if (Rmb < 0.01) + Rmb = 0.01; /* This would be silly */ + p->media.spec[i] = p->media.norm * Rmb/Ii; /* Convert media to base media */ + DBGF((DBGA,"ww %f, Eu %f, Rm %f, Rmb %f\n",ww, Eu, Rm, Rmb)); + + } + /* Prevent silliness */ + p->emits.spec[0] = 0.0; + p->emits.spec[p->emits.spec_n-1] = 0.0; + +#else /* Not STOCK_FWA */ + /* Setup spectrum to hold result over exected range */ + /* and base media reflectance */ + p->media = p->imedia; /* Take copy of media white */ + p->emits = p->imedia; /* Structure copy */ + xspect_denorm(&p->media); /* Set norm to 1.0 */ + xspect_denorm(&p->emits); + + /* Compute emission spectra that explains bump over line */ + /* plus estimated media without FWA spectrum. */ + /* Do this from long to short to allow for "filter off" trigger */ + flag = 1; /* Filter is active */ + for (i = (p->media.spec_n-1); i >= 0; i--) { + double Rl, Rm, Rmb; + double fwi = 25.0; /* Smoothing filter width +/- */ + int fres = 5; /* Smoothing filter resolution */ + double tweight; + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(i); +#endif + /* Wavelength we're generating */ + ww = (p->media.spec_wl_long - p->media.spec_wl_short) + * ((double)i/(p->media.spec_n-1.0)) + p->media.spec_wl_short; + + /* Compute the base media estimate at this point from */ + /* the triangular smoothed filter of the smaller of the */ + /* measured media and the line */ + tweight = 0.0; + Rmb = 0.0; + for (j = -fres; j <= fres; j++) { + double fww, weight; + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(j); +#endif + fww = ww + (double)j/(double)fres * fwi; + weight = 1.0 - fabs((double)j/(double)fres); + + Rl = (fww - aw)/(bw - aw) * (br - ar) + ar; /* Line at this point */ + getval_lxspec(&p->imedia, &Rm, fww); /* Media at this point */ + + if (Rm < Rl) + Rl = Rm; + + Rmb += Rl * weight; + tweight += weight; + } + Rmb /= tweight; /* Base media estimate */ + + /* Compute value of line and media at this wavelength */ + Rl = (ww - aw)/(bw - aw) * (br - ar) + ar; /* Line at this point */ + + getval_lxspec(&p->imedia, &Rm, ww); /* Media at this point */ + DBGF((DBGA,"ww %f, Rl %f, Rm %f, Rmb %f\n",ww,Rl,Rm,Rmb)); + + /* Stop following the filter once the actual media has crossed over it */ + if (ww < 450.0 && Rm < Rmb) + flag = 0; + + /* Don't follow smoothed at long wl or if media has caught up to filtered */ + if (flag == 0 || ww > 570.0) + Rmb = Rm; + + if (Rm > Rmb && ww <= 570.0) { /* Media is over the line */ + double Ii; + + p->media.spec[i] = p->media.norm * Rmb; /* Convert media to base media */ + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised illuminant at this wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + + p->emits.spec[i] = p->emits.norm * (Rm - Rmb) * Ii; + DBGF((DBGA,"ww %fnm, Rm %f, Rmb %f, Eu %f\n",ww, Rm, Rmb, p->emits.spec[i]/p->emits.norm)); + + } else { + p->emits.spec[i] = 0.0; + } +#ifdef DOPLOT + xx[i] = ww; + y1[i] = Rl; + y2[i] = Rm; + y3[i] = Rmb; + y4[i] = p->emits.spec[i]/p->emits.norm; +#endif + } +#ifdef DOPLOT + printf("Estimated vs. real media spectrum calculation\n"); + do_plot6(xx,y1,y2,y3,y4,NULL,NULL,p->media.spec_n); +#endif + /* Prevent silliness */ + p->emits.spec[0] = 0.0; + p->emits.spec[p->emits.spec_n-1] = 0.0; +#endif /* !STOCKFWA */ + + /* Compute level of UV stimulating FWA */ + p->Sm = 0.0; + for (ww = FWA1_stim.spec_wl_short; ww <= FWA1_stim.spec_wl_long; ww += p->bw) { + double Ii; + double Su; + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised illuminant at this wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + getval_lxspec(&FWA1_stim, &Su, ww); /* FWA stimulation profile at this wavelength */ + p->Sm += Su * Ii; + } + DBGF((DBGA,"Sm = %f\n",p->Sm)); + + /* Compute FWA content of this media, for information purposes */ + p->FWAc = 0.0; + for (ww = p->emits.spec_wl_short; ww <= p->emits.spec_wl_long; ww += p->bw) { + double Eu; + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + p->FWAc += Eu; + } + p->FWAc /= p->Sm; /* Divided by stimulation */ + DBGF((DBGA,"FWA content = %f\n",p->FWAc)); + + /* Turn on FWA compensation */ + p->convert = xsp2cie_fwa_convert; + p->sconvert = xsp2cie_fwa_sconvert; + p->extract = xsp2cie_fwa_extract; + p->apply = xsp2cie_fwa_apply; + +#if defined(DOPLOT) || defined(DEBUG) + /* Print the estimated vs. real media spectrum */ + for (i = 0, ww = p->media.spec_wl_short; ww <= p->media.spec_wl_long; ww += 1.0, i++) { + double Rm; /* Real media reflectance */ + double Rmb; /* Media reflectance without FWA */ + double Rmd; /* Estimated media reflectance with FWA */ + double Ii; + double Eu; + + getval_lxspec(&p->imedia, &Rm, ww); /* Media at this point */ + getval_lxspec(&p->media, &Rmb, ww); /* Base Media */ + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised illuminant at this wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + + Rmd = ((Ii * Rmb) + Eu)/Ii; /* Base Media plus FWA */ + DBGF((DBGA,"%fnm, is %f should be %f, Rmb %f, Eu %f\n",ww, Rm, Rmd, Rmb, Eu)); + +#ifdef DOPLOT + xx[i] = ww; +// y1[i] = Rm; + y1[i] = Rmb; + y2[i] = Eu; + y3[i] = Rmd; +#endif + } +#ifdef DOPLOT + printf("Estimated vs. real media spectrum\n"); + do_plot(xx,y1,y2,y3,i); +#endif +#endif /* DEBUG */ + + DBGF((DBGA,"We're done\n")); + return 0; +} + +/* Set FWA given instrument illuminant and white media measurement */ +static int xsp2cie_set_fwa(xsp2cie *p, /* this */ +xspect *iillum, /* Spectrum of instrument illuminent */ +xspect *tillum, /* Spectrum of target/simulated instrument illuminant */ + /* NULL to use observer illuminant. */ +xspect *media /* Spectrum of plain media measured under that instrument */ +) { + p->iillum = *iillum; /* Take copy of instrument illuminant */ + xspect_denorm(&p->iillum); /* Remove normalisation factor */ + if (tillum != NULL) { + p->tillum = *tillum; /* Take copy of target/simulated instrument illuminant */ + xspect_denorm(&p->tillum); /* Remove normalisation factor */ + } else { + p->tillum.spec_n = 0; + } + p->imedia = *media; /* Take copy of measured media */ + + return xsp2cie_set_fwa_imp(p); +} + +/* Set FWA given updated conversion illuminant. */ +/* We assume that xsp2cie_set_fwa has been called first. */ +static int xsp2cie_update_fwa_custillum( +xsp2cie *p, /* this */ +xspect *tillum, /* Spectrum of target/simulated instrument illuminant, */ + /* NULL to use previous set_fwa() value. */ +xspect *custIllum /* Spectrum of observer illuminant */ +) { + if (tillum != NULL) { + p->tillum = *tillum; /* Take copy of target/simulated instrument illuminant */ + xspect_denorm(&p->tillum); /* Remove normalisation factor */ + } + p->illuminant = *custIllum; + + return xsp2cie_set_fwa_imp(p); +} + +/* Get Fluorescent Whitening Agent compensation information */ +/* return NZ if error */ +static void xsp2cie_get_fwa_info( +xsp2cie *p, +double *FWAc) { + + if (FWAc != NULL) + *FWAc = p->FWAc; +} + +/* Do the FWA corrected spectral to CIE conversion. */ +/* If the instrument and target illuminant are the same, */ +/* then FWA correction is bypassed. */ +/* Note that the input spectrum normalisation value is used. */ +/* Emissive spectral values are assumed to be in mW/nm, and sampled */ +/* rather than integrated if they are not at 1nm spacing. */ +static void xsp2cie_fwa_sconvert( +xsp2cie *p, /* this */ +xspect *sout, /* Return corrected input spectrum (may be NULL, or same as imput) */ +double *out, /* Return XYZ or D50 Lab value (may be NULL) */ +xspect *in /* Spectrum to be converted */ +) { + double ww; + int i, j, k; + double Emc, Smc; /* Emission and Stimulation multipiers for instrument meas. */ + double Emct, Smct; /* Emission and Stimulation multipiers for target illum. */ + double scale = 0.0; + xspect tsout; /* Temporary sout */ + double wout[3]; /* Working CIE out */ +#ifdef DEBUG + double chout[3]; /* Out check values */ + double oout[3]; +#endif /* DEBUG */ +#ifdef DOPLOT_ALL_FWA + double xx[XSPECT_MAX_BANDS]; + double y1[XSPECT_MAX_BANDS]; + double y2[XSPECT_MAX_BANDS]; + double y3[XSPECT_MAX_BANDS]; + int plix = 0; +#endif /* DOPLOT_ALL_FWA */ + + tsout.spec_n = 0; + tsout.spec_wl_short = 0.0; + tsout.spec_wl_long = 0.0; + tsout.norm = 0.0; + +#define MIN_ILLUM 1e-8 /* Minimum assumed illumination level at wavelength */ +#define MIN_REFL 1e-6 /* Minimum assumed reflectance at wavelength */ + + /* With colorant, estimate stimulation level of FWA for instrument illuminant */ + /* and for target illuminant. Because the colorant estimate depends on the FWA */ + /* estimate, and the FWA emissions can contribute to FWA stimulation, */ + /* we itterate a few times to allow this to converge. */ + Emc = Emct = 0.0; + for (k = 0; k < 4; k++) { + Smct = Smc = 0.0; + for (ww = FWA1_stim.spec_wl_short; ww <= FWA1_stim.spec_wl_long; ww += p->bw) { + double Kc; /* FWA contribution for instrument illum */ + double Kct; /* FWA contribution for target illum */ + double Ii; /* Instrument illuminant level */ + double It; /* Target illuminant level */ + double Eu; /* FWA emmission profile */ + double Rc; /* Measured reflectance under inst. illum. */ + double Rmb; /* Media reflectance measured on instrument */ + double Rcch; /* Half colorant reflectance value */ + double Su; /* FWA sensitivity */ + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + Kct = Emct * Eu; /* FWA contribution under target illum. */ + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instr. illuminant at wavelength */ + if (Ii < MIN_ILLUM) + Ii = MIN_ILLUM; + + getval_lxspec(&p->tillum, &It, ww);/* Normalised target. illuminant at wavelength */ + if (It < MIN_ILLUM) + It = MIN_ILLUM; + + getval_lxspec(&p->media, &Rmb, ww); /* Base media reflectance at this wavelength */ + if (Rmb < MIN_REFL) + Rmb = MIN_REFL; + + getval_lxspec(in, &Rc, ww) ; /* Media + colorant reflectance at wavelength */ + if (Rc < 0.0) + Rc = 0.0; + +#ifdef NEVER + Rcch = sqrt(Rc/Rmb); /* Half reflectance estimate (valid if no FWA) */ + +#else + /* Solve for underlying colorant half reflectance, discounting FWA */ + if (Rmb <= MIN_REFL) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + else + Rcch = (-Kc + sqrt(Kc * Kc + 4.0 * Ii * Ii * Rmb * Rc))/(2.0 * Ii * Rmb); +#endif + + getval_lxspec(&FWA1_stim, &Su, ww); /* FWA stimulation sensitivity this wavelength */ + + + Smc += Su * (Ii * Rcch + Kc); + Smct += Su * (It * Rcch + Kct); + DBGF((DBGA,"at %.1fnm, Rmb %f, Rc %f, Rch %f, Rcch %f, Ii %f, It %f, Kct %f, Smc %f, Smct %f,\n",ww,Rmb,Rc,sqrt(Rc),Rcch,Ii,It,Kct,Su * (Ii * Rcch + Kc),Su * (It * Rcch + Kct))); + } + Emc = Smc/p->Sm; /* FWA Emmsion muliplier with colorant for instr. illum. */ + Emct = Smct/p->Sm; /* FWA Emmsion muliplier with colorant for target illum. */ + + DBGF((DBGA,"Itteration %d, Smc %f, Smct %f, Emc %f, Emct %f\n\n",k, Smc,Smct,Emc,Emct)); + } + + for (j = 0; j < 3; j++) { + wout[j] = 0.0; +#ifdef DEBUG + chout[j] = 0.0; +#endif /* DEBUG */ + } + + /* Compute CIE output over observer range in 1nm increments */ + scale = 0.0; + for (ww = p->observer[1].spec_wl_short; ww <= p->observer[1].spec_wl_long; ww += p->bw) { + double Kc; /* FWA contribution for instrument illum */ + double Kct; /* FWA contribution for target illum */ + double Ii; /* Instrument illuminant level */ + double It; /* Target illuminant level */ + double Io; /* Observer illuminant level */ + double Rmb; /* Base media reflectance estimate */ + double Eu; /* FWA emmission profile */ + double Rc; /* Measured reflectance under inst. illum. */ + /* Rch Measured half reflectance under inst. illum */ + double Rcch; /* Corrected Rc colorant half reflectance */ + double Rct; /* Corrected Rc for target illuminant */ + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + Kct = Emct * Eu; /* FWA contribution under target illum. */ + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instr. illuminant at wavelength */ + if (Ii < MIN_ILLUM) + Ii = MIN_ILLUM; + + getval_lxspec(&p->tillum, &It, ww);/* Normalised target. illuminant at wavelength */ + if (It < MIN_ILLUM) + It = MIN_ILLUM; + + getval_lxspec(&p->media, &Rmb, ww); /* Base media reflectance at this wavelength */ + if (Rmb < MIN_REFL) + Rmb = MIN_REFL; + + getval_lxspec(in, &Rc, ww) ; /* Media + colorant reflectance at wavelength */ + if (Rc < 0.0) + Rc = 0.0; + + /* Solve for underlying colorant half transmittance, discounting FWA */ + if (Rmb <= MIN_REFL) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + else + Rcch = (-Kc + sqrt(Kc * Kc + 4.0 * Ii * Ii * Rmb * Rc))/(2.0 * Ii * Rmb); + + /* Estimated corrected reflectance */ + Rct = ((It * Rcch * Rmb + Kct) * Rcch)/It; + + DBGF((DBGA,"at %.1fnm, Rmb %f, Rc %f, Rch %f, Rcch %f, Ii %f, It %f, Kct %f, Rct %f\n",ww,Rmb,Rc,sqrt(Rc),Rcch,Ii,It,Kct,Rct)); + + if (p->insteqtarget) /* Ignore FWA corrected value if same illuminant */ + Rct = Rc; + +#ifdef DOPLOT_ALL_FWA + xx[plix] = ww; + y1[plix] = Rc; /* Uncorrected reflectance */ +// y2[plix] = Rct - Rc; /* Difference between corrected and uncorrected */ +// y2[plix] = Rcch * Rcch; /* Estimated underlying colorant reflectance without FWA */ +// y2[plix] = Rmb; /* Base media relectance estimate */ + y2[plix] = Kct; /* FWA contribution under target illuminant */ + y3[plix++] = Rct; /* Corrected reflectance */ +#endif /* DOPLOT_ALL_FWA */ + + /* Observer illuminant */ + getval_lxspec(&p->oillum, &Io, ww); /* Normalised observer illuminant */ + + /* Compute CIE result */ + for (j = 0; j < 3; j++) { + double O; + getval_lxspec(&p->observer[j], &O, ww); + if (j == 1) + scale += Io * O; /* Integrate Y illuminant/observer values */ + wout[j] += Rct * Io * O; /* Corrected refl. * Observer illuminant */ +#ifdef DEBUG + chout[j] += Rc * It * O; +#endif /* DEBUG */ + } + } + if (p->isemis) { + scale = 0.683002; /* Convert from mW/m^2 to Lumens/m^2 */ + /* (== 683 Luments/Watt/m^2) */ + } else { + scale = 1.0/scale; + } + for (j = 0; j < 3; j++) { /* Scale for illuminant/observer normalisation of Y */ + wout[j] *= scale; +#ifdef CLAMP_XYZ + if (p->clamp && wout[j] < 0.0) + wout[j] = 0.0; /* Just to be sure we don't get silly values */ +#endif /* CLAMP_XYZ */ + } + +#ifdef DEBUG + for (j = 0; j < 3; j++) { /* Scale for illuminant/observer normalisation of Y */ + chout[j] *= scale; +#ifdef CLAMP_XYZ + if (p->clamp && chout[j] < 0.0) + chout[j] = 0.0; /* Just to be sure we don't get silly values */ +#endif /* CLAMP_XYZ */ + } + icmXYZ2Lab(&icmD50, oout, wout); + icmXYZ2Lab(&icmD50, chout, chout); + DBGF((DBGA,"Compensated %f %f %f, uncompensated %f %f %f\n", + oout[0], oout[1], oout[2], chout[0], chout[1], chout[2])); +#endif /* DEBUG */ + +#ifdef DOPLOT_ALL_FWA + printf("FWA compensated spectrum for sample\n"); + do_plot(xx,y1,y2,y3,plix); +#endif /* DOPLOT_ALL_FWA */ + + /* Do it again for output over optional returned spectrum range */ + if (sout != NULL) { + tsout.spec_n = in->spec_n; + tsout.spec_wl_short = in->spec_wl_short; + tsout.spec_wl_long = in->spec_wl_long; + tsout.norm = in->norm; + + for (i = 0; i < in->spec_n; i++) { + double Kc; /* FWA contribution for instrument illum */ + double Kct; /* FWA contribution for target illum */ + double Ii; /* Instrument illuminant level */ + double It; /* Target/simulated instrument illuminant level */ + double Rmb; /* Base media reflectance estimate */ + double Eu; /* FWA emmission profile */ + double Rc; /* Reflectance under inst. illum. */ + double Rcch; /* Corrected Rc half reflectance */ + double Rct; /* Corrected Rc for target illuminant */ + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(i); +#endif + ww = (in->spec_wl_long - in->spec_wl_short) + * ((double)i/(in->spec_n-1.0)) + in->spec_wl_short; + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + Kct = Emct * Eu; /* FWA contribution under target illum. */ + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instr. illuminant at wavelength */ + if (Ii < MIN_ILLUM) + Ii = MIN_ILLUM; + + getval_lxspec(&p->tillum, &It, ww);/* Normalised target. illuminant at wavelength */ + if (It < MIN_ILLUM) + It = MIN_ILLUM; + + getval_lxspec(&p->media, &Rmb, ww); /* Base media reflectance at this wavelength */ + if (Rmb < MIN_REFL) + Rmb = MIN_REFL; + + getval_lxspec(in, &Rc, ww) ; /* Media + colorant reflectance at wavelength */ + if (Rc < 0.0) + Rc = 0.0; + + if (Rmb < MIN_REFL) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + else + Rcch = (-Kc + sqrt(Kc * Kc + 4.0 * Ii * Ii * Rmb * Rc))/(2.0 * Ii * Rmb); + + Rct = ((It * Rcch * Rmb + Kct) * Rcch)/It; + + if (p->insteqtarget) /* Ignore FWA corrected value if same illuminant */ + Rct = Rc; + + tsout.spec[i] = tsout.norm * Rct; + } + } + + /* If Lab is target, convert to D50 Lab */ + if (p->doLab) { + icmXYZ2Lab(&icmD50, wout, wout); + } + + if (out != NULL) { + out[0] = wout[0]; + out[1] = wout[1]; + out[2] = wout[2]; + } + + if (sout != NULL) { + *sout = tsout; /* Structure copy */ + } + +#undef MIN_ILLUM +#undef MIN_REFL + +} + +/* Normal conversion without returning spectrum */ +static void xsp2cie_fwa_convert(xsp2cie *p, double *out, xspect *in) { + xsp2cie_fwa_sconvert(p, NULL, out, in); +} + +/* Extract the colorant reflectance value from the media. Takes FWA */ +/* into account if set. FWA must be set. */ +static int xsp2cie_fwa_extract(xsp2cie *p, /* this */ +xspect *out, /* Extracted colorant refl. spectrum */ +xspect *in /* Spectrum to be converted, normalised by norm */ +) { + double ww; + int i, j, k; + double Emc, Smc; /* Emission and Stimulation multipiers for instrument meas. */ + +#ifdef DOPLOT_ALL_FWA + double xx[XSPECT_MAX_BANDS]; + double y1[XSPECT_MAX_BANDS]; + double y2[XSPECT_MAX_BANDS]; + double y3[XSPECT_MAX_BANDS]; + int plix = 0; +#endif /* DOPLOT_ALL_FWA */ + + /* With colorant, estimate stimulation level of FWA for instrument illuminant */ + /* and for target illuminant. Because the colorant estimate depends on the FWA */ + /* estimate, and the FWA emissions can contribute to FWA stimulation, */ + /* we itterate a few times to allow this to converge. */ + Emc = 0.0; + for (k = 0; k < 4; k++) { + Smc = 0.0; + for (ww = FWA1_stim.spec_wl_short; ww <= FWA1_stim.spec_wl_long; ww += p->bw) { + double Kc; /* FWA contribution for instrument illum */ + double Ii; /* Instrument illuminant level */ + double Su; /* FWA sensitivity */ + double Rmb; /* Media reflectance measured on instrument */ + double Eu; /* FWA emmission profile */ + double Rc; /* Reflectance under inst. illum. */ + double Rcch; /* Half colorant reflectance value */ + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + + getval_lxspec(&p->media, &Rmb, ww); /* Base Media */ + getval_lxspec(in, &Rc, ww); /* Media + colorant reflectance at wavelength + FWA */ + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instrument illuminant */ + if (Ii < 1e-9) + Ii = 1e-9; + + if (Rmb < 1e-9) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + else + Rcch = (-Kc + sqrt(Kc * Kc + 4.0 * Ii * Ii * Rmb * Rc))/(2.0 * Ii * Rmb); + + getval_lxspec(&FWA1_stim, &Su, ww); /* FWA stimulation sensitivity this wavelength */ + Smc += Su * (Ii * Rcch + Kc); + +//DBGF((DBGA,"ww = %f, Rmb %f, Rcch %f, Ii %f, Su %f, Smc %f\n", ww,Rmb,Rcch,Ii,Su,Smc)); + } + Emc = Smc/p->Sm; /* FWA Emmsion muliplier with colorant for instr. illum. */ + } + + DBGF((DBGA,"extract:\n")); + DBGF((DBGA,"Smc = %f\n",Smc)); + DBGF((DBGA,"Emc = %f\n",Emc)); + + out->spec_n = in->spec_n; + out->spec_wl_short = in->spec_wl_short; + out->spec_wl_long = in->spec_wl_long; + out->norm = in->norm; + + for (i = 0; i < in->spec_n; i++) { + double Kc; /* FWA contribution for instrument illum */ + double Ii; /* Instrument illuminant level */ + double Rmb; /* Base media reflectance estimate */ + double Eu; /* FWA emmission profile */ + double Rc; /* Reflectance under inst. illum. */ + double Rcch; /* Corrected Rc half reflectance */ + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(i); +#endif + ww = (in->spec_wl_long - in->spec_wl_short) + * ((double)i/(in->spec_n-1.0)) + in->spec_wl_short; + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + + getval_lxspec(&p->media, &Rmb, ww); /* Base Media */ + getval_lxspec(in, &Rc, ww); /* Media + colorant reflectance at wavelength + FWA */ + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instrument illuminant */ + if (Ii < 1e-9) + Ii = 1e-9; + + if (Rmb < 1e-9) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + else + Rcch = (-Kc + sqrt(Kc * Kc + 4.0 * Ii * Ii * Rmb * Rc))/(2.0 * Ii * Rmb); + + Rcch *= Rcch; /* Full reflectance value */ + + out->spec[i] = out->norm * Rcch; + +#ifdef DOPLOT_ALL_FWA + xx[plix] = ww; + y1[plix] = Rmb; /* Base media */ + y2[plix] = Rc; /* Uncorrected reflectance */ + y3[plix] = Rcch; /* Underlying colorant reflectance without FWA */ + plix++; +#endif /* DOPLOT_ALL_FWA */ + } +#ifdef DOPLOT_ALL_FWA + printf("FWA compensated extraction for sample\n"); + do_plot(xx,y1,y2,y3,plix); +#endif /* DOPLOT_ALL_FWA */ + return 0; +} + + +/* Apply the colorant reflectance value from the media. Takes FWA */ +/* into account if set. DOESN'T convert to FWA target illumination! */ +/* FWA must be set. */ +static int xsp2cie_fwa_apply(xsp2cie *p, /* this */ +xspect *out, /* Applied refl. spectrum */ +xspect *in /* Colorant reflectance to be applied */ +) { + double ww; + int i, j, k; + double Emc, Smc; /* Emission and Stimulation multipiers for instrument meas. */ + +#ifdef DOPLOT_ALL_FWA + double xx[XSPECT_MAX_BANDS]; + double y1[XSPECT_MAX_BANDS]; + double y2[XSPECT_MAX_BANDS]; + double y3[XSPECT_MAX_BANDS]; + int plix = 0; +#endif /* DOPLOT_ALL_FWA */ + + /* With colorant, estimate stimulation level of FWA for instrument illuminant. */ + /* We itterate a few times to allow for FWA self stimulation. */ + Emc = 0.0; + for (k = 0; k < 4; k++) { + Smc = 0.0; + for (ww = FWA1_stim.spec_wl_short; ww <= FWA1_stim.spec_wl_long; ww += p->bw) { + double Kc; /* FWA contribution for instrument illum */ + double Ii; /* Instrument illuminant level */ + double Eu; /* FWA emmission profile */ + double Su; /* FWA sensitivity */ + double Rcch; /* Half colorant reflectance value */ + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + + getval_lxspec(in, &Rcch, ww); /* Colorant reflectance at wavelength */ + Rcch = sqrt(Rcch); /* Half reflectance estimate (valid if no FWA) */ + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instr. illuminant at wavelength */ + if (Ii < 1e-9) + Ii = 1e-9; + + getval_lxspec(&FWA1_stim, &Su, ww); /* FWA stimulation sensitivity this wavelength */ + Smc += Su * (Ii * Rcch + Kc); +//DBGF((DBGA,"ww = %f, Rcch %f, Ii %f, Su %f, Smc %f\n", ww,Rcch,Ii,Su,Smc)); + } + Emc = Smc/p->Sm; /* FWA Emmsion muliplier with colorant for instr. illum. */ + } + + DBGF((DBGA,"apply:\n")); + DBGF((DBGA,"Smc = %f\n",Smc)); + DBGF((DBGA,"Emc = %f\n",Emc)); + + out->spec_n = in->spec_n; + out->spec_wl_short = in->spec_wl_short; + out->spec_wl_long = in->spec_wl_long; + out->norm = in->norm; + + for (i = 0; i < in->spec_n; i++) { + double Kc; /* FWA contribution for instrument illum */ + double Ii; /* Instrument illuminant level */ + double Rmb; /* Base media reflectance estimate */ + double Eu; /* FWA emmission profile */ + double Rc; /* Reflectance under inst. illum. */ + double Rcch; /* Rc half reflectance */ + double RcI; /* Reconstituted Rc for inst. illuminant times illuminant */ + +#if defined(__APPLE__) && defined(__POWERPC__) + gcc_bug_fix(i); +#endif + ww = (in->spec_wl_long - in->spec_wl_short) + * ((double)i/(in->spec_n-1.0)) + in->spec_wl_short; + + getval_lxspec(&p->emits, &Eu, ww); /* FWA emission at this wavelength */ + Kc = Emc * Eu; /* FWA contribution under inst. illum. */ + + getval_lxspec(&p->media, &Rmb, ww); /* Base Media */ + getval_lxspec(in, &Rcch, ww); /* Colorant reflectance at wavelength */ + Rcch = sqrt(Rcch); /* Half reflectance at wavelength */ + if (Rmb < 1e-9) /* Hmm. */ + Rcch = sqrt(fabs(Rmb)); + + getval_lxspec(&p->iillum, &Ii, ww); /* Normalised instrument illuminant */ + if (Ii < 1e-9) + Ii = 1e-9; + + RcI = (Ii * Rcch * Rmb + Kc) * Rcch; + + out->spec[i] = out->norm * RcI/Ii; /* Reconstituted reflectance */ +#ifdef DOPLOT_ALL_FWA + xx[plix] = ww; + y1[plix] = Rmb; /* Base media */ + y2[plix] = Rcch; /* Underlying colorant reflectance without FWA */ + y3[plix] = RcI/Ii; /* Reconstituted reflectance */ + plix++; +#endif /* DOPLOT_ALL_FWA */ + } +#ifdef DOPLOT_ALL_FWA + printf("FWA compensated application for sample\n"); + do_plot(xx,y1,y2,y3,plix); +#endif /* DOPLOT_ALL_FWA */ + + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#endif /* !SALONEINSTLIB */ + +/* Do the normal spectral to CIE conversion. */ +/* Note that the input spectrum normalisation value is used. */ +/* Emissive spectral values are assumed to be in mW/nm, and sampled */ +/* rather than integrated if they are not at 1nm spacing. */ +void xsp2cie_sconvert( +xsp2cie *p, /* this */ +xspect *sout, /* Return input spectrum (may be NULL) */ +double *out, /* Return XYZ or D50 Lab value */ +xspect *in /* Spectrum to be converted */ +) { + int j; + double scale = 0.0; + + /* Compute the XYZ values (normalised to 1.0) */ + for (j = 0; j < 3; j++) { + double ww; + + /* Integrate at 1nm intervals over the observer range (as */ + /* per CIE recommendations). Lower resolution spectra are */ + /* upsampled using linear/3rd order polinomial interpolated */ + /* (also as per CIE recommendations), and consistent (?) with the */ + /* assumption of a triangular spectral response made in the */ + /* ANSI CGATS.5-1993 spec. If illumninant or material spectra */ + /* values are truncated at the extremes, then the last valid values */ + /* are used, also consistent with CIE and ANSI CGATS recommendations. */ + out[j] = 0.0; + for (ww = p->observer[j].spec_wl_short; ww <= p->observer[j].spec_wl_long; ww += 1.0) { + double I, O, S; + getval_xspec(&p->illuminant, &I, ww); + getval_xspec(&p->observer[j], &O, ww); + getval_xspec(in, &S, ww); + if (j == 1) + scale += I * O; /* Integrate Y illuminant/observer values */ + out[j] += I * O * S; + } + } + if (p->isemis) { + scale = 0.683002; /* Convert from mW/m^2 to Lumens/m^2 */ + /* (== 683 Luments/Watt/m^2) */ + } else { + scale = 1.0/scale; + } + for (j = 0; j < 3; j++) { /* Scale for illuminant/observer normalisation of Y */ + out[j] *= scale; +#ifdef CLAMP_XYZ + if (p->clamp && out[j] < 0.0) + out[j] = 0.0; /* Just to be sure we don't get silly values */ +#endif /* CLAMP_XYZ */ + } + +#ifndef SALONEINSTLIB + /* If Lab is target, convert to D50 Lab */ + if (p->doLab) { + icmXYZ2Lab(&icmD50, out, out); + } +#endif /* !SALONEINSTLIB */ + + if (sout != NULL) { + *sout = *in; /* Structure copy */ + } +} + +/* Normal Tristumulus conversion */ +void xsp2cie_convert(xsp2cie *p, double *out, xspect *in) { + xsp2cie_sconvert(p, NULL, out, in); +} + +void xsp2cie_del( +xsp2cie *p +) { + free(p); + return; +} + +/* Create and return a new spectral conversion object */ +xsp2cie *new_xsp2cie( +icxIllumeType ilType, /* Illuminant */ +xspect *custIllum, /* Optional custom illuminant */ +icxObserverType obType, /* Observer */ +xspect custObserver[3], /* Optional custom observer */ +icColorSpaceSignature rcs, /* Return color space, icSigXYZData or icSigLabData */ + /* ** Must be icSigXYZData if SALONEINSTLIB ** */ +icxClamping clamp /* NZ to clamp XYZ/Lab to be +ve */ +) { + xsp2cie *p; + + if ((p = (xsp2cie *) calloc(1,sizeof(xsp2cie))) == NULL) + return NULL; + + p->isemis = 0; + switch (ilType) { + case icxIT_none: + p->illuminant = il_none; /* Emissive */ + p->isemis = 1; + break; + case icxIT_custom: + p->illuminant = *custIllum; + break; + case icxIT_A: + p->illuminant = il_A; + break; + case icxIT_C: + p->illuminant = il_C; + break; + case icxIT_default: + case icxIT_D50: + p->illuminant = il_D50; + break; + case icxIT_D50M2: + if (il_D50M2.spec_n == 0) + uv_filter(&il_D50M2, &il_D50); + p->illuminant = il_D50M2; + break; + case icxIT_D65: + p->illuminant = il_D65; + break; + case icxIT_E: + p->illuminant = il_none; + break; +#ifndef SALONEINSTLIB + case icxIT_F5: + p->illuminant = il_F5; + break; + case icxIT_F8: + p->illuminant = il_F8; + break; + case icxIT_F10: + p->illuminant = il_F10; + break; + case icxIT_Spectrocam: + p->illuminant = il_Spectrocam; + break; +#endif /* !SALONEINSTLIB */ + default: + DBGF((DBGA,"new_xsp2cie() unrecognised illuminant 0x%x\n",ilType)); + free(p); + return NULL; + } + + /* Do 3 structure copies to record observer sensitivity curves */ + switch (obType) { + case icxOT_custom: + p->observer[0] = custObserver[0]; + p->observer[1] = custObserver[1]; + p->observer[2] = custObserver[2]; + break; + case icxOT_default: + case icxOT_CIE_1931_2: + p->observer[0] = ob_CIE_1931_2[0]; + p->observer[1] = ob_CIE_1931_2[1]; + p->observer[2] = ob_CIE_1931_2[2]; + break; + case icxOT_CIE_1964_10: + p->observer[0] = ob_CIE_1964_10[0]; + p->observer[1] = ob_CIE_1964_10[1]; + p->observer[2] = ob_CIE_1964_10[2]; + break; +#ifndef SALONEINSTLIB + case icxOT_Stiles_Burch_2: + p->observer[0] = ob_Stiles_Burch_2[0]; + p->observer[1] = ob_Stiles_Burch_2[1]; + p->observer[2] = ob_Stiles_Burch_2[2]; + break; + case icxOT_Judd_Voss_2: + p->observer[0] = ob_Judd_Voss_2[0]; + p->observer[1] = ob_Judd_Voss_2[1]; + p->observer[2] = ob_Judd_Voss_2[2]; + break; + case icxOT_CIE_1964_10c: + p->observer[0] = ob_CIE_1964_10c[0]; + p->observer[1] = ob_CIE_1964_10c[1]; + p->observer[2] = ob_CIE_1964_10c[2]; + break; + case icxOT_Shaw_Fairchild_2: + p->observer[0] = ob_Shaw_Fairchild_2[0]; + p->observer[1] = ob_Shaw_Fairchild_2[1]; + p->observer[2] = ob_Shaw_Fairchild_2[2]; + break; +#endif /* !SALONEINSTLIB */ + default: + DBGF((DBGA,"new_xsp2cie() unrecognised observer type 0x%x\n",obType)); + free(p); + return NULL; + } + + if (rcs == icSigXYZData) + p->doLab = 0; +#ifndef SALONEINSTLIB + else if (rcs == icSigLabData) + p->doLab = 1; +#endif /* !SALONEINSTLIB */ + else { + DBGF((DBGA,"new_xsp2cie() unrecognised CIE type 0x%x",rcs)); + free(p); + return NULL; + } + + p->clamp = clamp; + + p->convert = xsp2cie_convert; + p->sconvert = xsp2cie_sconvert; +#ifndef SALONEINSTLIB + p->set_mw = xsp2cie_set_mw; /* Default no media white */ + p->set_fwa = xsp2cie_set_fwa; /* Default no FWA compensation */ + p->update_fwa_custillum = xsp2cie_update_fwa_custillum; + p->get_fwa_info = xsp2cie_get_fwa_info; + p->extract = xsp2cie_extract; + p->apply = xsp2cie_apply; +#endif /* !SALONEINSTLIB */ + p->del = xsp2cie_del; + + return p; +} + + +#ifndef SALONEINSTLIB +/* -------------------------------------------------------- */ + +/* Return the spectrum locus rangefor the given observer */ +/* return 0 on sucecss, nz if observer not known */ +int icx_spectrum_locus_range(double *min_wl, double *max_wl, icxObserverType obType) { + xspect *sp[3]; + if (standardObserver(sp, obType)) + return 1; + if (min_wl != NULL) + *min_wl = sp[0]->spec_wl_short; + if (max_wl != NULL) + *max_wl = sp[0]->spec_wl_long; + + return 0; +} + +/* Return an XYZ that is on the spectrum locus for the given observer. */ +/* wl is the input wavelength in the range icx_spectrum_locus_range(), */ +/* and return clipped result if outside this range. */ +/* Return nz if observer unknown. */ +int icx_spectrum_locus(double xyz[3], double wl, icxObserverType obType) { + xspect *sp[3]; + + DBGF((DBGA,"icx_spectrum_locus got obs %d wl %f\n",obType, wl)); + + if (standardObserver(sp, obType)) + return 1; + + if (wl < sp[0]->spec_wl_short) + wl = sp[0]->spec_wl_short; + if (wl > sp[0]->spec_wl_long) + wl = sp[0]->spec_wl_long; + + xyz[0] = value_xspect(sp[0], wl); + xyz[1] = value_xspect(sp[1], wl); + xyz[2] = value_xspect(sp[2], wl); + + DBGF((DBGA,"returning %f %f %f\n", xyz[0], xyz[1], xyz[2])); + + return 0; +} + +/* Init a xslpoly */ +/* Return nz on error */ +static int icx_init_locus_poly(icxObserverType obType) { + xslpoly *poly; + + if ((poly = spectral_locus_poligon(obType)) == NULL) + return 1; + + /* Initialise (should have a mutex!) */ + if (poly->n == 0) { + int i, j, c; + double Yxy[3]; + double xyz[3]; + xspect *sp[3]; +double tt[3][3]; + + if (standardObserver(sp, obType)) + return 3; + + poly->n = sp[0]->spec_n; + poly->xmin = poly->ymin = 1e6; + poly->xmax = poly->ymax = -1e6; + + for (i = 0; i < poly->n; i++) { + xyz[0] = sp[0]->spec[i]; + xyz[1] = sp[1]->spec[i]; + xyz[2] = sp[2]->spec[i]; + + icmXYZ2Yxy(Yxy, xyz); + + poly->x[i] = Yxy[1]; + poly->y[i] = Yxy[2]; + if (poly->x[i] < poly->xmin) + poly->xmin = poly->x[i]; + if (poly->x[i] > poly->xmax) + poly->xmax = poly->x[i]; + if (poly->y[i] < poly->ymin) + poly->ymin = poly->y[i]; + if (poly->y[i] > poly->ymax) + poly->ymax = poly->y[i]; + } + + /* Select 3 points for inner triangle in RGB order */ + poly->tx[0] = poly->x[poly->n - 1]; + poly->ty[0] = poly->y[poly->n - 1]; + + xyz[0] = value_xspect(sp[0], 517.0); + xyz[1] = value_xspect(sp[1], 517.0); + xyz[2] = value_xspect(sp[2], 517.0); + icmXYZ2Yxy(Yxy, xyz); + poly->tx[1] = Yxy[1]; + poly->ty[1] = Yxy[2]; + + poly->tx[2] = poly->x[0]; + poly->ty[2] = poly->y[0]; + + /* Compute distance from triangles to 0.3, 0.3 */ +// for (i = 0; i < 3; i++) { +// poly->eed[i] = sqrt((poly->tx[i] - 0.3) * (poly->tx[i] - 0.3) +// + (poly->ty[i] - 0.3) * (poly->ty[i] - 0.3)); +// } + + /* Compute baricentric equations */ + for (i = 0; i < 3; i++) { + tt[0][i] = poly->tx[i]; + tt[1][i] = poly->ty[i]; + tt[2][i] = 1.0; + } + if (icmInverse3x3(poly->be, tt)) + error("icx_init_locus_poly: Matrix inversion failed"); + + /* Compute baricentric of 0.3 0.3 */ + /* (Not currently used. How to move center to 0.3 0.3 ?? */ +// for (i = 0; i < 3; i++) +// poly->eed[i] = poly->be[i][0] * 0.3 + poly->be[i][1] * 0.3 + poly->be[i][2]; + } + return 0; +} + +/* Determine whether the given XYZ is outside the spectrum locus */ +/* Return 0 if within locus */ +/* Return 1 if outside locus */ +/* Return 2 if unknown (bad observer) */ +int icx_outside_spec_locus(double xyz[3], icxObserverType obType) { + int i, j, c; + xslpoly *poly; + double Yxy[3]; + + if ((poly = spectral_locus_poligon(obType)) == NULL) + return 2; + + /* Init poly if needed */ + if (poly->n == 0 && icx_init_locus_poly(obType)) + return 2; + + icmXYZ2Yxy(Yxy, xyz); + + /* Quick test - bounding box */ + if (Yxy[1] < poly->xmin || Yxy[1] > poly->xmax + || Yxy[2] < poly->ymin || Yxy[2] > poly->ymax) + return 1; + + /* Quick test - inner triangle */ + for (c = 1, i = 0, j = 3-1; i < 3; j = i++) { + if ( ((poly->ty[i] > Yxy[2]) != (poly->ty[j] > Yxy[2])) + && (Yxy[1] < (poly->tx[j] - poly->tx[i]) * (Yxy[2] - poly->ty[i]) + / (poly->ty[j] - poly->ty[i]) + poly->tx[i]) ) + c = !c; + } + if (c == 0) + return 0; + + /* Do point in poligon test */ + /* (This could be speeded up in many ways) */ + for (c = 1, i = 0, j = poly->n-1; i < poly->n; j = i++) { + if ( ((poly->y[i] > Yxy[2]) != (poly->y[j] > Yxy[2])) + && (Yxy[1] < (poly->x[j] - poly->x[i]) * (Yxy[2] - poly->y[i]) + / (poly->y[j] - poly->y[i]) + poly->x[i]) ) + c = !c; + } + + return c; +} + +/* Return an aproximate RGB value for coloring within the spectrum locus */ +void icx_spec_locus_color(double rgb[3], double xyz[3], icxObserverType obType) { + int i, j; + xslpoly *poly; + double Yxy[3]; + double dtt[3]; /* Distances to triangle points */ + double v[3]; + double max; + + if ((poly = spectral_locus_poligon(obType)) == NULL) + return; + + /* Init poly if needed */ + if (poly->n == 0 && icx_init_locus_poly(obType)) + return; + + icmXYZ2Yxy(Yxy, xyz); + + /* Compute the baricentric coord for the input point, */ + for (max = -1e6, i = 0; i < 3; i++) { + v[i] = poly->be[i][0] * Yxy[1] + poly->be[i][1] * Yxy[2] + poly->be[i][2]; + if (v[i] < 0.0) + v[i] = 0.0; + else if (v[i] > 1.0) + v[i] = 1.0; + + /* Normalise to put wp at 0.3 0.3 */ + // ~~99 + + v[i] = pow(v[i], 1.0/2.2); + + if (v[i] > max) + max = v[i]; + } + + for (i = 0; i < 3; i++) { + rgb[i] = v[i]/max; + } +} + +/* -------------------------------------------------------- */ + +/* Status T log10 weightings */ +/* CMYV */ +static xspect denT[4] = { + { + 44, 340.0, 770.0, /* 44 bands from 340 to 770 nm in 10nm steps */ + 1.0, /* Log10 Scale factor */ + { + 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.500, 1.778, 2.653, 4.477, + 5.000, 4.929, 4.740, 4.398, 4.000, + 3.699, 3.176, 2.699, 2.477, 2.176, + 1.699, 1.000, 0.500, 0.000, 0.000, + 0.000, 0.000, 0.000 + } + }, + { + 44, 340.0, 770.0, /* 44 bands from 340 to 770 nm in 10nm steps */ + 1.0, /* Log10 Scale factor */ + { + 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.500, 3.000, 3.699, + 4.447, 4.833, 4.964, 5.000, 4.944, + 4.820, 4.623, 4.342, 3.954, 3.398, + 2.845, 1.954, 1.000, 0.500, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000 + } + }, + { + 44, 340.0, 770.0, /* 44 bands from 340 to 770 nm in 10nm steps */ + 1.0, /* Log10 Scale factor */ + { + 0.500, + 1.000, 1.301, 2.000, 2.477, 3.176, + 3.778, 4.230, 4.602, 4.778, 4.914, + 4.973, 5.000, 4.987, 4.929, 4.813, + 4.602, 4.255, 3.699, 2.301, 1.602, + 0.500, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000 + } + }, + { + 44, 340.0, 770.0, /* 44 bands from 340 to 770 nm in 10nm steps */ + 1.0, /* Log10 Scale factor */ + { + 0.000, + 0.000, 0.000, 0.000, 0.000, 0.000, + 0.500, 1.332, 1.914, 2.447, 2.881, + 3.090, 3.346, 3.582, 3.818, 4.041, + 4.276, 4.513, 4.702, 4.825, 4.905, + 4.957, 4.989, 5.000, 4.989, 4.956, + 4.902, 4.827, 4.731, 4.593, 4.433, + 4.238, 4.013, 3.749, 3.490, 3.188, + 2.901, 2.622, 2.334, 2.041, 1.732, + 1.431, 1.146, 0.500 + } + } +}; + +/* Given a reflectance or transmition spectral product, (Relative */ +/* to the scale factor), return status T CMYV log10 density values */ +void xsp_Tdensity( +double *out, /* Return CMYV density */ +xspect *in /* Spectral product to be converted */ +) { + int j; + + /* Compute the CMYV values (normalised to 1.0) */ + for (j = 0; j < 4; j++) { + double ww; + double sum; + + /* Integrate at 1nm intervals */ + sum = out[j] = 0.0; + for (ww = denT[j].spec_wl_short; ww <= denT[j].spec_wl_long; ww += 1.0) { + double W, S; + + getval_xspec(&denT[j], &W, ww); + getval_xspec(in, &S, ww); + W = pow(10.0, W); /* Convert from log to linear weighting */ + sum += W; /* Sum of weightings */ + out[j] += S * W; + } + out[j] /= sum; /* Normalise */ + if (out[j] < 0.00001) + out[j] = 0.00001; /* Just to be sure we don't get silly values */ + else if (out[j] > 1.0) + out[j] = 1.0; + + out[j] = -log10(out[j]); /* Convert to density */ + } +} + +/* XYZ to status T density aproximate conversion matrix */ +/* (Note we're multiplying by a 0.83 factor below to */ +/* avoid some limiting for some XYZ values) */ +static double xyz2tden[4][3] = { + { 1.750557, -0.361811, -0.265150 }, /* Red density */ + { -0.919004, 1.861722, 0.105787 }, /* Green density */ + { -0.047821, 0.093820, 1.163331 }, /* Blue density */ + { 0.369966, 0.708047, -0.076312 } /* Visual density */ +}; + +/* Given a reflectance or transmission XYZ value, */ +/* return approximate status T CMYV log10 density values */ +void icx_XYZ2Tdens( +double *out, /* Return aproximate CMYV log10 density */ +double *in /* Input XYZ values */ +) { + int i, j; + double den[4]; + +//DBGF((DBGA,"icx_XYZ2den got %f %f %f\n",in[0],in[1],in[2])); + for (i = 0; i < 4; i++) { + + den[i] = 0.0; + for (j = 0; j < 3; j++) + den[i] += 0.83 * xyz2tden[i][j] * in[j]; + +//DBGF((DBGA,"icx_XYZ2den raw den %d = %f\n",i,den[i])); + if (den[i] < 0.00001) + den[i] = 0.00001; /* Just to be sure we don't get silly values */ + else if (den[i] > 1.0) + den[i] = 1.0; + + out[i] = -log10(den[i]); /* Convert to density */ + } +//DBGF((DBGA,"icx_XYZ2den returning densities %f %f %f\n",out[0],out[1],out[2])); +} + +/* Given a reflectance or transmission XYZ value, */ +/* return log10 XYZ density values */ +void icx_XYZ2dens( +double *out, /* Return log10 XYZ density */ +double *in /* Input XYZ values */ +) { + int i; + double den[3]; + + for (i = 0; i < 3; i++) { + + den[i] = in[i]; + + if (den[i] < 0.00001) + den[i] = 0.00001; /* Just to be sure we don't get silly values */ + else if (den[i] > 1.0) + den[i] = 1.0; + + out[i] = -log10(den[i]); /* Convert to density */ + } +} + +/* Given an XYZ value, */ +/* return approximate sRGB values */ +void icx_XYZ2sRGB( +double *out, /* Return aproximate CMYV log10 density */ +double *wp, /* Input XYZ white point (may be NULL) */ +double *in /* Input XYZ values */ +) { + int i, j; + double XYZ[3]; + double d65[3] = { 0.950543, 1.0, 1.089303 }; + double mat[3][3] = { + { 3.2406, -1.5372, -0.4986 }, + { -0.9689, 1.8758, 0.0415 }, + { 0.0557, -0.2040, 1.0570 } + }; + + /* Do a simple Von Kries between input white point and D65 */ + if (wp != NULL) { + for (j = 0; j < 3; j++) + XYZ[j] = d65[j] * in[j]/wp[j]; + } else { + for (j = 0; j < 3; j++) + XYZ[j] = in[j]; + } + + /* Convert to sRGB cromaticities */ + for (i = 0; i < 3; i++) { + out[i] = 0.0; + for (j = 0; j < 3; j++) { + out[i] += XYZ[j] * mat[i][j]; + } + } + + /* Apply gamma */ + for (j = 0; j < 3; j++) { + if (out[j] <= (0.03928/12.92)) { + out[j] *= 12.92; + if (out[j] < 0.0) + out[j] = 0.0; + } else { + out[j] = pow(out[j], 1.0/2.4) * 1.055 - 0.055; + if (out[j] > 1.0) + out[j] = 1.0; + } + } +} + +/* ------------------- */ + +#ifdef NEVER /* Deprecated */ + +/* Given a daylight color temperature in degrees K, */ +/* return the corresponding XYZ value (standard 2 degree observer) */ +void icx_DTEMP2XYZ( +double *out, /* Return XYZ value with Y == 1 */ +double ct /* Input temperature in degrees K */ +) { + double Yxy[3]; + +//DBGF((DBGA,"computing temperature %f\n",ct)); + /* Compute chromaticity coordinates */ + if (ct < 7000.0) { + Yxy[1] = -4.6070e9/(ct * ct * ct) + 2.9678e6/(ct * ct) + 0.09911e3/ct + 0.244063; + } else { + Yxy[1] = -2.0064e9/(ct * ct * ct) + 1.9018e6/(ct * ct) + 0.24748e3/ct + 0.237040; + } + Yxy[2] = -3.000 * Yxy[1] * Yxy[1] + 2.870 * Yxy[1] - 0.275; + + Yxy[0] = 1.0; +//DBGF((DBGA,"Yxy = %f %f %f\n",Yxy[0],Yxy[1],Yxy[2])); + + /* Convert to XYZ */ + icmYxy2XYZ(out, Yxy); + +//DBGF((DBGA,"XYZ = %f %f %f\n",out[0],out[1],out[2])); +} + +#endif + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Given an illuminant definition and an observer model, return */ +/* the normalised XYZ value for that spectrum. */ +/* Return 0 on sucess, 1 on error */ +int icx_ill_sp2XYZ( +double xyz[3], /* Return XYZ value with Y == 1 */ +icxObserverType obType, /* Observer */ +xspect custObserver[3], /* Optional custom observer */ +icxIllumeType ilType, /* Type of illuminant, icxIT_Dtemp or icxIT_Ptemp */ +double ct, /* Input temperature in degrees K */ +xspect *custIllum /* Optional custom illuminant */ +) { + xspect sp; /* Xspect to fill in */ + xsp2cie *conv; /* Means of converting spectrum to XYZ */ + + if (ilType == icxIT_custom) + sp = *custIllum; + else if (standardIlluminant(&sp, ilType, ct) != 0) + return 1; + + if ((conv = new_xsp2cie(icxIT_none, NULL, obType, custObserver, icSigXYZData, 1)) == NULL) + return 1; + + conv->convert(conv, xyz, &sp); + + conv->del(conv); + + /* Normalise */ + xyz[0] /= xyz[1]; + xyz[2] /= xyz[1]; + xyz[1] /= xyz[1]; + + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Context for optimiser callback */ +typedef struct { + icxIllumeType ilType; /* Type of illuminant, icxIT_Dtemp or icxIT_Ptemp */ + double xyz[3]; /* Target XYZ */ + icmXYZNumber XYZ; /* Target as XYZ number */ + xsp2cie *conv; /* Means of converting spectrum to XYZ */ + int viscct; /* nz to use visual best match color temperature */ +} cctctx; + +static double cct_func(void *fdata, double tp[]) { + cctctx *x = (cctctx *)fdata; + double xyz[3]; /* Current value */ + double lab1[3], lab2[3]; + xspect sp; + double rv = 0.0; + icmXYZNumber *wp = &x->XYZ; + + /* Compute the XYZ for the given temperature */ + if (x->ilType == icxIT_Dtemp) { + if (daylight_il(&sp, tp[0]) != 0) + rv = 1e6; + } else { + if (planckian_il(&sp, tp[0]) != 0) + rv = 1e6; + } + + if (rv == 0.0) { + x->conv->convert(x->conv, xyz, &sp); + xyz[0] /= xyz[1]; + xyz[2] /= xyz[1]; + xyz[1] /= xyz[1]; + /* Compute the color difference to the target */ + if (x->viscct) { + /* Use modern CIEDE2000 color difference - gives a better visual match */ + icmXYZ2Lab(wp, lab1, x->xyz); + icmXYZ2Lab(wp, lab2, xyz); + rv = icmCIE2Ksq(lab1, lab2); + } else { + /* Use original CIE 1960 UCS space color difference */ + icmXYZ21960UCS(lab1, x->xyz); + icmXYZ21960UCS(lab2, xyz); + rv = icmLabDEsq(lab1, lab2); + } + } + +//DBGF((DBGA,"returning %f for temp = %f\n",rv,tp[0])); + return rv; + +} + +/* Given a choice of temperature dependent illuminant (icxIT_Dtemp or icxIT_Ptemp), */ +/* return the closest correlated color temperature to the given spectrum or XYZ. */ +/* An observer type can be chosen for interpretting the spectrum of the input and */ +/* the illuminant. */ +/* Note we can use CIEDE2000, rather than the traditional L*u*v* 2/3 space for CCT */ +/* Return -1 on erorr */ +double icx_XYZ2ill_ct( +double txyz[3], /* If not NULL, return the XYZ of the locus temperature */ +icxIllumeType ilType, /* Type of illuminant, icxIT_Dtemp or icxIT_Ptemp */ +icxObserverType obType, /* Observer */ +xspect custObserver[3], /* Optional custom observer */ +double xyz[3], /* Input XYZ value, NULL if spectrum intead */ +xspect *insp, /* Input spectrum value, NULL if xyz[] instead */ +int viscct /* nz to use visual CIEDE2000, 0 to use CCT CIE 1960 UCS. */ +) { + cctctx x; /* Context for callback */ + double cp[1], s[1]; + double rv; + int i; + double tc, ber, bct = 0.0; + + x.viscct = viscct; + + if (ilType != icxIT_Dtemp && ilType != icxIT_Ptemp) + return -1.0; + x.ilType = ilType; + + if ((x.conv = new_xsp2cie(icxIT_none, NULL, obType, custObserver, icSigXYZData, 1)) == NULL) + return -1; + + if (xyz == NULL) { + if (insp == NULL) + return -1.0; + x.conv->convert(x.conv, x.xyz, insp); + } else { + icmAry2Ary(x.xyz, xyz); + } + + /* Normalise target */ + x.xyz[0] /= x.xyz[1]; + x.xyz[2] /= x.xyz[1]; + x.xyz[1] /= x.xyz[1]; + icmAry2XYZ(x.XYZ, x.xyz); + + /* Do some start samples, to avoid getting trapped in local minima */ + for (ber = 1e9, i = 0; i < 6; i++) { + double er; + tc = 1000.0 + i * 2000.0; + if ((er = cct_func((void *)&x, &tc)) < ber) { + ber = er; + bct = tc; + } +//DBGF((DBGA,"tc = %f, er = %f\n",tc,er)); + } + cp[0] = bct; + s[0] = 500.0; + + /* Locate the CCT */ + if (powell(&rv, 1, cp, s, 0.01, 1000, cct_func, (void *)&x, NULL, NULL) != 0) { + x.conv->del(x.conv); + return -1.0; + } + + if (txyz != NULL) { + xspect sp; + if (x.ilType == icxIT_Dtemp) { + if (daylight_il(&sp, cp[0]) != 0) { + x.conv->del(x.conv); + txyz[0] = txyz[2] = txyz[1] = cp[0] = 0.0; + return cp[0]; + } + } else { + if (planckian_il(&sp, cp[0]) != 0) { + x.conv->del(x.conv); + txyz[0] = txyz[2] = txyz[1] = cp[0] = 0.0; + return cp[0]; + } + } + x.conv->convert(x.conv, txyz, &sp); + /* Make sure locus XYZ is Normalised */ + txyz[0] /= txyz[1]; + txyz[2] /= txyz[1]; + txyz[1] /= txyz[1]; + } + x.conv->del(x.conv); + +//DBGF((DBGA,"returning %f with error %f delta E94 %f\n",cp[0],sqrt(rv))); + return cp[0]; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Convert UCS Yuv to chromatic adaptation Ycd */ +static void UCSYuv2Ycd(double *out, double *in) { + double u, v; + u = in[1]; + v = in[2]; + + out[0] = in[0]; + out[1] = (4.0 - u - 10.0 * v)/v; + out[2] = (1.708 * v - 1.481 * u + 0.404)/v; +} + + + +/* Compute the CIE1995 CRI: Ra */ +/* Return < 0.0 on error */ +/* If invalid is not NULL, set it to nz if CRI */ +/* is invalid because the sample is not white enough. */ +double icx_CIE1995_CRI( +int *invalid, /* if not NULL, set to nz if invalid */ +xspect *sample /* Illuminant sample to compute CRI of */ +) { + int i; + double cct; + xspect wts; /* Reference white spectrum */ + xsp2cie *tocie; + double wt[3]; /* Reference white in CIE 1960 UCS */ + icmXYZNumber wtn; + double wt_Ycd[3]; /* Ycd reference white */ + double sa[3]; /* Sample white in CIE 1960 UCS */ + double sa_Ycd[3]; /* Ycd sample white */ + double dc; /* delta of sample to reference white in 1960 UCS */ + double ref[8][3]; /* reference XYZ/1964 color space */ + double sam[8][3]; /* sample XYZ/1964 color space */ + double c_ad, d_ad; /* Chromatic adaptation scaling factors */ + double cri = 0.0; + +//DBGF((DBGA,"icx_CIE1995_CRI called\n")); + + /* First find the standard 2 degree observer plankian CCT */ + if ((cct = icx_XYZ2ill_ct(NULL, icxIT_Ptemp, icxOT_CIE_1931_2, NULL, NULL, sample, 0)) < 0.0) + return -1.0; + +//DBGF((DBGA,"CCT = %f\n", cct)); + + /* Create a reference white spectrum with the same CCT */ + if (cct < 5000.0) { + if (planckian_il(&wts, cct)) + return -1.0; + } else { + if (daylight_il(&wts, cct)) + return -1.0; + } + + if ((tocie = new_xsp2cie(icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigXYZData, 1)) == NULL) + return -1.0; + + /* Compute the XYZ of the reference white and sample */ + tocie->convert(tocie, wt, &wts); + tocie->convert(tocie, sa, sample); + +//DBGF((DBGA,"XYZ white = %f %f %f\n",wt[0],wt[1],wt[2])); +//DBGF((DBGA,"XYZ sampl = %f %f %f\n",sa[0],sa[1],sa[2])); + + /* Normalize the spectra so as to create a normalized white */ + wts.norm *= wt[1]; + sample->norm *= sa[1]; /* ~~~ shouldn't change sample!!!! ~~~~ */ + tocie->convert(tocie, wt, &wts); + tocie->convert(tocie, sa, sample); + tocie->del(tocie); + +//DBGF((DBGA,"norm XYZ white = %f %f %f\n",wt[0],wt[1],wt[2])); +//DBGF((DBGA,"norm XYZ sampl = %f %f %f\n",sa[0],sa[1],sa[2])); + + /* Convert to perceptual CIE 1960 UCS */ + icmAry2XYZ(wtn, wt); /* Use reference white as UCS white */ + icmXYZ21960UCS(wt, wt); /* 1960 UCS Yuv reference white */ + UCSYuv2Ycd(wt_Ycd, wt); /* Ycd version for chromatic adapation */ + icmXYZ21960UCS(sa, sa); /* 1960 UCS Yuv sample white */ + UCSYuv2Ycd(sa_Ycd, sa); /* Ycd version for chromatic adapation */ + + c_ad = wt_Ycd[1]/sa_Ycd[1]; /* Chromatic adaptation scaling factors */ + d_ad = wt_Ycd[2]/sa_Ycd[2]; + +//DBGF((DBGA,"UCS white = %f %f %f\n",wt[0],wt[1],wt[2])); +//DBGF((DBGA,"UCS sampl = %f %f %f\n",sa[0],sa[1],sa[2])); + + dc = sqrt((wt[1] - sa[1]) * (wt[1] - sa[1]) + (wt[2] - sa[2]) * (wt[2] - sa[2])); + +//DBGF((DBGA,"dc = %f\n",dc)); +//if (dc > 0.0054) DBGF((DBGA,"CRI is invalid\n")); + + /* If dc > 0.0054 we should abort computing the CRI, */ + /* but this means we fail on lots of real world lighting. */ + if (invalid != NULL) { + if (dc > 0.0054) + *invalid = 1; + else + *invalid = 0; + } + + /* Check out the delta E for each reflective sample */ + if ((tocie = new_xsp2cie(icxIT_custom, &wts, icxOT_CIE_1931_2, NULL, icSigXYZData, 1)) == NULL) + return -1.0; + for (i = 0; i < 8; i++) { + tocie->convert(tocie, ref[i], &CIE1995_TCS[i]); + icmXYZ21964WUV(&wtn, ref[i], ref[i]); +//DBGF((DBGA,"ref samp %d = WUV %f %f %f\n", i,ref[i][0],ref[i][1],ref[i][2])); + } + tocie->del(tocie); + + if ((tocie = new_xsp2cie(icxIT_custom, sample, icxOT_CIE_1931_2, NULL, icSigXYZData, 1)) == NULL) + return -1.0; + for (i = 0; i < 8; i++) { + double c, d; + tocie->convert(tocie, sam[i], &CIE1995_TCS[i]); + + icmXYZ21960UCS(sam[i], sam[i]); + + /* Do chromatic adaptation */ + UCSYuv2Ycd(sam[i], sam[i]); + c = sam[i][1]; + d = sam[i][2]; + sam[i][1] = (10.872 + 0.404 * c * c_ad - 4.0 * d * d_ad)/ + (16.518 + 1.481 * c * c_ad - 1.0 * d * d_ad); + + sam[i][2] = (5.520)/ + (16.518 + 1.481 * c * c_ad - 1.0 * d * d_ad); + + icm1960UCS21964WUV(&wtn, sam[i], sam[i]); + +//DBGF((DBGA,"sam samp %d = WUV %f %f %f\n", i,sam[i][0],sam[i][1],sam[i][2])); + } + tocie->del(tocie); + + /* Compute the CRI */ + for (i = 0; i < 8; i++) { + double de, tcri; + + de = icmLabDE(ref[i], sam[i]); + tcri = 100.0 - 4.6 * de; +//DBGF((DBGA,"sample %d: de = %f, CRI = %f\n",i,de,tcri)); + cri += tcri; + } + cri /= 8.0; + +//DBGF((DBGA,"average CRI = %f\n",cri)); + if (cri < 0.0) + cri = -1.0; + +//DBGF((DBGA,"returning CRI = %f\n",cri)); + return cri; +} +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#endif /* !SALONEINSTLIB */ + + + + + diff --git a/xicc/xspect.h b/xicc/xspect.h new file mode 100644 index 0000000..caca8b2 --- /dev/null +++ b/xicc/xspect.h @@ -0,0 +1,430 @@ + +#ifndef XSPECT_H +#define XSPECT_H + +/* + * Author: Graeme W. Gill + * Date: 21/6/01 + * Version: 1.00 + * + * Copyright 2000 - 2010 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :- + * see the License2.txt file for licencing details. + */ + +/* + * This class supports converting spectral samples + * into CIE XYZ or D50 Lab tristimulous values. + */ + +/* + * TTBD: + * + */ + +#ifndef SALONEINSTLIB +#include "icc.h" /* icclib ICC definitions */ +#else /* SALONEINSTLIB */ +#include "conv.h" /* fake icclib ICC definitions */ +#endif /* SALONEINSTLIB */ + +#ifdef __cplusplus + extern "C" { +#endif + +/* ------------------------------------------------------------------------------ */ + +/* Structure for conveying spectral information */ + +/* NOTE :- should ditch norm, and replace it by */ +/* "units", ie. reflectance/transmittance 0..1, 0..100%, */ +/* W/nm/m^2 or mW/nm/m^2 */ +#define XSPECT_MAX_BANDS 601 /* Enought for 1nm from 300 to 900 */ + +typedef struct { + int spec_n; /* Number of spectral bands, 0 if not valid */ + double spec_wl_short; /* First reading wavelength in nm (shortest) */ + double spec_wl_long; /* Last reading wavelength in nm (longest) */ + double norm; /* Normalising scale value */ + double spec[XSPECT_MAX_BANDS]; /* Spectral value, shortest to longest */ +} xspect; + +/* Some helpful macro's: */ + +/* Copy everything except the spectral values */ +#define XSPECT_COPY_INFO(PDST, PSRC) \ + (PDST)->spec_n = (PSRC)->spec_n, \ + (PDST)->spec_wl_short = (PSRC)->spec_wl_short, \ + (PDST)->spec_wl_long = (PSRC)->spec_wl_long, \ + (PDST)->norm = (PSRC)->norm + +/* Given an index and the sampling ranges, compute the sample wavelength */ +#define XSPECT_WL(SHORT, LONG, N, IX) \ +((SHORT) + (double)(IX) * ((LONG) - (SHORT))/((N)-1.0)) + +/* Given the address of an xspect and an index, compute the sample wavelegth */ +#define XSPECT_XWL(PXSP, IX) \ +(((PXSP)->spec_wl_short) + (double)(IX) * (((PXSP)->spec_wl_long) - ((PXSP)->spec_wl_short))/(((PXSP)->spec_n)-1.0)) + +/* Given a wavelength and the sampling ranges, compute the double index */ +#define XSPECT_DIX(SHORT, LONG, N, WL) \ +(((N)-1.0) * ((WL) - (SHORT))/((LONG) - (SHORT))) + +/* Given the wavelength and address of an xspect, compute the double index */ +#define XSPECT_XDIX(PXSP, WL) \ +(((PXSP)->spec_n-1.0) * ((WL) - ((PXSP)->spec_wl_short))/(((PXSP)->spec_wl_long) - ((PXSP)->spec_wl_short))) + +/* Given a wavelength and the sampling ranges, compute the nearest index */ +#define XSPECT_IX(SHORT, LONG, N, WL) \ +((int)floor(XSPECT_DIX(SHORT, LONG, N, WL) + 0.5)) + +/* Given a wavelength and address of an xspect, compute the nearest index */ +#define XSPECT_XIX(PXSP, WL) \ +((int)floor(XSPECT_DIX(PXSP, WL) + 0.5)) + +#ifndef SALONEINSTLIB + +/* Single spectrum utility functions. Return NZ if error */ +int write_xspect(char *fname, xspect *s); +int read_xspect(xspect *sp, char *fname); + +/* CMF utility functions. Return NZ if error */ +int write_cmf(char *fname, xspect cmf[3]); +int read_cmf(xspect cmf[3], char *fname); + +/* Save a set of nspec spectrum to a CGATS file. Return NZ if error */ +/* type 0 = SPECT, 1 = CMF */ +int write_nxspect(char *fname, xspect *sp, int nspec, int type); + +/* Restore a set of up to nspec spectrum from a CGATS file. Return NZ if error */ +/* type = any, 1 = SPECT, 2 = CMF, 3 = both */ +int read_nxspect(xspect *sp, char *fname, int *nret, int off, int nspec, int type); + +#endif /* !SALONEINSTLIB*/ + +/* Get interpolated value at wavelenth (not normalised) */ +double value_xspect(xspect *sp, double wl); + +/* De-normalize and set normalisation factor to 1.0 */ +void xspect_denorm(xspect *sp); + +#ifndef SALONEINSTLIB +/* Convert from one xspect type to another */ +void xspect2xspect(xspect *dst, xspect *targ, xspect *src); +#endif /* !SALONEINSTLIB*/ + +/* ------------------------------------------------------------------------------ */ +/* Class for converting between spectral and CIE */ + +/* We build in some useful spectra */ + +/* Type of illumination */ +typedef enum { + icxIT_default = 0, /* Default illuminant (usually D50) */ + icxIT_none = 1, /* No illuminant - self luminous spectrum */ + icxIT_custom = 2, /* Custom illuminant spectrum */ + icxIT_A = 3, /* Standard Illuminant A */ + icxIT_C = 4, /* Standard Illuminant C */ + icxIT_D50 = 5, /* Daylight 5000K */ + icxIT_D50M2 = 6, /* Daylight 5000K, UV filtered (M2) */ + icxIT_D65 = 7, /* Daylight 6500K */ + icxIT_E = 8, /* Equal Energy */ +#ifndef SALONEINSTLIB + icxIT_F5 = 9, /* Fluorescent, Standard, 6350K, CRI 72 */ + icxIT_F8 = 10, /* Fluorescent, Broad Band 5000K, CRI 95 */ + icxIT_F10 = 11, /* Fluorescent Narrow Band 5000K, CRI 81 */ + icxIT_Spectrocam = 12, /* Spectrocam Xenon Lamp */ + icxIT_Dtemp = 13, /* Daylight at specified temperature */ + icxIT_Ptemp = 14 /* Planckian at specified temperature */ +#endif /* !SALONEINSTLIB*/ +} icxIllumeType; + +/* Fill in an xpsect with a standard illuminant spectrum */ +/* return 0 on sucecss, nz if not matched */ +int standardIlluminant( +xspect *sp, /* Xspect to fill in */ +icxIllumeType ilType, /* Type of illuminant */ +double temp); /* Optional temperature in degrees kelvin, for Dtemp and Ptemp */ + +/* Given an emission spectrum, set the UV output to the given level. */ +/* The shape of the UV is taken from FWA1_stim, and the level is */ +/* with respect to the average of the input spectrum. */ +void xsp_setUV(xspect *out, xspect *in, double uvlevel); + + +/* Type of observer */ +typedef enum { + icxOT_default = 0, /* Default observer (usually CIE_1931_2) */ + icxOT_none = 1, /* No observer - (don't compute XYZ) */ + icxOT_custom = 2, /* Custom observer type weighting */ + icxOT_CIE_1931_2 = 3, /* Standard CIE 1931 2 degree */ + icxOT_CIE_1964_10 = 4, /* Standard CIE 1964 10 degree */ +#ifndef SALONEINSTLIB + icxOT_Stiles_Burch_2 = 5, /* Stiles & Burch 1955 2 degree */ + icxOT_Judd_Voss_2 = 6, /* Judd & Voss 1978 2 degree */ + icxOT_CIE_1964_10c = 7, /* Standard CIE 1964 10 degree, 2 degree compatible */ + icxOT_Shaw_Fairchild_2 = 8 /* Shaw & Fairchild 1997 2 degree */ +#endif /* !SALONEINSTLIB*/ +} icxObserverType; + +/* Return pointers to three xpsects with a standard observer weighting curves */ +/* return 0 on sucecss, nz if not matched */ +int standardObserver(xspect *sp[3], icxObserverType obType); + +/* Return a string describing the standard observer */ +char *standardObserverDescription(icxObserverType obType); + +/* Clamping state */ +typedef enum { + icxNoClamp = 0, /* Don't clamp XYZ/Lab to +ve */ + icxClamp = 1, /* Clamp XYZ/Lab to +ve */ +} icxClamping; + +/* The conversion object */ +struct _xsp2cie { + /* Private: */ + xspect illuminant; /* Lookup conversion/observer illuminant */ + int isemis; /* nz if we are doing an emission conversion */ + xspect observer[3]; + int doLab; /* Return D50 Lab result */ + icxClamping clamp; /* Clamp XYZ and Lab to be +ve */ + +#ifndef SALONEINSTLIB + /* FWA compensation */ + double bw; /* Integration bandwidth */ + xspect iillum; /* Y = 1 Normalised instrument illuminant spectrum */ + xspect imedia; /* Instrument measured media */ + xspect emits; /* Estimated FWA emmission spectrum */ + xspect media; /* Estimated base media (ie. minus FWA) */ + xspect tillum; /* Y = 1 Normalised target/simulated instrument illuminant spectrum */ + xspect oillum; /* Y = 1 Normalised observer illuminant spectrum */ + double Sm; /* FWA Stimulation level for emits contribution */ + double FWAc; /* FWA content (informational) */ + int insteqtarget; /* iillum == tillum, bypass FWA */ +#endif /* !SALONEINSTLIB*/ + + /* Public: */ + void (*del)(struct _xsp2cie *p); + + /* Convert (and possibly fwa correct) reflectance spectrum */ + /* Note that the input spectrum normalisation value is used. */ + /* Note that the returned XYZ is 0..1 range for reflectanc. */ + /* Emissive spectral values are assumed to be in mW/nm, and sampled */ + /* rather than integrated if they are not at 1nm spacing. */ + void (*convert) (struct _xsp2cie *p, /* this */ + double *out, /* Return XYZ or D50 Lab value */ + xspect *in /* Spectrum to be converted, normalised by norm */ + ); + + /* Convert and also return (possibly corrected) reflectance spectrum */ + /* Spectrum will be same wlength range and readings as input spectrum */ + /* Note that the returned XYZ is 0..1 range for reflectanc. */ + /* Emissive spectral values are assumed to be in mW/nm, and sampled */ + /* rather than integrated if they are not at 1nm spacing. */ + void (*sconvert) (struct _xsp2cie *p, /* this */ + xspect *sout, /* Return corrected refl. spectrum (may be NULL) */ + double *out, /* Return XYZ or D50 Lab value (may be NULL) */ + xspect *in /* Spectrum to be converted, normalised by norm */ + ); + +#ifndef SALONEINSTLIB + /* Set Media White. This enables extracting and applying the */ + /* colorant reflectance value from/to the meadia. */ + /* return NZ if error */ + int (*set_mw) (struct _xsp2cie *p, /* this */ + xspect *white /* Spectrum of plain media */ + ); + + /* Set Fluorescent Whitening Agent compensation */ + /* return NZ if error */ + int (*set_fwa) (struct _xsp2cie *p, /* this */ + xspect *iillum, /* Spectrum of instrument illuminant */ + xspect *tillum, /* Spectrum of target/simulated instrument illuminant, */ + /* NULL to use observer illuminant. */ + xspect *white /* Spectrum of plain media */ + ); + + /* Set FWA given updated conversion illuminant. */ + /* (We assume that xsp2cie_set_fwa has been called first) */ + /* return NZ if error */ + int (*update_fwa_custillum) (struct _xsp2cie *p, + xspect *tillum, /* Spectrum of target/simulated instrument illuminant, */ + /* NULL to use set_fwa() value. */ + xspect *custIllum /* Spectrum of observer illuminant */ + ); + + /* Get Fluorescent Whitening Agent compensation information */ + /* return NZ if error */ + void (*get_fwa_info) (struct _xsp2cie *p, /* this */ + double *FWAc /* FWA content as a ratio. */ + ); + + /* Extract the colorant reflectance value from the media. Takes FWA */ + /* into account if set. Media white or FWA must be set. */ + /* return NZ if error */ + int (*extract) (struct _xsp2cie *p, /* this */ + xspect *out, /* Extracted colorant refl. spectrum */ + xspect *in /* Spectrum to be converted, normalised by norm */ + ); + + + /* Apply the colorant reflectance value from the media. Takes FWA */ + /* into account if set. DOESN'T convert to FWA target illumination! */ + /* FWA must be set. */ + int (*apply) (struct _xsp2cie *p, /* this */ + xspect *out, /* Applied refl. spectrum */ + xspect *in /* Colorant reflectance to be applied */ + ); +#endif /* !SALONEINSTLIB*/ + +}; typedef struct _xsp2cie xsp2cie; + +xsp2cie *new_xsp2cie( + icxIllumeType ilType, /* Observer Illuminant to use */ + xspect *custIllum, + + icxObserverType obType, /* Observer */ + xspect custObserver[3], + icColorSpaceSignature rcs, /* Return color space, icSigXYZData or icSigLabData */ + /* ** Must be icSigXYZData if SALONEINSTLIB ** */ + icxClamping clamp /* NZ to clamp XYZ/Lab to be +ve */ +); + +#ifndef SALONEINSTLIB +/* --------------------------- */ +/* Spectrum locus */ + +/* Return the spectrum locus range for the given observer. */ +/* return 0 on sucecss, nz if observer not known */ +int icx_spectrum_locus_range(double *min_wl, double *max_wl, icxObserverType obType); + +/* Return an XYZ that is on the spectrum locus for the given observer. */ +/* wl is the input wavelength in the range icx_spectrum_locus_range(), */ +/* and return clipped result if outside this range. */ +/* Return nz if observer unknown. */ +int icx_spectrum_locus(double xyz[3], double in, icxObserverType obType); + +/* Determine whether the given XYZ is outside the spectrum locus */ +/* Return 0 if within locus */ +/* Return 1 if outside locus */ +/* Return 2 if unknown (bad observer) */ +int icx_outside_spec_locus(double xyz[3], icxObserverType obType); + +/* Return an aproximate RGB value for coloring within the spectrum locus */ +void icx_spec_locus_color(double rgb[3], double xyz[3], icxObserverType obType); + +/* --------------------------- */ +/* Density and other functions */ + +/* Given a reflectance or transmition spectral product, */ +/* return status T CMY + V density values */ +void xsp_Tdensity(double *out, /* Return CMYV density */ + xspect *in /* Spectral product to be converted */ + ); + +/* Given a reflectance or transmission XYZ value, */ +/* return approximate status T CMYV log10 density values */ +void icx_XYZ2Tdens( +double *out, /* Return aproximate CMYV log10 density */ +double *in /* Input XYZ values */ +); + +/* Given a reflectance or transmission XYZ value, */ +/* return log10 XYZ density values */ +void icx_XYZ2dens( +double *out, /* Return log10 XYZ density */ +double *in /* Input XYZ values */ +); + +/* Given an XYZ value, */ +/* return sRGB values */ +void icx_XYZ2sRGB( +double *out, /* Return sRGB value */ +double *wp, /* Input XYZ white point (may be NULL) */ +double *in /* Input XYZ values */ +); + + + +/* Given an illuminant definition and an observer model, return */ +/* the normalised XYZ value for that spectrum. */ +/* Return 0 on sucess, 1 on error */ +int icx_ill_sp2XYZ( +double xyz[3], /* Return XYZ value with Y == 1 */ +icxObserverType obType, /* Observer */ +xspect custObserver[3], /* Optional custom observer */ +icxIllumeType ilType, /* Type of illuminant */ +double ct, /* Input temperature in degrees K */ +xspect *custIllum); /* Optional custom illuminant */ + + +/* Given a choice of temperature dependent illuminant (icxIT_Dtemp or icxIT_Ptemp), */ +/* return the closest correlated color temperature to the given spectrum or XYZ. */ +/* An observer type can be chosen for interpretting the spectrum of the input and */ +/* the illuminant. */ +/* Note we're using CICDE94, rather than the traditional L*u*v* 2/3 space for CCT */ +/* Return -1 on erorr */ +double icx_XYZ2ill_ct( +double txyz[3], /* If not NULL, return the XYZ of the black body temperature */ +icxIllumeType ilType, /* Type of illuminant, icxIT_Dtemp or icxIT_Ptemp */ +icxObserverType obType, /* Observer */ +xspect custObserver[3], /* Optional custom observer */ +double xyz[3], /* Input XYZ value, NULL if spectrum intead */ +xspect *insp0, /* Input spectrum value, NULL if xyz[] instead */ +int viscct); /* nz to use visual CIEDE2000, 0 to use CCT CIE 1960 UCS. */ + +/* Compute the CIE1995 CRI: Ra */ +/* Return < 0.0 on error */ +/* If invalid is not NULL, set it to nz if CRI */ +/* is invalid because the sample is not white enough. */ +double icx_CIE1995_CRI( +int *invalid, /* if not NULL, set to nz if invalid */ +xspect *sample /* Illuminant sample to compute CRI of */ +); +#endif /* !SALONEINSTLIB*/ + +#ifdef __cplusplus + } +#endif + +#endif /* XSPECTFM_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xutils.c b/xicc/xutils.c new file mode 100644 index 0000000..019cee6 --- /dev/null +++ b/xicc/xutils.c @@ -0,0 +1,294 @@ + +/* + * xicc standalone utilities + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000 - 2006 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + */ + +/* + * This module provides expanded capabilities, + * but is independent of other modules. + */ + +#include <sys/types.h> +#include <string.h> +#include <ctype.h> +#include <setjmp.h> +#ifdef __sun +#include <unistd.h> +#endif +#if defined(__IBMC__) && defined(_M_IX86) +#include <float.h> +#endif +#include "copyright.h" +#include "aconfig.h" +#include "icc.h" +#include "tiffio.h" +#include "jpeglib.h" +#include "iccjpeg.h" +#include "xutils.h" /* definitions for this library */ + + +#undef DEBUG + +#ifdef DEBUG +# define errout stderr +# define debug(xx) fprintf(errout, xx ) +# define debug2(xx) fprintf xx +#else +# define debug(xx) +# define debug2(xx) +#endif + +#if !defined(O_CREAT) && !defined(_O_CREAT) +# error "Need to #include fcntl.h!" +#endif + +/* ------------------------------------------------------ */ +/* Common clut table code */ + +/* Default table of clut resolutions */ +/* See discussion in imdi/imdi_gen.c for ideal numbers */ +static int lut_resolutions[9][4] = { + /* low, med, high, vhigh */ + { 0, 0, 0, 0 }, /* 0 */ + { 256, 772, 4370, 4370 }, /* 1 */ + { 86, 256, 256, 256 }, /* 2 */ + { 9, 17, 33, 52 }, /* 3 */ + { 6, 9, 18, 33 }, /* 4 */ + { 6, 9, 16, 18 }, /* 5 */ + { 6, 6, 9, 12 }, /* 6 */ + { 6, 7, 7, 9 }, /* 7 */ + { 3, 5, 5, 7 } /* 8 */ +}; + + +/* return a lut resolution given the input dimesion and quality */ +/* Input dimension [0-8], quality: low 0, medium 1, high 2, very high 3 . */ +/* A returned value of 0 indicates illegal. */ +int dim_to_clutres(int dim, int quality) { + if (dim < 0) + dim = 0; + else if (dim > 8) + dim = 8; + if (quality < 0) + quality = 0; + if (quality > 3) + quality = 3; + return lut_resolutions[dim][quality]; +} + +/* ------------------------------------------------------ */ + +/* JPEG error information */ +typedef struct { + jmp_buf env; /* setjmp/longjmp environment */ + char message[JMSG_LENGTH_MAX]; +} jpegerrorinfo; + +/* JPEG error handler */ +static void jpeg_error(j_common_ptr cinfo) { + jpegerrorinfo *p = (jpegerrorinfo *)cinfo->client_data; + (*cinfo->err->format_message) (cinfo, p->message); + longjmp(p->env, 1); +} + +/* ------------------------------------------------------ */ + +/* Open an ICC file or a TIFF or JPEG file with an embedded ICC profile for reading. */ +/* Return NULL on error */ +icc *read_embedded_icc(char *file_name) { + TIFF *rh = NULL; + int size; + void *tag, *buf; + icmAlloc *al; + icmFile *fp; + icc *icco; + TIFFErrorHandler olderrh, oldwarnh; + TIFFErrorHandlerExt olderrhx, oldwarnhx; + int rv; + + /* First see if the file can be opened as an ICC profile */ + if ((fp = new_icmFileStd_name(file_name,"r")) == NULL) { + debug2((errout,"Can't open file '%s'\n",file_name)); + return NULL; + } + + if ((icco = new_icc()) == NULL) { + debug("Creation of ICC object failed\n"); + fp->del(fp); + return NULL; + } + + if ((rv = icco->read_x(icco,fp,0,1)) == 0) { + debug2((errout,"Opened '%s' as an icc profile\n",file_name)); + return icco; + } + + debug2((errout,"icc read failed with %d, %s\n",rv,icco->err)); + icco->del(icco); /* icc wil fp->del() */ + + /* Not an ICC profile, see if it's a TIFF file */ + olderrh = TIFFSetErrorHandler(NULL); + oldwarnh = TIFFSetWarningHandler(NULL); + olderrhx = TIFFSetErrorHandlerExt(NULL); + oldwarnhx = TIFFSetWarningHandlerExt(NULL); + + if ((rh = TIFFOpen(file_name, "r")) != NULL) { + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + debug("TIFFOpen suceeded\n"); + + if (TIFFGetField(rh, TIFFTAG_ICCPROFILE, &size, &tag) == 0 || size == 0) { + debug2((errout,"no ICC profile found in '%s'\n",file_name)); + TIFFClose(rh); + return NULL; + } + + /* Make a copy of the profile to a memory buffer */ + if ((al = new_icmAllocStd()) == NULL) { + debug("new_icmAllocStd failed\n"); + TIFFClose(rh); + return NULL; + } + if ((buf = al->malloc(al, size)) == NULL) { + debug("malloc of profile buffer failed\n"); + al->del(al); + TIFFClose(rh); + return NULL; + } + + memmove(buf, tag, size); + TIFFClose(rh); + + } else { + jpegerrorinfo jpeg_rerr; + FILE *rf = NULL; + struct jpeg_decompress_struct rj; + struct jpeg_error_mgr jerr; + unsigned char *pdata; + unsigned int plen; + + debug2((errout,"TIFFOpen failed for '%s'\n",file_name)); + TIFFSetErrorHandler(olderrh); + TIFFSetWarningHandler(oldwarnh); + TIFFSetErrorHandlerExt(olderrhx); + TIFFSetWarningHandlerExt(oldwarnhx); + + /* We cope with the horrible ijg jpeg library error handling */ + /* by using a setjmp/longjmp. */ + jpeg_std_error(&jerr); + jerr.error_exit = jpeg_error; + if (setjmp(jpeg_rerr.env)) { + debug2((errout,"jpeg_read_header failed for '%s'\n",file_name)); + jpeg_destroy_decompress(&rj); + fclose(rf); + return NULL; + } + + rj.err = &jerr; + rj.client_data = &jpeg_rerr; + jpeg_create_decompress(&rj); + +#if defined(O_BINARY) || defined(_O_BINARY) + if ((rf = fopen(file_name,"rb")) == NULL) +#else + if ((rf = fopen(file_name,"r")) == NULL) +#endif + { + debug2((errout,"fopen failed for '%s'\n",file_name)); + jpeg_destroy_decompress(&rj); + return NULL; + } + + jpeg_stdio_src(&rj, rf); + setup_read_icc_profile(&rj); + + /* we'll longjmp on error */ + jpeg_read_header(&rj, TRUE); + + if (!read_icc_profile(&rj, &pdata, &plen)) { + debug2((errout,"no ICC profile found in '%s'\n",file_name)); + jpeg_destroy_decompress(&rj); + fclose(rf); + return NULL; + } + jpeg_destroy_decompress(&rj); + fclose(rf); + + /* Make a copy of the profile to a memory buffer */ + /* (icmAllocStd may not be the same as malloc ?) */ + if ((al = new_icmAllocStd()) == NULL) { + debug("new_icmAllocStd failed\n"); + return NULL; + } + if ((buf = al->malloc(al, plen)) == NULL) { + debug("malloc of profile buffer failed\n"); + al->del(al); + TIFFClose(rh); + return NULL; + } + memmove(buf, pdata, plen); + size = (int)plen; + free(pdata); + } + + /* Memory File fp that will free the buffer when deleted: */ + if ((fp = new_icmFileMem_ad(buf, size, al)) == NULL) { + debug("Creating memory file from CMProfileLocation failed"); + al->free(al, buf); + al->del(al); + return NULL; + } + + if ((icco = new_icc()) == NULL) { + debug("Creation of ICC object failed\n"); + fp->del(fp); /* fp will delete al */ + return NULL; + } + + if ((rv = icco->read_x(icco,fp,0,1)) == 0) { + debug2((errout,"Opened '%s' embedded icc profile\n",file_name)); + return icco; + } + + debug2((errout,"Failed to read '%s' embedded icc profile\n",file_name)); + icco->del(icco); /* icco will delete fp and al */ + return NULL; +} + +/* ------------------------------------------------------ */ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xicc/xutils.h b/xicc/xutils.h new file mode 100644 index 0000000..27de570 --- /dev/null +++ b/xicc/xutils.h @@ -0,0 +1,69 @@ +#ifndef XUTILS_H +#define XUTILS_H + +/* + * xicc standalone utilities. + * + * Author: Graeme W. Gill + * Date: 28/6/00 + * Version: 1.00 + * + * Copyright 2000 - 2006 Graeme W. Gill + * All rights reserved. + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + * + * Based on the old iccXfm class. + */ + +/* + * These utilities, while living in xicc, are meant to + * not depend on other modules. + */ + +/* Return a lut resolution given the input dimesion and quality */ +/* Input dimension [0-8], quality: low, medium, high, very high. */ +/* A returned value of 0 indicates illegal. */ +int dim_to_clutres(int dim, int quality); + + +/* Open an ICC file or a TIFF or JPEG file with an embedded ICC profile for reading. */ +/* Return NULL on error */ +icc *read_embedded_icc(char *file_name); + +#endif /* XUTILS_H */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + |