diff options
Diffstat (limited to 'link')
-rw-r--r-- | link/Jamfile | 36 | ||||
-rw-r--r-- | link/License.txt | 662 | ||||
-rw-r--r-- | link/Makefile.am | 11 | ||||
-rw-r--r-- | link/Readme.txt | 6 | ||||
-rw-r--r-- | link/afiles | 7 | ||||
-rw-r--r-- | link/collink.c | 3298 | ||||
-rw-r--r-- | link/monoplot.c | 231 | ||||
-rw-r--r-- | link/pathplot.c | 281 |
8 files changed, 4532 insertions, 0 deletions
diff --git a/link/Jamfile b/link/Jamfile new file mode 100644 index 0000000..4d4dc24 --- /dev/null +++ b/link/Jamfile @@ -0,0 +1,36 @@ + +# JAM style makefile for collink and friends + +#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on +PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags +#PREF_CCFLAGS += $(CCPROFFLAG) ; # Profile flags +#PREF_LINKFLAGS += $(LINKPROFFLAG) ; # Profile flags +#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags +PREF_LINKFLAGS += $(LINKDEBUGFLAG) ; # Link debugging flags + +#Products +Executables = collink ; + +#Install +InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ; + +HDRS = ../h ../icc ../rspl ../plot ../numlib $(TIFFINC) ; +LINKLIBS = $(TIFFLIB) $(JPEGLIB) ../icc/libicc ../plot/libplot ../numlib/libnum $(LibWin) ; + +# K only reprocessor +#Main icc2ko : icc2ko.c ; + +# Monochrome transfer curve plotter +Main monoplot : monoplot.c ; + +HDRS += ../cgats ../xicc ../spectro ../gamut ; +LINKLIBS = ../xicc/libxicc ../xicc/libxcolorants ../gamut/libgamut.c + ../gamut/libgammap ../rspl/librspl ../cgats/libcgats + ../plot/libvrml $(LINKLIBS) ; + +# ICC linker +Main collink : collink.c ; + +# Device path L curve plotter +Main pathplot : pathplot.c ; + diff --git a/link/License.txt b/link/License.txt new file mode 100644 index 0000000..a871fcf --- /dev/null +++ b/link/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/link/Makefile.am b/link/Makefile.am new file mode 100644 index 0000000..37a3ac9 --- /dev/null +++ b/link/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/Makefile.shared + +LDADD = ../spectro/libinsttypes.la ../xicc/libxicc.la \ + ../xicc/libxutils.la ../gamut/libgamut.la \ + ../gamut/libgammap.la ../plot/libplot.la ../plot/libvrml.la \ + ../rspl/librspl.la $(ICC_LIBS) ../cgats/libcgats.la \ + ../numlib/libargyllnum.la ../libargyll.la $(X_LIBS) $(TIFF_LIBS) + +bin_PROGRAMS = collink pathplot + +EXTRA_DIST = License.txt Readme.txt diff --git a/link/Readme.txt b/link/Readme.txt new file mode 100644 index 0000000..9e8dacc --- /dev/null +++ b/link/Readme.txt @@ -0,0 +1,6 @@ +This direcory holds the top level icc link code. + +collink.exe takes two device ICC profiles, and links + then together in a simple fashion, producing + a device link ICC profile. + diff --git a/link/afiles b/link/afiles new file mode 100644 index 0000000..7c73bd7 --- /dev/null +++ b/link/afiles @@ -0,0 +1,7 @@ +Readme.txt +License.txt +afiles +Jamfile +collink.c +monoplot.c +pathplot.c diff --git a/link/collink.c b/link/collink.c new file mode 100644 index 0000000..6c40efd --- /dev/null +++ b/link/collink.c @@ -0,0 +1,3298 @@ + +/* + * collink + * + * Link two device profiles to create a Device Link Profile + * + * Author: Graeme W. Gill + * Date: 25/11/00 + * Version: 2.10 + * + * Copyright 2000 - 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. + */ + +/* TTBD: + * + * Device curve resolution should be taken from the device profiles, + * rather than depending on the quality settings (scRGB compression curves) + * + * Abstract link support intent doesn't work properly for anything + * other than absolute. This should really be fixed. + */ + +/* NOTES: + + Normally the device side per channel curves are copied from + the source profiles to the link profile on the assumption that + the raw linearisation they do is good, and should be maintained + for best overall transform accuracy. Since the intermediate + link is done in an Lab like space, then this linearisation + will even out quantisation error introduced by the Lut + in perceptual space. In the case of a Matrix profile with a native + XYZ PCS, these assumptions break, since the device curves + would generally be linearising the device to Y, not a perceptual + space. For this reason we add a Y to L* type curve to the input + linearisation curves. For XYZ CLUT based profiles, we just have + to hope that the profile has been created well, and that the + input curves distribute the indexes reasonably perceptually + evenly. For an output (XYZ) Matrix profile, we also + use an L* style mixing space. + + In general the per channel curves should be: + + No curve, or + + An optimized curve, or + + The source profile per channel curve plus + a Y to L* curve it's a Matrix profile. + + + Colorspace representations are a bit of a mess. It's hard to know what space + color is in at any point, and difficult to transform to match + some other element. Putting the different colorspace support within + the profile transforms is neat, but may not be flexible enough, since the + needed information to do a transform (white point, viewing conditions) + might be a bit too closely bound into the profile. An alternative might be + to expand the colorspace definition from a tag to include all the + other needed information, and create a general colorspace adapter + to transform from one to the other, or to limit connections + to a cannonical spaces such as XYZ. + One big cause of confusion for user and implimentor is how to + handle the conflicting intents. What does it mean to link + a relative source to absolute abstract to CAM destination ??? + + A non-absolute abstract profile is poorly defined (just like + the device profiles) if it doesn't define the viewing conditions + within which the transform is defined. + + Having separated creating a gamut surface for a raster out + (for better modularity), it then creates problems in applying + an abstact transform. The abstract transform can't really be + applied to a gamut surface, it really needs to be applied + to the image data before gamut surface extraction. This is yet + another avenue for user & implementor confusion, with regard + to intents, even without allowing the user to set the intent + of the abstract profile. + + */ + +#undef USE_MERGE_CLUT_OPT /* [Undef] When using inverse A2B table, merge the output luts */ + /* with the clut for faster operation, and clipping in */ + /* Jab space. Turned off because it affects the accuracy too much, */ + /* and xicc handles Jab clip without this now. */ + +#define USE_CAM_CLIP_OPT /* [Define] Clip out of gamut in CAM space rather than XYZ or L*a*b* */ +#define ENKHACK /* [Define] Enable K hack code */ +#undef WARN_CLUT_CLIPPING /* [Undef] Print warning if setting clut clips */ + +#undef DEBUG /* Report values of each sample transformed */ +#undef DEBUGC /* ie "if (tt)" */ /* Debug condition */ +#undef DEBUG_ONE /* test a single value out. Look for DBGNO to set value. */ +#undef NEUTKDEBUG /* print info about neutral L -> K mapping */ + + +#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 "gammap.h" +// ~~~99 +#include "vrml.h" + +void usage(char *diag, ...) { + int i; + fprintf(stderr,"Link ICC profiles, 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: collink [options] srcprofile dstprofile linkedprofile\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -A manufacturer Manufacturer description string\n"); + fprintf(stderr," -M model Model description string\n"); + fprintf(stderr," -D description Profile Description string (Default \"inoutfile\")\n"); + fprintf(stderr," -C copyright Copyright string\n"); + fprintf(stderr," -V Verify existing profile, rather than link\n"); + fprintf(stderr," -q lmhu Quality - Low, Medium (def), High, Ultra\n"); +// fprintf(stderr," -q fmsu Speed - Fast, Medium (def), Slow, Ultra Slow\n"); + fprintf(stderr," -r res Override clut res. set by -q\n"); + fprintf(stderr," -n Don't preserve device linearization curves in result\n"); + fprintf(stderr," -f Special :- Force neutral colors to be K only output\n"); + fprintf(stderr," -fk Special :- Force K only neutral colors to be K only output\n"); + fprintf(stderr," -F Special :- Force all colors to be K only output\n"); + fprintf(stderr," -fcmy Special :- Force 100%% C,M or Y only to stay pure \n"); + fprintf(stderr," -p absprof Include abstract profile in link\n"); + fprintf(stderr," -s Simple Mode (default)\n"); + fprintf(stderr," -g [src.gam] Gamut Mapping Mode [optional source image gamut]\n"); + fprintf(stderr," -G [src.gam] Gamut Mapping Mode using inverse outprofile A2B\n"); + fprintf(stderr," Simple Mode Options:\n"); + fprintf(stderr," -i in_intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute colorimetric\n"); + fprintf(stderr," -o out_intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute colorimetric\n"); + fprintf(stderr," Gamut Mapping Mode Options:\n"); + fprintf(stderr," -i intent set linking intent from the following choice:\n"); + for (i = 0; ; i++) { + icxGMappingIntent gmi; + if (xicc_enum_gmapintent(&gmi, i, NULL) == icxIllegalGMIntent) + break; + + fprintf(stderr," %s\n",gmi.desc); + } + fprintf(stderr," -w [J,a,b] Use forced whitepoint hack [optional target point]\n"); +// fprintf(stderr," -W J,a,b Forced whitepoint adjustment by delta Jab\n"); + fprintf(stderr," -c viewcond set source viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or a parameter\n"); + fprintf(stderr," -d viewcond set destination viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or 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," -t tlimit set source total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -T klimit set source black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," Inverse outprofile A2B Options:\n"); + fprintf(stderr," -k tezhxr CMYK Black generation\n"); + fprintf(stderr," t = transfer K from source to destination, e = retain K of destination B2A table\n"); + fprintf(stderr," z = zero K, h = 0.5 K, x = maximum K, r = ramp K (default)\n"); + fprintf(stderr," -k p stle stpo enpo enle shape\n"); + fprintf(stderr," p = black target generation curve parameters\n"); + fprintf(stderr," -k q stle0 stpo0 enpo0 enle0 shape0 stle2 stpo2 enpo2 enle2 shape2\n"); + fprintf(stderr," q = transfer source K 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 destination total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set destination black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," -P Create gamut gammap.wrl diagostic\n"); + exit(1); +} + +/* ------------------------------------------- */ +/* structures to support icc calbacks */ + +/* Information needed from a profile */ +struct _profinfo { + /* Setup parameters */ + icRenderingIntent intent; /* Selected ICC rendering intent */ + icxViewCond vc; /* Viewing Condition for CAM */ + int inking; /* k inking algorithm, 0 = input, 1 = min, */ + /* 2 = 0.5, 3 = max, 4 = ramp, 5 = curve, 6 = dual curve */ + /* 7 = outpupt profile K value */ + int locus; /* 0 = K target value, 1 = K locus value */ + icxInk ink; /* Ink parameters */ + + /* Operational parameters */ + icc *c; + icmHeader *h; + xicc *x; + icxLuBase *luo; /* Base XLookup type object */ + icmLuAlgType alg; /* Type of lookup algorithm */ + icColorSpaceSignature csp; /* Colorspace */ + int chan; /* Channels */ + int nocurve; /* NZ to not use device curve in per channel curve */ + int lcurve; /* 1 to apply a Y like to L* curve for XYZ Matrix profiles */ + /* 2 to apply a Y to L* curve for XYZ space */ + double wp[3]; /* Lab/Jab white point for profile used by wphack & xyzscale */ + icxLuBase *b2aluo; /* B2A lookup for inking == 7 */ +}; typedef struct _profinfo profinfo; + +/* Structure that holds all the color lookup information */ +struct _clink { + /* Overall options */ + int verb; + int gamdiag; /* nz, create gammap.wrl diagnostic */ + int total, count, last; /* Progress count information */ + int mode; /* 0 = simple mode, 1 = mapping mode, 2 = mapping mode with inverse A2B */ + int quality; /* 0 = low, 1 = medium, 2 = high, 3 = ultra */ + int clutres; /* 0 = quality default, !0 = override, then actual during link */ + int src_kbp; /* nz = Use K only black point as src gamut black point */ + int dst_kbp; /* nz = Use K only black point as dst gamut black point */ + int dst_cmymap; /* masks C = 1, M = 2, Y = 4 to force 100% cusp map */ + + icColorSpaceSignature pcsor; /* PCS to use between in & out profiles */ + + int nhack; /* 0 = off, 1 = hack to map input neutrals to output K only, */ + /* 2 = map 000K to output K only, 3 = all to K */ + int cmyhack; /* CMY 100% colorant map though hack, 1 = C, 2 = M, 4 = Y */ + rspl *pcs2k; /* PCS L to K' lookup for nhack */ + int wphack; /* 0 = off, 1 = hack to map input wp to output wp, 2 = to hwp[] */ + double hwp[3]; /* hack destination white point in PCS space */ + int wphacked; /* Operation flag, set nz if white point was translated */ + int rel_oride; /* Relative override flag */ + + icmFile *abs_fp; /* Abstract profile transform */ + icRenderingIntent abs_intent; + icc *abs_icc; + xicc *abs_xicc; + icxLuBase *abs_luo; /* NULL if none */ + + /* (We current assume that xyzscale can't be used with gmi) */ + double xyzscale; /* < 1.0 if Y is to be scaled in destination XYZ space */ + double swxyz[3]; /* Source white point in XYZ */ + + icxGMappingIntent gmi; + gammap *map; /* Gamut mapping */ + gammap *Kmap; /* Gamut mapping K in to K out nhack == 2 and K in to K out */ + + + /* Per profile setup information */ + profinfo in; + profinfo out; + +}; typedef struct _clink clink; + + +/* ------------------------------------------- */ +//#define YSCALE 1.0 +#define YSCALE (2.0/1.3) + +/* Extra non-linearity applied to BtoA XYZ PCS */ +/* This distributes the LUT indexes more evenly in */ +/* perceptual space, greatly improving the B2A accuracy of XYZ LUT */ +/* Since typically XYZ doesn't use the full range of 0-2.0 allowed */ +/* for in the encoding, we scale the cLUT index values to use the 0-1.3 range */ + +/* (For these functions the encoded XYZ 0.0 - 2.0 range is 0.0 - 1.0 ??) */ + +/* Y to L* */ +static void y2l_curve(double *out, double *in, int isXYZ) { + int i; + double val; + double isc = 1.0, osc = 1.0; + + /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */ + /* + range adjustment */ + if (isXYZ) { + isc = 32768.0/65535.0 * YSCALE; + osc = 65535.0/32768.0; + } + + for (i = 0; i < 3; i++) { + val = in[i] * isc; + if (val > 0.008856451586) + val = 1.16 * pow(val,1.0/3.0) - 0.16; + else + val = 9.032962896 * val; + if (val > 1.0) + val = 1.0; + out[i] = val * osc; + } +} + +/* L* to Y */ +static void l2y_curve(double *out, double *in, int isXYZ) { + int i; + double val; + double isc = 1.0, osc = 1.0; + + /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */ + /* + range adjustment */ + if (isXYZ) { + isc = 32768.0/65535.0; + osc = 65535.0/32768.0 / YSCALE; + } + + /* Use an L* like curve, scaled to the maximum XYZ value */ + for (i = 0; i < 3; i++) { + val = in[i] * isc; + if (val > 0.08) + val = pow((val + 0.16)/1.16, 3.0); + else + val = val/9.032962896; + out[i] = val * osc; + } +} + +/* ------------------------------------------- */ +/* Functions called back in setting up the transform table */ + +#ifdef DEBUGC + +static int tt = 0; + +#endif /* DEBUGC */ + +/* Input table, DevIn -> DevIn' */ +void devi_devip(void *cntx, double *out, double *in) { + int rv = 0; + clink *p = (clink *)cntx; + +#ifdef DEBUGC + if (in[0] == 1.0 && in[1] == 1.0 && in[2] == 1.0 && in[3]) + tt = 1; +#endif + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn->DevIn' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout); +#endif + + if (p->in.nocurve) { /* Don't use profile per channel curves */ + int i; + for (i = 0; i < p->in.chan; i++) + out[i] = in[i]; + } else { /* Use input profile per channel curves */ + switch(p->in.alg) { + case icmMonoFwdType: { + icxLuMono *lu = (icxLuMono *)p->in.luo; /* Safe to coerce */ + rv |= lu->fwd_curve(lu, out, in); + break; + } + case icmMatrixFwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */ + rv |= lu->fwd_curve(lu, out, in); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->in.luo; /* Safe to coerce */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + rv |= lu->input(lu, out, in); + break; + } + default: + error("Unexpected algorithm type %d in devi_devip()",p->in.alg); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } + + if (p->in.lcurve) { /* Apply Y to L* */ +//printf("~1 y2l_curve got %f %f %f, isXYZ %d\n",in[0],in[1],in[2],p->in.lcurve == 2); + y2l_curve(out, out, p->in.lcurve == 2); + } + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn->DevIn' ret %f %f %f %f\n",out[0], out[1], out[2], in[3]); fflush(stdout); +#endif +} + + +/* - - - - - - - - - - - - */ +/* clut, DevIn' -> DevOut' */ +void devip_devop(void *cntx, double *out, double *in) { + double win[MAX_CHAN]; /* working input values */ + double pcsv[MAX_CHAN]; /* PCS intermediate value, pre-gamut map */ + double pcsvm[MAX_CHAN]; /* PCS intermediate value, post-gamut map */ + double locus[MAX_CHAN]; /* Auxiliary locus values */ + int wptrig = 0; /* White point hack triggered */ + double konlyness = 0.0; /* Degree of K onlyness */ + int ntrig = 0; /* K only output hack triggered */ + int cmytrig = 0; /* CMY output hack triggered */ + int i, rv = 0; + clink *p = (clink *)cntx; + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn'->DevOut' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout); +#endif + +#ifdef ENKHACK + /* Handle neutral recognition/output K only hack */ + /* (see discussion at top of file for generalization of this idea) */ + if (p->nhack == 1 || p->nhack == 2) { + double thr = (0.5)/(p->clutres-1.0); /* Match threshold */ + + if (p->nhack == 1) { + /* We want to see if the input colors are equal (Or a=b= 0.0 ??) */ + /* li.nhack should have set p->in.nocurve, so we should be getting raw */ + /* input space device values here. It also made sure that there are at */ + /* least 3 input channels. */ + + if (fabs(in[0] - in[1]) < thr + && fabs(in[1] - in[2]) < thr + && fabs(in[2] - in[0]) < thr) + ntrig = 1; /* K only output triggered flag */ + + } else if (p->nhack == 2) { + double maxcmy; /* Comute a degree of source "K onlyness" */ + double maxcmyk; + + maxcmy = in[0]; /* Compute minimum of CMY */ + if (in[1] > maxcmy) + maxcmy = in[1]; + if (in[2] > maxcmy) + maxcmy = in[2]; + + maxcmyk = maxcmy; /* Compute minimum of all inks */ + if (in[3] > maxcmyk) + maxcmyk = in[3]; + +//printf("~1 maxcmy = %f, maxcmyk = %f, in[3] = %f\n",maxcmy,maxcmyk,in[3]); + if (in[3] <= 0.0 || maxcmy > in[3]) { + konlyness = 0.0; + } else { + konlyness = (in[3] - maxcmy)/in[3]; + } + + /* As we approach no colorant, blend towards no Konlyness */ + if (maxcmyk < 0.2) + konlyness *= maxcmyk/0.2; + + /* We want to see if the input colors are exactly K only. */ + if (in[0] < thr + && in[1] < thr + && in[2] < thr) + ntrig = 1; /* K only output triggered flag */ +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("konlyness set to %f\n",konlyness); +#endif + + } + } + /* Handle 100% CMY hack */ + if (p->cmyhack != 0) { + double thr = (0.5)/(p->clutres-1.0); /* Match threshold */ + + if (p->cmyhack & 1) { + if (in[0] > (1.0 - thr) + && in[1] < thr + && in[2] < thr + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 1; + } + if (p->cmyhack & 2) { + if (in[0] < thr + && in[1] > (1.0 - thr) + && in[2] < thr + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 2; + } + if (p->cmyhack & 4) { + if (in[0] < thr + && in[1] < thr + && in[2] > (1.0 - thr) + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 4; + } + } +#endif /* ENKHACK */ + + if (p->in.lcurve) { /* Apply L* to Y */ + l2y_curve(win, in, p->in.lcurve == 2); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("win[] set to L* value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout); +#endif + + } else { + for (i = 0; i < p->in.chan; i++) + win[i] = in[i]; +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("win[] set to in[] value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout); +#endif + } + + /* Do DevIn' -> PCS */ + switch(p->in.alg) { + case icmMonoFwdType: { + icxLuMono *lu = (icxLuMono *)p->in.luo; /* Safe to coerce */ + + if (p->in.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->fwd_curve(lu, pcsv, win); + rv |= lu->fwd_map(lu, pcsv, pcsv); + } else { + rv |= lu->fwd_map(lu, pcsv, win); + } + rv |= lu->fwd_abs(lu, pcsv, pcsv); + break; + } + case icmMatrixFwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */ + + if (p->in.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->fwd_curve(lu, pcsv, win); + rv |= lu->fwd_matrix(lu, pcsv, pcsv); + } else { + rv |= lu->fwd_matrix(lu, pcsv, win); + } + rv |= lu->fwd_abs(lu, pcsv, pcsv); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->in.luo; /* Safe to coerce */ + if (p->in.nocurve) { /* No explicit curve, so we've got Dev */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + rv |= lu->input(lu, pcsv, win); /* Dev -> Dev' */ + rv |= lu->clut(lu, pcsv, pcsv); /* Dev' -> PCS' */ + } else { /* We've got Dev' */ + rv |= lu->clut(lu, pcsv, win); /* Dev' -> PCS' */ + } + /* We've got the input profile PCS' at this point. */ + + /* If we're transfering the K value from the input profile to the */ + /* output, copy it into locus[], which will be given to the inverse */ + /* lookup function, else the inverse lookup will generate a K using */ + /* the curve parameters. */ +//printf("~1 out.inking = %d\n",p->out.inking); + if (p->out.inking == 0 || p->out.inking == 6) { + if (p->out.locus) { + /* Converts PCS' to K locus proportion */ + lu->clut_locus(lu, locus, pcsv, win); /* Compute possible locus values */ +//printf("~1 looked up locus value\n"); + } else { + for (i = 0; i < p->in.chan; i++) /* Target is K input value */ + locus[i] = win[i]; + /* Convert K' to K value ready for aux target */ + if (!p->in.nocurve) { /* we have an input curve, so convert Dev' -> Dev */ + lu->inv_input(lu, locus, locus); + } +//printf("~1 copied win to locus\n"); + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got possible K %s of %f %f %f %f\n",p->out.locus ? "locus" : "value", locus[0],locus[1],locus[2],locus[3]); +#endif + } + rv |= lu->output(lu, pcsv, pcsv); /* PCS' -> */ + rv |= lu->out_abs(lu, pcsv, pcsv); /* PCS */ + break; + } + default: + error("Unexpected algorithm type %d in devip of devip_devop()",p->in.alg); + } + + /* At this point, the PCS is: + * + * If not gamut mapped: + * Lab in the intent selected for the source profile + * If gamut mapped: + * either + * Absolute Lab + * or + * Jab derived from absolute XYZ via the in/out viewing conditions + * + * and locus[] contains any auxiliar target values if the + * auxiliary is not being created by a rule applied to the PCS. + */ + + /* + * The order to do this intermediate processing is hard to figure out, + * as is the interaction between such elements. How should the + * abstract profile be properly handled ? + * what should we do if the wphack is on and Y scaling is on ? + */ + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS before map %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout); +#endif + + if (p->wphack) { + int e; + double dd = 0.0; + for (e = 0; e < 3; e++) { /* Does this match the input white point ? */ + double tt; + tt = pcsv[e] - p->in.wp[e]; + dd += tt * tt; + } + dd = sqrt(dd); + + if (dd < 1.0) { /* Triggered withing 1 delta E */ + p->wphacked++; + wptrig = 1; + if (p->wphack == 2) { + for (e = 0; e < 3; e++) /* Map input white to given white */ + pcsv[e] = p->hwp[e]; + } else { + for (e = 0; e < 3; e++) /* Map input white to output white */ + pcsv[e] = p->out.wp[e]; + } + +#ifndef DEBUG + if (p->verb) +#endif + { + printf("White point hack mapped %f %f %f to %f %f %f, hit withing %f\n", + p->in.wp[0],p->in.wp[1],p->in.wp[2],pcsv[0], pcsv[1], pcsv[2],dd); + fflush(stdout); + } + } + } + + /* Do luminence scaling if requested */ + if (wptrig == 0 && p->xyzscale < 1.0) { + double xyz[3]; + +//printf("~1 got xyzscale = %f\n",p->xyzscale); +//printf("PCS %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); + + /* Convert our PCS to XYZ */ + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->cam_to_XYZ(p->out.luo->cam, xyz, pcsv); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + /* Scale it */ + xyz[0] *= p->xyzscale; + xyz[1] *= p->xyzscale; + xyz[2] *= p->xyzscale; + +//printf("scaled XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + /* Convert back to PCS */ + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->XYZ_to_cam(p->out.luo->cam, pcsv, xyz); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("scaled PCS %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after Y scale %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout); +#endif + } + + + /* Do gamut mapping */ + if (wptrig == 0 && p->mode > 0 && p->gmi.usemap) { + /* We've used pcsor to ensure PCS space is appropriate */ + + /* Doing XXXK -> XXXK */ + if (p->nhack == 2) { + /* Ideally we would create a 4D PCSK -> PCSK gamut mapping */ + /* to smoothly and accurately cope with the changing source */ + /* and destination gamuts acording to their degree of "K onlyness". */ + /* In practice we're going to simply interpolated between */ + /* two extremes: unrestricted gamut and K only black gamut. */ + double map0[3], map1[3]; + + /* Compute blend of normal gamut map and Konly to Konly gamut map */ + { + p->map->domap(p->map, map0, pcsv); + p->Kmap->domap(p->Kmap, map1, pcsv); + icmBlend3(pcsvm, map0, map1, konlyness); + } + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after map0 %f %f %f map1 %f %f %f\n", map0[0], map0[1], map0[2], map1[0], map1[1], map1[2]); +#endif + + /* Normal gamut mapping */ + } else { + { + p->map->domap(p->map, pcsvm, pcsv); + } + } + + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after map %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout); +#endif + } else { + pcsvm[0] = pcsv[0]; + pcsvm[1] = pcsv[1]; + pcsvm[2] = pcsv[2]; + } + + /* Gamut mapped PCS value is now in pcsvm[] */ + + /* Abstract profile transform, PCS -> PCS */ + /* pcsor -> abstract -> pcsor conversion */ + /* We're applying any abstract profile after gamut mapping, */ + /* on the assumption is is primarily being used to "correct" the */ + /* output device. Ideally the gamut mapping should take the change */ + /* the abstract profile has on the output device into account, but */ + /* currently we're not doing this.. */ + if (wptrig == 0 && p->abs_luo != NULL) { + /* Abstract profile is either absolute or relative. */ + /* We need to convert the current PCS into something compatible. */ + /* This is more ugly than it really should be, so we're ignoring it. */ + /* We should really run the source through the abstract profile before */ + /* creating the gamut mapping, to be able to use abstract with gamut */ + /* mapping properly. */ + p->abs_luo->lookup(p->abs_luo, pcsvm, pcsvm); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after abstract %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout); +#endif + } + + /* If we're using the existing B2A inking to determine K, */ + /* lookup the output profiles K value for this PCS */ + if (p->mode >= 2 && p->out.inking == 7) { + double tdevv[MAX_CHAN]; + +//printf("~1 dealing with out.inking = %d\n",p->out.inking); + if (p->out.alg != icmLutType || p->out.c->header->colorSpace != icSigCmykData) + error ("Attempting to use non-CMYK output profile to determine K inking"); + + /* Lookup PCS in B2A of output profile to get target K value */ +//printf("~1 looking up pcs %f %f %f in B2A\n", pcsvm[0], pcsvm[1], pcsvm[2]); + p->out.b2aluo->lookup(p->out.b2aluo, tdevv, pcsvm); +//printf("~1 resulting dev %f %f %f %f\n", tdevv[0], tdevv[1], tdevv[2], tdevv[3]); + + if (p->out.locus) { + double tpcsv[MAX_CHAN]; + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + /* Convert PCS to PCS' ready for locus lookup */ + lu->in_abs(lu, tpcsv, pcsvm); + lu->matrix(lu, tpcsv, tpcsv); + lu->input(lu, tpcsv, tpcsv); + lu->clut_locus(lu, locus, tpcsv, tdevv); /* Compute locus values */ + } else { + for (i = 0; i < p->out.chan; i++) /* Target is K value */ + locus[i] = tdevv[i]; + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got possible K %s of %f %f %f %f\n",p->out.locus ? "locus" : "value", locus[0],locus[1],locus[2],locus[3]); +#endif + } + + /* Do PCS -> DevOut' */ + if (p->nhack == 3 /* All to K only */ + || ntrig /* Neutral or K only to K only hack has triggered */ + || cmytrig) { /* 100% CMY rough hack has triggered */ + + if (p->nhack == 3 || ntrig) { /* Neutral to K only hack has triggered */ + co pp; + pp.p[0] = pcsvm[0]; /* Input L value */ + p->pcs2k->interp(p->pcs2k, &pp); /* L -> K' */ + if (pp.v[0] < 0.0) /* rspl might have extrapolated */ + pp.v[0] = 0.0; + else if (pp.v[0] > 1.0) + pp.v[0] = 1.0; + out[0] = out[1] = out[2] = 0.0; /* We know output is CMYK' */ + out[3] = pp.v[0]; + +#ifndef DEBUG + if (p->verb) +#endif + if (ntrig) { + printf("Neutral hack mapped %s to 0 0 0 %f\n", icmPdv(p->in.chan,win), out[3]); + fflush(stdout); + } + } else if (cmytrig) { /* 100% CMY rough hack has triggered */ + if (cmytrig & 1) { + out[0] = 1.0; + out[1] = out[2] = out[3] = 0.0; + } + if (cmytrig & 2) { + out[1] = 1.0; + out[0] = out[2] = out[3] = 0.0; + } + if (cmytrig & 4) { + out[2] = 1.0; + out[0] = out[1] = out[3] = 0.0; + } + +#ifndef DEBUG + if (p->verb) +#endif + if (cmytrig != 0) { + if (p->in.chan == 4) + printf("CMY hack mapped %s to %s\n",icmPdv(p->in.chan, win), icmPdv(p->out.chan, out)); + fflush(stdout); + } + } + } else { /* Neutral to K hack has NOT triggered */ + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + + rv |= lu->bwd_abs(lu, pcsvm, pcsvm); + rv |= lu->bwd_map(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->bwd_curve(lu, out, out); + } + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + + rv |= lu->bwd_abs(lu, pcsvm, pcsvm); + rv |= lu->bwd_matrix(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->bwd_curve(lu, out, out); + } + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + if (p->mode < 2) { /* Using B2A table */ + rv |= lu->in_abs(lu, pcsvm, pcsvm); + rv |= lu->matrix(lu, pcsvm, pcsvm); + rv |= lu->input(lu, pcsvm, pcsvm); + rv |= lu->clut(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->output(lu, out, out); + } + + } else { /* Use inverse A2B table */ + int i; +#ifdef USE_MERGE_CLUT_OPT + /* Because we have used the ICX_MERGE_CLUT flag, we don't need */ + /* to call inv_out_abs() and inv_output() */ +#else + rv |= lu->inv_out_abs(lu, pcsvm, pcsvm); + rv |= lu->inv_output(lu, pcsvm, pcsvm); +#endif + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Calling inv_clut with K aux targets %f %f %f %f and pcsvm %f %f %f %f\n", + locus[0],locus[1],locus[2],locus[3],pcsvm[0],pcsvm[1],pcsvm[2],pcsvm[3]); +#endif + + /* locus[] contains possible K target or locus value, */ + /* so copy it to out[] so that inv_clut will use it. */ + for (i = 0; i < p->out.chan; i++) + out[i] = locus[i]; + + rv |= lu->inv_clut(lu, out, pcsvm); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got result %f %f %f %f\n", out[0],out[1],out[2],out[3]); +#endif + + + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->inv_input(lu, out, out); + } + } + break; + } + + default: + error("Unexpected algorithm type %d in devop of devip_devop()",p->out.alg); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } + + if (p->out.lcurve) /* Apply Y to L* */ + y2l_curve(out, out, p->out.lcurve == 2); + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn'->DevOut' ret %f %f %f %f\n\n",out[0], out[1], out[2], out[3]); fflush(stdout); +#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; + } + } +} + +/* - - - - - - - - - - - - - - - - */ +/* Output table, DevOut' -> DevOut */ +void devop_devo(void *cntx, double *out, double *in) { + int rv = 0; + clink *p = (clink *)cntx; + int i; + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevOut'->DevOut got %f %f %f %f\n",in[0], in[1], in[2], in[4]); fflush(stdout); +#endif + + for (i = 0; i < p->out.chan; i++) + out[i] = in[i]; + + if (p->out.lcurve) /* Apply L* to Y */ + l2y_curve(out, out, p->out.lcurve == 2); + + if (p->out.nocurve == 0) { /* Using per channel curves */ + + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + rv |= lu->bwd_curve(lu, out, out); + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + rv |= lu->bwd_curve(lu, out, out); + break; + } + case icmLutType: { + if (p->mode < 2) { /* Using B2A table */ + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + rv |= lu->output(lu, out, out); + /* Since not PCS, out_abs is never used */ + break; + } else { /* Use inverse A2B table */ + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + rv |= lu->inv_input(lu, out, out); + /* Since not PCS, inv_matrix and inv_in_abs is never used */ + break; + } + } + default: + error("Unexpected algorithm type in devop_devo()"); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevOut'->DevOut ret %f %f %f %f\n",out[0], out[1], out[2], out[3]); fflush(stdout); +#endif +#ifdef DEBUGC + tt = 0; +#endif +} + +/* ------------------------------------------- */ +/* Fixup L -> K only lookup table white and black values, */ +/* to compensate for inexact rspl fitting */ + +/* Context for fixup */ +typedef struct { + double kmax; + double kmin; +} pcs2k_ctx; + +/* Function to pass to rspl to re-set output values, */ +/* to make them relative to the white and black points */ +static void +fix_pcs2k_white( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + double f; + pcs2k_ctx *p = (pcs2k_ctx *)pp; + + /* Scale so that kmin->hmax becomes 0 to 1 */ + f = (out[0] - p->kmin)/(p->kmax - p->kmin); + + out[0] = f; +} + +/* ------------------------------------------- */ +/* powell() callback to set XYZ scaling factor */ + +static double xyzoptfunc(void *cntx, double *v) { + clink *p = (clink *)cntx; + double swxyz[3], jab[3], dev[MAX_CHAN]; + double rv; + int rc = 0; + + rv = 2.0 - v[0]; /* Make Y as large as possible */ + + /* If we wanted to use this function to maximise the brightness */ + /* we would not limit the scale to 1.0 */ + if (v[0] > 1.0) { + rv += 1000.0; + return rv; + } + if (v[0] < 0.0) { + rv += 100.0; + return rv; + } + swxyz[0] = v[0] * p->swxyz[0]; + swxyz[1] = v[0] * p->swxyz[1]; + swxyz[2] = v[0] * p->swxyz[2]; + +//printf("~1 scaled white XYZ = %f %f %f\n", swxyz[0], swxyz[1], swxyz[2]); + + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->XYZ_to_cam(p->out.luo->cam, jab, swxyz); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("~1 scaled white Jab = %f %f %f\n", jab[0], jab[1], jab[2]); + + /* Run the target PCS backwards through the output space to see if it clips */ + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + + rc = lu->bwd_lookup(p->out.luo, dev, jab); + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + + rc = lu->bwd_lookup(p->out.luo, dev, jab); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + if (p->mode < 2) /* Using B2A table */ + rc = lu->lookup(p->out.luo, dev, jab); + else /* Use inverse A2B table */ + rc = lu->inv_lookup(p->out.luo, dev, jab); + break; + } + default: + error("Unexpected algorithm type %d in devop of devip_devop()",p->out.alg); + } +//printf("~1 device = %f %f %f, rc = %d\n", dev[0], dev[1], dev[2], rc); + if (rc != 0) + rv += 500.0; + +//printf("~1 xyzoptfunc rv %f from xyzscale %f\n\n",rv,v[0]); + return rv; +} + +/* ------------------------------------------- */ + +int +main(int argc, char *argv[]) { + int fa, nfa, mfa; /* argument we're looking at */ + char in_name[MAXNAMEL+1]; + char sgam_name[MAXNAMEL+1] = "\000"; /* Source gamut name */ + char abs_name[MAXNAMEL+1] = "\000"; /* Abstract profile name */ + char out_name[MAXNAMEL+1]; + char link_name[MAXNAMEL+1]; + int verify = 0; /* Do verify pass */ + int outinkset = 0; /* The user specfied an output inking */ + int intentset = 0; /* The user specified an intent */ + int vcset = 0; /* Viewing conditions were set by user */ + int modeset = 0; /* The gamut mapping mode was set by the user */ + int rv = 0; + icxViewCond ivc, ovc; /* Viewing Condition Overrides for in and out profiles */ + int ivc_e = -1, ovc_e = -1; /* Enumerated viewing condition */ + clink li; /* Linking information structure */ + int isJab = 0; /* (Derived from li.mode & li.gmi) NZ if Jab link space */ + int in_curve_res = 0; /* Input profile A2B input curve resolution (if known) */ + int out_curve_res = 0; /* Output profile B2A output curve resolution (if known) */ + profxinf xpi; /* Extra profile information */ + int i; + + error_program = argv[0]; + check_if_not_interactive(); + memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */ + memset((void *)&li, 0, sizeof(clink)); + + /* Set defaults */ + li.verb = 0; + li.count = 0; + li.last = -1; + li.mode = 0; /* Default simple link mode */ + li.quality = 1; /* Medium quality */ + li.clutres = 0; /* No resolution override */ + li.nhack = 0; + li.cmyhack = 0; /* Mask for 100% purity through mapping of CMY */ + li.pcs2k = NULL; + li.wphack = 0; + li.wphacked = 0; + li.abs_luo = NULL; /* No abstract */ + li.xyzscale = 1.0; /* No XYZ scaling */ + li.hwp[0] = li.hwp[1] = li.hwp[2] = 0.0; + li.map = NULL; + li.Kmap = NULL; + li.in.intent = icmDefaultIntent; /* Default */ + li.in.ink.tlimit = -1.0; /* Default no total limit */ + li.in.ink.klimit = -1.0; /* Default no black limit */ + li.in.inking = 4; /* Inking algorithm default = ramp */ + li.in.locus = 0; /* Default K value target */ + li.in.nocurve = 0; /* Preserve device linearisation curve */ + li.in.lcurve = 0; /* Don't apply a Y to L* curve after device curve */ + li.out.intent = icmDefaultIntent; /* Default */ + li.out.ink.tlimit = -1.0; /* Default no total limit */ + li.out.ink.klimit = -1.0; /* Default no black limit */ + li.out.ink.KonlyLmin = 0; /* Use normal black Lmin for locus */ + li.out.ink.c.Ksmth = ICXINKDEFSMTH; /* default curve smoothing */ + li.out.ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + li.out.ink.x.Ksmth = ICXINKDEFSMTH; + li.out.ink.x.Kskew = ICXINKDEFSKEW; + li.out.inking = 4; /* Default ramp K */ + li.out.locus = 0; /* Default K value target */ + li.out.nocurve = 0; /* Preserve device linearisation curve */ + li.out.lcurve = 0; /* Don't apply an L* to Y curve before device curve */ + li.out.b2aluo = NULL; /* B2A lookup for inking == 7 */ + + xicc_enum_gmapintent(&li.gmi, icxDefaultGMIntent, NULL); /* Set default overall intent */ + + /* Init VC overrides so that we know when the've been set */ + ivc.Ev = -1; + ivc.Wxyz[0] = -1.0; ivc.Wxyz[1] = -1.0; ivc.Wxyz[2] = -1.0; + ivc.La = -1.0; + ivc.Yb = -1.0; + ivc.Lv = -1.0; + ivc.Yf = -1.0; + ivc.Fxyz[0] = -1.0; ivc.Fxyz[1] = -1.0; ivc.Fxyz[2] = -1.0; + + ovc.Ev = -1; + ovc.Wxyz[0] = -1.0; ovc.Wxyz[1] = -1.0; ovc.Wxyz[2] = -1.0; + ovc.La = -1.0; + ovc.Yb = -1.0; + ovc.Lv = -1.0; + ovc.Yf = -1.0; + ovc.Fxyz[0] = -1.0; ovc.Fxyz[1] = -1.0; ovc.Fxyz[2] = -1.0; + + if (argc < 4) + usage("Too few arguments, got %d expect at least 3",argc-1); + + /* Process the arguments */ + mfa = 3; /* Minimum final 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+mfa) < 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') { + li.verb = 1; + } + + /* Manufacturer description string */ + else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage("Expect argument to manufacturer description flag -A"); + xpi.deviceMfgDesc = na; + } + + /* Model description string */ + else if (argv[fa][1] == 'M') { + fa = nfa; + if (na == NULL) usage("Expect argument to model description flag -M"); + xpi.modelDesc = na; + } + + /* Profile Description */ + else if (argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage("Expect argument to profile description flag -D"); + xpi.profDesc = na; + } + + /* Copyright string */ + else if (argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage("Expect argument to copyright flag -C"); + xpi.copyright = na; + } + + /* Verify rather than link */ + else if (argv[fa][1] == 'V') + verify = 1; + + /* Disable profile per channel curve use in device link output */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + li.in.nocurve = 1; + li.out.nocurve = 1; + } + + /* Hack to force input neutrals to K only output */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + + if (argv[fa][1] == 'f') { + if (na != NULL) { /* XXXK -> XXXK hack */ + int j; + fa = nfa; + for (j = 0; ; j++) { + if (na[j] == '\000') + break; + if (na[j] == 'k' || na[j] == 'K') + li.nhack = 2; + else if (na[j] == 'c' || na[j] == 'C') + li.cmyhack |= 0x1; + else if (na[j] == 'm' || na[j] == 'M') + li.cmyhack |= 0x2; + else if (na[j] == 'y' || na[j] == 'Y') + li.cmyhack |= 0x4; + else + usage("Unexpected argument '%c' to -f flag",na[j]); + } + + } else { /* Neutral -> 000K hack */ + li.nhack = 1; + li.in.nocurve = 1; /* Disable input curve to preserve input equality */ + } + } else { + li.nhack = 3; /* All -> 000K Hack */ + } + } + + /* Quality */ + else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + fa = nfa; + if (na == NULL) usage("Quality flag (-q) needs an argument"); + switch (na[0]) { + case 'f': /* fast */ + case 'l': + case 'L': + li.quality = 0; + break; + case 'm': /* medium */ + case 'M': + li.quality = 1; + break; + case 's': /* slow */ + case 'h': + case 'H': + li.quality = 2; + break; + case 'u': /* ultra slow */ + case 'U': + li.quality = 3; + break; + default: + usage("Unrecognised quality flag (-q) argument '%c'",na[0]); +// usage("Unrecognised speed flag (-q) argument '%c'",na[0]); + } + } + + /* CLUT resolution override */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + int rr; + fa = nfa; + if (na == NULL) usage("Resolution flag (-r) needs an argument"); + rr = atoi(na); + if (rr < 1 || rr > 255) usage("Resolution flag (-r) argument out of range (%d)",rr); + li.clutres = rr; + } + + /* Abstract profile */ + else if (argv[fa][1] == 'p') { + if (na == NULL) usage("Expected abstract profile filename after -a"); + fa = nfa; + strncpy(abs_name,na,MAXNAMEL); abs_name[MAXNAMEL] = '\000'; + } + + /* Simple mode */ + else if (argv[fa][1] == 's') { + li.mode = 0; + modeset = 1; + } + + /* Maping mode */ + else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') { + li.mode = 1; + if (argv[fa][1] == 'G') { + li.mode = 2; + } + + if (na != NULL) { /* Found an optional source gamut */ + fa = nfa; + strncpy(sgam_name,na,MAXNAMEL); sgam_name[MAXNAMEL] = '\000'; + } + modeset = 1; + } + + /* White point hack */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + li.wphack = 1; + if (na != NULL) { // To a particular white point + fa = nfa; + if (sscanf(na, " %lf , %lf , %lf ",&li.hwp[0], &li.hwp[1], &li.hwp[2]) == 3) { + li.wphack = 2; + } else + usage("Couldn't parse hack white point (-w) value '%s'",na); + } + } + /* Input profile Intent or Mapping mode intent */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage("Input intent flag (-i) needs an argument"); + /* Record it for simple mode */ + switch (na[0]) { + case 'p': + case 'P': + li.in.intent = icPerceptual; + break; + case 'r': + case 'R': + li.in.intent = icRelativeColorimetric; + break; + case 's': + case 'S': + li.in.intent = icSaturation; + break; + case 'a': + case 'A': + li.in.intent = icAbsoluteColorimetric; + break; + default: + li.in.intent = icMaxEnumIntent; /* Detect error later */ + } + /* Record it for gamut mapping mode */ + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, na) == -999) + usage("Input intent (-i) argument '%s' isn't recognised",na); + intentset = 1; + } + + /* Output profile Intent */ + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage("Output intent flag (-o) needs an argument"); + switch (na[0]) { + case 'p': + case 'P': + li.out.intent = icPerceptual; + break; + case 'r': + case 'R': + li.out.intent = icRelativeColorimetric; + break; + case 's': + case 'S': + li.out.intent = icSaturation; + break; + case 'a': + case 'A': + li.out.intent = icAbsoluteColorimetric; + break; + default: + usage("Output intent (-o) argument '%s' not recognised",na); + } + } + + /* Viewing conditions */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C' + || argv[fa][1] == 'd' || argv[fa][1] == 'D') { + icxViewCond *vc; + + if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + vc = &ivc; + } else { + vc = &ovc; + } + + fa = nfa; + if (na == NULL) usage("Viewing conditions flag (-[cd]) needs an argument"); +#ifdef NEVER + if (na[0] >= '0' && na[0] <= '9') { + if (vc == &ivc) + ivc_e = atoi(na); + else + ovc_e = atoi(na); + } else +#endif + if (na[1] != ':') { + /* Enumerated condition index */ + if (vc == &ivc) { + if ((ivc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Unrecognised viewing condition enumeration '%s'",na); + } else { + if ((ovc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Unrecognised viewing condition enumeration '%s'",na); + } + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]s) missing ':'"); + if (na[2] == 'n' || na[2] == 'N') { + vc->Ev = vc_none; /* Automatic */ + } else if (na[2] == 'a' || na[2] == 'A') { + vc->Ev = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc->Ev = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc->Ev = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc->Ev = vc_cut_sheet; + } else + usage("Viewing condition (-[cd]) unrecognised surround '%c'",na[2]); + } 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->Wxyz[0] = x; vc->Wxyz[1] = y; + } else + usage("Viewing condition (-[cd]w) unrecognised white point '%s'",na+1); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]a) missing ':'"); + vc->La = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]b) missing ':'"); + vc->Yb = atof(na+2)/100.0; + } else if (na[0] == 'l' || na[0] == 'L') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]l) missing ':'"); + vc->Lv = 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->Fxyz[0] = x; vc->Fxyz[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc->Yf = x/100.0; + } else + usage("Viewing condition (-[cd]f) unrecognised flare '%s'",na+1); + } else + usage("Viewing condition (-[cd]) unrecognised sub flag '%c'",na[0]); + vcset = 1; /* Viewing conditions were set by user */ + } + + /* Inking rule */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + fa = nfa; + if (na == NULL) usage("Inking rule flag (-k) needs an argument"); + if (argv[fa][1] == 'k') + li.out.locus = 0; /* Use K value target */ + else + li.out.locus = 1; /* Use K locus target */ + switch (na[0]) { + case 't': + case 'T': + li.out.inking = 0; /* Use input K value for output */ + break; + case 'e': + case 'E': + li.out.inking = 7; /* Use output K value as guide */ + break; + case 'z': + case 'Z': + li.out.inking = 1; /* Use minimum k */ + break; + case 'h': + case 'H': + li.out.inking = 2; /* Use half k */ + break; + case 'x': + case 'X': + li.out.inking = 3; /* Use maximum k */ + break; + case 'r': + case 'R': + li.out.inking = 4; /* Use ramp k */ + break; + case 'p': + case 'P': + case 'q': + case 'Q': + li.out.inking = 5; /* Use curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kshap = atof(argv[fa]); + + if (na[0] == 'q' || na[0] == 'Q') { + li.out.inking = 6; /* Use transfer to dual curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kshap = atof(argv[fa]); + + } + break; + default: + usage("Inking rule (-k) unknown sub flag '%c'",na[0]); + } + outinkset = 1; /* The user set an inking */ + } + /* Input ink limits */ + else if (argv[fa][1] == 't') { + int tlimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -t"); + tlimit = atoi(na); + if (tlimit >= 0) + li.in.ink.tlimit = tlimit/100.0; + else + li.in.ink.tlimit = -1.0; + } + else if (argv[fa][1] == 'T') { + int klimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -T"); + klimit = atoi(na); + if (klimit >= 0) + li.in.ink.klimit = klimit/100.0; + else + li.in.ink.klimit = -1.0; + } + /* Output ink limits */ + else if (argv[fa][1] == 'l') { + int tlimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -l"); + tlimit = atoi(na); + if (tlimit >= 0) + li.out.ink.tlimit = tlimit/100.0; + else + li.out.ink.tlimit = -1.0; + if (li.mode < 2) /* Set minimum link mode */ + li.mode = 2; + } + else if (argv[fa][1] == 'L') { + int klimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -L"); + klimit = atoi(na); + if (klimit >= 0) + li.out.ink.klimit = klimit/100.0; + else + li.out.ink.klimit = -1.0; + if (li.mode < 2) /* Set minimum link mode */ + li.mode = 2; + } + + /* Gammut mapping diagnostic plots */ + else if (argv[fa][1] == 'P') + li.gamdiag = 1; + + else + usage("Unknown flag '%c'",argv[fa][1]); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Missing input profile"); + strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing output profile"); + strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing result profile"); + strncpy(link_name,argv[fa++],MAXNAMEL); link_name[MAXNAMEL] = '\000'; + + if (xpi.profDesc == NULL) + xpi.profDesc = link_name; /* Default description */ + + if (li.verb) + printf("Got options\n"); + + /* - - - - - - - - - - - - - - - - - - - */ +#ifndef ENKHACK /* Enable K hack code */ + warning("!!!!!! linkl/collink.c ENKHACK not enabled !!!!!!"); +#endif + /* - - - - - - - - - - - - - - - - - - - */ + /* Sanity checking/defaulting of options */ + + /* Deal with options that need link mode -g */ + if (li.mode < 1 + && (li.in.intent == icMaxEnumIntent /* User set a smart linking intent */ + || vcset /* Viewing conditions were set by user */ + || li.wphack)) { + if (modeset) { + if (li.in.intent == icMaxEnumIntent) + warning("Complex intent can't work with -s linking mode"); + else if (vcset) + warning("Viewing conditions are ignored with -s linking mode"); + else if (li.wphack) + warning("White point hack is ignored with -s linking mode"); + } else { + if (li.verb) { + if (li.in.intent == icMaxEnumIntent) + printf("Setting -g to enable Gamut Mapping mode intent\n"); + else if (vcset) + printf("Setting -g to enable viewing conditions\n"); + else if (li.wphack) + printf("Setting -g to enable white point hack\n"); + } + li.mode = 1; + } + } + + /* Deal with options that need link mode -G */ + if (li.mode < 2 + && (outinkset /* The user set a K inking rule */ + || li.out.ink.tlimit >= 0.0 /* The user set an output total limit */ + || li.out.ink.klimit >= 0.0)) { /* The user set an output black limit */ + if (modeset) { + if (outinkset) + warning("Black inking can't work with -s or -g linking mode"); + else if (li.out.ink.tlimit >= 0.0 || li.out.ink.klimit >= 0.0) + warning("Ink limiting can't work with -s linking mode"); + } else { + if (li.verb) { + if (outinkset) + printf("Setting -G to enable black inking\n"); + else if (li.out.ink.tlimit >= 0.0 || li.out.ink.klimit >= 0.0) + printf("Setting -G to enable ink limiting\n"); + } + li.mode = 2; + } + } + + /* Deal with options that complement -f -F */ + if (li.nhack || li.cmyhack) { + + /* Ideally we need to set K inking and map to K only black point, which require -G mode */ + if (li.mode < 2) { + if (li.nhack == 1) { /* All neutrals to K only */ + if (modeset) { + warning("-f will give best result with -G mode"); + } else { + if (li.verb) + printf("Setting -G mode to complement -f option\n"); + li.mode = 2; + } + } else if (li.nhack == 2) { /* K only in to K only out */ + if (modeset) { + warning("For better results use -G mode with -fk option"); + } else { + if (li.verb) + printf("Setting -G mode to complement -fk option\n"); + li.mode = 2; + } + } else if (li.nhack == 3) { /* All to K only out */ + if (modeset) { + warning("For better results use -G mode with -F option"); + } else { + if (li.verb) + printf("Setting -G mode to complement -F option\n"); + li.mode = 2; + } + } + if (li.cmyhack != 0) { /* Map pure 100% CMY to pure CMY */ + if (modeset) { + warning("For better results use -G mode with -fcmy options"); + } else { + if (li.verb) + printf("Setting -G mode to complement -fcmy options\n"); + li.mode = 2; + } + } + } + + /* Ideally we should use an appropriate K inking */ + if (li.mode >= 2) { /* Gammut mapping mode */ + if (li.nhack == 1 && li.out.inking != 3) { /* All neutrals to K only */ + if (outinkset) { + warning("For better results use -kx with -f option"); + } else { + if (li.verb) + printf("Setting -kx to complement -f option\n"); + li.out.inking = 3; /* Use maximum K */ + } + } else if (li.nhack == 2 && li.out.inking != 0) { /* K only in to K only out */ + if (outinkset) { + warning("For better results use -kt with -fk option"); + } else { + if (li.verb) + printf("Setting -kt to complement -fk option\n"); + li.out.inking = 0; /* Use input K value for output */ + } + } else if (li.nhack == 3 && li.out.inking != 3) { /* All colors to K only */ + if (modeset) { + warning("For better results use -kx with -F option"); + } else { + if (li.verb) + printf("Setting -kx to complement -f option\n"); + li.out.inking = 3; /* Use maximum K */ + } + } + } + + /* Ideally we should use an appropriate gamut mapping */ + if (li.mode >= 1) { /* Gammut mapping mode */ + + if (li.gmi.usemap == 0 || li.gmi.greymf < 1.0 /* Not mapping black point */ + || li.gmi.glumbcpf < 1.0 || li.gmi.glumbexf < 1.0) { + if (li.nhack == 1) { /* All neutrals to K only */ + if (intentset) { + warning("For better results use an intent that maps black point with -f option"); + } else { + if (li.verb) + printf("Setting -ip intent to complement -f option\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "p") == -999) + usage("Internal, intent 'p' isn't recognised"); + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + } else if (li.nhack == 3) { /* All to K only out */ + if (intentset) { + warning("For better results use an intent that maps black point with -F option"); + } else { + if (li.verb) + printf("Setting -ip intent to complement -F option\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "p") == -999) + usage("Internal, intent 'p' isn't recognised"); + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + } + + /* Got an appropriate intent, so set mapping to K only black point */ + } else if (li.nhack == 1 || li.nhack == 3) { + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + if (li.cmyhack != 0) { /* Map pure 100% CMY to pure CMY */ + if (intentset) { + if (strcmp(li.gmi.as, "s") != 0) + warning("For better results use -is with -fcmy options"); + } else { + if (li.verb) + printf("Setting -is intent to complement -fcmy options\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "s") == -999) + usage("Internal, intent 's' isn't recognised"); + li.dst_cmymap = li.cmyhack; + } + } + } + } + + if (li.mode == 0) { + if (li.in.intent == icMaxEnumIntent) + usage("Input intent (-i) argument isn't recognised for simple mapping mode"); + } + + if (li.wphack && (li.gmi.usecas & 0x100) != 0) + usage("Can't use 'white point hack' and Luminence scaling intent together"); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the input device profile for reading, and read header etc. */ + if ((li.in.c = read_embedded_icc(in_name)) == NULL) + error ("Can't open file '%s'",in_name); + li.in.h = li.in.c->header; + + /* Check that it is a suitable device input icc */ + if (li.in.h->deviceClass != icSigInputClass + && li.in.h->deviceClass != icSigDisplayClass + && li.in.h->deviceClass != icSigOutputClass + && li.in.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ + error("Input profile '%s' isn't a device profile",in_name); + + /* Wrap with an expanded icc */ + if ((li.in.x = new_xicc(li.in.c)) == NULL) + error ("Creation of input profile xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(li.in.x, &li.in.ink.tlimit, li.in.ink.tlimit, &li.in.ink.klimit, li.in.ink.klimit); + + if (li.verb) { + if (li.in.ink.tlimit >= 0.0) + printf("Input total ink limit assumed is %3.0f%%\n",100.0 * li.in.ink.tlimit); + if (li.in.ink.klimit >= 0.0) + printf("Input black ink limit assumed is %3.0f%%\n",100.0 * li.in.ink.klimit); + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the abstract profile if requested */ + if (abs_name[0] != '\000') { + if ((li.abs_fp = new_icmFileStd_name(abs_name,"r")) == NULL) + error ("Can't open abstract profile file '%s'",abs_name); + + if ((li.abs_icc = new_icc()) == NULL) + error ("Creation of Abstract profile ICC object failed"); + + /* Read header etc. */ + if ((rv = li.abs_icc->read(li.abs_icc,li.abs_fp,0)) != 0) + error ("%d, %s",rv,li.abs_icc->err); + + if (li.abs_icc->header->deviceClass != icSigAbstractClass) + error("Abstract profile isn't an abstract profile"); + + /* Take intended abstract intent from profile itself */ + if ((li.abs_intent = li.abs_icc->header->renderingIntent) != icAbsoluteColorimetric) + li.abs_intent = icRelativeColorimetric; + + /* Wrap with an expanded icc */ + if ((li.abs_xicc = new_xicc(li.abs_icc)) == NULL) + error ("Creation of abstract profile xicc failed"); + } + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the output device output profile for reading, and read header etc. */ + if ((li.out.c = read_embedded_icc(out_name)) == NULL) + error ("Can't open file '%s'",out_name); + li.out.h = li.out.c->header; + + if (li.out.h->deviceClass != icSigInputClass + && li.out.h->deviceClass != icSigDisplayClass + && li.out.h->deviceClass != icSigOutputClass + && li.out.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ + error("Output profile isn't a device profile"); + + /* Wrap with an expanded icc */ + if ((li.out.x = new_xicc(li.out.c)) == NULL) + error ("Creation of output profile xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(li.out.x, &li.out.ink.tlimit, li.out.ink.tlimit, &li.out.ink.klimit, li.out.ink.klimit); + + if (li.verb) { + if (li.out.ink.tlimit >= 0.0) + printf("Output total ink limit assumed is %3.0f%%\n",100.0 * li.out.ink.tlimit); + if (li.out.ink.klimit >= 0.0) + printf("Output black ink limit assumed is %3.0f%%\n",100.0 * li.out.ink.klimit); + } + + /* deal with output black generation. */ + /* Ink limits will have been set in option parsing */ + + switch (li.out.inking) { + case 0: /* Use input profile K level or locus */ + /* Sanity check */ + if (li.in.h->colorSpace != li.out.h->colorSpace) + error("Can't transfer black ink in & out unless the same colorspaces"); + li.out.ink.k_rule = li.out.locus ? icxKlocus : icxKvalue; /* Given as aux parameter in PCS -> Device */ + break; + case 7: /* Use output profile K level or locus */ + li.out.ink.k_rule = li.out.locus ? icxKlocus : icxKvalue; /* Given as aux parameter in PCS -> Device */ + break; + case 1: /* Minimum K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 0.0; + li.out.ink.c.Kshap = 1.0; + break; + case 2: /* 0.5 K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.5; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 0.5; + li.out.ink.c.Kshap = 1.0; + break; + case 3: /* Maximum K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 1.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 1.0; + li.out.ink.c.Kshap = 1.0; + break; + case 4: /* Ramp K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 1.0; + li.out.ink.c.Kshap = 1.0; + break; + case 5: /* Curve */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + break; /* Other params already set by options */ + case 6: /* Use input profile K locus + dual curve limits */ + /* Sanity check */ + if (li.in.h->colorSpace != li.out.h->colorSpace) + error("Can't transfer black ink in & out unless the same colorspaces"); + li.out.ink.k_rule = li.out.locus ? icxKl5l : icxKl5lk; /* Aux param in PCS -> Device */ + break; /* Other params already set by options */ + } + + for (i = 0; i < 2; i++) { + xicc *x; + icxViewCond *v, *vc; + int es; + + if (i == 0) { + v = &ivc; /* Override parameters */ + vc = &li.in.vc; /* Target parameters */ + es = ivc_e; + x = li.in.x; /* xicc */ + } else { + v = &ovc; /* Override parameters */ + vc = &li.out.vc; /* Target parameters */ + es = ovc_e; + x = li.out.x; /* xicc */ + } + + /* Set the default viewing conditions */ + xicc_enum_viewcond(x, vc, -1, NULL, 0, NULL); + + /* Override the default viewing conditions. */ + /* (?? Could move this code into xicc_enum_viewcond() as an option ??) */ + /* First any enumerated selection */ + if (es != -1) { + if (xicc_enum_viewcond(x, vc, es, NULL, 0, NULL) == -999) + error ("%d, %s",x->errc, x->err); + } + /* Then any individual paramaters */ + if (v->Ev >= 0) + vc->Ev = v->Ev; + if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] > 0.0 && v->Wxyz[2] >= 0.0) { + /* Normalise XYZ to current media white */ + vc->Wxyz[0] = v->Wxyz[0]/v->Wxyz[1] * vc->Wxyz[1]; + vc->Wxyz[2] = v->Wxyz[2]/v->Wxyz[1] * vc->Wxyz[1]; + } + if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] >= 0.0 && v->Wxyz[2] < 0.0) { + /* Convert Yxy to XYZ */ + double x = v->Wxyz[0]; + double y = v->Wxyz[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 (v->La >= 0.0) + vc->La = v->La; + if (v->Yb >= 0.0) + vc->Yb = v->Yb; + if (v->Lv >= 0.0) + vc->Lv = v->Lv; + if (v->Yf >= 0.0) + vc->Yf = v->Yf; + if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] > 0.0 && v->Fxyz[2] >= 0.0) { + /* Normalise XYZ to current media white */ + vc->Fxyz[0] = v->Fxyz[0]/v->Fxyz[1] * vc->Fxyz[1]; + vc->Fxyz[2] = v->Fxyz[2]/v->Fxyz[1] * vc->Fxyz[1]; + } + if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] >= 0.0 && v->Fxyz[2] < 0.0) { + /* Convert Yxy to XYZ */ + double x = v->Fxyz[0]; + double y = v->Fxyz[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 (li.verb) + printf("Configured options\n"); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Setup the profile color lookup information */ + { + icmLuAlgType oalg; /* Native output algorithm */ + icColorSpaceSignature natpcs; /* Underlying native output PCS */ + int flb = 0, fl = 0; /* luobj flags */ + + li.pcsor = icSigLabData; /* Default use Lab as PCS */ + + /* If we are using the gamut map mode, then setup */ + /* the intents and pcsor appropriately. */ + if (li.mode > 0) { + + if ((li.gmi.usecas & 0xff) != 0) { + li.pcsor = icxSigJabData; /* Use CAM as PCS */ + isJab = 1; + + if ((li.gmi.usecas & 0xff) == 0x2) { /* Absolute Appearance space */ + double mxw; + + li.in.intent = li.out.intent = li.abs_intent = icxAbsAppearance; + + /* Make absolute common white point average between the two */ + li.in.vc.Wxyz[0] = 0.5 * (li.in.vc.Wxyz[0] + li.out.vc.Wxyz[0]); + li.in.vc.Wxyz[1] = 0.5 * (li.in.vc.Wxyz[1] + li.out.vc.Wxyz[1]); + li.in.vc.Wxyz[2] = 0.5 * (li.in.vc.Wxyz[2] + li.out.vc.Wxyz[2]); + + /* And scale it Y to be equal to 1.0 */ + mxw = 1.0/li.in.vc.Wxyz[1]; + li.in.vc.Wxyz[0] *= mxw; + li.in.vc.Wxyz[1] *= mxw; + li.in.vc.Wxyz[2] *= mxw; + + /* Set the output vc to be the same as the input */ + li.out.vc = li.in.vc; /* Structure copy */ + } else { + /* Not Abs Appearance space */ + li.in.intent = li.out.intent = li.abs_intent = icxAppearance; + } + } else { + /* Not Appearance space */ + li.in.intent = li.out.intent = li.abs_intent = icAbsoluteColorimetric; + } + } + + if (li.verb) + printf("Loading input A2B table\n"); + + /* default flags for all xicc luobj's */ + flb = ICX_CLIP_NEAREST; + if (li.verb) + flb |= ICX_VERBOSE; + + /* Get an input profile xicc conversion object */ + fl = flb; +#ifdef USE_MERGE_CLUT_OPT + fl |= ICX_MERGE_CLUT; +#endif + +#ifdef NEVER + printf("~1 input space flags = 0x%x\n",fl); + printf("~1 input space intent = %s\n",icx2str(icmRenderingIntent,li.in.intent)); + printf("~1 input space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor)); + printf("~1 input space viewing conditions =\n"); xicc_dump_viewcond(&li.in.vc); + printf("~1 input space inking =\n"); xicc_dump_inking(&li.in.ink); +#endif + if ((li.in.luo = li.in.x->get_luobj(li.in.x, fl, icmFwd, li.in.intent, + li.pcsor, icmLuOrdNorm, &li.in.vc, &li.in.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.in.x->errc,li.in.x->err); + } + + /* Get details of overall conversion */ + li.in.luo->spaces(li.in.luo, &li.in.csp, &li.in.chan, NULL, NULL, &li.in.alg, + NULL, NULL, NULL); + + /* Get the input profile A2B input curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.in.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.in.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + in_curve_res = lut->inputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.in.luo->efv_wh_bk_points(li.in.luo, li.in.wp, NULL, NULL); + + /* Get native PCS space */ + li.in.luo->lutspaces(li.in.luo, NULL, NULL, &natpcs, NULL, NULL); + + if (li.in.nocurve == 0 && natpcs == icSigXYZData + && (li.in.alg == icmMatrixFwdType || li.in.alg == icmMatrixBwdType + || li.in.csp == icSigXYZData)) { + li.in.lcurve = 1; /* Use Y to L* and L* to Y for input */ + if (li.in.csp == icSigXYZData) { + li.in.lcurve = 2; /* Use real Y to L* and L* to Y for input */ + li.in.nocurve = 1; /* Don't trust the curve that comes with it */ + } + if (li.verb) + printf("Using Y to L* and L* to Y curves for input\n"); + } + + /* Setup any abstract profile to match the chosen PCS */ + /* We aren't checking whether the input/abstract/output profile */ + /* intents really make any sense. It's assumed at the moment */ + /* that the user knows what they're doing! */ + if (abs_name[0] != '\000') { + + if ((li.abs_luo = li.abs_xicc->get_luobj(li.abs_xicc, flb, icmFwd, li.abs_intent, + li.pcsor, icmLuOrdNorm, &li.out.vc, NULL)) == NULL) + error ("%d, %s",li.abs_icc->errc, li.abs_icc->err); + } + + // Figure out whether the output profile is a Lut profile or not */ + { + icmLuBase *plu; + + /* Get temporary icm lookup object */ + /* (Use Fwd just in case profile is missing B2A !!!!) */ + if ((plu = li.out.c->get_luobj(li.out.c, icmFwd, icmDefaultIntent, icmSigDefaultData, + icmLuOrdNorm)) == NULL) { + error("get icm lookup object failed: on '%s' %d, %s",out_name,li.out.c->errc,li.out.c->err); + } + + /* Check what the algorithm is */ + plu->spaces(plu, NULL, NULL, NULL, NULL, &oalg, NULL, NULL, NULL, NULL); + + /* release the icm lookup */ + plu->del(plu); + + } + + if (oalg != icmLutType || li.mode < 2) { /* Using B2A table or inv. mono/matrix */ + if (li.verb) + printf("Loading output B2A table\n"); + + if ((li.out.luo = li.out.x->get_luobj(li.out.x, flb, icmBwd, li.out.intent, + li.pcsor, icmLuOrdNorm, &li.out.vc, &li.out.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + /* Get details of overall conversion */ + li.out.luo->spaces(li.out.luo, NULL, NULL, NULL, &li.out.chan, &li.out.alg, + NULL, NULL, NULL); + + /* Get the output profile B2A output curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.out.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.out.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + out_curve_res = lut->outputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.out.luo->efv_wh_bk_points(li.out.luo, li.out.wp, NULL, NULL); + + /* Get native PCS space */ + li.out.luo->lutspaces(li.out.luo, &natpcs, NULL, NULL, NULL, NULL); + + } else { /* Using inverse A2B Lut for output conversion */ + + fl = flb; +#ifdef USE_MERGE_CLUT_OPT + fl |= ICX_MERGE_CLUT; +#endif +#ifdef USE_CAM_CLIP_OPT + fl |= ICX_CAM_CLIP; +#endif + if (li.verb) + printf("Loading output inverse A2B table\n"); + +#ifdef NEVER + printf("~1 output space flags = 0x%x\n",fl); + printf("~1 output space intent = %s\n",icx2str(icmRenderingIntent,li.out.intent)); + printf("~1 output space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor)); + printf("~1 output space viewing conditions =\n"); xicc_dump_viewcond(&li.out.vc); + printf("~1 output space inking =\n"); xicc_dump_inking(&li.out.ink); +#endif + + if ((li.out.luo = li.out.x->get_luobj(li.out.x, fl, icmFwd, + li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, + &li.out.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + + /* Get details of overall conversion */ + li.out.luo->spaces(li.out.luo, &li.out.csp, &li.out.chan, NULL, NULL, &li.out.alg, + NULL, NULL, NULL); + + /* Get the output profile A2B input curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.out.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.out.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + out_curve_res = lut->inputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.out.luo->efv_wh_bk_points(li.out.luo, li.out.wp, NULL, NULL); + + /* Get native PCS space */ + li.out.luo->lutspaces(li.out.luo, NULL, NULL, &natpcs, NULL, NULL); + + /* If we need a B2A lookup to get the existing K */ + if (li.out.inking == 7) { + if ((li.out.b2aluo = li.out.x->get_luobj(li.out.x, flb, icmBwd, + li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, NULL)) == NULL) { + error("get B2A xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + } + } + + /* If we need an PCS->K' mapping for the neutral axis to K hack. */ + /* What we do is lookup the L for K values from 0 to 1, */ + /* and then invert this to create an L to K lookup. */ + /* If the gamut mapping is set to map to the K only black point, */ + /* it should all work well... */ + if (li.nhack) { + icxLuBase *luo; /* Base XLookup type object */ + icmLuAlgType alg; /* Type of lookup algorithm */ + co ips[256]; /* Initialisation points */ + datai glow; /* Grid low scale */ + datai ghigh; /* Grid high scale */ + datao vlow; /* Data value low normalize */ + datao vhigh; /* Data value high normalize */ + double Lmax, Lmin; /* Max and Min L values that result */ + int grres; + double avgdev[MXDO]; + + if (li.out.h->colorSpace != icSigCmykData) + error("Neutral Axis K only requested with non CMYK output profile"); + + if (li.in.chan < 3) + error("Neutral Axis K only requested with input profile with less than 3 channels"); + + if (li.nhack == 2 && li.in.h->colorSpace != icSigCmykData) + error("Neutral Axis 000K only requested with input profile that is not CMYK"); + + if ((li.pcs2k = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + error("Failed to create an rspl object"); + } + + /* Get a device to PCS lookup object to use to lookup K->PCS */ + if ((luo = li.out.x->get_luobj(li.out.x, flb, + icmFwd, li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, + NULL)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + /* Get details of overall conversion */ + luo->spaces(luo, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL); + if (alg != icmLutType) + error ("Unexpected algorithm type for CMYK output profile"); + + /* Setup the initialisation points */ + Lmax = -100.0; + Lmin = 1000.0; + for (i = 0; i < 256; i++) { + icxLuLut *lu = (icxLuLut *)luo; /* Safe to coerce */ + double in[4], pcsv[4]; + in[0] = in[1] = in[2] = 0.0; + in[3] = i/(255.0); + + /* Want to do dev' -> PCS conversion to match the */ + /* normal inverse PCS-> dev' used in devip_devop() */ + if (li.out.nocurve) { /* No explicit curve, so do implicit here */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + lu->input(lu, pcsv, in); + lu->clut(lu, pcsv, pcsv); + } else { + lu->clut(lu, pcsv, in); + } + lu->output(lu, pcsv, pcsv); + lu->out_abs(lu, pcsv, pcsv); + + /* We force the rspl to be a forward conversion by swapping K and PCS */ + ips[i].p[0] = pcsv[0]; /* PCS as input */ + ips[i].v[0] = in[3]; /* K as output */ +#ifdef NEUTKDEBUG + printf("L %f -> K' %f\n",pcsv[0], in[3]); +#endif /* NEUTKDEBUG */ + + if (pcsv[0] > Lmax) /* Track min and max L values */ + Lmax = pcsv[0]; + if (pcsv[0] < Lmin) + Lmin = pcsv[0]; + } + + glow[0] = 0.0; + ghigh[0] = 100.0; + vlow[0] = 0.0; + vhigh[0] = 1.0; + grres = 256; + avgdev[0] = 0.005; + + li.pcs2k->fit_rspl(li.pcs2k, 0, ips, 256, glow, ghigh, &grres, vlow, vhigh, 1.0, avgdev, NULL); + + /* Fixup the white and black points for neutral axis to K hack. */ + /* This is to make sure that they exactly match the fwd mapping */ + /* after the rspl is fitted. */ + { + pcs2k_ctx cx; /* White point fixup context */ + co pp; /* Lookup the min and max K values */ + + pp.p[0] = Lmax; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmin = pp.v[0]; /* Ideally would be 0 */ + pp.p[0] = Lmin; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmax = pp.v[0]; /* Ideally would be 1 */ + +#ifdef NEUTKDEBUG + printf("Before fix: Lmax %f, Lmin %f, Kmin %f, Kmax %f\n",Lmax, Lmin, cx.kmin, cx.kmax); +#endif /* NEUTKDEBUG */ + + li.pcs2k->re_set_rspl(li.pcs2k, 0, (void *)&cx, fix_pcs2k_white); +#ifdef NEUTKDEBUG + pp.p[0] = Lmax; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmin = pp.v[0]; /* Ideally would be 0 */ + pp.p[0] = Lmin; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmax = pp.v[0]; /* Ideally would be 1 */ + printf("After fix: Lmax %f, Lmin %f, Kmin %f, Kmax %f\n",Lmax, Lmin, cx.kmin, cx.kmax); +#endif /* NEUTKDEBUG */ + } + + } /* end if neutral axis to K hack */ + + if (li.cmyhack != 0) { + if (li.in.h->colorSpace != icSigCmyData + && li.in.h->colorSpace != icSigCmykData) + error("100% CMY mapping requested with non CMY or CMYK input profile"); + + if (li.out.h->colorSpace != icSigCmyData + && li.out.h->colorSpace != icSigCmykData) + error("100% CMY mapping requested with non CMY or CMYK output profile"); + } + + if (li.out.nocurve == 0 && natpcs == icSigXYZData + && (li.out.alg == icmMatrixFwdType || li.out.alg == icmMatrixBwdType + || li.out.csp == icSigXYZData)) { + li.out.lcurve = 1; /* Use Y to L* and L* to Y for output */ + if (li.out.csp == icSigXYZData) { + li.out.lcurve = 2; /* Use real Y to L* and L* to Y for output */ + li.out.nocurve = 1; /* Don't trust the curve that comes with it */ + } + if (li.verb) + printf("Using Y to L* and L* to Y curves for output\n"); + } + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Setup the gamut mapping */ +// ~~~~ need to account for possible abstract profile after source !!!! +// ~~~~ also need to fix tiffgamut to allow for abstract profile !!!! + + if (li.verb) + printf("Gamut mapping mode is '%s'\n",li.mode == 0 ? "Simple" : li.mode == 1 ? "Mapping" : "Mapping inverse A2B"); + if (li.verb && li.mode > 0) + printf("Gamut mapping intent is '%s'\n",li.gmi.desc); + + /* In gamut mapping mode, the PCS used will always be absolute */ + /* intent from the input and output profiles, and either */ + /* lab or Jab space, with the given in/out viewing conditions */ + /* for the latter. The xluo->get_gamut work in the set li.pcsor */ + /* for each xluo. */ + if (li.mode > 0 && li.gmi.usemap) { + gamut *csgam, *igam, *ogam; + double sgres; /* Source gamut surface feature resolution */ + double dgres; /* Destination gamut surface feature resolution */ + int mapres; /* Mapping rspl resolution */ + + if (li.verb) + printf("Creating Gamut Mapping\n"); + + /* Gamut mapping will extend given grid res to encompas */ + /* source gamut by a margin. */ + if (li.quality == 3) { /* Ultra High */ + sgres = 7.0; + dgres = 7.0; + mapres = 41; + } else if (li.quality == 2) { /* High */ + sgres = 8.0; + dgres = 8.0; + mapres = 33; + } else if (li.quality == 1) { /* Medium */ + sgres = 10.0; + dgres = 10.0; + mapres = 25; + } else { /* Low quality */ + sgres = 12.0; + dgres = 12.0; + mapres = 17; + } + + /* Creat the source colorspace gamut surface */ + if (li.verb) + printf(" Finding Source Colorspace Gamut with res %f\n",sgres); + + /* Creat the source image gamut surface in the selected li.pcsor space */ + if ((csgam = li.in.luo->get_gamut(li.in.luo, sgres)) == NULL) + error ("%d, %s",li.in.x->errc, li.in.x->err); + + /* Grab a given source image gamut. */ + if (sgam_name[0] != '\000') { /* Optional source gamut - ie. from an images */ + + if (li.verb) + printf(" Loading Image Source Gamut '%s'\n",sgam_name); + + igam = new_gamut(sgres, isJab, 0); /* isJab will be overriden by gamut file */ + + if (igam->read_gam(igam, sgam_name)) + error("Reading source gamut '%s' failed",sgam_name); + + if (igam->getisjab(igam) != isJab) { + /* Should really convert to/from Jab here! */ + warning("Image gamut is wrong colorspace for link (Lab != Jab)"); + + /* This will actually error in the gamut mapping code */ + /* Note that we're not checking relative/absolute colorspace here. */ + /* At the moment it's up to the user to get this right. */ + } + + } else { + igam = NULL; /* NULL signals no source image gamut */ + } + + /* Creat the destination gamut surface */ + if (li.verb) + printf(" Finding Destination Gamut with res %f\n",dgres); + + if ((ogam = li.out.luo->get_gamut(li.out.luo, dgres)) == NULL) + error ("%d, %s",li.out.x->errc, li.out.x->err); + + if (li.verb) + printf(" Creating Gamut match\n"); + + li.map = new_gammap(li.verb, csgam, igam, ogam, &li.gmi, + li.src_kbp, li.dst_kbp, li.cmyhack, li.rel_oride, + mapres, NULL, NULL, li.gamdiag ? "gammap.wrl" : NULL + ); + if (li.map == NULL) + error ("Failed to make gamut map transform"); + + if (li.nhack == 2) { + if (li.verb) + printf(" Creating K only black to K only black Gamut match\n"); + + li.Kmap = new_gammap(li.verb, csgam, igam, ogam, &li.gmi, + 1, 1, li.cmyhack, li.rel_oride, + mapres, NULL, NULL, li.gamdiag ? "gammap.wrl" : NULL + ); + if (li.Kmap == NULL) + error ("Failed to make K only gamut map transform"); + } + + ogam->del(ogam); + if (igam != NULL) + igam->del(igam); + csgam->del(csgam); + } + + /* If we've got a request for Absolute Appearance mode with scaling */ + /* to avoid clipping the source white point, compute the needed XYZ scaling factor. */ + /* We assume that the white point hack can't be used at the same time. */ + if (li.mode > 0 && li.wphack == 0 && (li.gmi.usecas & 0x100) != 0) { + double xyzscale[1], sa[1]; + + /* We already have the source space white point in li.in.wp[] */ + + /* Convert it to destination XYZ */ + if (li.pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + li.out.luo->cam->cam_to_XYZ(li.out.luo->cam, li.swxyz, li.in.wp); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("~1 Source white Jab = %f %f %f\n", li.in.wp[0], li.in.wp[1], li.in.wp[2]); +//printf("~1 Source white XYZ = %f %f %f\n", li.swxyz[0], li.swxyz[1], li.swxyz[2]); + + /* Compute the bigest scale factor less than or equal to 1.0, */ + /* that doesn't clip the li.swxyz[] on the destination gamut */ + sa[0] = 0.1; + xyzscale[0] = 0.5; + if (powell(NULL, 1, xyzscale, sa, 1e-6, 2000, xyzoptfunc, (void *)&li, NULL, NULL) != 0) { + warning("set_icxLuLut: XYZ scale powell failed to converge - set scale to 1.0"); + } else { + li.xyzscale = xyzscale[0]; + if (li.verb) + printf("Set XYZ scale factor to %f\n",li.xyzscale); + } + } + + /* Create the link profile */ + if (verify == 0) { + icmFile *wr_fp; + icc *wr_icc; + + if (li.verb) + printf("Creating link profile\n"); + + /* Open up the link file for writing */ + if ((wr_fp = new_icmFileStd_name(link_name,"w")) == NULL) + error ("Write: Can't open file '%s'",link_name); + + if ((wr_icc = new_icc()) == NULL) + error ("Write: Creation of ICC object failed"); + + /* Add all the tags required */ + + /* The header: */ + { + icmHeader *wh = wr_icc->header; + + /* Values that must be set before writing */ + wh->deviceClass = icSigLinkClass; /* We are creating a link ! */ + wh->colorSpace = li.in.h->colorSpace; /* Input profile device space */ + wh->pcs = li.out.h->colorSpace; /* Output profile device space */ + if (li.mode > 0) { + wh->renderingIntent = li.gmi.icci; /* Closest ICC intent */ + } else { + wh->renderingIntent = li.out.intent; /* Output intent chosen */ + } + + /* Values that should be set before writing */ + if (xpi.manufacturer != 0L) + wh->manufacturer = xpi.manufacturer; + else + wh->manufacturer = icmSigUnknownType; + + if (xpi.model != 0L) + wh->model = xpi.model; + else + wh->model = icmSigUnknownType; + + /* Values that may be set before writing */ + if (xpi.creator != 0L) + wh->creator = xpi.creator; + + wh->attributes.l = 0; + wh->flags = 0; +#ifdef NT + wh->platform = icSigMicrosoft; +#endif +#ifdef __APPLE__ + wh->platform = icSigMacintosh; +#endif +#if defined(UNIX) && !defined(__APPLE__) + wh->platform = icmSig_nix; +#endif + } + /* Profile Description Tag: */ + { + icmTextDescription *wo; + char *dst, dstm[200]; /* description */ + + if (xpi.profDesc != NULL) + dst = xpi.profDesc; + else { + dst = "Device Link profile - See ProfileSequenceDescTag for more information"; + dst = dstm; + } + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* Copyright Tag: */ + { + icmText *wo; + char *crt; + + if (xpi.copyright != NULL) + crt = xpi.copyright; + else + crt = "Copyright, the creator of this profile"; + + if ((wo = (icmText *)wr_icc->add_tag( + wr_icc, icSigCopyrightTag, icSigTextType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->data, crt); /* Copy the text in */ + } + /* Device Manufacturers Description Tag: */ + if (xpi.deviceMfgDesc != NULL) { + icmTextDescription *wo; + char *dst = xpi.deviceMfgDesc; + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigDeviceMfgDescTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* Model Description Tag: */ + if (xpi.modelDesc != NULL) { + icmTextDescription *wo; + char *dst = xpi.modelDesc; + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigDeviceModelDescTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* ProfileSequenceDescTag: */ + { + unsigned int i; + icmProfileSequenceDesc *wo; + if ((wo = (icmProfileSequenceDesc *)wr_icc->add_tag( + wr_icc, icSigProfileSequenceDescTag, icSigProfileSequenceDescType)) == NULL) + return 1; + + wo->count = 2; /* Number of descriptions in sequence */ + if (wo->allocate((icmBase *)wo) != 0) /* Allocate space for all the DescStructures */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Fill in each description structure in sequence */ + + /* For each profile in the chain */ + for (i = 0; i < wo->count; i++) { + icc *iccs = NULL; + icmHeader *sh = NULL; + icmSignature *tsig; + icmTextDescription *ddesc; + icmTextDescription *mdesc; + + if (i == 0) { + iccs = li.in.c; /* Input profile */ + sh = li.in.h; /* Input profile header */ + } else if (i == (wo->count-1)) { + iccs = li.out.c; /* Output profile */ + sh = li.out.h; /* Output profile header */ + } else { + error("Abstract profiles in link not implemented yet!"); + } + + /* Try and read the technology tag */ + if ((tsig = (icmSignature *)iccs->read_tag(iccs, icSigTechnologyTag)) != NULL) { + if (tsig->ttype != icSigSignatureType) /* oops */ + tsig = NULL; + } + + /* Try and read the Device Manufacturers Description Tag */ + if ((ddesc = (icmTextDescription *)iccs->read_tag( + iccs, icSigDeviceMfgDescTag)) != NULL) { + if (ddesc->ttype != icSigTextDescriptionType) /* oops */ + ddesc = NULL; + } + + /* Try and read the Model Manufacturers Description Tag */ + if ((mdesc = (icmTextDescription *)iccs->read_tag( + iccs, icSigDeviceModelDescTag)) != NULL) { + if (mdesc->ttype != icSigTextDescriptionType) /* oops */ + mdesc = NULL; + } + + /* Header information */ + wo->data[i].deviceMfg = sh->manufacturer; + wo->data[i].deviceModel = sh->model; + wo->data[i].attributes = sh->attributes; + + /* Technology signature */ + if (tsig != NULL) + wo->data[i].technology = tsig->sig; + + if (ddesc != NULL) { + wo->data[i].device.size = ddesc->size; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + strcpy(wo->data[i].device.desc, ddesc->desc); + + wo->data[i].device.ucLangCode = ddesc->ucLangCode; + wo->data[i].device.ucSize = ddesc->ucSize; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + memmove(wo->data[i].device.ucDesc, ddesc->ucDesc, 2 * ddesc->ucSize); + + wo->data[i].device.scCode = ddesc->scCode; + wo->data[i].device.scSize = ddesc->scSize; + strcpy((char *)wo->data[i].device.scDesc, (char *)ddesc->scDesc); + } + + /* model Text description */ + if (mdesc != NULL) { + wo->data[i].model.size = mdesc->size; + if (wo->data[i].allocate(&wo->data[i])!= 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + strcpy(wo->data[i].model.desc, mdesc->desc); + + wo->data[i].model.ucLangCode = mdesc->ucLangCode; + wo->data[i].model.ucSize = mdesc->ucSize; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + memmove(wo->data[i].model.ucDesc, mdesc->ucDesc, 2 * mdesc->ucSize); + + wo->data[i].model.scCode = mdesc->scCode; + wo->data[i].model.scSize = mdesc->scSize; + strcpy((char *)wo->data[i].model.scDesc, (char *)mdesc->scDesc); + } + } + } + /* ColorantTable: */ + { + int i; + unsigned int j; + int repclip = 0; + + /* For the first and last profile in the chain */ + /* (Note that we're assuming that the link output PCS is always Lab) */ + for (i = 0; i < 2; i++) { + icc *iccs; + icmHeader *sh; + icmColorantTable *ro; + icmColorantTable *wo; + icTagSignature cts; + + if (i == 0) { + iccs = li.in.c; /* Input profile */ + sh = li.in.h; /* Input profile header */ + cts = icSigColorantTableTag; + } else { + iccs = li.out.c; /* Output profile */ + sh = li.out.h; /* Output profile header */ + cts = icSigColorantTableOutTag; + } + + /* Try and read the input ColorantTable */ + if ((ro = (icmColorantTable *)iccs->read_tag( + iccs, icSigColorantTableTag)) != NULL) { + + /* Create a ColorantTable in the output device link */ + if ((wo = (icmColorantTable *)wr_icc->add_tag( + wr_icc, cts, icSigColorantTableType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Copy everything across */ + wo->count = ro->count; + if (wo->allocate((icmBase *)wo) != 0) + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + for (j = 0; j < wo->count; j++) { + strcpy(wo->data[j].name, ro->data[j].name); + if (sh->pcs != icSigLabData) { + icmXYZ2Lab(&icmD50, wo->data[j].pcsCoords, ro->data[j].pcsCoords); + /* For device links the colorant table must be Lab PCS, */ + /* but embarassingly, XYZ profiles can have colorant values */ + /* not representable in the Lab PCS range. */ + if (icmClipLab(wo->data[j].pcsCoords, wo->data[j].pcsCoords)) { + if (repclip) + warning("Colorant Tag Lab value was clipped"); + repclip = 1; + } + } else { + icmAry2Ary(wo->data[j].pcsCoords, ro->data[j].pcsCoords); + } + } + + } else { /* Do this the hard way */ + icmLuBase *luo; + unsigned int count; + double dv[MAX_CHAN]; + double cvals[MAX_CHAN][3]; + inkmask imask; + + /* Get a lookup to read colorant values */ + if ((luo = iccs->get_luobj(iccs, icmFwd, icRelativeColorimetric, + icSigLabData, icmLuOrdNorm)) == NULL) + goto skip_coloranttable; + + count = icmCSSig2nchan(sh->colorSpace); + for (j = 0; j < count; j++) + dv[j] = 0.0; + + /* Lookup the colorant Lab values the recommended ICC way */ + for (j = 0; j < count; j++) { + dv[j] = 1.0; + luo->lookup(luo, cvals[j], dv); + /* For device links the colorant table must be Lab PCS, */ + /* but embarassingly, XYZ profiles can have colorant values */ + /* not representable in the Lab PCS range. */ + if (icmClipLab(cvals[j], cvals[j])) { + if (repclip) + warning("Colorant Tag Lab value was clipped"); + repclip = 1; + } + dv[j] = 0.0; + } + luo->del(luo); + + /* Lookup colorant names */ + if ((imask = icx_icc_cv_to_colorant_comb(sh->colorSpace, iccs->header->deviceClass, cvals)) == 0) + goto skip_coloranttable; + + /* Create a ColorantTable in the output device link */ + if ((wo = (icmColorantTable *)wr_icc->add_tag( + wr_icc, cts, icSigColorantTableType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->count = count; + if (wo->allocate((icmBase *)wo) != 0) + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + for (j = 0; j < count; j++) { + inkmask iimask; /* Individual ink mask */ + char *name; + + iimask = icx_index2ink(imask, j); + name = icx_ink2string(iimask); + if (strlen(name) > 31) + error("Internal: colorant name exceeds 31 characters"); + strcpy(wo->data[j].name, name); + wo->data[j].pcsCoords[0] = cvals[j][0]; + wo->data[j].pcsCoords[1] = cvals[j][1]; + wo->data[j].pcsCoords[2] = cvals[j][2]; + } + } + /* Jump to here if we can't figure out what to put in ColorantTag */ + skip_coloranttable:; + } + } + /* 16 bit input device -> output device lut: */ + { + int inputEnt, outputEnt, clutPoints; + icmLut *wo; + + /* Setup the cLUT resolutions */ + if (li.quality >= 3) + inputEnt = 4096; + else if (li.quality == 2) + inputEnt = 2048; + else + inputEnt = 256; + + /* Make sure that we have at least the number of input entries as the */ + /* input profile. */ + if (in_curve_res > inputEnt) + inputEnt = in_curve_res; + + /* See discussion in imdi/imdi_gen.c for ideal numbers */ + switch (li.in.chan) { + case 0: + error ("Illegal number of input chanels"); + case 1: + if (li.quality >= 3) + clutPoints = 255; + else if (li.quality == 2) + clutPoints = 255; + else + clutPoints = 255; + break; + + case 2: + if (li.quality >= 2) + clutPoints = 255; + else + clutPoints = 86; + break; + case 3: + if (li.quality >= 3) + clutPoints = 52; + else if (li.quality == 2) + clutPoints = 33; + else if (li.quality == 1) + clutPoints = 17; + else + clutPoints = 9; + break; + case 4: + if (li.quality >= 3) + clutPoints = 33; + else if (li.quality == 2) + clutPoints = 18; + else if (li.quality == 1) + clutPoints = 9; + else + clutPoints = 6; + break; + case 5: + if (li.quality >= 3) + clutPoints = 18; + else if (li.quality == 2) + clutPoints = 16; + else + clutPoints = 9; + break; + case 6: + if (li.quality >= 3) + clutPoints = 12; + else if (li.quality == 2) + clutPoints = 9; + else + clutPoints = 6; + break; + case 7: + if (li.quality >= 3) + clutPoints = 8; + else if (li.quality == 2) + clutPoints = 7; + else + clutPoints = 6; + break; + case 8: + if (li.quality >= 3) + clutPoints = 7; + else if (li.quality == 2) + clutPoints = 6; + else + clutPoints = 5; + break; + default: /* > 8 chan */ + clutPoints = 3; + break; + } + + if (li.clutres > 0) /* clut resolution override */ + clutPoints = li.clutres; + li.clutres = clutPoints; /* Actual resolution */ + + if (li.quality >= 3) + outputEnt = 4096; + else if (li.quality == 2) + outputEnt = 2048; + else + outputEnt = 256; + + /* Make sure that we have at least the number of input entries as the */ + /* output profile. */ + if (out_curve_res > outputEnt) + outputEnt = out_curve_res; + + + /* Link Lut = AToB0 */ + if ((wo = (icmLut *)wr_icc->add_tag( + wr_icc, icSigAToB0Tag, icSigLut16Type)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->inputChan = li.in.chan; + wo->outputChan = li.out.chan; + + /* Setup the tables resolutions */ + wo->inputEnt = inputEnt; + wo->clutPoints = clutPoints; + wo->outputEnt = outputEnt; + + if (wo->allocate((icmBase *)wo) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Special case if input profile is Lut with matrix */ + /* (Does this do anything since input is not XYZ ?) */ + if (li.in.alg == icmLutType && li.in.nocurve == 0) { + icxLuLut *lu = (icxLuLut *)li.in.luo; + lu->get_matrix(lu, wo->e); /* Copy it across */ + } + + + + if (li.verb) + printf("Filling in Lut table\n"); +#ifdef DEBUG_ONE +#define DBGNO 1 /* Up to 10 */ + +#ifndef NEVER + /* Test a single given rgb/cmyk -> cmyk value */ + { + double in[10][MAX_CHAN]; + double out[MAX_CHAN]; + in[0][0] = 1.0; + in[0][1] = 1.0; + in[0][2] = 1.0; + in[0][3] = 0.0; + + in[1][0] = 1.0; + in[1][1] = 1.0; + in[1][2] = 1.0; + in[1][3] = 0.0; + + for (i = 0; i < DBGNO; i++) { + printf("Input %f %f %f %f\n",in[i][0], in[i][1], in[i][2], in[i][3]); + devi_devip((void *)&li, out, in[i]); + devip_devop((void *)&li, out, out); + devop_devo((void *)&li, out, out); + printf("Output %f %f %f %f\n\n",out[0], out[1], out[2], out[3]); + } + } +#endif /* NEVER */ + +#else /* !DEBUG_ONE */ + /* Use helper function to do the hard work. */ + if (li.verb) { + unsigned int ui; + int itotal; + for (itotal = 1, ui = 0; ui < li.in.chan; ui++, itotal *= clutPoints) + ; + li.total = itotal; + /* Allow for extra lookups due to ICM_CLUT_SET_APXLS */ + for (itotal = 1, ui = 0; ui < li.in.chan; ui++, itotal *= (clutPoints-1)) + ; + li.total += itotal; + li.count = 0; + printf(" 0%%"); fflush(stdout); + } + if (icmSetMultiLutTables( + 1, + &wo, + ICM_CLUT_SET_APXLS, /* Use aproximate least squares */ + &li, /* Context */ + li.in.h->colorSpace, /* Input color space */ + li.out.h->colorSpace, /* Output color space */ + devi_devip, /* Input transfer tables devi->devi' */ + NULL, NULL, /* Use default input colorspace range */ + devip_devop, /* devi' -> devo' transfer function */ + NULL, NULL, /* Default output colorspace range */ + devop_devo /* Output transfer tables, devo'->devo */ + ) != 0) { + error("Setting 16 bit Lut failed: %d, %s",wr_icc->errc,wr_icc->err); + } + if (li.verb) { + printf("\n"); + } +#ifdef WARN_CLUT_CLIPPING + if (wr_icc->warnc) + warning("Values clipped in setting device link LUT"); +#endif /* WARN_CLUT_CLIPPING */ + +#endif /* !DEBUG_ONE */ + + } + + if (li.verb && li.wphack && li.wphacked == 0) + printf("Warning :- white point hack didn't trigger!\n"); + if (li.verb && li.wphack && li.wphacked > 1) + printf("Warning :- white point hack trigger more than once! (%d)\n",li.wphacked); + + if (li.verb) + printf("Writing out file\n"); + + /* Write the file out */ + if ((rv = wr_icc->write(wr_icc,wr_fp,0)) != 0) + error ("Write file: %d, %s",rv,wr_icc->err); + + wr_icc->del(wr_icc); + wr_fp->del(wr_fp); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Verify the given link, assuming all the options are the same */ + } else { + icmFile *rd_fp; + icc *rd_icc; + icmLuBase *luo; + + 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 */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + + int gc[MAX_CHAN]; /* Grid counter */ + int vres = 8; //~~9 + double imin[MAX_CHAN], imax[MAX_CHAN]; /* Range of input values */ + double omin[MAX_CHAN], omax[MAX_CHAN]; /* Range of output values */ + double in[MAX_CHAN]; /* Input value */ + double ref[MAX_CHAN]; /* Reference output value */ + double out[MAX_CHAN]; /* Output value */ + double aerr, perr; /* Average, Peak error */ + double nerr; + int count, total; + int pc, lastpc; + double pin[MAX_CHAN], pref[MAX_CHAN], pout[MAX_CHAN]; /* Peak error values */ + + if (li.verb) + printf("Setting up to verify the link\n"); + + /* Open up the link file for reading */ + if ((rd_fp = new_icmFileStd_name(link_name,"r")) == NULL) + error ("Verify: Can't open file '%s'",link_name); + + if ((rd_icc = new_icc()) == NULL) + error ("Verify: Creation of ICC object failed"); + + if ((rv = rd_icc->read(rd_icc,rd_fp,0)) != 0) + error ("%d, %s",rv,rd_icc->err); + + /* Check that the profile is appropriate */ + if (rd_icc->header->deviceClass != icSigLinkClass) + error("Profile isn't a device link profile"); + + /* Get a conversion object */ + if ((luo = rd_icc->get_luobj(rd_icc, func, intent, icmSigDefaultData, order)) == NULL) + error ("%d, %s",rd_icc->errc, rd_icc->err); + + /* Get details of conversion (Arguments may be NULL if info not needed) */ + luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL, NULL); + + if (alg != icmLutType) + error ("DeviceLink profile doesn't have Lut !"); + + /* Get the icm value ranges */ + luo->get_ranges(luo, imin, imax, omin, omax); + + /* Init the grid counter */ + for (i = 0; i < inn; i++) + gc[i] = 0; + + for (total = 1, i = 0; i < inn; i++, total *= vres) + ; + + count = 0; + lastpc = 0; + nerr = aerr = perr = 0.0; + for(i = 0; i < inn; ) { + int j; + double err; + + /* Create and scale input */ + for (j = 0; j < inn; j++) { + in[j] = gc[j]/(vres-1.0); + in[j] = in[j] * (imax[j] - imin[j]) + imin[j]; + } + +// printf("Input %f %f %f %f\n",in[0], in[1], in[2], in[3]); + + /* Create the reference output value */ + devi_devip((void *)&li, ref, in); + devip_devop((void *)&li, ref, ref); + devop_devo((void *)&li, ref, ref); + + /* Lookup the icm output value */ + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",rd_icc->errc,rd_icc->err); + +// printf("Output %f %f %f %f\n",out[0], out[1], out[2], out[3]); +// printf("Ref %f %f %f %f\n",ref[0], ref[1], ref[2], ref[3]); +// printf("\n"); + + /* Unscale output and compare the results */ + for (err = 0.0, j = 0; j < outn; j++) { + double o,r; + o = (out[j] - omin[j])/(omax[j] - omin[j]); + r = (ref[j] - omin[j])/(omax[j] - omin[j]); + err += (o - r) * (o - r); + } + err = sqrt(err); + + aerr += err; + nerr++; + if (err > perr) { + perr = err; + for (j = 0; j < outn; j++) { + pin[j] = in[j]; + pref[j] = ref[j]; + pout[j] = out[j]; + } + } + + count++; + pc = (int)(count * 100.0/total + 0.5); + if (pc != lastpc) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + lastpc = pc; + } + + /* Increment the grid counter */ + for (i = 0; i < inn; i++) { + if (++gc[i] < vres) + break; /* No carry */ + gc[i] = 0; /* Reset digit */ + } + } + + if (li.verb) + printf("Finished verfication\n"); + + printf("Average error = %f%%, peak error = %f%%\n",aerr * 100.0/nerr, perr * 100.0); + printf("Input %f %f %f %f\n",pin[0], pin[1], pin[2], pin[3]); + printf("Output %f %f %f %f\n",pout[0], pout[1], pout[2], pout[3]); + printf("Ref %f %f %f %f\n",pref[0], pref[1], pref[2], pref[3]); + + luo->del(luo); + rd_icc->del(rd_icc); + rd_fp->del(rd_fp); + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Cleanup source profiles and exit */ + + if (li.pcs2k != NULL) /* Free up PCS->K lookup for neutral hack */ + li.pcs2k->del(li.pcs2k); + + if (li.map != NULL) + li.map->del(li.map); + if (li.Kmap != NULL) + li.Kmap->del(li.Kmap); + + if (li.abs_luo != NULL) { /* Free up abstract transform */ + li.abs_luo->del(li.abs_luo); + li.abs_xicc->del(li.abs_xicc); + li.abs_icc->del(li.abs_icc); + li.abs_fp->del(li.abs_fp); + } + + li.in.luo->del(li.in.luo); + li.in.x->del(li.in.x); + li.in.c->del(li.in.c); + + if (li.out.b2aluo != NULL) + li.out.b2aluo->del(li.out.b2aluo); + li.out.luo->del(li.out.luo); + li.out.x->del(li.out.x); + li.out.c->del(li.out.c); + + return 0; +} + + + + + + + diff --git a/link/monoplot.c b/link/monoplot.c new file mode 100644 index 0000000..781342b --- /dev/null +++ b/link/monoplot.c @@ -0,0 +1,231 @@ + +/* + * Argyll Color Management System + * + * Plot the monochrome axis transfer curve of a given link. + * + * Author: Graeme W. Gill + * Date: 01/26/8 + * Version: 2.00 + * + * Copyright 2001 Graeme W. Gill + * + * 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 "icc.h" +#include "plot.h" + +#define PRES 100 + +void error(char *fmt, ...), warning(char *fmt, ...); + +/* ---------------------------------------- */ + +void usage(void) { + fprintf(stderr,"Plot monochrome axis of ICC link file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"PCS->DEV ->link-> DEV->PCS\n"); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: monoplot inprof linkprof outprof\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; + char in_name[100]; + char link_name[100]; + char out_name[100]; + icmFile *in_fp, *link_fp, *out_fp; + icc *in_icco, *link_icco, *out_icco; + icmLuBase *in_lu, *link_lu, *out_lu; + int rv = 0; + + if (argc < 4) + 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++]); + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(link_name,argv[fa++]); + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(out_name,argv[fa++]); + + /* Open up the files for reading */ + if ((in_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((in_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = in_icco->read(in_icco,in_fp,0)) != 0) + error ("Read: %d, %s",rv,in_icco->err); + + + if ((link_fp = new_icmFileStd_name(link_name,"r")) == NULL) + error ("Read: Can't open file '%s'",link_name); + + if ((link_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = link_icco->read(link_icco,link_fp,0)) != 0) + error ("Read: %d, %s",rv,link_icco->err); + + + if ((out_fp = new_icmFileStd_name(out_name,"r")) == NULL) + error ("Read: Can't open file '%s'",out_name); + + if ((out_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = out_icco->read(out_icco,out_fp,0)) != 0) + error ("Read: %d, %s",rv,out_icco->err); + + + /* Get a PCS to Device conversion object */ + if ((in_lu = in_icco->get_luobj(in_icco, icmBwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((in_lu = in_icco->get_luobj(in_icco, icmBwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",in_icco->errc, in_icco->err); + } + + /* Get a Device to Device conversion object */ + if ((link_lu = link_icco->get_luobj(link_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",link_icco->errc, link_icco->err); + + /* Get a Device to PCS conversion object */ + if ((out_lu = out_icco->get_luobj(out_icco, icmFwd, icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { + if ((out_lu = out_icco->get_luobj(out_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) + error ("%d, %s",out_icco->errc, out_icco->err); + } + + { + double xx[100], yy[PRES]; + int i; + double tt[10]; + + for (i = 0; i < PRES; i++) { + + tt[0] = 100.0 * i/(PRES-1.0); + tt[1] = tt[2] = 0.0; + + xx[i] = tt[0]; + + if (verb) + printf(" %f %f %f -> ",tt[0],tt[1],tt[2]); + + /* PCS to input device space */ + if ((rv = in_lu->lookup(in_lu, tt, tt)) > 1) + error ("%d, %s",in_icco->errc,in_icco->err); + + + /* input device space to output device space */ + if ((rv = link_lu->lookup(link_lu, tt, tt)) > 1) + error ("%d, %s",link_icco->errc,link_icco->err); + + /* output device space to PCS */ + if ((rv = out_lu->lookup(out_lu, tt, tt)) > 1) + error ("%d, %s",out_icco->errc,out_icco->err); + + yy[i] = tt[0]; + + if (verb) + printf("%f %f %f\n",tt[0],tt[1],tt[2]); + } + + if (do_plot(xx,yy,NULL,NULL,PRES) < 0) + error("do_plot returned -1!\n"); + + } + + /* Done with lookup objects */ + in_lu->del(in_lu); + link_lu->del(link_lu); + out_lu->del(out_lu); + + in_icco->del(in_icco); + in_fp->del(in_fp); + link_icco->del(link_icco); + link_fp->del(link_fp); + out_icco->del(out_icco); + out_fp->del(out_fp); + + return 0; +} + +/* ------------------------------------------------ */ +/* Basic printf type error() and warning() routines */ + +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"monoplot: 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,"monoplot: Warning - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} diff --git a/link/pathplot.c b/link/pathplot.c new file mode 100644 index 0000000..fc12943 --- /dev/null +++ b/link/pathplot.c @@ -0,0 +1,281 @@ + +/* + * Argyll Color Management System + * + * Plot the L in vs. L out for a path through the + * input device space, for a given link. + * + * We are assuming RGB device input space !!!! + * + * Author: Graeme W. Gill + * Date: 2002/1/28 + * Version: 1.00 + * + * Copyright 2002 Graeme W. Gill + * + * 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" +#include "plot.h" + +#define PRES 100 + +static double start[13][4] = { + { 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}, + { 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.0, 0.0, 0.0, 0.0}, + { 0.0, 1.0, 0.0, 0.0}, + { 0.0, 0.0, 1.0, 0.0}, + { 0.0, 1.0, 1.0, 0.0}, + { 1.0, 0.0, 1.0, 0.0}, + { 1.0, 1.0, 0.0, 0.0} +}; + +static double end[13][4] = { + { 1.0, 0.0, 0.0, 0.0}, + { 0.0, 1.0, 0.0, 0.0}, + { 0.0, 0.0, 1.0, 0.0}, + { 0.0, 1.0, 1.0, 0.0}, + { 1.0, 0.0, 1.0, 0.0}, + { 1.0, 1.0, 0.0, 0.0}, + + { 1.0, 1.0, 1.0, 0.0}, + + { 1.0, 1.0, 1.0, 0.0}, + { 1.0, 1.0, 1.0, 0.0}, + { 1.0, 1.0, 1.0, 0.0}, + { 1.0, 1.0, 1.0, 0.0}, + { 1.0, 1.0, 1.0, 0.0}, + { 1.0, 1.0, 1.0, 0.0} +}; + +static char *name[13] = { + "Black - Red", "Black - Green", "Black - Blue", + "Black - Cyan", "Black - Magenta", "Black - Yellow", + "Grey", + "Red - White", "Green - White", "Blue - White", + "Cyan - White", "Magenta - White", "Yellow - White", + }; + +#define USE_JAB /* Assume CRT in (2), print out (0) */ + +/* ---------------------------------------- */ + +void usage(void) { + fprintf(stderr,"Plot device space path L in/out curve from an ICC link file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill\n"); + fprintf(stderr,"usage: pathplot inprof linkprof outprof\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; + char in_name[100]; + char link_name[100]; + char out_name[100]; + icmFile *in_fp, *link_fp, *out_fp; + icc *in_icco, *link_icco, *out_icco; + xicc *in_xicco, *out_xicco; + icmLuBase *link_lu; + icxLuBase *in_lu, *out_lu; + icColorSpaceSignature pcsor; /* PCS to use */ + icxViewCond ivc[1], ovc[1]; + int rv = 0; + + error_program = argv[0]; + + if (argc < 4) + 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++]); + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(link_name,argv[fa++]); + + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(out_name,argv[fa++]); + + /* Open up the files for reading */ + if ((in_fp = new_icmFileStd_name(in_name,"r")) == NULL) + error ("Read: Can't open file '%s'",in_name); + + if ((in_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = in_icco->read(in_icco,in_fp,0)) != 0) + error ("Read: %d, %s",rv,in_icco->err); + + if ((in_xicco = new_xicc(in_icco)) == NULL) + error ("Creation of input profile xicc failed"); + + + if ((link_fp = new_icmFileStd_name(link_name,"r")) == NULL) + error ("Read: Can't open file '%s'",link_name); + + if ((link_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = link_icco->read(link_icco,link_fp,0)) != 0) + error ("Read: %d, %s",rv,link_icco->err); + + + if ((out_fp = new_icmFileStd_name(out_name,"r")) == NULL) + error ("Read: Can't open file '%s'",out_name); + + if ((out_icco = new_icc()) == NULL) + error ("Read: Creation of ICC object failed"); + + if ((rv = out_icco->read(out_icco,out_fp,0)) != 0) + error ("Read: %d, %s",rv,out_icco->err); + + if ((out_xicco = new_xicc(out_icco)) == NULL) + error ("Creation of output profile xicc failed"); + + +#ifdef USE_JAB + pcsor = icxSigJabData; /* Use CIECAM as PCS */ + + if (xicc_enum_viewcond(in_xicco, ivc, -2, "mt", 0, NULL) == -999) /* Set input at monitor in typical */ + error ("%d, %s",in_xicco->errc, in_xicco->err); + + if (xicc_enum_viewcond(out_xicco, ovc, -2, "pp", 0, NULL) == -999) /* Set output at practical reflection print */ + error ("%d, %s",out_xicco->errc, out_xicco->err); + +#else + pcsor = icSigLabData; /* Default use Lab as PCS */ +#endif /* !USE_JAB */ + + /* Device to PCS conversion object */ + if ((in_lu = in_xicco->get_luobj(in_xicco, ICX_CLIP_NEAREST, icmFwd, icAbsoluteColorimetric, pcsor, icmLuOrdNorm, ivc, NULL)) == NULL) { + if ((in_lu = in_xicco->get_luobj(in_xicco, ICX_CLIP_NEAREST, icmBwd, icmDefaultIntent, pcsor, icmLuOrdNorm, ivc, NULL)) == NULL) + error ("%d, %s",in_xicco->errc, in_xicco->err); + } + + /* Get a Device to Device conversion object */ + if ((link_lu = link_icco->get_luobj(link_icco, icmFwd, icmDefaultIntent, pcsor, icmLuOrdNorm)) == NULL) + error ("%d, %s",link_icco->errc, link_icco->err); + + /* Get a Device to PCS conversion object */ + if ((out_lu = out_xicco->get_luobj(out_xicco, ICX_CLIP_NEAREST, icmFwd, icAbsoluteColorimetric, pcsor, icmLuOrdNorm, ovc, NULL)) == NULL) { + if ((out_lu = out_xicco->get_luobj(out_xicco, ICX_CLIP_NEAREST, icmFwd, icmDefaultIntent, pcsor, icmLuOrdNorm, ovc, NULL)) == NULL) + error ("%d, %s",out_xicco->errc, out_xicco->err); + } + + { + double xx[PRES], yy[PRES]; + int k, i, j; + double tt[10], tt2[10]; + + for (k = 0; k < 13; k++) { + printf("Doing %s\n",name[k]); + + for (i = 0; i < PRES; i++) { + double frac = i/(PRES-1.0); + + for (j = 0; j < 4; j++) + tt[j] = (end[k][j] - start[k][j]) * frac + start[k][j]; + + if (verb) + printf(" %f %f %f -> ",tt[0],tt[1],tt[2]); + + /* input device space to PCS */ + if ((rv = in_lu->lookup(in_lu, tt2, tt)) > 1) + error ("%d, %s",in_icco->errc,in_icco->err); + + xx[i] = tt2[0]; /* L value */ + + /* input device space to output device space */ + if ((rv = link_lu->lookup(link_lu, tt, tt)) > 1) + error ("%d, %s",link_icco->errc,link_icco->err); + + /* output device space to PCS */ + if ((rv = out_lu->lookup(out_lu, tt, tt)) > 1) + error ("%d, %s",out_icco->errc,out_icco->err); + + yy[i] = tt[0]; /* L value */ + + if (verb) + printf("%f %f %f\n",tt[0],tt[1],tt[2]); + } + + if (do_plot(xx,yy,NULL,NULL,PRES) < 0) + error("do_plot returned -1!\n"); + } + + } + + /* Done with lookup objects */ + in_lu->del(in_lu); + link_lu->del(link_lu); + out_lu->del(out_lu); + + in_icco->del(in_icco); + in_fp->del(in_fp); + link_icco->del(link_icco); + link_fp->del(link_fp); + out_icco->del(out_icco); + out_fp->del(out_fp); + + return 0; +} + |