diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..94a9ed024d38 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + 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. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + 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 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. Use with the GNU Affero General Public License. + + 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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 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 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 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. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + 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 GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000000..9b5dbebc2ad8 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS=src +EXTRA_DIST=darktable.gtkrc darktable.glade diff --git a/README b/README new file mode 100644 index 000000000000..f3d930458aa8 --- /dev/null +++ b/README @@ -0,0 +1,29 @@ +this is darktable, a free raw photo viewer and organiser. +darktable is not a light table. it's digital. + +to build it, you need: + + - lcms + - gtk+-2.0 + - libglade-2.0 + - libgthread-2.0 + - cairo + - MagickCore + +other used packages (supplied in the source tree): + + - LibRaw + - nikon_curve (taken from ufraw) + +then, type: + + $ autoreconf --install + $ ./configure [--enable-qwerty] [--enable-debug] + $ make + $ src/darkroom + +(the first step is only necessary when building from svn for the first time) +and enjoy! + +send any bugreports to me: hanatos@gmail.com + diff --git a/TODO b/TODO new file mode 100644 index 000000000000..4345d04f6bf8 --- /dev/null +++ b/TODO @@ -0,0 +1,95 @@ +new bugs: + - exif tag in libraw not really handled well (aperture+time missing) + - JPEG loading needs shrink + orientation + - dev switch resolution AFTER setting gui stuff. + control->width is updated in expose, and configure + is called in dev_enter. (just before) :( + - image_load_job should take img id and get it from cache himself! + (this is a serious problem if not all images fit into cache) + +broken since sqlite backend: + - remove last film in favour of film table with timestamp. + +feature requests: + - GMM for color model using EM in opencv! + - img table + date inserted/modified + - faster (unsafe) sql option: PRAGMA synchronous=OFF + - cache auf platte, echte bilder irgendwo auf dvd/platte extern/kamera chip etc + - moeglicherweise ECHTE bilder (dias im schrank) + - bilder sortieren, evtl manuell (mehrere kameras in einen film usw) + - zoom in in 3 steps? + - hide gui triangles andere form und switch in/out + - inotify dir watching + - execute iop: pass global image window! + - zoom in dev mode via mouse wheel. + - dt_film_sort with global settings + - tags and some tag search facility + - brightness/exposure highlight histogram stuff + +bugs: + - strange G_OBJECT cast errors when loading pfm/exr/pdf? + - control add history item > 10 (in gui representation. core can do)! + - import folder file chooser sometimes not sized appropriately + - histogram updates in fixed pipeline come too early/uselessly? + - export muss erst dev mode haben, sonst kaputt. + +priority list: + - selection behaviour (ctl shift etc) + - magick + exif (read)! + - clean up dev events: add history-> cache up -> fixed pipeline up + - modular one-file plugin support: + src/imageio/saturation.{c,h}: + - dr_gui_XX_reset -> reset op to neutral/default params + dr_gui_XX_init -> init gui elements and callbacks + dr_iop_XX -> transform buffers + - add expander to right vbox, + init gui with expander->child + expose: if op !=, gui_reset + reserve imageop, need string hash? + - image properties when zoomed in a lot + - clean up namings/modules. + - move expose to ctl or gui? + - split ctl and sched? + - control panel with buttons for lib->dev->lib + +image ops: + - white balance + - exposure + - luma/chroma denoising + - crop + +usability: + - aoe, keys to change +-= table_cols/1 resp. + - write out image_t at quit (exif, filenames, selected flags) + - navigation: buttons for zoom levels + - closeup in 3:1 5:1 or 2:1 ? + - disable dev controls/make invisible in lib? + - make directory loading interruptible + - adjust navigation preview in dev on the fly? + - image loading: load thumbnails first, if any. + - navigation widget for lib? + - curve widget presets + - x-axis adjusters + - active live histogram (exposure etc) + - all functions mouse-accessible + - library graph with keyword edges. + - expander with exif data (+filename of saved image) + +look: + - dev img border + - border between center and outer panels? + - adjust .gtkrc to have consistently cool look even in filechooser + - end markers :) + - dev full res image possibly smaller as lo-res (paint border with bg) + +cleanup: + - QWERTZ/dvorak keyboard shortcuts run-time switching + +cache: + - user profile guided prefetching for full raw load! + - load cache from disk only if md5sum matches + +build/packaging: + - consistent prefix naming: dev, lib, ctl, gui + - doxygen documentation + diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000000..04d819f116e6 --- /dev/null +++ b/configure.ac @@ -0,0 +1,70 @@ +AC_INIT([darktable], [0.1], [hanatos@gmail.com]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign dist-bzip2]) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_RANLIB +PKG_CHECK_MODULES(GTK, gtk+-2.0 >= 2.14) +PKG_CHECK_MODULES(CAIRO, [cairo]) +PKG_CHECK_MODULES(GTHREAD, [gthread-2.0]) +PKG_CHECK_MODULES(GLADE, [libglade-2.0]) +PKG_CHECK_MODULES(SQLITE, [sqlite3]) + +dnl *** check for MagickCore *** +AC_CHECK_PROGS(MAGICKCONFIG, [Magick-config GraphicsMagick-config]) +usemagick=0 +if test -n "$MAGICKCONFIG"; then + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LIBS="$LIBS" + MAGICK_CPPFLAGS="`$MAGICKCONFIG --cppflags`" + MAGICK_LIBS="`$MAGICKCONFIG --ldflags` `$MAGICKCONFIG --libs`" + CPPFLAGS="$CPPFLAGS $MAGICK_CPPFLAGS" + LIBS="$MAGICK_LIBS $LIBS" + AC_CHECK_FUNC(ExportImagePixels, usemagick=1, AC_MSG_NOTICE([ImageMagick/GraphicsMagick does not support the function + ExportImagePixels. Please upgrade to ImageMagick 5.5.7 or newer (or + the corresponding GraphicsMagick version)])) + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" +fi + +if test "$usemagick" != 1; then + AC_CHECK_LIB(z, zlibVersion, test, AC_MSG_ERROR([You must have libz(-devel) installed])) + + AC_CHECK_LIB(png, png_sig_cmp, test, AC_MSG_ERROR([You must have libpng(-devel) installed]), -lz -lm) + MAGICK_CPPFLAGS= + MAGICK_LIBS="-lpng -lz -lm" +else + AC_DEFINE(HAVE_MAGICK, 1, [Whether the ImageMagick or GraphicsMagick libraries are available]) +fi +AC_SUBST(MAGICK_CPPFLAGS) +AC_SUBST(MAGICK_LIBS) + +dnl *** Check for LittleCMS *** +AC_CHECK_LIB(lcms, cmsOpenProfileFromFile, + [AC_DEFINE(HAVE_LCMS, 1, [Define if you have the LittleCMS development environment]) + LCMS_LIBS="-llcms"]) +AC_SUBST(LCMS_LIBS) + +dnl *** check for lamer keyboard *** +AC_ARG_ENABLE(qwerty, [ --enable-qwerty Enable QWERTY key accels], [if test "$enableval" = yes; then + AC_DEFINE(HAVE_QWERTY, 1, [Use to switch from default dvorak key accels to qwerty]) + fi + ]) + +AC_MSG_CHECKING([whether to enable debugging]) +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug], [build debug executable])) +if test "$enable_debug" = "yes"; then + CFLAGS="$CFLAGS -Wall -O0 -g -D_DEBUG" + AC_MSG_RESULT([yes]) +else + CFLAGS="$CFLAGS -Wall -O4" + AC_MSG_RESULT([no]) +fi + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([ + Makefile + src/Makefile + src/LibRaw/Makefile + ]) +AC_OUTPUT diff --git a/darktable.glade b/darktable.glade new file mode 100644 index 000000000000..9490814af2ba --- /dev/null +++ b/darktable.glade @@ -0,0 +1,717 @@ + + + + + + 640 + 480 + + + True + 3 + + + 200 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + + + True + GTK_RESIZE_QUEUE + + + True + 10 + + + True + True + True + + + 200 + 133 + True + GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK + + + + + True + histogram + + + label_item + + + + + False + False + + + + + True + True + True + + + True + + + 200 + 200 + True + GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK + + + + + True + + + True + presets + + + False + False + 5 + 1 + + + + + True + False + False + linear +med contrast +high contrast + + + False + False + 5 + GTK_PACK_END + 1 + + + + + False + False + 1 + + + + + + + True + tone curve + + + label_item + + + + + False + False + 1 + + + + + True + True + 10 + + + True + + + True + + + True + 0 + linear + True + + + + + True + 0 + gamma + True + + + 1 + + + + + False + False + 5 + + + + + True + + + True + True + 0.10000000000000001 0 1 0.01 0.10000000000000001 0 + 2 + GTK_POS_LEFT + + + + + True + True + 0.45000000000000001 0 1 0.01 0.10000000000000001 0 + 2 + GTK_POS_LEFT + + + 1 + + + + + 5 + 1 + + + + + + + True + gamma correction + + + label_item + + + + + False + False + 2 + + + + + True + True + 10 + + + True + + + True + + + True + 0 + hue + + + + + True + 0 + saturation + + + 1 + + + + + True + 0 + brightness + + + 2 + + + + + False + False + 5 + + + + + True + + + True + True + 1 0 2 0.01 0.10000000000000001 0 + 2 + GTK_POS_LEFT + + + False + False + + + + + True + True + 1 0 2 0.01 0.10000000000000001 0 + 2 + GTK_POS_LEFT + + + False + False + 1 + + + + + True + True + 1 0 2 0.01 0.10000000000000001 0 + 2 + GTK_POS_LEFT + + + False + False + 2 + + + + + 5 + 1 + + + + + + + True + saturation + + + label_item + + + + + False + False + 3 + + + + + True + True + 10 + + + True + + + True + float pfm +16-bit ppm +8-bit jpg +8-bit png + + + 5 + + + + + True + True + True + export + 0 + + + False + False + 5 + 1 + + + + + + + True + export selected + + + label_item + + + + + False + False + 4 + + + + + + + + + 2 + 3 + GTK_SHRINK + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + True + + + True + TODO: lib: sorting, dev: file op/save + + + False + False + + + + + 500 + 500 + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK + + + 1 + + + + + True + + + True + True + 13 1 13 1 1 0 + 0 + GTK_POS_RIGHT + + + GTK_PACK_END + -1 + + + + + False + False + 2 + + + + + 1 + 2 + + + + + 200 + True + 10 + + + True + True + True + + + 200 + 133 + True + GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + + + True + navigation + + + label_item + + + + + False + False + + + + + True + True + True + 5 + + + True + + + True + True + True + import folder + 0 + + + False + False + + + + + True + True + + + 1 + + + + + + + True + library + + + label_item + + + + + False + False + 1 + + + + + True + True + + + True + TODO: store hist stacks + + + + + True + snapshots + + + label_item + + + + + False + False + 2 + + + + + True + True + True + + + True + + + True + True + button + 0 + + + False + False + + + + + True + True + button + 0 + + + False + False + 1 + + + + + True + True + button + 0 + + + False + False + 2 + + + + + True + True + button + 0 + + + False + False + 3 + + + + + True + True + button + 0 + + + False + False + 4 + + + + + True + True + button + 0 + + + False + False + 5 + + + + + True + True + button + 0 + + + False + False + 6 + + + + + True + True + button + 0 + + + False + False + 7 + + + + + True + True + button + 0 + + + False + False + 8 + + + + + True + True + original + 0 + + + False + False + 9 + + + + + + + True + history + + + label_item + + + + + False + False + 3 + + + + + GTK_SHRINK + GTK_EXPAND | GTK_SHRINK | GTK_FILL + + + + + + diff --git a/darktable.gtkrc b/darktable.gtkrc new file mode 100644 index 000000000000..945197f1df7a --- /dev/null +++ b/darktable.gtkrc @@ -0,0 +1,19 @@ +style "default"{ + +#fonts +fg[NORMAL] = "#F0F0F0" +fg[PRELIGHT] = "#000000" +fg[SELECTED] = "#000000" + +#selected areas +base[SELECTED] = "#8a8aa0" +base[ACTIVE] = "#e3e3e3" +base[PRELIGHT] = "#E5E5F7" +base[INSENSITIVE]= "#ced0d7" +bg[SELECTED]= "#828aa4" + +#app backgrouds +bg[NORMAL] = "#333333" +# bg_pixmap[NORMAL] = "lines_background.png" +# bg_pixmap[PRELIGHT] = "lines_background_blue.png" +}class "GtkWidget" style "default" diff --git a/src/LibRaw/Makefile.am b/src/LibRaw/Makefile.am new file mode 100644 index 000000000000..eb98ba992842 --- /dev/null +++ b/src/LibRaw/Makefile.am @@ -0,0 +1,5 @@ +AM_CFLAGS=-O4 -I$(srcdir) -Wall -fopenmp -pthread +LDADD=$(LCMS_LIBS) +noinst_LIBRARIES=libraw_r.a +libraw_r_a_SOURCES=internal/dcraw_common.cpp src/libraw_cxx.cpp internal/dcraw_fileio.cpp internal/foveon.cpp src/libraw_c_api.cpp +noinst_HEADERS=internal/defines.h internal/libraw_internal_funcs.h internal/var_defines.h libraw/libraw_internal.h libraw/libraw_alloc.h libraw/libraw_types.h libraw/libraw.h libraw/libraw_version.h libraw/libraw_const.h libraw/libraw_datastream.h diff --git a/src/LibRaw/internal/dcraw_common.cpp b/src/LibRaw/internal/dcraw_common.cpp new file mode 100644 index 000000000000..c55184ef5f72 --- /dev/null +++ b/src/LibRaw/internal/dcraw_common.cpp @@ -0,0 +1,8244 @@ +/* + GENERATED FILE, DO NOT EDIT + Generated from dcraw/dcraw.c at Sat Feb 7 20:23:43 2009 + Look into original file (probably http://cybercom.net/~dcoffin/dcraw/dcraw.c) + for copyright information. +*/ + +#line 259 "dcraw/dcraw.c" +#define CLASS LibRaw:: +#include "libraw/libraw_types.h" +#define LIBRAW_LIBRARY_BUILD +#define LIBRAW_IO_REDEFINED +#include "libraw/libraw.h" +#include "internal/defines.h" +#include "internal/var_defines.h" + +#line 269 "dcraw/dcraw.c" + +#ifndef __GLIBC__ +char *my_memmem (char *haystack, size_t haystacklen, + char *needle, size_t needlelen) +{ + char *c; + for (c = haystack; c <= haystack + haystacklen - needlelen; c++) + if (!memcmp (c, needle, needlelen)) + return c; + return 0; +} +#define memmem my_memmem +#endif + +#line 303 "dcraw/dcraw.c" + +ushort CLASS sget2 (uchar *s) +{ + if (order == 0x4949) /* "II" means little-endian */ + return s[0] | s[1] << 8; + else /* "MM" means big-endian */ + return s[0] << 8 | s[1]; +} + +ushort CLASS get2() +{ + uchar str[2] = { 0xff,0xff }; + fread (str, 1, 2, ifp); + return sget2(str); +} + +unsigned CLASS sget4 (uchar *s) +{ + if (order == 0x4949) + return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24; + else + return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]; +} +#define sget4(s) sget4((uchar *)s) + +unsigned CLASS get4() +{ + uchar str[4] = { 0xff,0xff,0xff,0xff }; + fread (str, 1, 4, ifp); + return sget4(str); +} + +unsigned CLASS getint (int type) +{ + return type == 3 ? get2() : get4(); +} + +float CLASS int_to_float (int i) +{ + union { int i; float f; } u; + u.i = i; + return u.f; +} + +double CLASS getreal (int type) +{ + union { char c[8]; double d; } u; + int i, rev; + + switch (type) { + case 3: return (unsigned short) get2(); + case 4: return (unsigned int) get4(); + case 5: u.d = (unsigned int) get4(); + return u.d / (unsigned int) get4(); + case 8: return (signed short) get2(); + case 9: return (signed int) get4(); + case 10: u.d = (signed int) get4(); + return u.d / (signed int) get4(); + case 11: return int_to_float (get4()); + case 12: + rev = 7 * ((order == 0x4949) == (ntohs(0x1234) == 0x1234)); + for (i=0; i < 8; i++) + u.c[i ^ rev] = fgetc(ifp); + return u.d; + default: return fgetc(ifp); + } +} + +void CLASS read_shorts (ushort *pixel, int count) +{ + if (fread (pixel, 2, count, ifp) < count) derror(); + if ((order == 0x4949) == (ntohs(0x1234) == 0x1234)) + swab ((char*)pixel, (char*)pixel, count*2); +} +#line 380 "dcraw/dcraw.c" +void CLASS canon_black (double dark[2]) +{ + int c, diff, row, col; + + if (raw_width < width+4) return; + FORC(2) dark[c] /= (raw_width-width-2) * height >> 1; + if ((diff = dark[0] - dark[1])) + for (row=0; row < height; row++) + for (col=1; col < width; col+=2) + BAYER(row,col) += diff; + dark[1] += diff; + black = (dark[0] + dark[1] + 1) / 2; +} + +void CLASS canon_600_fixed_wb (int temp) +{ + static const short mul[4][5] = { + { 667, 358,397,565,452 }, + { 731, 390,367,499,517 }, + { 1119, 396,348,448,537 }, + { 1399, 485,431,508,688 } }; + int lo, hi, i; + float frac=0; + + for (lo=4; --lo; ) + if (*mul[lo] <= temp) break; + for (hi=0; hi < 3; hi++) + if (*mul[hi] >= temp) break; + if (lo != hi) + frac = (float) (temp - *mul[lo]) / (*mul[hi] - *mul[lo]); + for (i=1; i < 5; i++) + pre_mul[i-1] = 1 / (frac * mul[hi][i] + (1-frac) * mul[lo][i]); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif +} + +/* Return values: 0 = white 1 = near white 2 = not white */ +int CLASS canon_600_color (int ratio[2], int mar) +{ + int clipped=0, target, miss; + + if (flash_used) { + if (ratio[1] < -104) + { ratio[1] = -104; clipped = 1; } + if (ratio[1] > 12) + { ratio[1] = 12; clipped = 1; } + } else { + if (ratio[1] < -264 || ratio[1] > 461) return 2; + if (ratio[1] < -50) + { ratio[1] = -50; clipped = 1; } + if (ratio[1] > 307) + { ratio[1] = 307; clipped = 1; } + } + target = flash_used || ratio[1] < 197 + ? -38 - (398 * ratio[1] >> 10) + : -123 + (48 * ratio[1] >> 10); + if (target - mar <= ratio[0] && + target + 20 >= ratio[0] && !clipped) return 0; + miss = target - ratio[0]; + if (abs(miss) >= mar*4) return 2; + if (miss < -20) miss = -20; + if (miss > mar) miss = mar; + ratio[0] = target - miss; + return 1; +} + +void CLASS canon_600_auto_wb() +{ + int mar, row, col, i, j, st, count[] = { 0,0 }; + int test[8], total[2][8], ratio[2][2], stat[2]; + + memset (&total, 0, sizeof total); + i = canon_ev + 0.5; + if (i < 10) mar = 150; + else if (i > 12) mar = 20; + else mar = 280 - 20 * i; + if (flash_used) mar = 80; + for (row=14; row < height-14; row+=4) + for (col=10; col < width; col+=2) { + for (i=0; i < 8; i++) + test[(i & 4) + FC(row+(i >> 1),col+(i & 1))] = + BAYER(row+(i >> 1),col+(i & 1)); + for (i=0; i < 8; i++) + if (test[i] < 150 || test[i] > 1500) goto next; + for (i=0; i < 4; i++) + if (abs(test[i] - test[i+4]) > 50) goto next; + for (i=0; i < 2; i++) { + for (j=0; j < 4; j+=2) + ratio[i][j >> 1] = ((test[i*4+j+1]-test[i*4+j]) << 10) / test[i*4+j]; + stat[i] = canon_600_color (ratio[i], mar); + } + if ((st = stat[0] | stat[1]) > 1) goto next; + for (i=0; i < 2; i++) + if (stat[i]) + for (j=0; j < 2; j++) + test[i*4+j*2+1] = test[i*4+j*2] * (0x400 + ratio[i][j]) >> 10; + for (i=0; i < 8; i++) + total[st][i] += test[i]; + count[st]++; +next: ; + } + if (count[0] | count[1]) { + st = count[0]*200 < count[1]; + for (i=0; i < 4; i++) + pre_mul[i] = 1.0 / (total[st][i] + total[st][i+4]); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CALCULATED; +#endif + } +} + +void CLASS canon_600_coeff() +{ + static const short table[6][12] = { + { -190,702,-1878,2390, 1861,-1349,905,-393, -432,944,2617,-2105 }, + { -1203,1715,-1136,1648, 1388,-876,267,245, -1641,2153,3921,-3409 }, + { -615,1127,-1563,2075, 1437,-925,509,3, -756,1268,2519,-2007 }, + { -190,702,-1886,2398, 2153,-1641,763,-251, -452,964,3040,-2528 }, + { -190,702,-1878,2390, 1861,-1349,905,-393, -432,944,2617,-2105 }, + { -807,1319,-1785,2297, 1388,-876,769,-257, -230,742,2067,-1555 } }; + int t=0, i, c; + float mc, yc; + + mc = pre_mul[1] / pre_mul[2]; + yc = pre_mul[3] / pre_mul[2]; + if (mc > 1 && mc <= 1.28 && yc < 0.8789) t=1; + if (mc > 1.28 && mc <= 2) { + if (yc < 0.8789) t=3; + else if (yc <= 2) t=4; + } + if (flash_used) t=5; + for (raw_color = i=0; i < 3; i++) + FORCC rgb_cam[i][c] = table[t][i*4 + c] / 1024.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_CALCULATED; +#endif +} + +void CLASS canon_600_load_raw() +{ + uchar data[1120], *dp; + ushort pixel[896], *pix; + int irow, row, col, val; + static const short mul[4][2] = + { { 1141,1145 }, { 1128,1109 }, { 1178,1149 }, { 1128,1109 } }; + + for (irow=row=0; irow < height; irow++) { + if (fread (data, 1, raw_width*5/4, ifp) < raw_width*5/4) derror(); + for (dp=data, pix=pixel; dp < data+1120; dp+=10, pix+=8) { + pix[0] = (dp[0] << 2) + (dp[1] >> 6 ); + pix[1] = (dp[2] << 2) + (dp[1] >> 4 & 3); + pix[2] = (dp[3] << 2) + (dp[1] >> 2 & 3); + pix[3] = (dp[4] << 2) + (dp[1] & 3); + pix[4] = (dp[5] << 2) + (dp[9] & 3); + pix[5] = (dp[6] << 2) + (dp[9] >> 2 & 3); + pix[6] = (dp[7] << 2) + (dp[9] >> 4 & 3); + pix[7] = (dp[8] << 2) + (dp[9] >> 6 ); + } + for (col=0; col < width; col++) + BAYER(row,col) = pixel[col]; + for (col=width; col < raw_width; col++) + { + black += pixel[col]; +#ifdef LIBRAW_LIBRARY_BUILD + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = pixel[col]; +#endif + } + if ((row+=2) > height) row = 1; + } + if (raw_width > width) + black = black / ((raw_width - width) * height) - 4; + for (row=0; row < height; row++) + for (col=0; col < width; col++) { +#ifdef LIBRAW_LIBRARY_BUILD + if( filtering_mode & LIBRAW_FILTERING_NOBLACKS) + val = BAYER(row,col); + else +#endif + if ((val = BAYER(row,col) - black) < 0) val = 0; + val = val * mul[row & 3][col & 1] >> 9; + BAYER(row,col) = val; + } + canon_600_fixed_wb(1311); + canon_600_auto_wb(); + canon_600_coeff(); + maximum = (0x3ff - black) * 1109 >> 9; + black = 0; +} + +void CLASS remove_zeroes() +{ + unsigned row, col, tot, n, r, c; + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_REMOVE_ZEROES,0,2); +#endif + for (row=0; row < height; row++) + for (col=0; col < width; col++) + if (BAYER(row,col) == 0) { + tot = n = 0; + for (r = row-2; r <= row+2; r++) + for (c = col-2; c <= col+2; c++) + if (r < height && c < width && + FC(r,c) == FC(row,col) && BAYER(r,c)) + tot += (n++,BAYER(r,c)); + if (n) BAYER(row,col) = tot/n; + } +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_REMOVE_ZEROES,1,2); +#endif +} + +int CLASS canon_s2is() +{ + unsigned row; + + for (row=0; row < 100; row++) { + fseek (ifp, row*3340 + 3284, SEEK_SET); + if (getc(ifp) > 15) return 1; + } + return 0; +} + +void CLASS canon_a5_load_raw() +{ + ushort data[2565], *dp, pixel; + int vbits=0, buf=0, row, col, bc=0; + + order = 0x4949; + for (row=-top_margin; row < raw_height-top_margin; row++) { + read_shorts (dp=data, raw_width * 10 / 16); + for (col=-left_margin; col < raw_width-left_margin; col++) { + if ((vbits -= 10) < 0) + buf = (vbits += 16, (buf << 16) + *dp++); + pixel = buf >> vbits & 0x3ff; +#ifdef LIBRAW_LIBRARY_BUILD + ushort *dfp = get_masked_pointer(row+top_margin,col+left_margin); + if(dfp) *dfp = pixel; +#endif + if ((unsigned) row < height && (unsigned) col < width) + BAYER(row,col) = pixel; + else if (col > 1-left_margin && col != width) + black += (bc++,pixel); + } + } + if (bc) black /= bc; + maximum = 0x3ff; + +#ifdef LIBRAW_LIBRARY_BUILD + if(!(filtering_mode & LIBRAW_FILTERING_NOZEROES)) +#endif + if (raw_width > 1600) remove_zeroes(); +} + +/* + getbits(-1) initializes the buffer + getbits(n) where 0 <= n <= 25 returns an n-bit integer + */ +unsigned CLASS getbits (int nbits) +{ +#ifdef LIBRAW_NOTHREADS + static unsigned bitbuf=0; + static int vbits=0, reset=0; +#else +#define bitbuf tls->getbits.bitbuf +#define vbits tls->getbits.vbits +#define reset tls->getbits.reset +#endif + unsigned c; + + if (nbits == -1) + return bitbuf = vbits = reset = 0; + if (nbits == 0 || reset) return 0; + while (vbits < nbits) { + if ((c = fgetc(ifp)) == EOF) derror(); + if ((reset = zero_after_ff && c == 0xff && fgetc(ifp))) return 0; + bitbuf = (bitbuf << 8) + (uchar) c; + vbits += 8; + } + vbits -= nbits; + return bitbuf << (32-nbits-vbits) >> (32-nbits); +#ifndef LIBRAW_NOTHREADS +#undef bitbuf +#undef vbits +#undef reset +#endif +} + +void CLASS init_decoder() +{ + memset (first_decode, 0, sizeof first_decode); + free_decode = first_decode; +} + +/* + Construct a decode tree according to the specification in *source. + The first 16 bytes specify how many codes should be 1-bit, 2-bit + 3-bit, etc. Bytes after that are the leaf values. + + For example, if the source is + + { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, + 0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff }, + + then the code is + + 00 0x04 + 010 0x03 + 011 0x05 + 100 0x06 + 101 0x02 + 1100 0x07 + 1101 0x01 + 11100 0x08 + 11101 0x09 + 11110 0x00 + 111110 0x0a + 1111110 0x0b + 1111111 0xff + */ +uchar * CLASS make_decoder (const uchar *source, int level) +{ + struct decode *cur; +#ifndef LIBRAW_NOTHREADS +#define t_leaf tls->make_decoder_leaf +#else + static int t_leaf; +#endif + int i, next; + + if (level==0) t_leaf=0; + cur = free_decode++; + if (free_decode > first_decode+2048) { +#ifdef LIBRAW_LIBRARY_BUILD + throw LIBRAW_EXCEPTION_DECODE_RAW; +#else + fprintf (stderr,_("%s: decoder table overflow\n"), ifname); + longjmp (failure, 2); +#endif + } + for (i=next=0; i <= t_leaf && next < 16; ) + i += source[next++]; + if (i > t_leaf) { + if (level < next) { + cur->branch[0] = free_decode; + make_decoder (source, level+1); + cur->branch[1] = free_decode; + make_decoder (source, level+1); + } else + cur->leaf = source[16 + t_leaf++]; + } + return (uchar *) source + 16 + t_leaf; +#ifndef LIBRAW_NOTHREADS +#undef t_leaf +#endif +} + +void CLASS crw_init_tables (unsigned table) +{ + static const uchar first_tree[3][29] = { + { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, + 0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff }, + { 0,2,2,3,1,1,1,1,2,0,0,0,0,0,0,0, + 0x03,0x02,0x04,0x01,0x05,0x00,0x06,0x07,0x09,0x08,0x0a,0x0b,0xff }, + { 0,0,6,3,1,1,2,0,0,0,0,0,0,0,0,0, + 0x06,0x05,0x07,0x04,0x08,0x03,0x09,0x02,0x00,0x0a,0x01,0x0b,0xff }, + }; + static const uchar second_tree[3][180] = { + { 0,2,2,2,1,4,2,1,2,5,1,1,0,0,0,139, + 0x03,0x04,0x02,0x05,0x01,0x06,0x07,0x08, + 0x12,0x13,0x11,0x14,0x09,0x15,0x22,0x00,0x21,0x16,0x0a,0xf0, + 0x23,0x17,0x24,0x31,0x32,0x18,0x19,0x33,0x25,0x41,0x34,0x42, + 0x35,0x51,0x36,0x37,0x38,0x29,0x79,0x26,0x1a,0x39,0x56,0x57, + 0x28,0x27,0x52,0x55,0x58,0x43,0x76,0x59,0x77,0x54,0x61,0xf9, + 0x71,0x78,0x75,0x96,0x97,0x49,0xb7,0x53,0xd7,0x74,0xb6,0x98, + 0x47,0x48,0x95,0x69,0x99,0x91,0xfa,0xb8,0x68,0xb5,0xb9,0xd6, + 0xf7,0xd8,0x67,0x46,0x45,0x94,0x89,0xf8,0x81,0xd5,0xf6,0xb4, + 0x88,0xb1,0x2a,0x44,0x72,0xd9,0x87,0x66,0xd4,0xf5,0x3a,0xa7, + 0x73,0xa9,0xa8,0x86,0x62,0xc7,0x65,0xc8,0xc9,0xa1,0xf4,0xd1, + 0xe9,0x5a,0x92,0x85,0xa6,0xe7,0x93,0xe8,0xc1,0xc6,0x7a,0x64, + 0xe1,0x4a,0x6a,0xe6,0xb3,0xf1,0xd3,0xa5,0x8a,0xb2,0x9a,0xba, + 0x84,0xa4,0x63,0xe5,0xc5,0xf3,0xd2,0xc4,0x82,0xaa,0xda,0xe4, + 0xf2,0xca,0x83,0xa3,0xa2,0xc3,0xea,0xc2,0xe2,0xe3,0xff,0xff }, + { 0,2,2,1,4,1,4,1,3,3,1,0,0,0,0,140, + 0x02,0x03,0x01,0x04,0x05,0x12,0x11,0x06, + 0x13,0x07,0x08,0x14,0x22,0x09,0x21,0x00,0x23,0x15,0x31,0x32, + 0x0a,0x16,0xf0,0x24,0x33,0x41,0x42,0x19,0x17,0x25,0x18,0x51, + 0x34,0x43,0x52,0x29,0x35,0x61,0x39,0x71,0x62,0x36,0x53,0x26, + 0x38,0x1a,0x37,0x81,0x27,0x91,0x79,0x55,0x45,0x28,0x72,0x59, + 0xa1,0xb1,0x44,0x69,0x54,0x58,0xd1,0xfa,0x57,0xe1,0xf1,0xb9, + 0x49,0x47,0x63,0x6a,0xf9,0x56,0x46,0xa8,0x2a,0x4a,0x78,0x99, + 0x3a,0x75,0x74,0x86,0x65,0xc1,0x76,0xb6,0x96,0xd6,0x89,0x85, + 0xc9,0xf5,0x95,0xb4,0xc7,0xf7,0x8a,0x97,0xb8,0x73,0xb7,0xd8, + 0xd9,0x87,0xa7,0x7a,0x48,0x82,0x84,0xea,0xf4,0xa6,0xc5,0x5a, + 0x94,0xa4,0xc6,0x92,0xc3,0x68,0xb5,0xc8,0xe4,0xe5,0xe6,0xe9, + 0xa2,0xa3,0xe3,0xc2,0x66,0x67,0x93,0xaa,0xd4,0xd5,0xe7,0xf8, + 0x88,0x9a,0xd7,0x77,0xc4,0x64,0xe2,0x98,0xa5,0xca,0xda,0xe8, + 0xf3,0xf6,0xa9,0xb2,0xb3,0xf2,0xd2,0x83,0xba,0xd3,0xff,0xff }, + { 0,0,6,2,1,3,3,2,5,1,2,2,8,10,0,117, + 0x04,0x05,0x03,0x06,0x02,0x07,0x01,0x08, + 0x09,0x12,0x13,0x14,0x11,0x15,0x0a,0x16,0x17,0xf0,0x00,0x22, + 0x21,0x18,0x23,0x19,0x24,0x32,0x31,0x25,0x33,0x38,0x37,0x34, + 0x35,0x36,0x39,0x79,0x57,0x58,0x59,0x28,0x56,0x78,0x27,0x41, + 0x29,0x77,0x26,0x42,0x76,0x99,0x1a,0x55,0x98,0x97,0xf9,0x48, + 0x54,0x96,0x89,0x47,0xb7,0x49,0xfa,0x75,0x68,0xb6,0x67,0x69, + 0xb9,0xb8,0xd8,0x52,0xd7,0x88,0xb5,0x74,0x51,0x46,0xd9,0xf8, + 0x3a,0xd6,0x87,0x45,0x7a,0x95,0xd5,0xf6,0x86,0xb4,0xa9,0x94, + 0x53,0x2a,0xa8,0x43,0xf5,0xf7,0xd4,0x66,0xa7,0x5a,0x44,0x8a, + 0xc9,0xe8,0xc8,0xe7,0x9a,0x6a,0x73,0x4a,0x61,0xc7,0xf4,0xc6, + 0x65,0xe9,0x72,0xe6,0x71,0x91,0x93,0xa6,0xda,0x92,0x85,0x62, + 0xf3,0xc5,0xb2,0xa4,0x84,0xba,0x64,0xa5,0xb3,0xd2,0x81,0xe5, + 0xd3,0xaa,0xc4,0xca,0xf2,0xb1,0xe4,0xd1,0x83,0x63,0xea,0xc3, + 0xe2,0x82,0xf1,0xa3,0xc2,0xa1,0xc1,0xe3,0xa2,0xe1,0xff,0xff } + }; + if (table > 2) table = 2; + init_decoder(); + make_decoder ( first_tree[table], 0); + second_decode = free_decode; + make_decoder (second_tree[table], 0); +} + +/* + Return 0 if the image starts with compressed data, + 1 if it starts with uncompressed low-order bits. + + In Canon compressed data, 0xff is always followed by 0x00. + */ +int CLASS canon_has_lowbits() +{ + uchar test[0x4000]; + int ret=1, i; + + fseek (ifp, 0, SEEK_SET); + fread (test, 1, sizeof test, ifp); + for (i=540; i < sizeof test - 1; i++) + if (test[i] == 0xff) { + if (test[i+1]) return 1; + ret=0; + } + return ret; +} + +void CLASS canon_compressed_load_raw() +{ + ushort *pixel, *prow; + int nblocks, lowbits, i, row, r, col, save, val; + unsigned irow, icol; + struct decode *decode, *dindex; + int block, diffbuf[64], leaf, len, diff, carry=0, pnum=0, base[2]; + double dark[2] = { 0,0 }; + uchar c; + + crw_init_tables (tiff_compress); + pixel = (ushort *) calloc (raw_width*8, sizeof *pixel); + merror (pixel, "canon_compressed_load_raw()"); + lowbits = canon_has_lowbits(); + if (!lowbits) maximum = 0x3ff; + fseek (ifp, 540 + lowbits*raw_height*raw_width/4, SEEK_SET); + zero_after_ff = 1; + getbits(-1); + for (row=0; row < raw_height; row+=8) { + nblocks = MIN (8, raw_height-row) * raw_width >> 6; + for (block=0; block < nblocks; block++) { + memset (diffbuf, 0, sizeof diffbuf); + decode = first_decode; + for (i=0; i < 64; i++ ) { + for (dindex=decode; dindex->branch[0]; ) + dindex = dindex->branch[getbits(1)]; + leaf = dindex->leaf; + decode = second_decode; + if (leaf == 0 && i) break; + if (leaf == 0xff) continue; + i += leaf >> 4; + len = leaf & 15; + if (len == 0) continue; + diff = getbits(len); + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - 1; + if (i < 64) diffbuf[i] = diff; + } + diffbuf[0] += carry; + carry = diffbuf[0]; + for (i=0; i < 64; i++ ) { + if (pnum++ % raw_width == 0) + base[0] = base[1] = 512; + if ((pixel[(block << 6) + i] = base[i & 1] += diffbuf[i]) >> 10) + derror(); + } + } + if (lowbits) { + save = ftell(ifp); + fseek (ifp, 26 + row*raw_width/4, SEEK_SET); + for (prow=pixel, i=0; i < raw_width*2; i++) { + c = fgetc(ifp); + for (r=0; r < 8; r+=2, prow++) { + val = (*prow << 2) + ((c >> r) & 3); + if (raw_width == 2672 && val < 512) val += 2; + *prow = val; + } + } + fseek (ifp, save, SEEK_SET); + } + for (r=0; r < 8; r++) { + irow = row - top_margin + r; +#ifndef LIBRAW_LIBRARY_BUILD + if (irow >= height) continue; +#endif + for (col=0; col < raw_width; col++) { +#ifdef LIBRAW_LIBRARY_BUILD + ushort *dfp = get_masked_pointer(row+r,col); + if(dfp) *dfp = pixel[r*raw_width+col]; + if (irow >= height) continue; // skip for top/bottom rows +#endif + icol = col - left_margin; + if (icol < width) + BAYER(irow,icol) = pixel[r*raw_width+col]; + else if (col > 1) + dark[icol & 1] += pixel[r*raw_width+col]; + } + } + } + free (pixel); +#ifdef LIBRAW_LIBRARY_BUILD + if(!( filtering_mode & LIBRAW_FILTERING_NOBLACKS) ) +#endif + canon_black (dark); +} + +#line 922 "dcraw/dcraw.c" +int CLASS ljpeg_start (struct jhead *jh, int info_only) +{ + int c, tag, len; + uchar data[0x10000], *dp; + + init_decoder(); + memset (jh, 0, sizeof *jh); + FORC(6) jh->huff[c] = free_decode; + jh->restart = INT_MAX; + fread (data, 2, 1, ifp); + if (data[1] != 0xd8) return 0; + do { + fread (data, 2, 2, ifp); + tag = data[0] << 8 | data[1]; + len = (data[2] << 8 | data[3]) - 2; + if (tag <= 0xff00) return 0; + fread (data, 1, len, ifp); + switch (tag) { + case 0xffc3: + jh->sraw = ((data[7] >> 4) * (data[7] & 15) - 1) & 3; + case 0xffc0: + jh->bits = data[0]; + jh->high = data[1] << 8 | data[2]; + jh->wide = data[3] << 8 | data[4]; + jh->clrs = data[5] + jh->sraw; + if (len == 9 && !dng_version) getc(ifp); + break; + case 0xffc4: + if (info_only) break; + for (dp = data; dp < data+len && *dp < 4; ) { + jh->huff[*dp] = free_decode; + dp = make_decoder (++dp, 0); + } + break; + case 0xffda: + jh->psv = data[1+data[0]*2]; + jh->bits -= data[3+data[0]*2] & 15; + break; + case 0xffdd: + jh->restart = data[0] << 8 | data[1]; + } + } while (tag != 0xffda); + if (info_only) return 1; + if (jh->sraw) { + FORC(4) jh->huff[2+c] = jh->huff[1]; + FORC(jh->sraw) jh->huff[1+c] = jh->huff[0]; + } + jh->row = (ushort *) calloc (jh->wide*jh->clrs, 4); + merror (jh->row, "ljpeg_start()"); + return zero_after_ff = 1; +} + +int CLASS ljpeg_diff (struct decode *dindex) +{ + int len, diff; + + while (dindex->branch[0]) + dindex = dindex->branch[getbits(1)]; + len = dindex->leaf; + if (len == 16 && (!dng_version || dng_version >= 0x1010000)) + return -32768; + diff = getbits(len); + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - 1; + return diff; +} + +ushort * CLASS ljpeg_row (int jrow, struct jhead *jh) +{ + int col, c, diff, pred, spred=0; + ushort mark=0, *row[3]; + + if (jrow * jh->wide % jh->restart == 0) { + FORC(6) jh->vpred[c] = 1 << (jh->bits-1); + if (jrow) + do mark = (mark << 8) + (c = fgetc(ifp)); + while (c != EOF && mark >> 4 != 0xffd); + getbits(-1); + } + FORC3 row[c] = jh->row + jh->wide*jh->clrs*((jrow+c) & 1); + for (col=0; col < jh->wide; col++) + FORC(jh->clrs) { + diff = ljpeg_diff (jh->huff[c]); + if (jh->sraw && c <= jh->sraw && (col | c)) + pred = spred; + else if (col) pred = row[0][-jh->clrs]; + else pred = (jh->vpred[c] += diff) - diff; + if (jrow && col) switch (jh->psv) { + case 1: break; + case 2: pred = row[1][0]; break; + case 3: pred = row[1][-jh->clrs]; break; + case 4: pred = pred + row[1][0] - row[1][-jh->clrs]; break; + case 5: pred = pred + ((row[1][0] - row[1][-jh->clrs]) >> 1); break; + case 6: pred = row[1][0] + ((pred - row[1][-jh->clrs]) >> 1); break; + case 7: pred = (pred + row[1][0]) >> 1; break; + default: pred = 0; + } + if ((**row = pred + diff) >> jh->bits) derror(); + if (c <= jh->sraw) spred = **row; + row[0]++; row[1]++; + } + return row[2]; +} + +void CLASS lossless_jpeg_load_raw() +{ + int jwide, jrow, jcol, val, jidx, i, j, row=0, col=0; + double dark[2] = { 0,0 }; + struct jhead jh; + int min=INT_MAX; + ushort *rp; + + if (!ljpeg_start (&jh, 0)) return; + jwide = jh.wide * jh.clrs; + + for (jrow=0; jrow < jh.high; jrow++) { + rp = ljpeg_row (jrow, &jh); + for (jcol=0; jcol < jwide; jcol++) { + val = *rp++; + if (jh.bits <= 12) +#ifdef LIBRAW_LIBRARY_BUILD + if( !(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) +#endif + val = curve[val & 0xfff]; + if (cr2_slice[0]) { + jidx = jrow*jwide + jcol; + i = jidx / (cr2_slice[1]*jh.high); + if ((j = i >= cr2_slice[0])) + i = cr2_slice[0]; + jidx -= i * (cr2_slice[1]*jh.high); + row = jidx / cr2_slice[1+j]; + col = jidx % cr2_slice[1+j] + i*cr2_slice[1]; + } + if (raw_width == 3984 && (col -= 2) < 0) + col += (row--,raw_width); +#ifdef LIBRAW_LIBRARY_BUILD + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = val; +#endif + if ((unsigned) (row-top_margin) < height) { + if ((unsigned) (col-left_margin) < width) { + BAYER(row-top_margin,col-left_margin) = val; + if (min > val) min = val; + } else if (col > 1) + dark[(col-left_margin) & 1] += val; + } + if (++col >= raw_width) + col = (row++,0); + } + } + free (jh.row); +#ifdef LIBRAW_LIBRARY_BUILD + if(!(filtering_mode & LIBRAW_FILTERING_NOBLACKS)) +#endif + canon_black (dark); + if (!strcasecmp(make,"KODAK")) + black = min; +} + +void CLASS canon_sraw_load_raw() +{ + struct jhead jh; + short *rp=0, (*ip)[4]; + int jwide, slice, scol, ecol, row, col, jrow=0, jcol=0, pix[3], c; + + if (!ljpeg_start (&jh, 0)) return; + jwide = (jh.wide >>= 1) * jh.clrs; + + for (ecol=slice=0; slice <= cr2_slice[0]; slice++) { + scol = ecol; + ecol += cr2_slice[1] * 2 / jh.clrs; + if (!cr2_slice[0] || ecol > raw_width-1) ecol = raw_width & -2; + for (row=0; row < height; row += (jh.clrs >> 1) - 1) { + ip = (short (*)[4]) image + row*width; + for (col=scol; col < ecol; col+=2, jcol+=jh.clrs) { + if ((jcol %= jwide) == 0) + rp = (short *) ljpeg_row (jrow++, &jh); + if (col >= width) continue; + FORC (jh.clrs-2) + ip[col + (c >> 1)*width + (c & 1)][0] = rp[jcol+c]; + ip[col][1] = rp[jcol+jh.clrs-2] - 16384; + ip[col][2] = rp[jcol+jh.clrs-1] - 16384; + } + } + } + ip = (short (*)[4]) image; + rp = ip[0]; + for (row=0; row < height; row++, ip+=width) { + if (row & (jh.sraw >> 1)) + for (col=0; col < width; col+=2) + for (c=1; c < 3; c++) + if (row == height-1) + ip[col][c] = ip[col-width][c]; + else ip[col][c] = (ip[col-width][c] + ip[col+width][c] + 1) >> 1; + for (col=1; col < width; col+=2) + for (c=1; c < 3; c++) + if (col == width-1) + ip[col][c] = ip[col-1][c]; + else ip[col][c] = (ip[col-1][c] + ip[col+1][c] + 1) >> 1; + } + for ( ; rp < ip[0]; rp+=4) { + if (unique_id < 0x80000200) { + pix[0] = rp[0] + rp[2] - 512; + pix[2] = rp[0] + rp[1] - 512; + pix[1] = rp[0] + ((-778*rp[1] - (rp[2] << 11)) >> 12) - 512; + } else { + rp[1] += jh.sraw+1; + rp[2] += jh.sraw+1; + pix[0] = rp[0] + (( 200*rp[1] + 22929*rp[2]) >> 12); + pix[1] = rp[0] + ((-5640*rp[1] - 11751*rp[2]) >> 12); + pix[2] = rp[0] + ((29040*rp[1] - 101*rp[2]) >> 12); + } + FORC3 rp[c] = CLIP(pix[c] * sraw_mul[c] >> 10); + } + free (jh.row); + maximum = 0x3fff; +} + +void CLASS adobe_copy_pixel (int row, int col, ushort **rp) +{ + unsigned r, c; + + r = row -= top_margin; + c = col -= left_margin; + if (is_raw == 2 && shot_select) (*rp)++; + if (filters) { +#ifndef LIBRAW_LIBRARY_BUILD + if (fuji_width) { + r = row + fuji_width - 1 - (col >> 1); + c = row + ((col+1) >> 1); + } +#endif +#ifdef LIBRAW_LIBRARY_BUILD + ushort val = **rp; + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) + val = **rp < 0x1000 ? curve[**rp] : **rp; + if (r < height && c < width) + BAYER(r,c) = val; + else + { + ushort *dfp = get_masked_pointer(row+top_margin,col+left_margin); + if(dfp) *dfp = val; + } +#else + if (r < height && c < width) + BAYER(r,c) = **rp < 0x1000 ? curve[**rp] : **rp; +#endif + *rp += is_raw; + } else { + if (r < height && c < width) + FORC(tiff_samples) + image[row*width+col][c] = (*rp)[c] < 0x1000 ? curve[(*rp)[c]]:(*rp)[c]; + *rp += tiff_samples; + } + if (is_raw == 2 && shot_select) (*rp)--; +} + +void CLASS adobe_dng_load_raw_lj() +{ + unsigned save, trow=0, tcol=0, jwide, jrow, jcol, row, col; + struct jhead jh; + ushort *rp; + + while (trow < raw_height) { + save = ftell(ifp); + if (tile_length < INT_MAX) + fseek (ifp, get4(), SEEK_SET); + if (!ljpeg_start (&jh, 0)) break; + jwide = jh.wide; + if (filters) jwide *= jh.clrs; + jwide /= is_raw; + for (row=col=jrow=0; jrow < jh.high; jrow++) { + rp = ljpeg_row (jrow, &jh); + for (jcol=0; jcol < jwide; jcol++) { + adobe_copy_pixel (trow+row, tcol+col, &rp); + if (++col >= tile_width || col >= raw_width) + row += 1 + (col = 0); + } + } + fseek (ifp, save+4, SEEK_SET); + if ((tcol += tile_width) >= raw_width) + trow += tile_length + (tcol = 0); + free (jh.row); + } +} + +void CLASS adobe_dng_load_raw_nc() +{ + ushort *pixel, *rp; + int row, col; + + pixel = (ushort *) calloc (raw_width * tiff_samples, sizeof *pixel); + merror (pixel, "adobe_dng_load_raw_nc()"); + for (row=0; row < raw_height; row++) { + if (tiff_bps == 16) + read_shorts (pixel, raw_width * tiff_samples); + else { + getbits(-1); + for (col=0; col < raw_width * tiff_samples; col++) + pixel[col] = getbits(tiff_bps); + } + for (rp=pixel, col=0; col < raw_width; col++) + adobe_copy_pixel (row, col, &rp); + } + free (pixel); +} + +void CLASS pentax_k10_load_raw() +{ + static const uchar pentax_tree[] = + { 0,2,3,1,1,1,1,1,1,2,0,0,0,0,0,0, + 3,4,2,5,1,6,0,7,8,9,10,11,12 }; + int row, col, diff; + ushort vpred[2][2] = {{0,0},{0,0}}, hpred[2]; + + init_decoder(); + make_decoder (pentax_tree, 0); + getbits(-1); + for (row=0; row < raw_height; row++) + { +#ifndef LIBRAW_LIBRARY_BUILD + if(row >= height) break; +#endif + for (col=0; col < raw_width; col++) { + diff = ljpeg_diff (first_decode); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + if (col < width && row < height) + BAYER(row,col) = hpred[col & 1]; +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = hpred[col & 1]; + } + + if (col < width && row < height) +#endif + if (hpred[col & 1] >> 12) derror(); + } + } +} + +void CLASS nikon_compressed_load_raw() +{ + static const uchar nikon_tree[][32] = { + { 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 }, + { 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 }, + { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 }, + { 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 }, + { 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 }, + { 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 } }; + struct decode *dindex; + ushort ver0, ver1, vpred[2][2], hpred[2], csize; + int i, min, max, step=0, huff=0, split=0, row, col, len, shl, diff; + + fseek (ifp, meta_offset, SEEK_SET); + ver0 = fgetc(ifp); + ver1 = fgetc(ifp); + if (ver0 == 0x49 || ver1 == 0x58) + fseek (ifp, 2110, SEEK_CUR); + if (ver0 == 0x46) huff = 2; + if (tiff_bps == 14) huff += 3; + read_shorts (vpred[0], 4); + max = 1 << tiff_bps & 0x7fff; + if ((csize = get2()) > 1) + step = max / (csize-1); + if (ver0 == 0x44 && ver1 == 0x20 && step > 0) { + for (i=0; i < csize; i++) + curve[i*step] = get2(); + for (i=0; i < max; i++) + curve[i] = ( curve[i-i%step]*(step-i%step) + + curve[i-i%step+step]*(i%step) ) / step; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_LOADED; +#endif + fseek (ifp, meta_offset+562, SEEK_SET); + split = get2(); + } else if (ver0 != 0x46 && csize <= 0x4001) + { + read_shorts (curve, max=csize); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + while (curve[max-2] == curve[max-1]) max--; + init_decoder(); + make_decoder (nikon_tree[huff], 0); + fseek (ifp, data_offset, SEEK_SET); + getbits(-1); + for (min=row=0; row < height; row++) { + if (split && row == split) { + init_decoder(); + make_decoder (nikon_tree[huff+1], 0); + max += (min = 16) << 1; + } + for (col=0; col < raw_width; col++) { + for (dindex=first_decode; dindex->branch[0]; ) + dindex = dindex->branch[getbits(1)]; + len = dindex->leaf & 15; + shl = dindex->leaf >> 4; + diff = ((getbits(len-shl) << 1) + 1) << shl >> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - !shl; + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + if ((ushort)(hpred[col & 1] + min) >= max) derror(); +#ifndef LIBRAW_LIBRARY_BUILD + if ((unsigned) (col-left_margin) < width) + BAYER(row,col-left_margin) = curve[LIM((short)hpred[col & 1],0,0x3fff)]; +#else + ushort xval = hpred[col & 1]; + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) + xval = curve[LIM((short)xval,0,0x3fff)]; + if ((unsigned) (col-left_margin) < width) + { + BAYER(row,col-left_margin) = xval; + } + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = xval; + } +#endif + + } + } +} + +/* + Figure out if a NEF file is compressed. These fancy heuristics + are only needed for the D100, thanks to a bug in some cameras + that tags all images as "compressed". + */ +int CLASS nikon_is_compressed() +{ + uchar test[256]; + int i; + + fseek (ifp, data_offset, SEEK_SET); + fread (test, 1, 256, ifp); + for (i=15; i < 256; i+=16) + if (test[i]) return 1; + return 0; +} + +/* + Returns 1 for a Coolpix 995, 0 for anything else. + */ +int CLASS nikon_e995() +{ + int i, histo[256]; + const uchar often[] = { 0x00, 0x55, 0xaa, 0xff }; + + memset (histo, 0, sizeof histo); + fseek (ifp, -2000, SEEK_END); + for (i=0; i < 2000; i++) + histo[fgetc(ifp)]++; + for (i=0; i < 4; i++) + if (histo[often[i]] < 200) + return 0; + return 1; +} + +/* + Returns 1 for a Coolpix 2100, 0 for anything else. + */ +int CLASS nikon_e2100() +{ + uchar t[12]; + int i; + + fseek (ifp, 0, SEEK_SET); + for (i=0; i < 1024; i++) { + fread (t, 1, 12, ifp); + if (((t[2] & t[4] & t[7] & t[9]) >> 4 + & t[1] & t[6] & t[8] & t[11] & 3) != 3) + return 0; + } + return 1; +} + +void CLASS nikon_3700() +{ + int bits, i; + uchar dp[24]; + static const struct { + int bits; + char t_make[12], t_model[15]; + } table[] = { + { 0x00, "PENTAX", "Optio 33WR" }, + { 0x03, "NIKON", "E3200" }, + { 0x32, "NIKON", "E3700" }, + { 0x33, "OLYMPUS", "C740UZ" } }; + + fseek (ifp, 3072, SEEK_SET); + fread (dp, 1, 24, ifp); + bits = (dp[8] & 3) << 4 | (dp[20] & 3); + for (i=0; i < sizeof table / sizeof *table; i++) + if (bits == table[i].bits) { + strcpy (make, table[i].t_make ); + strcpy (model, table[i].t_model); + } +} + +/* + Separates a Minolta DiMAGE Z2 from a Nikon E4300. + */ +int CLASS minolta_z2() +{ + int i, nz; + char tail[424]; + + fseek (ifp, -sizeof tail, SEEK_END); + fread (tail, 1, sizeof tail, ifp); + for (nz=i=0; i < sizeof tail; i++) + if (tail[i]) nz++; + return nz > 20; +} + +/* Here raw_width is in bytes, not pixels. */ +void CLASS nikon_e900_load_raw() +{ + int offset=0, irow, row, col; + + for (irow=0; irow < height; irow++) { + row = irow * 2 % height; + if (row == 1) + offset = - (-offset & -4096); + fseek (ifp, offset, SEEK_SET); + offset += raw_width; + getbits(-1); + for (col=0; col < width; col++) + BAYER(row,col) = getbits(10); + } +} + +/* + The Fuji Super CCD is just a Bayer grid rotated 45 degrees. + */ +void CLASS fuji_load_raw() +{ + ushort *pixel; +#ifndef LIBRAW_LIBRARY_BUILD + int wide, row, col, r, c; + + fseek (ifp, (top_margin*raw_width + left_margin) * 2, SEEK_CUR); + wide = fuji_width << !fuji_layout; + pixel = (ushort *) calloc (wide, sizeof *pixel); + merror (pixel, "fuji_load_raw()"); + for (row=0; row < raw_height; row++) { + read_shorts (pixel, wide); + fseek (ifp, 2*(raw_width - wide), SEEK_CUR); + for (col=0; col < wide; col++) { + if (fuji_layout) { + r = fuji_width - 1 - col + (row >> 1); + c = col + ((row+1) >> 1); + } else { + r = fuji_width - 1 + row - (col >> 1); + c = row + ((col+1) >> 1); + } + BAYER(r,c) = pixel[col]; + } + } + free (pixel); +#else + int row,col; + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "fuji_load_raw()"); + for (row=0; row < raw_height; row++) { + read_shorts (pixel, raw_width); + for (col=0; col < raw_width; col++) { + if(col >= left_margin && col < width+left_margin + && row >= top_margin && row < height+top_margin) + { + BAYER(row-top_margin,col-left_margin) = pixel[col]; + } + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = pixel[col]; + } + } + } + free (pixel); +#endif +} +#line 1520 "dcraw/dcraw.c" +void CLASS ppm_thumb (FILE *tfp) +{ + char *thumb; + thumb_length = thumb_width*thumb_height*3; + thumb = (char *) malloc (thumb_length); + merror (thumb, "ppm_thumb()"); + fprintf (tfp, "P6\n%d %d\n255\n", thumb_width, thumb_height); + fread (thumb, 1, thumb_length, ifp); + fwrite (thumb, 1, thumb_length, tfp); + free (thumb); +} + +void CLASS layer_thumb (FILE *tfp) +{ + int i, c; + char *thumb, map[][4] = { "012","102" }; + + colors = thumb_misc >> 5 & 7; + thumb_length = thumb_width*thumb_height; + thumb = (char *) calloc (colors, thumb_length); + merror (thumb, "layer_thumb()"); + fprintf (tfp, "P%d\n%d %d\n255\n", + 5 + (colors >> 1), thumb_width, thumb_height); + fread (thumb, thumb_length, colors, ifp); + for (i=0; i < thumb_length; i++) + FORCC putc (thumb[i+thumb_length*(map[thumb_misc >> 8][c]-'0')], tfp); + free (thumb); +} + +void CLASS rollei_thumb (FILE *tfp) +{ + unsigned i; + ushort *thumb; + + thumb_length = thumb_width * thumb_height; + thumb = (ushort *) calloc (thumb_length, 2); + merror (thumb, "rollei_thumb()"); + fprintf (tfp, "P6\n%d %d\n255\n", thumb_width, thumb_height); + read_shorts (thumb, thumb_length); + for (i=0; i < thumb_length; i++) { + putc (thumb[i] << 3, tfp); + putc (thumb[i] >> 5 << 2, tfp); + putc (thumb[i] >> 11 << 3, tfp); + } + free (thumb); +} + +void CLASS rollei_load_raw() +{ + uchar pixel[10]; + unsigned iten=0, isix, i, buffer=0, row, col, todo[16]; + + isix = raw_width * raw_height * 5 / 8; + while (fread (pixel, 1, 10, ifp) == 10) { + for (i=0; i < 10; i+=2) { + todo[i] = iten++; + todo[i+1] = pixel[i] << 8 | pixel[i+1]; + buffer = pixel[i] >> 2 | buffer << 6; + } + for ( ; i < 16; i+=2) { + todo[i] = isix++; + todo[i+1] = buffer >> (14-i)*5; + } + for (i=0; i < 16; i+=2) { + row = todo[i] / raw_width - top_margin; + col = todo[i] % raw_width - left_margin; + if (row < height && col < width) + BAYER(row,col) = (todo[i+1] & 0x3ff); +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(todo[i] / raw_width,todo[i] % raw_width); + if(dfp) *dfp = (todo[i+1] & 0x3ff); + } +#endif + } + } + maximum = 0x3ff; +} + +int CLASS bayer (unsigned row, unsigned col) +{ + return (row < height && col < width) ? BAYER(row,col) : 0; +} + +void CLASS phase_one_flat_field (int is_float, int nc) +{ + ushort head[8]; + unsigned wide, y, x, c, rend, cend, row, col; + float *mrow, num, mult[4]; + + read_shorts (head, 8); + wide = head[2] / head[4]; + mrow = (float *) calloc (nc*wide, sizeof *mrow); + merror (mrow, "phase_one_flat_field()"); + for (y=0; y < head[3] / head[5]; y++) { + for (x=0; x < wide; x++) + for (c=0; c < nc; c+=2) { + num = is_float ? getreal(11) : get2()/32768.0; + if (y==0) mrow[c*wide+x] = num; + else mrow[(c+1)*wide+x] = (num - mrow[c*wide+x]) / head[5]; + } + if (y==0) continue; + rend = head[1]-top_margin + y*head[5]; + for (row = rend-head[5]; row < height && row < rend; row++) { + for (x=1; x < wide; x++) { + for (c=0; c < nc; c+=2) { + mult[c] = mrow[c*wide+x-1]; + mult[c+1] = (mrow[c*wide+x] - mult[c]) / head[4]; + } + cend = head[0]-left_margin + x*head[4]; + for (col = cend-head[4]; col < width && col < cend; col++) { + c = nc > 2 ? FC(row,col) : 0; + if (!(c & 1)) { + c = BAYER(row,col) * mult[c]; + BAYER(row,col) = LIM(c,0,65535); + } + for (c=0; c < nc; c+=2) + mult[c] += mult[c+1]; + } + } + for (x=0; x < wide; x++) + for (c=0; c < nc; c+=2) + mrow[c*wide+x] += mrow[(c+1)*wide+x]; + } + } + free (mrow); +} + +void CLASS phase_one_correct() +{ + unsigned entries, tag, data, save, col, row, type; + int len, i, j, k, cip, val[4], dev[4], sum, max; + int head[9], diff, mindiff=INT_MAX, off_412=0; + static const signed char dir[12][2] = + { {-1,-1}, {-1,1}, {1,-1}, {1,1}, {-2,0}, {0,-2}, {0,2}, {2,0}, + {-2,-2}, {-2,2}, {2,-2}, {2,2} }; + float poly[8], num, cfrac, frac, mult[2], *yval[2]; + ushort t_curve[0x10000], *xval[2]; + + if (half_size || !meta_length) return; +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Phase One correction...\n")); +#endif + fseek (ifp, meta_offset, SEEK_SET); + order = get2(); + fseek (ifp, 6, SEEK_CUR); + fseek (ifp, meta_offset+get4(), SEEK_SET); + entries = get4(); get4(); + while (entries--) { + tag = get4(); + len = get4(); + data = get4(); + save = ftell(ifp); + fseek (ifp, meta_offset+data, SEEK_SET); + if (tag == 0x419) { /* Polynomial curve */ + for (get4(), i=0; i < 8; i++) + poly[i] = getreal(11); + poly[3] += (ph1.tag_210 - poly[7]) * poly[6] + 1; + for (i=0; i < 0x10000; i++) { + num = (poly[5]*i + poly[3])*i + poly[1]; + t_curve[i] = LIM(num,0,65535); + } goto apply; /* apply to right half */ + } else if (tag == 0x41a) { /* Polynomial curve */ + for (i=0; i < 4; i++) + poly[i] = getreal(11); + for (i=0; i < 0x10000; i++) { + for (num=0, j=4; j--; ) + num = num * i + poly[j]; + t_curve[i] = LIM(num+i,0,65535); + } apply: /* apply to whole image */ + for (row=0; row < height; row++) + for (col = (tag & 1)*ph1.split_col; col < width; col++) + BAYER(row,col) = t_curve[BAYER(row,col)]; + } else if (tag == 0x400) { /* Sensor defects */ + while ((len -= 8) >= 0) { + col = get2() - left_margin; + row = get2() - top_margin; + type = get2(); get2(); + if (col >= width) continue; + if (type == 131) /* Bad column */ + for (row=0; row < height; row++) + if (FC(row,col) == 1) { + for (sum=i=0; i < 4; i++) + sum += val[i] = bayer (row+dir[i][0], col+dir[i][1]); + for (max=i=0; i < 4; i++) { + dev[i] = abs((val[i] << 2) - sum); + if (dev[max] < dev[i]) max = i; + } + BAYER(row,col) = (sum - val[max])/3.0 + 0.5; + } else { + for (sum=0, i=8; i < 12; i++) + sum += bayer (row+dir[i][0], col+dir[i][1]); + BAYER(row,col) = 0.5 + sum * 0.0732233 + + (bayer(row,col-2) + bayer(row,col+2)) * 0.3535534; + } + else if (type == 129) { /* Bad pixel */ + if (row >= height) continue; + j = (FC(row,col) != 1) * 4; + for (sum=0, i=j; i < j+8; i++) + sum += bayer (row+dir[i][0], col+dir[i][1]); + BAYER(row,col) = (sum + 4) >> 3; + } + } + } else if (tag == 0x401) { /* All-color flat fields */ + phase_one_flat_field (1, 2); + } else if (tag == 0x416 || tag == 0x410) { + phase_one_flat_field (0, 2); + } else if (tag == 0x40b) { /* Red+blue flat field */ + phase_one_flat_field (0, 4); + } else if (tag == 0x412) { + fseek (ifp, 36, SEEK_CUR); + diff = abs (get2() - ph1.tag_21a); + if (mindiff > diff) { + mindiff = diff; + off_412 = ftell(ifp) - 38; + } + } + fseek (ifp, save, SEEK_SET); + } + if (off_412) { + fseek (ifp, off_412, SEEK_SET); + for (i=0; i < 9; i++) head[i] = get4() & 0x7fff; + yval[0] = (float *) calloc (head[1]*head[3] + head[2]*head[4], 6); + merror (yval[0], "phase_one_correct()"); + yval[1] = (float *) (yval[0] + head[1]*head[3]); + xval[0] = (ushort *) (yval[1] + head[2]*head[4]); + xval[1] = (ushort *) (xval[0] + head[1]*head[3]); + get2(); + for (i=0; i < 2; i++) + for (j=0; j < head[i+1]*head[i+3]; j++) + yval[i][j] = getreal(11); + for (i=0; i < 2; i++) + for (j=0; j < head[i+1]*head[i+3]; j++) + xval[i][j] = get2(); + for (row=0; row < height; row++) + for (col=0; col < width; col++) { + cfrac = (float) col * head[3] / raw_width; + cfrac -= cip = cfrac; + num = BAYER(row,col) * 0.5; + for (i=cip; i < cip+2; i++) { + for (k=j=0; j < head[1]; j++) + if (num < xval[0][k = head[1]*i+j]) break; + frac = (j == 0 || j == head[1]) ? 0 : + (xval[0][k] - num) / (xval[0][k] - xval[0][k-1]); + mult[i-cip] = yval[0][k-1] * frac + yval[0][k] * (1-frac); + } + i = ((mult[0] * (1-cfrac) + mult[1] * cfrac) + * (row + top_margin) + num) * 2; + BAYER(row,col) = LIM(i,0,65535); + } + free (yval[0]); + } +} + +void CLASS phase_one_load_raw() +{ + int row, col, a, b; + ushort *pixel, akey, bkey, mask; + + fseek (ifp, ph1.key_off, SEEK_SET); + akey = get2(); + bkey = get2(); + mask = ph1.format == 1 ? 0x5555:0x1354; +#ifndef LIBRAW_LIBRARY_BUILD + fseek (ifp, data_offset + top_margin*raw_width*2, SEEK_SET); + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "phase_one_load_raw()"); + for (row=0; row < height; row++) { + read_shorts (pixel, raw_width); + for (col=0; col < raw_width; col+=2) { + a = pixel[col+0] ^ akey; + b = pixel[col+1] ^ bkey; + pixel[col+0] = (a & mask) | (b & ~mask); + pixel[col+1] = (b & mask) | (a & ~mask); + } + for (col=0; col < width; col++) + BAYER(row,col) = pixel[col+left_margin]; + } + free (pixel); +#else + fseek (ifp, data_offset, SEEK_SET); + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "phase_one_load_raw()"); + for (row=0; row < raw_height; row++) { + read_shorts (pixel, raw_width); + for (col=0; col < raw_width; col+=2) { + a = pixel[col+0] ^ akey; + b = pixel[col+1] ^ bkey; + pixel[col+0] = (a & mask) | (b & ~mask); + pixel[col+1] = (b & mask) | (a & ~mask); + } + for (col=0; col < raw_width; col++) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) + *dfp = pixel[col]; + else + BAYER(row,col-left_margin) = pixel[col]; + } + } + free (pixel); + if(!( filtering_mode & LIBRAW_FILTERING_NORAWCURVE) ) +#endif + phase_one_correct(); +} + +unsigned CLASS ph1_bits (int nbits) +{ +#ifndef LIBRAW_NOTHREADS +#define bitbuf tls->ph1_bits.bitbuf +#define vbits tls->ph1_bits.vbits +#else + static UINT64 bitbuf=0; + static int vbits=0; +#endif + if (nbits == -1) + return bitbuf = vbits = 0; + if (nbits == 0) return 0; + if ((vbits -= nbits) < 0) { + bitbuf = bitbuf << 32 | get4(); + vbits += 32; + } + return bitbuf << (64-nbits-vbits) >> (64-nbits); +#ifndef LIBRAW_NOTHREADS +#undef bitbuf +#undef vbits +#endif +} + +void CLASS phase_one_load_raw_c() +{ + static const int length[] = { 8,7,6,9,11,10,5,12,14,13 }; + int *offset, len[2], pred[2], row, col, i, j; + ushort *pixel; + short (*t_black)[2]; + + pixel = (ushort *) calloc (raw_width + raw_height*4, 2); + merror (pixel, "phase_one_load_raw_c()"); + offset = (int *) (pixel + raw_width); + fseek (ifp, strip_offset, SEEK_SET); + for (row=0; row < raw_height; row++) + offset[row] = get4(); + t_black = (short (*)[2]) offset + raw_height; + fseek (ifp, ph1.black_off, SEEK_SET); + if (ph1.black_off) + { + read_shorts ((ushort *) t_black[0], raw_height*2); +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.masked_pixels.ph1_black = (ushort (*)[2])calloc(raw_height*2,sizeof(ushort)); + merror (imgdata.masked_pixels.ph1_black, "phase_one_load_raw_c()"); + memmove(imgdata.masked_pixels.ph1_black,(ushort *) t_black[0],raw_height*2*sizeof(ushort)); +#endif + } + for (i=0; i < 256; i++) + curve[i] = i*i / 3.969 + 0.5; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_CALCULATED; +#endif + for (row=0; row < raw_height; row++) { + fseek (ifp, data_offset + offset[row], SEEK_SET); + ph1_bits(-1); + pred[0] = pred[1] = 0; + for (col=0; col < raw_width; col++) { + if (col >= (raw_width & -8)) + len[0] = len[1] = 14; + else if ((col & 7) == 0) + for (i=0; i < 2; i++) { + for (j=0; j < 5 && !ph1_bits(1); j++); + if (j--) len[i] = length[j*2 + ph1_bits(1)]; + } + if ((i = len[col & 1]) == 14) + pixel[col] = pred[col & 1] = ph1_bits(16); + else + pixel[col] = pred[col & 1] += ph1_bits(i) + 1 - (1 << (i - 1)); + if (pred[col & 1] >> 16) derror(); +#ifdef LIBRAW_LIBRARY_BUILD + if(!( filtering_mode & LIBRAW_FILTERING_NORAWCURVE) ) +#endif + if (ph1.format == 5 && pixel[col] < 256) + pixel[col] = curve[pixel[col]]; + } + if ((unsigned) (row-top_margin) < height) +#ifndef LIBRAW_LIBRARY_BUILD + for (col=0; col < width; col++) { + i = (pixel[col+left_margin] << 2) + - ph1.t_black + t_black[row][col >= ph1.split_col]; + if (i > 0) BAYER(row-top_margin,col) = i; + } +#else + { + for (col=0; col < raw_width; col++) { + if( filtering_mode & LIBRAW_FILTERING_NOBLACKS) + i = (pixel[col] << 2); + else + i = (pixel[col] << 2) + - ph1.t_black + t_black[row][(col /* - left_margin */) >= ph1.split_col]; // changed to fix Coffin's bug! + if(col >= left_margin && col < width+left_margin) + { + if (i > 0) BAYER(row-top_margin,col-left_margin) = i; + } + else + { + ushort *dfp = get_masked_pointer(row,col); + if(i>0 && dfp) *dfp = i; + } + } + } + else + { + // top-bottom fields + for (col=0; col < raw_width; col++) { + i = (pixel[col] << 2) + - ph1.t_black + t_black[row][(col+left_margin) >= ph1.split_col]; + if (i > 0) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = i; + } + } + } +#endif + } + free (pixel); +#ifdef LIBRAW_LIBRARY_BUILD + if(!( filtering_mode & LIBRAW_FILTERING_NORAWCURVE) ) +#endif + phase_one_correct(); + maximum = 0xfffc - ph1.t_black; +} + +void CLASS hasselblad_load_raw() +{ + struct jhead jh; + struct decode *dindex; + int row, col, pred[2], len[2], diff, i; + + if (!ljpeg_start (&jh, 0)) return; + free (jh.row); + order = 0x4949; + ph1_bits(-1); + for (row=-top_margin; row < raw_height-top_margin; row++) { + pred[0] = pred[1] = 0x8000; + for (col=-left_margin; col < raw_width-left_margin; col+=2) { + for (i=0; i < 2; i++) { + for (dindex=jh.huff[0]; dindex->branch[0]; ) + dindex = dindex->branch[ph1_bits(1)]; + len[i] = dindex->leaf; + } + for (i=0; i < 2; i++) { + diff = ph1_bits(len[i]); + if ((diff & (1 << (len[i]-1))) == 0) + diff -= (1 << len[i]) - 1; + if (diff == 65535) diff = -32768; + pred[i] += diff; + if (row >= 0 && row < height && (unsigned)(col+i) < width) + BAYER(row,col+i) = pred[i]; +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(row+top_margin,col+left_margin); + if(dfp) *dfp = pred[i]; + } +#endif + } + } + } + maximum = 0xffff; +} + +void CLASS leaf_hdr_load_raw() +{ + ushort *pixel; + unsigned tile=0, r, c, row, col; + + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "leaf_hdr_load_raw()"); + FORC(tiff_samples) + for (r=0; r < raw_height; r++) { + if (r % tile_length == 0) { + fseek (ifp, data_offset + 4*tile++, SEEK_SET); + fseek (ifp, get4() + 2*left_margin, SEEK_SET); + } + if (filters && c != shot_select) continue; + read_shorts (pixel, raw_width); + if ((row = r - top_margin) >= height) continue; + for (col=0; col < width; col++) + if (filters) BAYER(row,col) = pixel[col]; + else image[row*width+col][c] = pixel[col]; + } + free (pixel); + if (!filters) { + maximum = 0xffff; + raw_color = 1; + } +} + +#line 2021 "dcraw/dcraw.c" +void CLASS sinar_4shot_load_raw() +{ + ushort *pixel; + unsigned shot, row, col, r, c; + + if ((shot = shot_select) || half_size) { + if (shot) shot--; + if (shot > 3) shot = 3; + fseek (ifp, data_offset + shot*4, SEEK_SET); + fseek (ifp, get4(), SEEK_SET); + unpacked_load_raw(); + return; + } + free (image); + image = (ushort (*)[4]) + calloc ((iheight=height)*(iwidth=width), sizeof *image); + merror (image, "sinar_4shot_load_raw()"); + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "sinar_4shot_load_raw()"); + for (shot=0; shot < 4; shot++) { + fseek (ifp, data_offset + shot*4, SEEK_SET); + fseek (ifp, get4(), SEEK_SET); + for (row=0; row < raw_height; row++) { + read_shorts (pixel, raw_width); + if ((r = row-top_margin - (shot >> 1 & 1)) >= height) continue; + for (col=0; col < raw_width; col++) { + if ((c = col-left_margin - (shot & 1)) >= width) continue; + image[r*width+c][FC(row,col)] = pixel[col]; + } + } + } + free (pixel); + shrink = filters = 0; +} + +void CLASS imacon_full_load_raw() +{ + int row, col; + + for (row=0; row < height; row++) + for (col=0; col < width; col++) + read_shorts (image[row*width+col], 3); +} + +void CLASS packed_12_load_raw() +{ + int vbits=0, rbits=0, irow, row, col; + UINT64 bitbuf=0; + + if (raw_width * 2 >= width * 3) { /* If raw_width is in bytes, */ + rbits = raw_width * 8; + raw_width = raw_width * 2 / 3; /* convert it to pixels and */ + rbits -= raw_width * 12; /* save the remainder. */ + } + order = load_flags & 1 ? 0x4949 : 0x4d4d; + for (irow=0; irow < height; irow++) { + row = irow; + if (load_flags & 2 && + (row = irow * 2 % height + irow / (height/2)) == 1 && + load_flags & 4) { + if (vbits=0, tiff_compress) + fseek (ifp, data_offset - (-width*height*3/4 & -2048), SEEK_SET); + else { + fseek (ifp, 0, SEEK_END); + fseek (ifp, ftell(ifp)/2, SEEK_SET); + } + } + for (col=0; col < raw_width; col++) { + if ((vbits -= 12) < 0) { + bitbuf = bitbuf << 32 | get4(); + vbits += 32; + } + if ((unsigned) (col-left_margin) < width) + BAYER(row,col-left_margin) = bitbuf << (52-vbits) >> 52; +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = bitbuf << (52-vbits) >> 52; + } +#endif + if (load_flags & 8 && (col % 10) == 9) + if (vbits=0, bitbuf & 255) derror(); + } + vbits -= rbits; + } + if (!strcmp(make,"OLYMPUS")) black >>= 4; +} + +void CLASS unpacked_load_raw() +{ + ushort *pixel; + int row, col, bits=0; + + while (1 << ++bits < maximum); +#ifndef LIBRAW_LIBRARY_BUILD + fseek (ifp, (top_margin*raw_width + left_margin) * 2, SEEK_CUR); + pixel = (ushort *) calloc (width, sizeof *pixel); + merror (pixel, "unpacked_load_raw()"); + for (row=0; row < height; row++) { + read_shorts (pixel, width); + fseek (ifp, 2*(raw_width - width), SEEK_CUR); + for (col=0; col < width; col++) + if ((BAYER2(row,col) = pixel[col]) >> bits) derror(); + } + free (pixel); +#else + // fseek (ifp, (top_margin*raw_width + left_margin) * 2, SEEK_CUR); + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "unpacked_load_raw()"); + for (row=0; row < raw_height; row++) { + read_shorts (pixel, raw_width); + //fseek (ifp, 2*(raw_width - width), SEEK_CUR); + for (col=0; col < raw_width; col++) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) + *dfp = pixel[col]; + else + { + if ((BAYER2(row-top_margin,col-left_margin) = pixel[col]) >> bits) derror(); + } + } + } + free (pixel); +#endif +} + +void CLASS nokia_load_raw() +{ + uchar *data, *dp; + ushort *pixel, *pix; + int dwide, row, c; + + dwide = raw_width * 5 / 4; + data = (uchar *) malloc (dwide + raw_width*2); + merror (data, "nokia_load_raw()"); + pixel = (ushort *) (data + dwide); + for (row=0; row < raw_height; row++) { + if (fread (data, 1, dwide, ifp) < dwide) derror(); + for (dp=data, pix=pixel; pix < pixel+raw_width; dp+=5, pix+=4) + FORC4 pix[c] = (dp[c] << 2) | (dp[4] >> (c << 1) & 3); + if (row < top_margin) +#ifdef LIBRAW_LIBRARY_BUILD + { + int col; + for(col=0;colpana_bits.buf +#define vbits tls->pana_bits.vbits +#else + static uchar buf[0x4000]; + static int vbits; +#endif + int byte; + + if (!nbits) return vbits=0; + if (!vbits) { + fread (buf+load_flags, 1, 0x4000-load_flags, ifp); + fread (buf, 1, load_flags, ifp); + } + vbits = (vbits - nbits) & 0x1ffff; + byte = vbits >> 3 ^ 0x3ff0; + return (buf[byte] | buf[byte+1] << 8) >> (vbits & 7) & ~(-1 << nbits); +#ifndef LIBRAW_NOTHREADS +#undef buf +#undef vbits +#endif +} + +void CLASS panasonic_load_raw() +{ + int row, col, i, j, sh=0, pred[2], nonz[2]; + + pana_bits(0); + for (row=0; row < height; row++) + for (col=0; col < raw_width; col++) { + if ((i = col % 14) == 0) + pred[0] = pred[1] = nonz[0] = nonz[1] = 0; + if (i % 3 == 2) sh = 4 >> (3 - pana_bits(2)); + if (nonz[i & 1]) { + if ((j = pana_bits(8))) { + if ((pred[i & 1] -= 0x80 << sh) < 0 || sh == 4) + pred[i & 1] &= ~(-1 << sh); + pred[i & 1] += j << sh; + } + } else if ((nonz[i & 1] = pana_bits(8)) || i > 11) + pred[i & 1] = nonz[i & 1] << 4 | pana_bits(4); + if (col < width) + if ((BAYER(row,col) = pred[col & 1]) > 4098) derror(); +#ifdef LIBRAW_LIBRARY_BUILD + if(col>=width) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp)*dfp = pred[col & 1]; + } +#endif + } +} + +void CLASS olympus_e300_load_raw() +{ + uchar *data, *dp; + ushort *pixel, *pix; + int dwide, row, col; + + dwide = raw_width * 16 / 10; +#ifndef LIBRAW_LIBRARY_BUILD + fseek (ifp, dwide*top_margin, SEEK_CUR); +#endif + data = (uchar *) malloc (dwide + raw_width*2); + merror (data, "olympus_e300_load_raw()"); + pixel = (ushort *) (data + dwide); +#ifndef LIBRAW_LIBRARY_BUILD + for (row=0; row < height; row++) +#else + for (row=0; row < raw_height; row++) +#endif + { + if (fread (data, 1, dwide, ifp) < dwide) derror(); + for (dp=data, pix=pixel; pix < pixel+raw_width; dp+=3, pix+=2) { + if (((dp-data) & 15) == 15) + if (*dp++ && pix < pixel+width+left_margin) derror(); + pix[0] = dp[1] << 8 | dp[0]; + pix[1] = dp[2] << 4 | dp[1] >> 4; + } +#ifndef LIBRAW_LIBRARY_BUILD + for (col=0; col < width; col++) + BAYER(row,col) = (pixel[col+left_margin] & 0xfff); +#else + for (col=0; col < raw_width; col++) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) + *dfp = (pixel[col] & 0xfff); + else + BAYER(row-top_margin,col-left_margin) = (pixel[col] & 0xfff); + } + +#endif + } + free (data); + maximum >>= 4; + black >>= 4; +} + +void CLASS olympus_e410_load_raw() +{ + int row, col, nbits, sign, low, high, i, w, n, nw; + int acarry[2][3], *carry, pred, diff; + + fseek (ifp, 7, SEEK_CUR); + getbits(-1); + for (row=0; row < height; row++) { + memset (acarry, 0, sizeof acarry); + for (col=0; col < width; col++) { + carry = acarry[col & 1]; + i = 2 * (carry[2] < 3); + for (nbits=2+i; (ushort) carry[0] >> (nbits+i); nbits++); + sign = getbits(1) * -1; + low = getbits(2); + for (high=0; high < 12; high++) + if (getbits(1)) break; + if (high == 12) + high = getbits(16-nbits) >> 1; + carry[0] = (high << nbits) | getbits(nbits); + diff = (carry[0] ^ sign) + carry[1]; + carry[1] = (diff*3 + carry[1]) >> 5; + carry[2] = carry[0] > 16 ? 0 : carry[2]+1; + if (row < 2 && col < 2) pred = 0; + else if (row < 2) pred = BAYER(row,col-2); + else if (col < 2) pred = BAYER(row-2,col); + else { + w = BAYER(row,col-2); + n = BAYER(row-2,col); + nw = BAYER(row-2,col-2); + if ((w < nw && nw < n) || (n < nw && nw < w)) { + if (ABS(w-nw) > 32 || ABS(n-nw) > 32) + pred = w + n - nw; + else pred = (w + n) >> 1; + } else pred = ABS(w-nw) > ABS(n-nw) ? w : n; + } + if ((BAYER(row,col) = pred + ((diff << 2) | low)) >> 12) derror(); + } + } +} + +void CLASS minolta_rd175_load_raw() +{ + uchar pixel[768]; + unsigned irow, box, row, col; + + for (irow=0; irow < 1481; irow++) { + if (fread (pixel, 1, 768, ifp) < 768) derror(); + box = irow / 82; + row = irow % 82 * 12 + ((box < 12) ? box | 1 : (box-12)*2); + switch (irow) { + case 1477: case 1479: continue; + case 1476: row = 984; break; + case 1480: row = 985; break; + case 1478: row = 985; box = 1; + } + if ((box < 12) && (box & 1)) { + for (col=0; col < 1533; col++, row ^= 1) + if (col != 1) BAYER(row,col) = (col+1) & 2 ? + pixel[col/2-1] + pixel[col/2+1] : pixel[col/2] << 1; + BAYER(row,1) = pixel[1] << 1; + BAYER(row,1533) = pixel[765] << 1; + } else + for (col=row & 1; col < 1534; col+=2) + BAYER(row,col) = pixel[col/2] << 1; + } + maximum = 0xff << 1; +} + +void CLASS casio_qv5700_load_raw() +{ + uchar data[3232], *dp; + ushort pixel[2576], *pix; + int row, col; + + for (row=0; row < height; row++) { + fread (data, 1, 3232, ifp); + for (dp=data, pix=pixel; dp < data+3220; dp+=5, pix+=4) { + pix[0] = (dp[0] << 2) + (dp[1] >> 6); + pix[1] = (dp[1] << 4) + (dp[2] >> 4); + pix[2] = (dp[2] << 6) + (dp[3] >> 2); + pix[3] = (dp[3] << 8) + (dp[4] ); + } + for (col=0; col < width; col++) + BAYER(row,col) = (pixel[col] & 0x3ff); + } + maximum = 0x3fc; +} + +void CLASS quicktake_100_load_raw() +{ + uchar pixel[484][644]; + static const short gstep[16] = + { -89,-60,-44,-32,-22,-15,-8,-2,2,8,15,22,32,44,60,89 }; + static const short rstep[6][4] = + { { -3,-1,1,3 }, { -5,-1,1,5 }, { -8,-2,2,8 }, + { -13,-3,3,13 }, { -19,-4,4,19 }, { -28,-6,6,28 } }; + static const short t_curve[256] = + { 0,1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27, + 28,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53, + 54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,74,75,76,77,78, + 79,80,81,82,83,84,86,88,90,92,94,97,99,101,103,105,107,110,112,114,116, + 118,120,123,125,127,129,131,134,136,138,140,142,144,147,149,151,153,155, + 158,160,162,164,166,168,171,173,175,177,179,181,184,186,188,190,192,195, + 197,199,201,203,205,208,210,212,214,216,218,221,223,226,230,235,239,244, + 248,252,257,261,265,270,274,278,283,287,291,296,300,305,309,313,318,322, + 326,331,335,339,344,348,352,357,361,365,370,374,379,383,387,392,396,400, + 405,409,413,418,422,426,431,435,440,444,448,453,457,461,466,470,474,479, + 483,487,492,496,500,508,519,531,542,553,564,575,587,598,609,620,631,643, + 654,665,676,687,698,710,721,732,743,754,766,777,788,799,810,822,833,844, + 855,866,878,889,900,911,922,933,945,956,967,978,989,1001,1012,1023 }; + int rb, row, col, sharp, val=0; + + getbits(-1); + memset (pixel, 0x80, sizeof pixel); + for (row=2; row < height+2; row++) { + for (col=2+(row & 1); col < width+2; col+=2) { + val = ((pixel[row-1][col-1] + 2*pixel[row-1][col+1] + + pixel[row][col-2]) >> 2) + gstep[getbits(4)]; + pixel[row][col] = val = LIM(val,0,255); + if (col < 4) + pixel[row][col-2] = pixel[row+1][~row & 1] = val; + if (row == 2) + pixel[row-1][col+1] = pixel[row-1][col+3] = val; + } + pixel[row][col] = val; + } + for (rb=0; rb < 2; rb++) + for (row=2+rb; row < height+2; row+=2) + for (col=3-(row & 1); col < width+2; col+=2) { + if (row < 4 || col < 4) sharp = 2; + else { + val = ABS(pixel[row-2][col] - pixel[row][col-2]) + + ABS(pixel[row-2][col] - pixel[row-2][col-2]) + + ABS(pixel[row][col-2] - pixel[row-2][col-2]); + sharp = val < 4 ? 0 : val < 8 ? 1 : val < 16 ? 2 : + val < 32 ? 3 : val < 48 ? 4 : 5; + } + val = ((pixel[row-2][col] + pixel[row][col-2]) >> 1) + + rstep[sharp][getbits(2)]; + pixel[row][col] = val = LIM(val,0,255); + if (row < 4) pixel[row-2][col+2] = val; + if (col < 4) pixel[row+2][col-2] = val; + } + for (row=2; row < height+2; row++) + for (col=3-(row & 1); col < width+2; col+=2) { + val = ((pixel[row][col-1] + (pixel[row][col] << 2) + + pixel[row][col+1]) >> 1) - 0x100; + pixel[row][col] = LIM(val,0,255); + } + for (row=0; row < height; row++) + for (col=0; col < width; col++) + BAYER(row,col) = t_curve[pixel[row+2][col+2]]; + maximum = 0x3ff; +} + +const int * CLASS make_decoder_int (const int *source, int level) +{ + struct decode *cur; + + cur = free_decode++; + if (level < source[0]) { + cur->branch[0] = free_decode; + source = make_decoder_int (source, level+1); + cur->branch[1] = free_decode; + source = make_decoder_int (source, level+1); + } else { + cur->leaf = source[1]; + source += 2; + } + return source; +} + +int CLASS radc_token (int tree) +{ + int t; +#ifndef LIBRAW_NOTHREADS +#define dstart tls->radc_token.dstart +#define dindex tls->radc_token.dindex +#define s tls->radc_token.s + + static const int source[] = { +#else + static struct decode *dstart[18], *dindex; + static const int *s, source[] = { +#endif + 1,1, 2,3, 3,4, 4,2, 5,7, 6,5, 7,6, 7,8, + 1,0, 2,1, 3,3, 4,4, 5,2, 6,7, 7,6, 8,5, 8,8, + 2,1, 2,3, 3,0, 3,2, 3,4, 4,6, 5,5, 6,7, 6,8, + 2,0, 2,1, 2,3, 3,2, 4,4, 5,6, 6,7, 7,5, 7,8, + 2,1, 2,4, 3,0, 3,2, 3,3, 4,7, 5,5, 6,6, 6,8, + 2,3, 3,1, 3,2, 3,4, 3,5, 3,6, 4,7, 5,0, 5,8, + 2,3, 2,6, 3,0, 3,1, 4,4, 4,5, 4,7, 5,2, 5,8, + 2,4, 2,7, 3,3, 3,6, 4,1, 4,2, 4,5, 5,0, 5,8, + 2,6, 3,1, 3,3, 3,5, 3,7, 3,8, 4,0, 5,2, 5,4, + 2,0, 2,1, 3,2, 3,3, 4,4, 4,5, 5,6, 5,7, 4,8, + 1,0, 2,2, 2,-2, + 1,-3, 1,3, + 2,-17, 2,-5, 2,5, 2,17, + 2,-7, 2,2, 2,9, 2,18, + 2,-18, 2,-9, 2,-2, 2,7, + 2,-28, 2,28, 3,-49, 3,-9, 3,9, 4,49, 5,-79, 5,79, + 2,-1, 2,13, 2,26, 3,39, 4,-16, 5,55, 6,-37, 6,76, + 2,-26, 2,-13, 2,1, 3,-39, 4,16, 5,-55, 6,-76, 6,37 + }; + + if (free_decode == first_decode) + for (s=source, t=0; t < 18; t++) { + dstart[t] = free_decode; + s = make_decoder_int (s, 0); + } + if (tree == 18) { + if (kodak_cbpp == 243) + return (getbits(6) << 2) + 2; /* most DC50 photos */ + else + return (getbits(5) << 3) + 4; /* DC40, Fotoman Pixtura */ + } + for (dindex = dstart[tree]; dindex->branch[0]; ) + dindex = dindex->branch[getbits(1)]; + return dindex->leaf; + +#ifndef LIBRAW_NOTHREADS +#undef dstart +#undef dindex +#undef s +#endif +} + +#define FORYX for (y=1; y < 3; y++) for (x=col+1; x >= col; x--) + +#define PREDICTOR (c ? (buf[c][y-1][x] + buf[c][y][x+1]) / 2 \ +: (buf[c][y-1][x+1] + 2*buf[c][y-1][x] + buf[c][y][x+1]) / 4) + +void CLASS kodak_radc_load_raw() +{ + int row, col, tree, nreps, rep, step, i, c, s, r, x, y, val; + short last[3] = { 16,16,16 }, mul[3], buf[3][3][386]; + + init_decoder(); + getbits(-1); + for (i=0; i < sizeof(buf)/sizeof(short); i++) + buf[0][0][i] = 2048; + for (row=0; row < height; row+=4) { + FORC3 mul[c] = getbits(6); + FORC3 { + val = ((0x1000000/last[c] + 0x7ff) >> 12) * mul[c]; + s = val > 65564 ? 10:12; + x = ~(-1 << (s-1)); + val <<= 12-s; + for (i=0; i < sizeof(buf[0])/sizeof(short); i++) + buf[c][0][i] = (buf[c][0][i] * val + x) >> s; + last[c] = mul[c]; + for (r=0; r <= !c; r++) { + buf[c][1][width/2] = buf[c][2][width/2] = mul[c] << 7; + for (tree=1, col=width/2; col > 0; ) { + if ((tree = radc_token(tree))) { + col -= 2; + if (tree == 8) + FORYX buf[c][y][x] = radc_token(tree+10) * mul[c]; + else + FORYX buf[c][y][x] = radc_token(tree+10) * 16 + PREDICTOR; + } else + do { + nreps = (col > 2) ? radc_token(9) + 1 : 1; + for (rep=0; rep < 8 && rep < nreps && col > 0; rep++) { + col -= 2; + FORYX buf[c][y][x] = PREDICTOR; + if (rep & 1) { + step = radc_token(10) << 4; + FORYX buf[c][y][x] += step; + } + } + } while (nreps == 9); + } + for (y=0; y < 2; y++) + for (x=0; x < width/2; x++) { + val = (buf[c][y+1][x] << 4) / mul[c]; + if (val < 0) val = 0; + if (c) BAYER(row+y*2+c-1,x*2+2-c) = val; + else BAYER(row+r*2+y,x*2+y) = val; + } + memcpy (buf[c][0]+!c, buf[c][2], sizeof buf[c][0]-2*!c); + } + } + for (y=row; y < row+4; y++) + for (x=0; x < width; x++) + if ((x+y) & 1) { + r = x ? x-1 : x+1; + s = x+1 < width ? x+1 : x-1; + val = (BAYER(y,x)-2048)*2 + (BAYER(y,r)+BAYER(y,s))/2; + if (val < 0) val = 0; + BAYER(y,x) = val; + } + } + maximum = 0xfff; + use_gamma = 0; +} + +#undef FORYX +#undef PREDICTOR + +#ifdef NO_JPEG +void CLASS kodak_jpeg_load_raw() {} +#else + +METHODDEF(boolean) +fill_input_buffer (j_decompress_ptr cinfo) +{ +#ifndef LIBRAW_NOTHREADS +#define jpeg_buffer tls->jpeg_buffer +#else + static uchar jpeg_buffer[4096]; +#endif + size_t nbytes; + + nbytes = fread (jpeg_buffer, 1, 4096, ifp); + swab (jpeg_buffer, jpeg_buffer, nbytes); + cinfo->src->next_input_byte = jpeg_buffer; + cinfo->src->bytes_in_buffer = nbytes; + return TRUE; +#ifndef LIBRAW_NOTHREADS +#undef jpeg_buffer +#endif +} + +void CLASS kodak_jpeg_load_raw() +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPARRAY buf; + JSAMPLE (*pixel)[3]; + int row, col; + + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_decompress (&cinfo); + jpeg_stdio_src (&cinfo, ifp); + cinfo.src->fill_input_buffer = fill_input_buffer; + jpeg_read_header (&cinfo, TRUE); + jpeg_start_decompress (&cinfo); + if ((cinfo.output_width != width ) || + (cinfo.output_height*2 != height ) || + (cinfo.output_components != 3 )) { +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s: incorrect JPEG dimensions\n"), ifname); +#endif + jpeg_destroy_decompress (&cinfo); +#ifdef LIBRAW_LIBRARY_BUILD + throw LIBRAW_EXCEPTION_DECODE_JPEG; +#else + longjmp (failure, 3); +#endif + } + buf = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, width*3, 1); + + while (cinfo.output_scanline < cinfo.output_height) { + row = cinfo.output_scanline * 2; + jpeg_read_scanlines (&cinfo, buf, 1); + pixel = (JSAMPLE (*)[3]) buf[0]; + for (col=0; col < width; col+=2) { + BAYER(row+0,col+0) = pixel[col+0][1] << 1; + BAYER(row+1,col+1) = pixel[col+1][1] << 1; + BAYER(row+0,col+1) = pixel[col][0] + pixel[col+1][0]; + BAYER(row+1,col+0) = pixel[col][2] + pixel[col+1][2]; + } + } + jpeg_finish_decompress (&cinfo); + jpeg_destroy_decompress (&cinfo); + maximum = 0xff << 1; +} +#endif + +void CLASS kodak_dc120_load_raw() +{ + static const int mul[4] = { 162, 192, 187, 92 }; + static const int add[4] = { 0, 636, 424, 212 }; + uchar pixel[848]; + int row, shift, col; + + for (row=0; row < height; row++) { + if (fread (pixel, 1, 848, ifp) < 848) derror(); + shift = row * mul[row & 3] + add[row & 3]; + for (col=0; col < width; col++) + BAYER(row,col) = (ushort) pixel[(col + shift) % 848]; + } + maximum = 0xff; +} + +void CLASS eight_bit_load_raw() +{ + uchar *pixel; + unsigned row, col, val, lblack=0; + + pixel = (uchar *) calloc (raw_width, sizeof *pixel); + merror (pixel, "eight_bit_load_raw()"); +#ifndef LIBRAW_LIBRARY_BUILD + fseek (ifp, top_margin*raw_width, SEEK_CUR); + for (row=0; row < height; row++) { + if (fread (pixel, 1, raw_width, ifp) < raw_width) derror(); + for (col=0; col < raw_width; col++) { + val = curve[pixel[col]]; + if ((unsigned) (col-left_margin) < width) + BAYER(row,col-left_margin) = val; + else lblack += val; + } + } +#else + for (row=0; row < raw_height; row++) { + if (fread (pixel, 1, raw_width, ifp) < raw_width) derror(); + for (col=0; col < raw_width; col++) { + if(filtering_mode & LIBRAW_FILTERING_NORAWCURVE) + { + val = pixel[col]; + if(val>maximum) maximum = val; + } + else + val = curve[pixel[col]]; + if((unsigned) (row-top_margin)< height) + { + if ((unsigned) (col-left_margin) < width) + BAYER(row,col-left_margin) = val; + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = val; + lblack += val; + } + } + else // top/bottom margins + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = val; + } + } + } +#endif + + free (pixel); + if (raw_width > width+1) + black = lblack / ((raw_width - width) * height); + if (!strncmp(model,"DC2",3)) + black = 0; +#ifdef LIBRAW_LIBRARY_BUILD + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) +#endif + maximum = curve[0xff]; +} + +void CLASS kodak_yrgb_load_raw() +{ + uchar *pixel; + int row, col, y, cb, cr, rgb[3], c; + + pixel = (uchar *) calloc (raw_width, 3*sizeof *pixel); + merror (pixel, "kodak_yrgb_load_raw()"); + for (row=0; row < height; row++) { + if (~row & 1) + if (fread (pixel, raw_width, 3, ifp) < 3) derror(); + for (col=0; col < raw_width; col++) { + y = pixel[width*2*(row & 1) + col]; + cb = pixel[width + (col & -2)] - 128; + cr = pixel[width + (col & -2)+1] - 128; + rgb[1] = y-((cb + cr + 2) >> 2); + rgb[2] = rgb[1] + cb; + rgb[0] = rgb[1] + cr; + FORC3 image[row*width+col][c] = LIM(rgb[c],0,255); + } + } + free (pixel); + use_gamma = 0; +} + +void CLASS kodak_262_load_raw() +{ + static const uchar kodak_tree[2][26] = + { { 0,1,5,1,1,2,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9 }, + { 0,3,1,1,1,1,1,2,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9 } }; + struct decode *decode[2]; + uchar *pixel; + int *strip, ns, i, row, col, chess, pi=0, pi1, pi2, pred, val; + + init_decoder(); + for (i=0; i < 2; i++) { + decode[i] = free_decode; + make_decoder (kodak_tree[i], 0); + } + ns = (raw_height+63) >> 5; + pixel = (uchar *) malloc (raw_width*32 + ns*4); + merror (pixel, "kodak_262_load_raw()"); + strip = (int *) (pixel + raw_width*32); + order = 0x4d4d; + for (i=0; i < ns; i++) + strip[i] = get4(); + for (row=0; row < raw_height; row++) { + if ((row & 31) == 0) { + fseek (ifp, strip[row >> 5], SEEK_SET); + getbits(-1); + pi = 0; + } + for (col=0; col < raw_width; col++) { + chess = (row + col) & 1; + pi1 = chess ? pi-2 : pi-raw_width-1; + pi2 = chess ? pi-2*raw_width : pi-raw_width+1; + if (col <= chess) pi1 = -1; + if (pi1 < 0) pi1 = pi2; + if (pi2 < 0) pi2 = pi1; + if (pi1 < 0 && col > 1) pi1 = pi2 = pi-2; + pred = (pi1 < 0) ? 0 : (pixel[pi1] + pixel[pi2]) >> 1; + pixel[pi] = val = pred + ljpeg_diff (decode[chess]); + if (val >> 8) derror(); +#ifdef LIBRAW_LIBRARY_BUILD + if(filtering_mode & LIBRAW_FILTERING_NORAWCURVE) + val = pixel[pi++]; + else + val = curve[pixel[pi++]]; +#else + val = curve[pixel[pi++]]; +#endif + if ((unsigned) (col-left_margin) < width) + BAYER(row,col-left_margin) = val; + else +#ifndef LIBRAW_LIBRARY_BUILD + black += val; +#else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = val; + black += val; + } +#endif + } + } + free (pixel); + if (raw_width > width) + black /= (raw_width - width) * height; +} + +int CLASS kodak_65000_decode (short *out, int bsize) +{ + uchar c, blen[768]; + ushort raw[6]; + INT64 bitbuf=0; + int save, bits=0, i, j, len, diff; + + save = ftell(ifp); + bsize = (bsize + 3) & -4; + for (i=0; i < bsize; i+=2) { + c = fgetc(ifp); + if ((blen[i ] = c & 15) > 12 || + (blen[i+1] = c >> 4) > 12 ) { + fseek (ifp, save, SEEK_SET); + for (i=0; i < bsize; i+=8) { + read_shorts (raw, 6); + out[i ] = raw[0] >> 12 << 8 | raw[2] >> 12 << 4 | raw[4] >> 12; + out[i+1] = raw[1] >> 12 << 8 | raw[3] >> 12 << 4 | raw[5] >> 12; + for (j=0; j < 6; j++) + out[i+2+j] = raw[j] & 0xfff; + } + return 1; + } + } + if ((bsize & 7) == 4) { + bitbuf = fgetc(ifp) << 8; + bitbuf += fgetc(ifp); + bits = 16; + } + for (i=0; i < bsize; i++) { + len = blen[i]; + if (bits < len) { + for (j=0; j < 32; j+=8) + bitbuf += (INT64) fgetc(ifp) << (bits+(j^8)); + bits += 32; + } + diff = bitbuf & (0xffff >> (16-len)); + bitbuf >>= len; + bits -= len; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - 1; + out[i] = diff; + } + return 0; +} + +void CLASS kodak_65000_load_raw() +{ + short buf[256]; + int row, col, len, pred[2], ret, i; + + for (row=0; row < height; row++) + for (col=0; col < width; col+=256) { + pred[0] = pred[1] = 0; + len = MIN (256, width-col); + ret = kodak_65000_decode (buf, len); + for (i=0; i < len; i++) +#ifndef LIBRAW_LIBRARY_BUILD + if ((BAYER(row,col+i) = curve[ret ? buf[i] : + (pred[i & 1] += buf[i])]) >> 12) derror(); +#else + { + ushort val = ret ? buf[i] : (pred[i & 1] += buf[i]); + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) + val = curve[val]; + BAYER(row,col+i)=val; + if(curve[val]>>12) derror(); + } +#endif + } +} + +void CLASS kodak_ycbcr_load_raw() +{ + short buf[384], *bp; + int row, col, len, c, i, j, k, y[2][2], cb, cr, rgb[3]; + ushort *ip; + + for (row=0; row < height; row+=2) + for (col=0; col < width; col+=128) { + len = MIN (128, width-col); + kodak_65000_decode (buf, len*3); + y[0][1] = y[1][1] = cb = cr = 0; + for (bp=buf, i=0; i < len; i+=2, bp+=2) { + cb += bp[4]; + cr += bp[5]; + rgb[1] = -((cb + cr + 2) >> 2); + rgb[2] = rgb[1] + cb; + rgb[0] = rgb[1] + cr; + for (j=0; j < 2; j++) + for (k=0; k < 2; k++) { + if ((y[j][k] = y[j][k^1] + *bp++) >> 10) derror(); + ip = image[(row+j)*width + col+i+k]; +#ifndef LIBRAW_LIBRARY_BUILD + FORC3 ip[c] = curve[LIM(y[j][k]+rgb[c], 0, 0xfff)]; +#else + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) + FORC3 ip[c] = curve[LIM(y[j][k]+rgb[c], 0, 0xfff)]; + else + FORC3 ip[c] = y[j][k]+rgb[c];; +#endif + } + } + } +} + +void CLASS kodak_rgb_load_raw() +{ + short buf[768], *bp; + int row, col, len, c, i, rgb[3]; + ushort *ip=image[0]; + + for (row=0; row < height; row++) + for (col=0; col < width; col+=256) { + len = MIN (256, width-col); + kodak_65000_decode (buf, len*3); + memset (rgb, 0, sizeof rgb); + for (bp=buf, i=0; i < len; i++, ip+=4) + FORC3 if ((ip[c] = rgb[c] += *bp++) >> 12) derror(); + } +} + +void CLASS kodak_thumb_load_raw() +{ + int row, col; + colors = thumb_misc >> 5; + for (row=0; row < height; row++) + for (col=0; col < width; col++) + read_shorts (image[row*width+col], colors); + maximum = (1 << (thumb_misc & 31)) - 1; +} + +void CLASS sony_decrypt (unsigned *data, int len, int start, int key) +{ +#ifndef LIBRAW_NOTHREADS +#define pad tls->sony_decrypt.pad +#define p tls->sony_decrypt.p +#else + static unsigned pad[128], p; +#endif + + if (start) { + for (p=0; p < 4; p++) + pad[p] = key = key * 48828125 + 1; + pad[3] = pad[3] << 1 | (pad[0]^pad[2]) >> 31; + for (p=4; p < 127; p++) + pad[p] = (pad[p-4]^pad[p-2]) << 1 | (pad[p-3]^pad[p-1]) >> 31; + for (p=0; p < 127; p++) + pad[p] = htonl(pad[p]); + } + while (len--) + *data++ ^= pad[p++ & 127] = pad[(p+1) & 127] ^ pad[(p+65) & 127]; +#ifndef LIBRAW_NOTHREADS +#undef pad +#undef p +#endif +} + +void CLASS sony_load_raw() +{ + uchar head[40]; + ushort *pixel; + unsigned i, key, row, col; + + fseek (ifp, 200896, SEEK_SET); + fseek (ifp, (unsigned) fgetc(ifp)*4 - 1, SEEK_CUR); + order = 0x4d4d; + key = get4(); + fseek (ifp, 164600, SEEK_SET); + fread (head, 1, 40, ifp); + sony_decrypt ((unsigned int *) head, 10, 1, key); + for (i=26; i-- > 22; ) + key = key << 8 | head[i]; + fseek (ifp, data_offset, SEEK_SET); + pixel = (ushort *) calloc (raw_width, sizeof *pixel); + merror (pixel, "sony_load_raw()"); + for (row=0; row < height; row++) { + if (fread (pixel, 2, raw_width, ifp) < raw_width) derror(); + sony_decrypt ((unsigned int *) pixel, raw_width/2, !row, key); +#ifdef LIBRAW_LIBRARY_BUILD + for (col=0; col < left_margin; col++) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = ntohs(pixel[col]); + } + for (col=left_margin+width; col < raw_width; col++) + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = ntohs(pixel[col]); + } +#endif + for (col=9; col < left_margin; col++) + black += ntohs(pixel[col]); + for (col=0; col < width; col++) + if ((BAYER(row,col) = ntohs(pixel[col+left_margin])) >> 14) + derror(); + } + free (pixel); + if (left_margin > 9) + black /= (left_margin-9) * height; + maximum = 0x3ff0; +} + +void CLASS sony_arw_load_raw() +{ + int col, row, len, diff, sum=0; + + getbits(-1); + for (col = raw_width; col--; ) + for (row=0; row < raw_height+1; row+=2) { + if (row == raw_height) row = 1; + len = 4 - getbits(2); + if (len == 3 && getbits(1)) len = 0; + if (len == 4) + while (len < 17 && !getbits(1)) len++; + diff = getbits(len); + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - 1; + if ((sum += diff) >> 12) derror(); + if (row < height) BAYER(row,col) = sum; +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(row,col); + if(dfp) *dfp = sum; + } +#endif + } +} + +void CLASS sony_arw2_load_raw() +{ + uchar *data, *dp; + ushort pix[16]; + int row, col, val, max, min, imax, imin, sh, bit, i; + + data = (uchar *) malloc (raw_width*tiff_bps >> 3); + merror (data, "sony_arw2_load_raw()"); + for (row=0; row < height; row++) { + fread (data, 1, raw_width*tiff_bps >> 3, ifp); + if (tiff_bps == 8) { + for (dp=data, col=0; col < width-30; dp+=16) { + max = 0x7ff & (val = sget4(dp)); + min = 0x7ff & val >> 11; + imax = 0x0f & val >> 22; + imin = 0x0f & val >> 26; + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((sget2(dp+(bit >> 3)) >> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) +#ifndef LIBRAW_LIBRARY_BUILD + BAYER(row,col) = curve[pix[i] << 1] >> 1; +#else + { + ushort val = pix[i]; + if(!(filtering_mode & LIBRAW_FILTERING_NORAWCURVE)) + val = curve[val<<1]>>1; + BAYER(row,col)=val; + } +#endif + col -= col & 1 ? 1:31; + } + } else if (tiff_bps == 12) + for (dp=data, col=0; col < width; dp+=3, col+=2) { + BAYER(row,col) = ((dp[1] << 8 | dp[0]) & 0xfff) << 1; + BAYER(row,col+1) = (dp[2] << 4 | dp[1] >> 4) << 1; + } + } + free (data); +} + +#define HOLE(row) ((holes >> (((row) - raw_height) & 7)) & 1) + +/* Kudos to Rich Taylor for figuring out SMaL's compression algorithm. */ +void CLASS smal_decode_segment (unsigned seg[2][2], int holes) +{ + uchar hist[3][13] = { + { 7, 7, 0, 0, 63, 55, 47, 39, 31, 23, 15, 7, 0 }, + { 7, 7, 0, 0, 63, 55, 47, 39, 31, 23, 15, 7, 0 }, + { 3, 3, 0, 0, 63, 47, 31, 15, 0 } }; + int low, high=0xff, carry=0, nbits=8; + int s, count, bin, next, i, sym[3]; + uchar diff, pred[]={0,0}; + ushort data=0, range=0; + unsigned pix, row, col; + + fseek (ifp, seg[0][1]+1, SEEK_SET); + getbits(-1); + for (pix=seg[0][0]; pix < seg[1][0]; pix++) { + for (s=0; s < 3; s++) { + data = data << nbits | getbits(nbits); + if (carry < 0) + carry = (nbits += carry+1) < 1 ? nbits-1 : 0; + while (--nbits >= 0) + if ((data >> nbits & 0xff) == 0xff) break; + if (nbits > 0) + data = ((data & ((1 << (nbits-1)) - 1)) << 1) | + ((data + (((data & (1 << (nbits-1)))) << 1)) & (-1 << nbits)); + if (nbits >= 0) { + data += getbits(1); + carry = nbits - 8; + } + count = ((((data-range+1) & 0xffff) << 2) - 1) / (high >> 4); + for (bin=0; hist[s][bin+5] > count; bin++); + low = hist[s][bin+5] * (high >> 4) >> 2; + if (bin) high = hist[s][bin+4] * (high >> 4) >> 2; + high -= low; + for (nbits=0; high << nbits < 128; nbits++); + range = (range+low) << nbits; + high <<= nbits; + next = hist[s][1]; + if (++hist[s][2] > hist[s][3]) { + next = (next+1) & hist[s][0]; + hist[s][3] = (hist[s][next+4] - hist[s][next+5]) >> 2; + hist[s][2] = 1; + } + if (hist[s][hist[s][1]+4] - hist[s][hist[s][1]+5] > 1) { + if (bin < hist[s][1]) + for (i=bin; i < hist[s][1]; i++) hist[s][i+5]--; + else if (next <= bin) + for (i=hist[s][1]; i < bin; i++) hist[s][i+5]++; + } + hist[s][1] = next; + sym[s] = bin; + } + diff = sym[2] << 5 | sym[1] << 2 | (sym[0] & 3); + if (sym[0] & 4) + diff = diff ? -diff : 0x80; + if (ftell(ifp) + 12 >= seg[1][1]) + diff = 0; + pred[pix & 1] += diff; + row = pix / raw_width - top_margin; + col = pix % raw_width - left_margin; + if (row < height && col < width) + BAYER(row,col) = pred[pix & 1]; +#ifdef LIBRAW_LIBRARY_BUILD + else + { + ushort *dfp = get_masked_pointer(row+top_margin,col+left_margin); + if(dfp) *dfp = pred[pix &1]; + } +#endif + if (!(pix & 1) && HOLE(row)) pix += 2; + } + maximum = 0xff; +} + +void CLASS smal_v6_load_raw() +{ + unsigned seg[2][2]; + + fseek (ifp, 16, SEEK_SET); + seg[0][0] = 0; + seg[0][1] = get2(); + seg[1][0] = raw_width * raw_height; + seg[1][1] = INT_MAX; + smal_decode_segment (seg, 0); + use_gamma = 0; +} + +int CLASS median4 (int *p) +{ + int min, max, sum, i; + + min = max = sum = p[0]; + for (i=1; i < 4; i++) { + sum += p[i]; + if (min > p[i]) min = p[i]; + if (max < p[i]) max = p[i]; + } + return (sum - min - max) >> 1; +} + +void CLASS fill_holes (int holes) +{ + int row, col, val[4]; + + for (row=2; row < height-2; row++) { + if (!HOLE(row)) continue; + for (col=1; col < width-1; col+=4) { + val[0] = BAYER(row-1,col-1); + val[1] = BAYER(row-1,col+1); + val[2] = BAYER(row+1,col-1); + val[3] = BAYER(row+1,col+1); + BAYER(row,col) = median4(val); + } + for (col=2; col < width-2; col+=4) + if (HOLE(row-2) || HOLE(row+2)) + BAYER(row,col) = (BAYER(row,col-2) + BAYER(row,col+2)) >> 1; + else { + val[0] = BAYER(row,col-2); + val[1] = BAYER(row,col+2); + val[2] = BAYER(row-2,col); + val[3] = BAYER(row+2,col); + BAYER(row,col) = median4(val); + } + } +} + +void CLASS smal_v9_load_raw() +{ + unsigned seg[256][2], offset, nseg, holes, i; + + fseek (ifp, 67, SEEK_SET); + offset = get4(); + nseg = fgetc(ifp); + fseek (ifp, offset, SEEK_SET); + for (i=0; i < nseg*2; i++) + seg[0][i] = get4() + data_offset*(i & 1); + fseek (ifp, 78, SEEK_SET); + holes = fgetc(ifp); + fseek (ifp, 88, SEEK_SET); + seg[nseg][0] = raw_height * raw_width; + seg[nseg][1] = get4() + data_offset; + for (i=0; i < nseg; i++) + smal_decode_segment (seg+i, holes); + if (holes) fill_holes (holes); +} +#line 4122 "dcraw/dcraw.c" + +void CLASS pseudoinverse (double (*in)[3], double (*out)[3], int size) +{ + double work[3][6], num; + int i, j, k; + + for (i=0; i < 3; i++) { + for (j=0; j < 6; j++) + work[i][j] = j == i+3; + for (j=0; j < 3; j++) + for (k=0; k < size; k++) + work[i][j] += in[k][i] * in[k][j]; + } + for (i=0; i < 3; i++) { + num = work[i][i]; + for (j=0; j < 6; j++) + work[i][j] /= num; + for (k=0; k < 3; k++) { + if (k==i) continue; + num = work[k][i]; + for (j=0; j < 6; j++) + work[k][j] -= work[i][j] * num; + } + } + for (i=0; i < size; i++) + for (j=0; j < 3; j++) + for (out[i][j]=k=0; k < 3; k++) + out[i][j] += work[j][k+3] * in[i][k]; +} + +void CLASS cam_xyz_coeff (double cam_xyz[4][3]) +{ + double cam_rgb[4][3], inverse[4][3], num; + int i, j, k; + + for (i=0; i < colors; i++) /* Multiply out XYZ colorspace */ + for (j=0; j < 3; j++) + for (cam_rgb[i][j] = k=0; k < 3; k++) + cam_rgb[i][j] += cam_xyz[i][k] * xyz_rgb[k][j]; + + for (i=0; i < colors; i++) { /* Normalize cam_rgb so that */ + for (num=j=0; j < 3; j++) /* cam_rgb * (1,1,1) is (1,1,1,1) */ + num += cam_rgb[i][j]; + for (j=0; j < 3; j++) + cam_rgb[i][j] /= num; + pre_mul[i] = 1 / num; + } + pseudoinverse (cam_rgb, inverse, colors); + for (raw_color = i=0; i < 3; i++) + for (j=0; j < colors; j++) + rgb_cam[i][j] = inverse[j][i]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_CONST; +#endif +} + +#ifdef COLORCHECK +void CLASS colorcheck() +{ +#define NSQ 24 +// Coordinates of the GretagMacbeth ColorChecker squares +// width, height, 1st_column, 1st_row + static const int cut[NSQ][4] = { + { 241, 231, 234, 274 }, + { 251, 235, 534, 274 }, + { 255, 239, 838, 272 }, + { 255, 240, 1146, 274 }, + { 251, 237, 1452, 278 }, + { 243, 238, 1758, 288 }, + { 253, 253, 218, 558 }, + { 255, 249, 524, 562 }, + { 261, 253, 830, 562 }, + { 260, 255, 1144, 564 }, + { 261, 255, 1450, 566 }, + { 247, 247, 1764, 576 }, + { 255, 251, 212, 862 }, + { 259, 259, 518, 862 }, + { 263, 261, 826, 864 }, + { 265, 263, 1138, 866 }, + { 265, 257, 1450, 872 }, + { 257, 255, 1762, 874 }, + { 257, 253, 212, 1164 }, + { 262, 251, 516, 1172 }, + { 263, 257, 826, 1172 }, + { 263, 255, 1136, 1176 }, + { 255, 252, 1452, 1182 }, + { 257, 253, 1760, 1180 } }; +// ColorChecker Chart under 6500-kelvin illumination + static const double gmb_xyY[NSQ][3] = { + { 0.400, 0.350, 10.1 }, // Dark Skin + { 0.377, 0.345, 35.8 }, // Light Skin + { 0.247, 0.251, 19.3 }, // Blue Sky + { 0.337, 0.422, 13.3 }, // Foliage + { 0.265, 0.240, 24.3 }, // Blue Flower + { 0.261, 0.343, 43.1 }, // Bluish Green + { 0.506, 0.407, 30.1 }, // Orange + { 0.211, 0.175, 12.0 }, // Purplish Blue + { 0.453, 0.306, 19.8 }, // Moderate Red + { 0.285, 0.202, 6.6 }, // Purple + { 0.380, 0.489, 44.3 }, // Yellow Green + { 0.473, 0.438, 43.1 }, // Orange Yellow + { 0.187, 0.129, 6.1 }, // Blue + { 0.305, 0.478, 23.4 }, // Green + { 0.539, 0.313, 12.0 }, // Red + { 0.448, 0.470, 59.1 }, // Yellow + { 0.364, 0.233, 19.8 }, // Magenta + { 0.196, 0.252, 19.8 }, // Cyan + { 0.310, 0.316, 90.0 }, // White + { 0.310, 0.316, 59.1 }, // Neutral 8 + { 0.310, 0.316, 36.2 }, // Neutral 6.5 + { 0.310, 0.316, 19.8 }, // Neutral 5 + { 0.310, 0.316, 9.0 }, // Neutral 3.5 + { 0.310, 0.316, 3.1 } }; // Black + double gmb_cam[NSQ][4], gmb_xyz[NSQ][3]; + double inverse[NSQ][3], cam_xyz[4][3], num; + int c, i, j, k, sq, row, col, count[4]; + + memset (gmb_cam, 0, sizeof gmb_cam); + for (sq=0; sq < NSQ; sq++) { + FORCC count[c] = 0; + for (row=cut[sq][3]; row < cut[sq][3]+cut[sq][1]; row++) + for (col=cut[sq][2]; col < cut[sq][2]+cut[sq][0]; col++) { + c = FC(row,col); + if (c >= colors) c -= 2; + gmb_cam[sq][c] += BAYER(row,col); + count[c]++; + } + FORCC gmb_cam[sq][c] = gmb_cam[sq][c]/count[c] - black; + gmb_xyz[sq][0] = gmb_xyY[sq][2] * gmb_xyY[sq][0] / gmb_xyY[sq][1]; + gmb_xyz[sq][1] = gmb_xyY[sq][2]; + gmb_xyz[sq][2] = gmb_xyY[sq][2] * + (1 - gmb_xyY[sq][0] - gmb_xyY[sq][1]) / gmb_xyY[sq][1]; + } + pseudoinverse (gmb_xyz, inverse, NSQ); + for (i=0; i < colors; i++) + for (j=0; j < 3; j++) + for (cam_xyz[i][j] = k=0; k < NSQ; k++) + cam_xyz[i][j] += gmb_cam[k][i] * inverse[k][j]; + cam_xyz_coeff (cam_xyz); +#ifdef DCRAW_VERBOSE + if (verbose) { + printf (" { \"%s %s\", %d,\n\t{", make, model, black); + num = 10000 / (cam_xyz[1][0] + cam_xyz[1][1] + cam_xyz[1][2]); + FORCC for (j=0; j < 3; j++) + printf ("%c%d", (c | j) ? ',':' ', (int) (cam_xyz[c][j] * num + 0.5)); + puts (" } },"); + } +#endif +#undef NSQ +} +#endif + +void CLASS hat_transform (float *temp, float *base, int st, int size, int sc) +{ + int i; + for (i=0; i < sc; i++) + temp[i] = 2*base[st*i] + base[st*(sc-i)] + base[st*(i+sc)]; + for (; i+sc < size; i++) + temp[i] = 2*base[st*i] + base[st*(i-sc)] + base[st*(i+sc)]; + for (; i < size; i++) + temp[i] = 2*base[st*i] + base[st*(i-sc)] + base[st*(2*size-2-(i+sc))]; +} + +#ifndef _OPENMP +void CLASS wavelet_denoise() +{ + float *fimg=0, *temp, thold, mul[2], avg, diff; + int scale=1, size, lev, hpass, lpass, row, col, nc, c, i, wlast; + ushort *window[4]; + static const float noise[] = + { 0.8002,0.2735,0.1202,0.0585,0.0291,0.0152,0.0080,0.0044 }; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Wavelet denoising...\n")); +#endif + + while (maximum << scale < 0x10000) scale++; + maximum <<= --scale; + black <<= scale; + if ((size = iheight*iwidth) < 0x15550000) + fimg = (float *) malloc ((size*3 + iheight + iwidth) * sizeof *fimg); + merror (fimg, "wavelet_denoise()"); + temp = fimg + size*3; + if ((nc = colors) == 3 && filters) nc++; + FORC(nc) { /* denoise R,G1,B,G3 individually */ + for (i=0; i < size; i++) + fimg[i] = 256 * sqrt((double)(image[i][c] << scale)); + for (hpass=lev=0; lev < 5; lev++) { + lpass = size*((lev & 1)+1); + for (row=0; row < iheight; row++) { + hat_transform (temp, fimg+hpass+row*iwidth, 1, iwidth, 1 << lev); + for (col=0; col < iwidth; col++) + fimg[lpass + row*iwidth + col] = temp[col] * 0.25; + } + for (col=0; col < iwidth; col++) { + hat_transform (temp, fimg+lpass+col, iwidth, iheight, 1 << lev); + for (row=0; row < iheight; row++) + fimg[lpass + row*iwidth + col] = temp[row] * 0.25; + } + thold = threshold * noise[lev]; + for (i=0; i < size; i++) { + fimg[hpass+i] -= fimg[lpass+i]; + if (fimg[hpass+i] < -thold) fimg[hpass+i] += thold; + else if (fimg[hpass+i] > thold) fimg[hpass+i] -= thold; + else fimg[hpass+i] = 0; + if (hpass) fimg[i] += fimg[hpass+i]; + } + hpass = lpass; + } + for (i=0; i < size; i++) + image[i][c] = CLIP(SQR(fimg[i]+fimg[lpass+i])/0x10000); + } + if (filters && colors == 3) { /* pull G1 and G3 closer together */ + for (row=0; row < 2; row++) + mul[row] = 0.125 * pre_mul[FC(row+1,0) | 1] / pre_mul[FC(row,0) | 1]; + for (i=0; i < 4; i++) + window[i] = (ushort *) fimg + width*i; + for (wlast=-1, row=1; row < height-1; row++) { + while (wlast < row+1) { + for (wlast++, i=0; i < 4; i++) + window[(i+3) & 3] = window[i]; + for (col = FC(wlast,1) & 1; col < width; col+=2) + window[2][col] = BAYER(wlast,col); + } + thold = threshold/512; + for (col = (FC(row,0) & 1)+1; col < width-1; col+=2) { + avg = ( window[0][col-1] + window[0][col+1] + + window[2][col-1] + window[2][col+1] - black*4 ) + * mul[row & 1] + (window[1][col] - black) * 0.5 + black; + avg = avg < 0 ? 0 : sqrt(avg); + diff = sqrt((double)(BAYER(row,col))) - avg; + if (diff < -thold) diff += thold; + else if (diff > thold) diff -= thold; + else diff = 0; + BAYER(row,col) = CLIP(SQR(avg+diff) + 0.5); + } + } + } + free (fimg); +} +#else +void CLASS wavelet_denoise() +{ + float *fimg=0, *temp, thold, mul[2], avg, diff; + int scale=1, size, lev, hpass, lpass, row, col, nc, c, i, wlast; + ushort *window[4]; + static const float noise[] = + { 0.8002,0.2735,0.1202,0.0585,0.0291,0.0152,0.0080,0.0044 }; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Wavelet denoising...\n")); +#endif + + while (maximum << scale < 0x10000) scale++; + maximum <<= --scale; + black <<= scale; + if ((size = iheight*iwidth) < 0x15550000) + fimg = (float *) malloc ((size*3 + iheight + iwidth) * sizeof *fimg); + merror (fimg, "wavelet_denoise()"); + temp = fimg + size*3; + if ((nc = colors) == 3 && filters) nc++; +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp parallel default(shared) private(i,col,row,thold,lev,lpass,hpass,temp) firstprivate(c,scale,size) +#endif + { + temp = (float*)malloc( (iheight + iwidth) * sizeof *fimg); + FORC(nc) { /* denoise R,G1,B,G3 individually */ +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp for +#endif + for (i=0; i < size; i++) + fimg[i] = 256 * sqrt((double)(image[i][c] << scale)); + for (hpass=lev=0; lev < 5; lev++) { + lpass = size*((lev & 1)+1); +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp for +#endif + for (row=0; row < iheight; row++) { + hat_transform (temp, fimg+hpass+row*iwidth, 1, iwidth, 1 << lev); + for (col=0; col < iwidth; col++) + fimg[lpass + row*iwidth + col] = temp[col] * 0.25; + } +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp for +#endif + for (col=0; col < iwidth; col++) { + hat_transform (temp, fimg+lpass+col, iwidth, iheight, 1 << lev); + for (row=0; row < iheight; row++) + fimg[lpass + row*iwidth + col] = temp[row] * 0.25; + } + thold = threshold * noise[lev]; +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp for +#endif + for (i=0; i < size; i++) { + fimg[hpass+i] -= fimg[lpass+i]; + if (fimg[hpass+i] < -thold) fimg[hpass+i] += thold; + else if (fimg[hpass+i] > thold) fimg[hpass+i] -= thold; + else fimg[hpass+i] = 0; + if (hpass) fimg[i] += fimg[hpass+i]; + } + hpass = lpass; + } +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp for +#endif + for (i=0; i < size; i++) + image[i][c] = CLIP(SQR(fimg[i]+fimg[lpass+i])/0x10000); + } + free(temp); + } /* end omp parallel */ +/* the following loops are hard to parallize, no idea yes, + * problem is wlast which is carrying dependency + * second part should be easyer, but did not yet get it right. + */ + if (filters && colors == 3) { /* pull G1 and G3 closer together */ + for (row=0; row < 2; row++) + mul[row] = 0.125 * pre_mul[FC(row+1,0) | 1] / pre_mul[FC(row,0) | 1]; + for (i=0; i < 4; i++) + window[i] = (ushort *) fimg + width*i; + for (wlast=-1, row=1; row < height-1; row++) { + while (wlast < row+1) { + for (wlast++, i=0; i < 4; i++) + window[(i+3) & 3] = window[i]; + for (col = FC(wlast,1) & 1; col < width; col+=2) + window[2][col] = BAYER(wlast,col); + } + thold = threshold/512; + for (col = (FC(row,0) & 1)+1; col < width-1; col+=2) { + avg = ( window[0][col-1] + window[0][col+1] + + window[2][col-1] + window[2][col+1] - black*4 ) + * mul[row & 1] + (window[1][col] - black) * 0.5 + black; + avg = avg < 0 ? 0 : sqrt(avg); + diff = sqrt(BAYER(row,col)) - avg; + if (diff < -thold) diff += thold; + else if (diff > thold) diff -= thold; + else diff = 0; + BAYER(row,col) = CLIP(SQR(avg+diff) + 0.5); + } + } + } + free (fimg); +} + +#endif + +void CLASS scale_colors() +{ + unsigned bottom, right, size, row, col, ur, uc, i, x, y, c, sum[8]; + int val, dark, sat; + double dsum[8], dmin, dmax; + float scale_mul[4], fr, fc; + ushort *img=0, *pix; + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS,0,2); +#endif + + if (user_mul[0]) + memcpy (pre_mul, user_mul, sizeof pre_mul); + if (use_auto_wb || (use_camera_wb && cam_mul[0] == -1)) { + memset (dsum, 0, sizeof dsum); + bottom = MIN (greybox[1]+greybox[3], height); + right = MIN (greybox[0]+greybox[2], width); + for (row=greybox[1]; row < bottom; row += 8) + for (col=greybox[0]; col < right; col += 8) { + memset (sum, 0, sizeof sum); + for (y=row; y < row+8 && y < bottom; y++) + for (x=col; x < col+8 && x < right; x++) + FORC4 { + if (filters) { + c = FC(y,x); + val = BAYER(y,x); + } else + val = image[y*width+x][c]; + if (val > maximum-25) goto skip_block; + if ((val -= black) < 0) val = 0; + sum[c] += val; + sum[c+4]++; + if (filters) break; + } + FORC(8) dsum[c] += sum[c]; +skip_block: ; + } + FORC4 if (dsum[c]) pre_mul[c] = dsum[c+4] / dsum[c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CALCULATED; +#endif + } + if (use_camera_wb && cam_mul[0] != -1) { + memset (sum, 0, sizeof sum); + for (row=0; row < 8; row++) + for (col=0; col < 8; col++) { + c = FC(row,col); + if ((val = white[row][col] - black) > 0) + sum[c] += val; + sum[c+4]++; + } + if (sum[0] && sum[1] && sum[2] && sum[3]) + { + FORC4 pre_mul[c] = (float) sum[c+4] / sum[c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CALCULATED; +#endif + } + else if (cam_mul[0] && cam_mul[2]) + { + memcpy (pre_mul, cam_mul, sizeof pre_mul); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state =color_flags.pre_mul_state; +#endif + } + else + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_BAD_CAMERA_WB; +#endif +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s: Cannot use camera white balance.\n"), ifname); +#endif + } + } + if (pre_mul[3] == 0) pre_mul[3] = colors < 4 ? pre_mul[1] : 1; + dark = black; + sat = maximum; + if (threshold) wavelet_denoise(); + maximum -= black; + for (dmin=DBL_MAX, dmax=c=0; c < 4; c++) { + if (dmin > pre_mul[c]) + dmin = pre_mul[c]; + if (dmax < pre_mul[c]) + dmax = pre_mul[c]; + } + if (!highlight) dmax = dmin; + FORC4 scale_mul[c] = (pre_mul[c] /= dmax) * 65535.0 / maximum; +#ifdef DCRAW_VERBOSE + if (verbose) { + fprintf (stderr, + _("Scaling with darkness %d, saturation %d, and\nmultipliers"), dark, sat); + FORC4 fprintf (stderr, " %f", pre_mul[c]); + fputc ('\n', stderr); + } +#endif + size = iheight*iwidth; + for (i=0; i < size*4; i++) { + val = image[0][i]; + if (!val) continue; + val -= black; + val *= scale_mul[i & 3]; + image[0][i] = CLIP(val); + } + if ((aber[0] != 1 || aber[2] != 1) && colors == 3) { +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr,_("Correcting chromatic aberration...\n")); +#endif + for (c=0; c < 4; c+=2) { + if (aber[c] == 1) continue; + img = (ushort *) malloc (size * sizeof *img); + merror (img, "scale_colors()"); + for (i=0; i < size; i++) + img[i] = image[i][c]; + for (row=0; row < iheight; row++) { + ur = fr = (row - iheight*0.5) * aber[c] + iheight*0.5; + if (ur > iheight-2) continue; + fr -= ur; + for (col=0; col < iwidth; col++) { + uc = fc = (col - iwidth*0.5) * aber[c] + iwidth*0.5; + if (uc > iwidth-2) continue; + fc -= uc; + pix = img + ur*iwidth + uc; + image[row*iwidth+col][c] = + (pix[ 0]*(1-fc) + pix[ 1]*fc) * (1-fr) + + (pix[iwidth]*(1-fc) + pix[iwidth+1]*fc) * fr; + } + } + free(img); + } + } +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS,1,2); +#endif +} + +void CLASS pre_interpolate() +{ + ushort (*img)[4]; + int row, col, c; + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_PRE_INTERPOLATE,0,2); +#endif + if (shrink) { + if (half_size) { + height = iheight; + width = iwidth; + } else { + img = (ushort (*)[4]) calloc (height*width, sizeof *img); + merror (img, "pre_interpolate()"); + for (row=0; row < height; row++) + for (col=0; col < width; col++) { + c = fc(row,col); + img[row*width+col][c] = image[(row >> 1)*iwidth+(col >> 1)][c]; + } + free (image); + image = img; + shrink = 0; + } + } + if (filters && colors == 3) { + if ((mix_green = four_color_rgb)) colors++; + else { + for (row = FC(1,0) >> 1; row < height; row+=2) + for (col = FC(row,1) & 1; col < width; col+=2) + image[row*width+col][1] = image[row*width+col][3]; + filters &= ~((filters & 0x55555555) << 1); + } + } + if (half_size) filters = 0; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_PRE_INTERPOLATE,1,2); +#endif +} + +void CLASS border_interpolate (int border) +{ + unsigned row, col, y, x, f, c, sum[8]; + + for (row=0; row < height; row++) + for (col=0; col < width; col++) { + if (col==border && row >= border && row < height-border) + col = width-border; + memset (sum, 0, sizeof sum); + for (y=row-1; y != row+2; y++) + for (x=col-1; x != col+2; x++) + if (y < height && x < width) { + f = fc(y,x); + sum[f] += image[y*width+x][f]; + sum[f+4]++; + } + f = fc(row,col); + FORCC if (c != f && sum[c+4]) + image[row*width+col][c] = sum[c] / sum[c+4]; + } +} + +void CLASS lin_interpolate() +{ + int code[16][16][32], *ip, sum[4]; + int c, i, x, y, row, col, shift, color; + ushort *pix; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Bilinear interpolation...\n")); +#endif + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,0,3); +#endif + border_interpolate(1); + for (row=0; row < 16; row++) + for (col=0; col < 16; col++) { + ip = code[row][col]; + memset (sum, 0, sizeof sum); + for (y=-1; y <= 1; y++) + for (x=-1; x <= 1; x++) { + shift = (y==0) + (x==0); + if (shift == 2) continue; + color = fc(row+y,col+x); + *ip++ = (width*y + x)*4 + color; + *ip++ = shift; + *ip++ = color; + sum[color] += 1 << shift; + } + FORCC + if (c != fc(row,col)) { + *ip++ = c; + *ip++ = 256 / sum[c]; + } + } +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,1,3); +#endif + for (row=1; row < height-1; row++) + for (col=1; col < width-1; col++) { + pix = image[row*width+col]; + ip = code[row & 15][col & 15]; + memset (sum, 0, sizeof sum); + for (i=8; i--; ip+=3) + sum[ip[2]] += pix[ip[0]] << ip[1]; + for (i=colors; --i; ip+=2) + pix[ip[0]] = sum[ip[0]] * ip[1] >> 8; + } +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,2,3); +#endif +} + +/* + This algorithm is officially called: + + "Interpolation using a Threshold-based variable number of gradients" + + described in http://scien.stanford.edu/class/psych221/projects/99/tingchen/algodep/vargra.html + + I've extended the basic idea to work with non-Bayer filter arrays. + Gradients are numbered clockwise from NW=0 to W=7. + */ +void CLASS vng_interpolate() +{ + static const signed char *cp, terms[] = { + -2,-2,+0,-1,0,0x01, -2,-2,+0,+0,1,0x01, -2,-1,-1,+0,0,0x01, + -2,-1,+0,-1,0,0x02, -2,-1,+0,+0,0,0x03, -2,-1,+0,+1,1,0x01, + -2,+0,+0,-1,0,0x06, -2,+0,+0,+0,1,0x02, -2,+0,+0,+1,0,0x03, + -2,+1,-1,+0,0,0x04, -2,+1,+0,-1,1,0x04, -2,+1,+0,+0,0,0x06, + -2,+1,+0,+1,0,0x02, -2,+2,+0,+0,1,0x04, -2,+2,+0,+1,0,0x04, + -1,-2,-1,+0,0,0x80, -1,-2,+0,-1,0,0x01, -1,-2,+1,-1,0,0x01, + -1,-2,+1,+0,1,0x01, -1,-1,-1,+1,0,0x88, -1,-1,+1,-2,0,0x40, + -1,-1,+1,-1,0,0x22, -1,-1,+1,+0,0,0x33, -1,-1,+1,+1,1,0x11, + -1,+0,-1,+2,0,0x08, -1,+0,+0,-1,0,0x44, -1,+0,+0,+1,0,0x11, + -1,+0,+1,-2,1,0x40, -1,+0,+1,-1,0,0x66, -1,+0,+1,+0,1,0x22, + -1,+0,+1,+1,0,0x33, -1,+0,+1,+2,1,0x10, -1,+1,+1,-1,1,0x44, + -1,+1,+1,+0,0,0x66, -1,+1,+1,+1,0,0x22, -1,+1,+1,+2,0,0x10, + -1,+2,+0,+1,0,0x04, -1,+2,+1,+0,1,0x04, -1,+2,+1,+1,0,0x04, + +0,-2,+0,+0,1,0x80, +0,-1,+0,+1,1,0x88, +0,-1,+1,-2,0,0x40, + +0,-1,+1,+0,0,0x11, +0,-1,+2,-2,0,0x40, +0,-1,+2,-1,0,0x20, + +0,-1,+2,+0,0,0x30, +0,-1,+2,+1,1,0x10, +0,+0,+0,+2,1,0x08, + +0,+0,+2,-2,1,0x40, +0,+0,+2,-1,0,0x60, +0,+0,+2,+0,1,0x20, + +0,+0,+2,+1,0,0x30, +0,+0,+2,+2,1,0x10, +0,+1,+1,+0,0,0x44, + +0,+1,+1,+2,0,0x10, +0,+1,+2,-1,1,0x40, +0,+1,+2,+0,0,0x60, + +0,+1,+2,+1,0,0x20, +0,+1,+2,+2,0,0x10, +1,-2,+1,+0,0,0x80, + +1,-1,+1,+1,0,0x88, +1,+0,+1,+2,0,0x08, +1,+0,+2,-1,0,0x40, + +1,+0,+2,+1,0,0x10 + }, chood[] = { -1,-1, -1,0, -1,+1, 0,+1, +1,+1, +1,0, +1,-1, 0,-1 }; + ushort (*brow[5])[4], *pix; + int prow=7, pcol=1, *ip, *code[16][16], gval[8], gmin, gmax, sum[4]; + int row, col, x, y, x1, x2, y1, y2, t, weight, grads, color, diag; + int g, diff, thold, num, c; + lin_interpolate(); +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("VNG interpolation...\n")); +#endif + + if (filters == 1) prow = pcol = 15; + ip = (int *) calloc ((prow+1)*(pcol+1), 1280); + merror (ip, "vng_interpolate()"); + for (row=0; row <= prow; row++) /* Precalculate for VNG */ + for (col=0; col <= pcol; col++) { + code[row][col] = ip; + for (cp=terms, t=0; t < 64; t++) { + y1 = *cp++; x1 = *cp++; + y2 = *cp++; x2 = *cp++; + weight = *cp++; + grads = *cp++; + color = fc(row+y1,col+x1); + if (fc(row+y2,col+x2) != color) continue; + diag = (fc(row,col+1) == color && fc(row+1,col) == color) ? 2:1; + if (abs(y1-y2) == diag && abs(x1-x2) == diag) continue; + *ip++ = (y1*width + x1)*4 + color; + *ip++ = (y2*width + x2)*4 + color; + *ip++ = weight; + for (g=0; g < 8; g++) + if (grads & 1< gval[g]) gmin = gval[g]; + if (gmax < gval[g]) gmax = gval[g]; + } + if (gmax == 0) { + memcpy (brow[2][col], pix, sizeof *image); + continue; + } + thold = gmin + (gmax >> 1); + memset (sum, 0, sizeof sum); + color = fc(row,col); + for (num=g=0; g < 8; g++,ip+=2) { /* Average the neighbors */ + if (gval[g] <= thold) { + FORCC + if (c == color && ip[1]) + sum[c] += (pix[c] + pix[ip[1]]) >> 1; + else + sum[c] += pix[ip[0] + c]; + num++; + } + } + FORCC { /* Save to buffer */ + t = pix[color]; + if (c != color) + t += (sum[c] - sum[color]) / num; + brow[2][col][c] = CLIP(t); + } + } + if (row > 3) /* Write buffer to image */ + memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image); + for (g=0; g < 4; g++) + brow[(g-1) & 3] = brow[g]; + } + memcpy (image[(row-2)*width+2], brow[0]+2, (width-4)*sizeof *image); + memcpy (image[(row-1)*width+2], brow[1]+2, (width-4)*sizeof *image); + free (brow[4]); + free (code[0][0]); +} + +/* + Patterned Pixel Grouping Interpolation by Alain Desbiolles +*/ +void CLASS ppg_interpolate() +{ + int dir[5] = { 1, width, -1, -width, 1 }; + int row, col, diff[2], guess[2], c, d, i; + ushort (*pix)[4]; + + border_interpolate(3); +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("PPG interpolation...\n")); +#endif + +/* Fill in the green layer with gradients and pattern recognition: */ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,0,3); +#endif + for (row=3; row < height-3; row++) + for (col=3+(FC(row,3) & 1), c=FC(row,col); col < width-3; col+=2) { + pix = image + row*width+col; + for (i=0; (d=dir[i]) > 0; i++) { + guess[i] = (pix[-d][1] + pix[0][c] + pix[d][1]) * 2 + - pix[-2*d][c] - pix[2*d][c]; + diff[i] = ( ABS(pix[-2*d][c] - pix[ 0][c]) + + ABS(pix[ 2*d][c] - pix[ 0][c]) + + ABS(pix[ -d][1] - pix[ d][1]) ) * 3 + + ( ABS(pix[ 3*d][1] - pix[ d][1]) + + ABS(pix[-3*d][1] - pix[-d][1]) ) * 2; + } + d = dir[i = diff[0] > diff[1]]; + pix[0][1] = ULIM(guess[i] >> 2, pix[d][1], pix[-d][1]); + } +/* Calculate red and blue for each green pixel: */ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,1,3); +#endif + for (row=1; row < height-1; row++) + for (col=1+(FC(row,2) & 1), c=FC(row,col+1); col < width-1; col+=2) { + pix = image + row*width+col; + for (i=0; (d=dir[i]) > 0; c=2-c, i++) + pix[0][c] = CLIP((pix[-d][c] + pix[d][c] + 2*pix[0][1] + - pix[-d][1] - pix[d][1]) >> 1); + } +/* Calculate blue for red pixels and vice versa: */ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,2,3); +#endif + for (row=1; row < height-1; row++) + for (col=1+(FC(row,1) & 1), c=2-FC(row,col); col < width-1; col+=2) { + pix = image + row*width+col; + for (i=0; (d=dir[i]+dir[i+1]) > 0; i++) { + diff[i] = ABS(pix[-d][c] - pix[d][c]) + + ABS(pix[-d][1] - pix[0][1]) + + ABS(pix[ d][1] - pix[0][1]); + guess[i] = pix[-d][c] + pix[d][c] + 2*pix[0][1] + - pix[-d][1] - pix[d][1]; + } + if (diff[0] != diff[1]) + pix[0][c] = CLIP(guess[diff[0] > diff[1]] >> 1); + else + pix[0][c] = CLIP((guess[0]+guess[1]) >> 2); + } +} + +/* + Adaptive Homogeneity-Directed interpolation is based on + the work of Keigo Hirakawa, Thomas Parks, and Paul Lee. + */ +#define TS 256 /* Tile Size */ +#ifndef _OPENMP +void CLASS ahd_interpolate() +{ + int i, j, k, top, left, row, col, tr, tc, c, d, val, hm[2]; + ushort (*pix)[4], (*rix)[3]; + static const int dir[4] = { -1, 1, -TS, TS }; + unsigned ldiff[2][4], abdiff[2][4], leps, abeps; + float r, cbrt[0x10000], xyz[3], xyz_cam[3][4]; + ushort (*rgb)[TS][TS][3]; + short (*lab)[TS][TS][3], (*lix)[3]; + char (*homo)[TS][TS], *buffer; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("AHD interpolation...\n")); +#endif + + for (i=0; i < 0x10000; i++) { + r = i / 65535.0; + cbrt[i] = r > 0.008856 ? pow((double)r,1/3.0) : 7.787*r + 16/116.0; + } + for (i=0; i < 3; i++) + for (j=0; j < colors; j++) + for (xyz_cam[i][j] = k=0; k < 3; k++) + xyz_cam[i][j] += xyz_rgb[i][k] * rgb_cam[k][j] / d65_white[i]; + + border_interpolate(5); + buffer = (char *) malloc (26*TS*TS); /* 1664 kB */ + merror (buffer, "ahd_interpolate()"); + rgb = (ushort(*)[TS][TS][3]) buffer; + lab = (short (*)[TS][TS][3])(buffer + 12*TS*TS); + homo = (char (*)[TS][TS]) (buffer + 24*TS*TS); + + for (top=2; top < height-5; top += TS-6) + { + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,(top-2)/(TS-6),(height-7)/(TS-6)+1); +#endif + for (left=2; left < width-5; left += TS-6) { + +/* Interpolate green horizontally and vertically: */ + for (row = top; row < top+TS && row < height-2; row++) { + col = left + (FC(row,left) & 1); + for (c = FC(row,col); col < left+TS && col < width-2; col+=2) { + pix = image + row*width+col; + val = ((pix[-1][1] + pix[0][c] + pix[1][1]) * 2 + - pix[-2][c] - pix[2][c]) >> 2; + rgb[0][row-top][col-left][1] = ULIM(val,pix[-1][1],pix[1][1]); + val = ((pix[-width][1] + pix[0][c] + pix[width][1]) * 2 + - pix[-2*width][c] - pix[2*width][c]) >> 2; + rgb[1][row-top][col-left][1] = ULIM(val,pix[-width][1],pix[width][1]); + } + } +/* Interpolate red and blue, and convert to CIELab: */ + for (d=0; d < 2; d++) + for (row=top+1; row < top+TS-1 && row < height-3; row++) + for (col=left+1; col < left+TS-1 && col < width-3; col++) { + pix = image + row*width+col; + rix = &rgb[d][row-top][col-left]; + lix = &lab[d][row-top][col-left]; + if ((c = 2 - FC(row,col)) == 1) { + c = FC(row+1,col); + val = pix[0][1] + (( pix[-1][2-c] + pix[1][2-c] + - rix[-1][1] - rix[1][1] ) >> 1); + rix[0][2-c] = CLIP(val); + val = pix[0][1] + (( pix[-width][c] + pix[width][c] + - rix[-TS][1] - rix[TS][1] ) >> 1); + } else + val = rix[0][1] + (( pix[-width-1][c] + pix[-width+1][c] + + pix[+width-1][c] + pix[+width+1][c] + - rix[-TS-1][1] - rix[-TS+1][1] + - rix[+TS-1][1] - rix[+TS+1][1] + 1) >> 2); + rix[0][c] = CLIP(val); + c = FC(row,col); + rix[0][c] = pix[0][c]; + xyz[0] = xyz[1] = xyz[2] = 0.5; + FORCC { + xyz[0] += xyz_cam[0][c] * rix[0][c]; + xyz[1] += xyz_cam[1][c] * rix[0][c]; + xyz[2] += xyz_cam[2][c] * rix[0][c]; + } + xyz[0] = cbrt[CLIP((int) xyz[0])]; + xyz[1] = cbrt[CLIP((int) xyz[1])]; + xyz[2] = cbrt[CLIP((int) xyz[2])]; + lix[0][0] = 64 * (116 * xyz[1] - 16); + lix[0][1] = 64 * 500 * (xyz[0] - xyz[1]); + lix[0][2] = 64 * 200 * (xyz[1] - xyz[2]); + } +/* Build homogeneity maps from the CIELab images: */ + memset (homo, 0, 2*TS*TS); + for (row=top+2; row < top+TS-2 && row < height-4; row++) { + tr = row-top; + for (col=left+2; col < left+TS-2 && col < width-4; col++) { + tc = col-left; + for (d=0; d < 2; d++) { + lix = &lab[d][tr][tc]; + for (i=0; i < 4; i++) { + ldiff[d][i] = ABS(lix[0][0]-lix[dir[i]][0]); + abdiff[d][i] = SQR(lix[0][1]-lix[dir[i]][1]) + + SQR(lix[0][2]-lix[dir[i]][2]); + } + } + leps = MIN(MAX(ldiff[0][0],ldiff[0][1]), + MAX(ldiff[1][2],ldiff[1][3])); + abeps = MIN(MAX(abdiff[0][0],abdiff[0][1]), + MAX(abdiff[1][2],abdiff[1][3])); + for (d=0; d < 2; d++) + for (i=0; i < 4; i++) + if (ldiff[d][i] <= leps && abdiff[d][i] <= abeps) + homo[d][tr][tc]++; + } + } +/* Combine the most homogenous pixels for the final result: */ + for (row=top+3; row < top+TS-3 && row < height-5; row++) { + tr = row-top; + for (col=left+3; col < left+TS-3 && col < width-5; col++) { + tc = col-left; + for (d=0; d < 2; d++) + for (hm[d]=0, i=tr-1; i <= tr+1; i++) + for (j=tc-1; j <= tc+1; j++) + hm[d] += homo[d][i][j]; + if (hm[0] != hm[1]) + FORC3 image[row*width+col][c] = rgb[hm[1] > hm[0]][tr][tc][c]; + else + FORC3 image[row*width+col][c] = + (rgb[0][tr][tc][c] + rgb[1][tr][tc][c]) >> 1; + } + } + } + } + free (buffer); +} +#else +void CLASS ahd_interpolate() +{ + int i, j, k, top, left, row, col, tr, tc, c, d, val, hm[2]; + ushort (*pix)[4], (*rix)[3]; + static const int dir[4] = { -1, 1, -TS, TS }; + unsigned ldiff[2][4], abdiff[2][4], leps, abeps; + float r, cbrt[0x10000], xyz[3], xyz_cam[3][4]; + ushort (*rgb)[TS][TS][3]; + short (*lab)[TS][TS][3], (*lix)[3]; + char (*homo)[TS][TS], *buffer; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("AHD interpolation...\n")); +#endif + + for (i=0; i < 0x10000; i++) { + r = i / 65535.0; + cbrt[i] = r > 0.008856 ? pow(r,1/3.0) : 7.787*r + 16/116.0; + } + for (i=0; i < 3; i++) + for (j=0; j < colors; j++) + for (xyz_cam[i][j] = k=0; k < 3; k++) + xyz_cam[i][j] += xyz_rgb[i][k] * rgb_cam[k][j] / d65_white[i]; + + border_interpolate(5); + +#ifdef LIBRAW_LIBRARY_BUILD +#pragma omp parallel private(buffer,rgb,lab,homo,top,left,row,c,col,pix,val,d,rix,xyz,lix,tc,tr,ldiff,abdiff,leps,abeps,hm,i,j) firstprivate(cbrt) shared(xyz_cam) +#endif + { + buffer = (char *) malloc (26*TS*TS); /* 1664 kB */ + merror (buffer, "ahd_interpolate()"); + rgb = (ushort(*)[TS][TS][3]) buffer; + lab = (short (*)[TS][TS][3])(buffer + 12*TS*TS); + homo = (char (*)[TS][TS]) (buffer + 24*TS*TS); + +#pragma omp for schedule(dynamic) + for (top=2; top < height-5; top += TS-6){ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE,(top-2)/(TS-6)+1,(height-7)/(TS-6)+1); +#endif + for (left=2; left < width-5; left += TS-6) { + + /* Interpolate green horizontally and vertically: */ + for (row = top; row < top+TS && row < height-2; row++) { + col = left + (FC(row,left) & 1); + for (c = FC(row,col); col < left+TS && col < width-2; col+=2) { + pix = image + row*width+col; + val = ((pix[-1][1] + pix[0][c] + pix[1][1]) * 2 + - pix[-2][c] - pix[2][c]) >> 2; + rgb[0][row-top][col-left][1] = ULIM(val,pix[-1][1],pix[1][1]); + val = ((pix[-width][1] + pix[0][c] + pix[width][1]) * 2 + - pix[-2*width][c] - pix[2*width][c]) >> 2; + rgb[1][row-top][col-left][1] = ULIM(val,pix[-width][1],pix[width][1]); + } + } + /* Interpolate red and blue, and convert to CIELab: */ + for (d=0; d < 2; d++) + for (row=top+1; row < top+TS-1 && row < height-3; row++) + for (col=left+1; col < left+TS-1 && col < width-3; col++) { + pix = image + row*width+col; + rix = &rgb[d][row-top][col-left]; + lix = &lab[d][row-top][col-left]; + if ((c = 2 - FC(row,col)) == 1) { + c = FC(row+1,col); + val = pix[0][1] + (( pix[-1][2-c] + pix[1][2-c] + - rix[-1][1] - rix[1][1] ) >> 1); + rix[0][2-c] = CLIP(val); + val = pix[0][1] + (( pix[-width][c] + pix[width][c] + - rix[-TS][1] - rix[TS][1] ) >> 1); + } else + val = rix[0][1] + (( pix[-width-1][c] + pix[-width+1][c] + + pix[+width-1][c] + pix[+width+1][c] + - rix[-TS-1][1] - rix[-TS+1][1] + - rix[+TS-1][1] - rix[+TS+1][1] + 1) >> 2); + rix[0][c] = CLIP(val); + c = FC(row,col); + rix[0][c] = pix[0][c]; + xyz[0] = xyz[1] = xyz[2] = 0.5; + FORCC { + xyz[0] += xyz_cam[0][c] * rix[0][c]; + xyz[1] += xyz_cam[1][c] * rix[0][c]; + xyz[2] += xyz_cam[2][c] * rix[0][c]; + } + xyz[0] = cbrt[CLIP((int) xyz[0])]; + xyz[1] = cbrt[CLIP((int) xyz[1])]; + xyz[2] = cbrt[CLIP((int) xyz[2])]; + lix[0][0] = 64 * (116 * xyz[1] - 16); + lix[0][1] = 64 * 500 * (xyz[0] - xyz[1]); + lix[0][2] = 64 * 200 * (xyz[1] - xyz[2]); + } + /* Build homogeneity maps from the CIELab images: */ + memset (homo, 0, 2*TS*TS); + for (row=top+2; row < top+TS-2 && row < height-4; row++) { + tr = row-top; + for (col=left+2; col < left+TS-2 && col < width-4; col++) { + tc = col-left; + for (d=0; d < 2; d++) { + lix = &lab[d][tr][tc]; + for (i=0; i < 4; i++) { + ldiff[d][i] = ABS(lix[0][0]-lix[dir[i]][0]); + abdiff[d][i] = SQR(lix[0][1]-lix[dir[i]][1]) + + SQR(lix[0][2]-lix[dir[i]][2]); + } + } + leps = MIN(MAX(ldiff[0][0],ldiff[0][1]), + MAX(ldiff[1][2],ldiff[1][3])); + abeps = MIN(MAX(abdiff[0][0],abdiff[0][1]), + MAX(abdiff[1][2],abdiff[1][3])); + for (d=0; d < 2; d++) + for (i=0; i < 4; i++) + if (ldiff[d][i] <= leps && abdiff[d][i] <= abeps) + homo[d][tr][tc]++; + } + } + /* Combine the most homogenous pixels for the final result: */ + for (row=top+3; row < top+TS-3 && row < height-5; row++) { + tr = row-top; + for (col=left+3; col < left+TS-3 && col < width-5; col++) { + tc = col-left; + for (d=0; d < 2; d++) + for (hm[d]=0, i=tr-1; i <= tr+1; i++) + for (j=tc-1; j <= tc+1; j++) + hm[d] += homo[d][i][j]; + if (hm[0] != hm[1]) + FORC3 image[row*width+col][c] = rgb[hm[1] > hm[0]][tr][tc][c]; + else + FORC3 image[row*width+col][c] = + (rgb[0][tr][tc][c] + rgb[1][tr][tc][c]) >> 1; + } + } + } + } + free (buffer); + } +} + +#endif +#undef TS + +void CLASS median_filter() +{ + ushort (*pix)[4]; + int pass, c, i, j, k, med[9]; + static const uchar opt[] = /* Optimal 9-element median search */ + { 1,2, 4,5, 7,8, 0,1, 3,4, 6,7, 1,2, 4,5, 7,8, + 0,3, 5,8, 4,7, 3,6, 1,4, 2,5, 4,7, 4,2, 6,4, 4,2 }; + + for (pass=1; pass <= med_passes; pass++) { +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_MEDIAN_FILTER,pass-1,med_passes); +#endif +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr,_("Median filter pass %d...\n"), pass); +#endif + for (c=0; c < 3; c+=2) { + for (pix = image; pix < image+width*height; pix++) + pix[0][3] = pix[0][c]; + for (pix = image+width; pix < image+width*(height-1); pix++) { + if ((pix-image+1) % width < 2) continue; + for (k=0, i = -width; i <= width; i += width) + for (j = i-1; j <= i+1; j++) + med[k++] = pix[j][3] - pix[j][1]; + for (i=0; i < sizeof opt; i+=2) + if (med[opt[i]] > med[opt[i+1]]) + SWAP (med[opt[i]] , med[opt[i+1]]); + pix[0][c] = CLIP(med[4] + pix[0][1]); + } + } + } +} + +void CLASS blend_highlights() +{ + int clip=INT_MAX, row, col, c, i, j; + static const float trans[2][4][4] = + { { { 1,1,1 }, { 1.7320508,-1.7320508,0 }, { -1,-1,2 } }, + { { 1,1,1,1 }, { 1,-1,1,-1 }, { 1,1,-1,-1 }, { 1,-1,-1,1 } } }; + static const float itrans[2][4][4] = + { { { 1,0.8660254,-0.5 }, { 1,-0.8660254,-0.5 }, { 1,0,1 } }, + { { 1,1,1,1 }, { 1,-1,1,-1 }, { 1,1,-1,-1 }, { 1,-1,-1,1 } } }; + float cam[2][4], lab[2][4], sum[2], chratio; + + if ((unsigned) (colors-3) > 1) return; +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Blending highlights...\n")); +#endif + FORCC if (clip > (i = 65535*pre_mul[c])) clip = i; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS,0,2); +#endif + for (row=0; row < height; row++) + for (col=0; col < width; col++) { + FORCC if (image[row*width+col][c] > clip) break; + if (c == colors) continue; + FORCC { + cam[0][c] = image[row*width+col][c]; + cam[1][c] = MIN(cam[0][c],clip); + } + for (i=0; i < 2; i++) { + FORCC for (lab[i][c]=j=0; j < colors; j++) + lab[i][c] += trans[colors-3][c][j] * cam[i][j]; + for (sum[i]=0,c=1; c < colors; c++) + sum[i] += SQR(lab[i][c]); + } + chratio = sqrt(sum[1]/sum[0]); + for (c=1; c < colors; c++) + lab[0][c] *= chratio; + FORCC for (cam[0][c]=j=0; j < colors; j++) + cam[0][c] += itrans[colors-3][c][j] * lab[0][j]; + FORCC image[row*width+col][c] = cam[0][c] / colors; + } +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS,1,2); +#endif +} + +#define SCALE (4 >> shrink) +void CLASS recover_highlights() +{ + float *map, sum, wgt, grow; + int hsat[4], count, spread, change, val, i; + unsigned high, wide, mrow, mcol, row, col, kc, c, d, y, x; + ushort *pixel; + static const signed char dir[8][2] = + { {-1,-1}, {-1,0}, {-1,1}, {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1} }; + +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Rebuilding highlights...\n")); +#endif + + grow = pow (2.0, 4.0-highlight); + FORCC hsat[c] = 32000 * pre_mul[c]; + for (kc=0, c=1; c < colors; c++) + if (pre_mul[kc] < pre_mul[c]) kc = c; + high = height / SCALE; + wide = width / SCALE; + map = (float *) calloc (high*wide, sizeof *map); + merror (map, "recover_highlights()"); + FORCC if (c != kc) { +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS,c-1,colors-1); +#endif + memset (map, 0, high*wide*sizeof *map); + for (mrow=0; mrow < high; mrow++) + for (mcol=0; mcol < wide; mcol++) { + sum = wgt = count = 0; + for (row = mrow*SCALE; row < (mrow+1)*SCALE; row++) + for (col = mcol*SCALE; col < (mcol+1)*SCALE; col++) { + pixel = image[row*width+col]; + if (pixel[c] / hsat[c] == 1 && pixel[kc] > 24000) { + sum += pixel[c]; + wgt += pixel[kc]; + count++; + } + } + if (count == SCALE*SCALE) + map[mrow*wide+mcol] = sum / wgt; + } + for (spread = 32/grow; spread--; ) { + for (mrow=0; mrow < high; mrow++) + for (mcol=0; mcol < wide; mcol++) { + if (map[mrow*wide+mcol]) continue; + sum = count = 0; + for (d=0; d < 8; d++) { + y = mrow + dir[d][0]; + x = mcol + dir[d][1]; + if (y < high && x < wide && map[y*wide+x] > 0) { + sum += (1 + (d & 1)) * map[y*wide+x]; + count += 1 + (d & 1); + } + } + if (count > 3) + map[mrow*wide+mcol] = - (sum+grow) / (count+grow); + } + for (change=i=0; i < high*wide; i++) + if (map[i] < 0) { + map[i] = -map[i]; + change = 1; + } + if (!change) break; + } + for (i=0; i < high*wide; i++) + if (map[i] == 0) map[i] = 1; + for (mrow=0; mrow < high; mrow++) + for (mcol=0; mcol < wide; mcol++) { + for (row = mrow*SCALE; row < (mrow+1)*SCALE; row++) + for (col = mcol*SCALE; col < (mcol+1)*SCALE; col++) { + pixel = image[row*width+col]; + if (pixel[c] / hsat[c] > 1) { + val = pixel[kc] * map[mrow*wide+mcol]; + if (pixel[c] < val) pixel[c] = CLIP(val); + } + } + } + } + free (map); +} +#undef SCALE + +void CLASS tiff_get (unsigned base, + unsigned *tag, unsigned *type, unsigned *len, unsigned *save) +{ + *tag = get2(); + *type = get2(); + *len = get4(); + *save = ftell(ifp) + 4; + if (*len * ("11124811248488"[*type < 14 ? *type:0]-'0') > 4) + fseek (ifp, get4()+base, SEEK_SET); +} + +void CLASS parse_thumb_note (int base, unsigned toff, unsigned tlen) +{ + unsigned entries, tag, type, len, save; + + entries = get2(); + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + if (tag == toff) thumb_offset = get4()+base; + if (tag == tlen) thumb_length = get4(); + fseek (ifp, save, SEEK_SET); + } +} + +#line 5393 "dcraw/dcraw.c" +void CLASS parse_makernote (int base, int uptag) +{ + static const uchar xlat[2][256] = { + { 0xc1,0xbf,0x6d,0x0d,0x59,0xc5,0x13,0x9d,0x83,0x61,0x6b,0x4f,0xc7,0x7f,0x3d,0x3d, + 0x53,0x59,0xe3,0xc7,0xe9,0x2f,0x95,0xa7,0x95,0x1f,0xdf,0x7f,0x2b,0x29,0xc7,0x0d, + 0xdf,0x07,0xef,0x71,0x89,0x3d,0x13,0x3d,0x3b,0x13,0xfb,0x0d,0x89,0xc1,0x65,0x1f, + 0xb3,0x0d,0x6b,0x29,0xe3,0xfb,0xef,0xa3,0x6b,0x47,0x7f,0x95,0x35,0xa7,0x47,0x4f, + 0xc7,0xf1,0x59,0x95,0x35,0x11,0x29,0x61,0xf1,0x3d,0xb3,0x2b,0x0d,0x43,0x89,0xc1, + 0x9d,0x9d,0x89,0x65,0xf1,0xe9,0xdf,0xbf,0x3d,0x7f,0x53,0x97,0xe5,0xe9,0x95,0x17, + 0x1d,0x3d,0x8b,0xfb,0xc7,0xe3,0x67,0xa7,0x07,0xf1,0x71,0xa7,0x53,0xb5,0x29,0x89, + 0xe5,0x2b,0xa7,0x17,0x29,0xe9,0x4f,0xc5,0x65,0x6d,0x6b,0xef,0x0d,0x89,0x49,0x2f, + 0xb3,0x43,0x53,0x65,0x1d,0x49,0xa3,0x13,0x89,0x59,0xef,0x6b,0xef,0x65,0x1d,0x0b, + 0x59,0x13,0xe3,0x4f,0x9d,0xb3,0x29,0x43,0x2b,0x07,0x1d,0x95,0x59,0x59,0x47,0xfb, + 0xe5,0xe9,0x61,0x47,0x2f,0x35,0x7f,0x17,0x7f,0xef,0x7f,0x95,0x95,0x71,0xd3,0xa3, + 0x0b,0x71,0xa3,0xad,0x0b,0x3b,0xb5,0xfb,0xa3,0xbf,0x4f,0x83,0x1d,0xad,0xe9,0x2f, + 0x71,0x65,0xa3,0xe5,0x07,0x35,0x3d,0x0d,0xb5,0xe9,0xe5,0x47,0x3b,0x9d,0xef,0x35, + 0xa3,0xbf,0xb3,0xdf,0x53,0xd3,0x97,0x53,0x49,0x71,0x07,0x35,0x61,0x71,0x2f,0x43, + 0x2f,0x11,0xdf,0x17,0x97,0xfb,0x95,0x3b,0x7f,0x6b,0xd3,0x25,0xbf,0xad,0xc7,0xc5, + 0xc5,0xb5,0x8b,0xef,0x2f,0xd3,0x07,0x6b,0x25,0x49,0x95,0x25,0x49,0x6d,0x71,0xc7 }, + { 0xa7,0xbc,0xc9,0xad,0x91,0xdf,0x85,0xe5,0xd4,0x78,0xd5,0x17,0x46,0x7c,0x29,0x4c, + 0x4d,0x03,0xe9,0x25,0x68,0x11,0x86,0xb3,0xbd,0xf7,0x6f,0x61,0x22,0xa2,0x26,0x34, + 0x2a,0xbe,0x1e,0x46,0x14,0x68,0x9d,0x44,0x18,0xc2,0x40,0xf4,0x7e,0x5f,0x1b,0xad, + 0x0b,0x94,0xb6,0x67,0xb4,0x0b,0xe1,0xea,0x95,0x9c,0x66,0xdc,0xe7,0x5d,0x6c,0x05, + 0xda,0xd5,0xdf,0x7a,0xef,0xf6,0xdb,0x1f,0x82,0x4c,0xc0,0x68,0x47,0xa1,0xbd,0xee, + 0x39,0x50,0x56,0x4a,0xdd,0xdf,0xa5,0xf8,0xc6,0xda,0xca,0x90,0xca,0x01,0x42,0x9d, + 0x8b,0x0c,0x73,0x43,0x75,0x05,0x94,0xde,0x24,0xb3,0x80,0x34,0xe5,0x2c,0xdc,0x9b, + 0x3f,0xca,0x33,0x45,0xd0,0xdb,0x5f,0xf5,0x52,0xc3,0x21,0xda,0xe2,0x22,0x72,0x6b, + 0x3e,0xd0,0x5b,0xa8,0x87,0x8c,0x06,0x5d,0x0f,0xdd,0x09,0x19,0x93,0xd0,0xb9,0xfc, + 0x8b,0x0f,0x84,0x60,0x33,0x1c,0x9b,0x45,0xf1,0xf0,0xa3,0x94,0x3a,0x12,0x77,0x33, + 0x4d,0x44,0x78,0x28,0x3c,0x9e,0xfd,0x65,0x57,0x16,0x94,0x6b,0xfb,0x59,0xd0,0xc8, + 0x22,0x36,0xdb,0xd2,0x63,0x98,0x43,0xa1,0x04,0x87,0x86,0xf7,0xa6,0x26,0xbb,0xd6, + 0x59,0x4d,0xbf,0x6a,0x2e,0xaa,0x2b,0xef,0xe6,0x78,0xb6,0x4e,0xe0,0x2f,0xdc,0x7c, + 0xbe,0x57,0x19,0x32,0x7e,0x2a,0xd0,0xb8,0xba,0x29,0x00,0x3c,0x52,0x7d,0xa8,0x49, + 0x3b,0x2d,0xeb,0x25,0x49,0xfa,0xa3,0xaa,0x39,0xa7,0xc5,0xa7,0x50,0x11,0x36,0xfb, + 0xc6,0x67,0x4a,0xf5,0xa5,0x12,0x65,0x7e,0xb0,0xdf,0xaf,0x4e,0xb3,0x61,0x7f,0x2f } }; + unsigned offset=0, entries, tag, type, len, save, c; + unsigned ver97=0, serial=0, i, wbi=0, wb[4]={0,0,0,0}; + uchar buf97[324], ci, cj, ck; + short sorder=order; + char buf[10]; +/* + The MakerNote might have its own TIFF header (possibly with + its own byte-order!), or it might just be a table. + */ + fread (buf, 1, 10, ifp); + if (!strncmp (buf,"KDK" ,3) || /* these aren't TIFF tables */ + !strncmp (buf,"VER" ,3) || + !strncmp (buf,"IIII",4) || + !strncmp (buf,"MMMM",4)) return; + if (!strncmp (buf,"KC" ,2) || /* Konica KD-400Z, KD-510Z */ + !strncmp (buf,"MLY" ,3)) { /* Minolta DiMAGE G series */ + order = 0x4d4d; + while ((i=ftell(ifp)) < data_offset && i < 16384) { + wb[0] = wb[2]; wb[2] = wb[1]; wb[1] = wb[3]; + wb[3] = get2(); + if (wb[1] == 256 && wb[3] == 256 && + wb[0] > 256 && wb[0] < 640 && wb[2] > 256 && wb[2] < 640) + FORC4 cam_mul[c] = wb[c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + goto quit; + } + if (!strcmp (buf,"Nikon")) { + base = ftell(ifp); + order = get2(); + if (get2() != 42) goto quit; + offset = get4(); + fseek (ifp, offset-8, SEEK_CUR); + } else if (!strcmp (buf,"OLYMPUS")) { + base = ftell(ifp)-10; + fseek (ifp, -2, SEEK_CUR); + order = get2(); get2(); + } else if (!strncmp (buf,"FUJIFILM",8) || + !strncmp (buf,"SONY",4) || + !strcmp (buf,"Panasonic")) { + order = 0x4949; + fseek (ifp, 2, SEEK_CUR); + } else if (!strcmp (buf,"OLYMP") || + !strcmp (buf,"LEICA") || + !strcmp (buf,"Ricoh") || + !strcmp (buf,"EPSON")) + fseek (ifp, -2, SEEK_CUR); + else if (!strcmp (buf,"AOC") || + !strcmp (buf,"QVC")) + fseek (ifp, -4, SEEK_CUR); + else fseek (ifp, -10, SEEK_CUR); + + entries = get2(); + if (entries > 1000) return; + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + tag |= uptag << 16; + if (tag == 2 && strstr(make,"NIKON")) + iso_speed = (get2(),get2()); + if (tag == 4 && len > 26 && len < 35) { + if ((i=(get4(),get2())) != 0x7fff && !iso_speed) + iso_speed = 50 * pow (2, i/32.0 - 4); + if ((i=(get2(),get2())) != 0x7fff && !aperture) + aperture = pow (2, i/64.0); + if ((i=get2()) != 0xffff && !shutter) + shutter = pow (2, (short) i/-32.0); + wbi = (get2(),get2()); + shot_order = (get2(),get2()); + } + if (tag == 8 && type == 4) + shot_order = get4(); + if (tag == 9 && !strcmp(make,"Canon")) + fread (artist, 64, 1, ifp); + if (tag == 0xc && len == 4) { + cam_mul[0] = getreal(type); + cam_mul[2] = getreal(type); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0x10 && type == 4) + unique_id = get4(); + if (tag == 0x11 && is_raw && !strncmp(make,"NIKON",5)) { + fseek (ifp, get4()+base, SEEK_SET); + parse_tiff_ifd (base); + } + if (tag == 0x14 && len == 2560 && type == 7) { + fseek (ifp, 1248, SEEK_CUR); + goto get2_256; + } + if (tag == 0x15 && type == 2 && is_raw) + fread (model, 64, 1, ifp); + if (strstr(make,"PENTAX")) { + if (tag == 0x1b) tag = 0x1018; + if (tag == 0x1c) tag = 0x1017; + } + if (tag == 0x1d) + while ((c = fgetc(ifp)) && c != EOF) + serial = serial*10 + (isdigit(c) ? c - '0' : c % 10); + if (tag == 0x81 && type == 4) { + data_offset = get4(); + fseek (ifp, data_offset + 41, SEEK_SET); + raw_height = get2() * 2; + raw_width = get2(); + filters = 0x61616161; + } + if (tag == 0x29 && type == 1) { + c = wbi < 18 ? "012347800000005896"[wbi]-'0' : 0; + fseek (ifp, 8 + c*32, SEEK_CUR); + FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get4(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if ((tag == 0x81 && type == 7) || + (tag == 0x100 && type == 7) || + (tag == 0x280 && type == 1)) { + thumb_offset = ftell(ifp); + thumb_length = len; + } + if (tag == 0x88 && type == 4 && (thumb_offset = get4())) + thumb_offset += base; + if (tag == 0x89 && type == 4) + thumb_length = get4(); + if (tag == 0x8c || tag == 0x96) + meta_offset = ftell(ifp); + if (tag == 0x97) { + for (i=0; i < 4; i++) + ver97 = ver97 * 10 + fgetc(ifp)-'0'; + switch (ver97) { + case 100: + fseek (ifp, 68, SEEK_CUR); + FORC4 cam_mul[(c >> 1) | ((c & 1) << 1)] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 102: + fseek (ifp, 6, SEEK_CUR); + goto get2_rggb; + case 103: + fseek (ifp, 16, SEEK_CUR); + FORC4 cam_mul[c] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (ver97 >= 200) { + if (ver97 != 205) fseek (ifp, 280, SEEK_CUR); + fread (buf97, 324, 1, ifp); + } + } + if (tag == 0xa4 && type == 3) { + fseek (ifp, wbi*48, SEEK_CUR); + FORC3 cam_mul[c] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0xa7 && (unsigned) (ver97-200) < 12 && !cam_mul[0]) { + ci = xlat[0][serial & 0xff]; + cj = xlat[1][fgetc(ifp)^fgetc(ifp)^fgetc(ifp)^fgetc(ifp)]; + ck = 0x60; + for (i=0; i < 324; i++) + buf97[i] ^= (cj += ci * ck++); + i = "66666>666;6A"[ver97-200] - '0'; + FORC4 cam_mul[c ^ (c >> 1) ^ (i & 1)] = + sget2 (buf97 + (i & -2) + c*2); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0x200 && len == 3) + shot_order = (get4(),get4()); + if (tag == 0x200 && len == 4) + black = (get2()+get2()+get2()+get2())/4; + if (tag == 0x201 && len == 4) + goto get2_rggb; + if (tag == 0x401 && len == 4) { + black = (get4()+get4()+get4()+get4())/4; + } + if (tag == 0xe01) { /* Nikon Capture Note */ + type = order; + order = 0x4949; + fseek (ifp, 22, SEEK_CUR); + for (offset=22; offset+22 < len; offset += 22+i) { + tag = get4(); + fseek (ifp, 14, SEEK_CUR); + i = get4()-4; + if (tag == 0x76a43207) flip = get2(); + else fseek (ifp, i, SEEK_CUR); + } + order = type; + } + if (tag == 0xe80 && len == 256 && type == 7) { + fseek (ifp, 48, SEEK_CUR); + cam_mul[0] = get2() * 508 * 1.078 / 0x10000; + cam_mul[2] = get2() * 382 * 1.173 / 0x10000; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0xf00 && type == 7) { + if (len == 614) + fseek (ifp, 176, SEEK_CUR); + else if (len == 734 || len == 1502) + fseek (ifp, 148, SEEK_CUR); + else goto next; + goto get2_256; + } + if ((tag == 0x1011 && len == 9) || tag == 0x20400200) + { + for (i=0; i < 3; i++) + FORC3 cmatrix[i][c] = ((short) get2()) / 256.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cmatrix_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if ((tag == 0x1012 || tag == 0x20400600) && len == 4) + for (black = i=0; i < 4; i++) + black += get2() << 2; + if (tag == 0x1017 || tag == 0x20400100) + { + cam_mul[0] = get2() / 256.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0x1018 || tag == 0x20400100) + { + cam_mul[2] = get2() / 256.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 0x2011 && len == 2) { +get2_256: + order = 0x4d4d; + cam_mul[0] = get2() / 256.0; + cam_mul[2] = get2() / 256.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if ((tag | 0x70) == 0x2070 && type == 4) + fseek (ifp, get4()+base, SEEK_SET); + if (tag == 0x2010 && type != 7) + load_raw = &CLASS olympus_e410_load_raw; + if (tag == 0x2020) + parse_thumb_note (base, 257, 258); + if (tag == 0x2040) + parse_makernote (base, 0x2040); + if (tag == 0xb028) { + fseek (ifp, get4(), SEEK_SET); + parse_thumb_note (base, 136, 137); + } + if (tag == 0x4001 && len > 500) { + i = len == 582 ? 50 : len == 653 ? 68 : len == 5120 ? 142 : 126; + fseek (ifp, i, SEEK_CUR); +get2_rggb: + FORC4 cam_mul[c ^ (c >> 1)] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + fseek (ifp, 22, SEEK_CUR); + FORC4 sraw_mul[c ^ (c >> 1)] = get2(); + } +next: + fseek (ifp, save, SEEK_SET); + } +quit: + order = sorder; +} + +/* + Since the TIFF DateTime string has no timezone information, + assume that the camera's clock was set to Universal Time. + */ +void CLASS get_timestamp (int reversed) +{ + struct tm t; + char str[20]; + int i; + + str[19] = 0; + if (reversed) + for (i=19; i--; ) str[i] = fgetc(ifp); + else + fread (str, 19, 1, ifp); + memset (&t, 0, sizeof t); + if (sscanf (str, "%d:%d:%d %d:%d:%d", &t.tm_year, &t.tm_mon, + &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec) != 6) + return; + t.tm_year -= 1900; + t.tm_mon -= 1; + if (mktime(&t) > 0) + timestamp = mktime(&t); +} + +void CLASS parse_exif (int base) +{ + unsigned kodak, entries, tag, type, len, save, c; + double expo; + + kodak = !strncmp(make,"EASTMAN",7); + entries = get2(); + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + switch (tag) { + case 33434: shutter = getreal(type); break; + case 33437: aperture = getreal(type); break; + case 34855: iso_speed = get2(); break; + case 36867: + case 36868: get_timestamp(0); break; + case 37377: if ((expo = -getreal(type)) < 128) + shutter = pow (2, expo); break; + case 37378: aperture = pow (2, getreal(type)/2); break; + case 37386: focal_len = getreal(type); break; + case 37500: parse_makernote (base, 0); break; + case 40962: if (kodak) raw_width = get4(); break; + case 40963: if (kodak) raw_height = get4(); break; + case 41730: + if (get4() == 0x20002) + for (exif_cfa=c=0; c < 8; c+=2) + exif_cfa |= fgetc(ifp) * 0x01010101 << c; + } + fseek (ifp, save, SEEK_SET); + } +} + +void CLASS parse_gps (int base) +{ + unsigned entries, tag, type, len, save, c; + + entries = get2(); + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + switch (tag) { + case 1: case 3: case 5: + gpsdata[29+tag/2] = getc(ifp); break; + case 2: case 4: case 7: + FORC(6) gpsdata[tag/3*6+c] = get4(); break; + case 6: + FORC(2) gpsdata[18+c] = get4(); break; + case 18: case 29: + fgets ((char *) (gpsdata+14+tag/3), MIN(len,12), ifp); + } + fseek (ifp, save, SEEK_SET); + } +} + +void CLASS romm_coeff (float romm_cam[3][3]) +{ + static const float rgb_romm[3][3] = /* ROMM == Kodak ProPhoto */ + { { 2.034193, -0.727420, -0.306766 }, + { -0.228811, 1.231729, -0.002922 }, + { -0.008565, -0.153273, 1.161839 } }; + int i, j, k; + + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) + for (cmatrix[i][j] = k=0; k < 3; k++) + cmatrix[i][j] += rgb_romm[i][k] * romm_cam[k][j]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cmatrix_state = LIBRAW_COLORSTATE_CALCULATED; +#endif +} + +void CLASS parse_mos (int offset) +{ + char data[40]; + int skip, from, i, c, neut[4], planes=0, frot=0; + static const char *mod[] = + { "","DCB2","Volare","Cantare","CMost","Valeo 6","Valeo 11","Valeo 22", + "Valeo 11p","Valeo 17","","Aptus 17","Aptus 22","Aptus 75","Aptus 65", + "Aptus 54S","Aptus 65S","Aptus 75S","AFi 5","AFi 6","AFi 7" }; + float romm_cam[3][3]; + + fseek (ifp, offset, SEEK_SET); + while (1) { + if (get4() != 0x504b5453) break; + get4(); + fread (data, 1, 40, ifp); + skip = get4(); + from = ftell(ifp); + if (!strcmp(data,"JPEG_preview_data")) { + thumb_offset = from; + thumb_length = skip; + } + if (!strcmp(data,"icc_camera_profile")) { + profile_offset = from; + profile_length = skip; + } + if (!strcmp(data,"ShootObj_back_type")) { + fscanf (ifp, "%d", &i); + if ((unsigned) i < sizeof mod / sizeof (*mod)) + strcpy (model, mod[i]); + } + if (!strcmp(data,"icc_camera_to_tone_matrix")) { + for (i=0; i < 9; i++) + romm_cam[0][i] = int_to_float(get4()); + romm_coeff (romm_cam); + } + if (!strcmp(data,"CaptProf_color_matrix")) { + for (i=0; i < 9; i++) + fscanf (ifp, "%f", &romm_cam[0][i]); + romm_coeff (romm_cam); + } + if (!strcmp(data,"CaptProf_number_of_planes")) + fscanf (ifp, "%d", &planes); + if (!strcmp(data,"CaptProf_raw_data_rotation")) + fscanf (ifp, "%d", &flip); + if (!strcmp(data,"CaptProf_mosaic_pattern")) + FORC4 { + fscanf (ifp, "%d", &i); + if (i == 1) frot = c ^ (c >> 1); + } + if (!strcmp(data,"ImgProf_rotation_angle")) { + fscanf (ifp, "%d", &i); + flip = i - flip; + } + if (!strcmp(data,"NeutObj_neutrals") && !cam_mul[0]) { + FORC4 fscanf (ifp, "%d", neut+c); + FORC3 cam_mul[c] = (float) neut[0] / neut[c+1]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + parse_mos (from); + fseek (ifp, skip+from, SEEK_SET); + } + if (planes) + filters = (planes == 1) * 0x01010101 * + (uchar) "\x94\x61\x16\x49"[(flip/90 + frot) & 3]; +} + +void CLASS linear_table (unsigned len) +{ + int i; + if (len > 0x1000) len = 0x1000; + read_shorts (curve, len); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_LOADED; +#endif + for (i=len; i < 0x1000; i++) + curve[i] = curve[i-1]; + maximum = curve[0xfff]; +} + +void CLASS parse_kodak_ifd (int base) +{ + unsigned entries, tag, type, len, save; + int i, c, wbi=-2, wbtemp=6500; + float mul[3], num; + + entries = get2(); + if (entries > 1024) return; + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + if (tag == 1020) wbi = getint(type); + if (tag == 1021 && len == 72) { /* WB set in software */ + fseek (ifp, 40, SEEK_CUR); + FORC3 cam_mul[c] = 2048.0 / get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + wbi = -2; + } + if (tag == 2118) wbtemp = getint(type); + if (tag == 2130 + wbi) + FORC3 mul[c] = getreal(type); + if (tag == 2140 + wbi && wbi >= 0) + { + FORC3 { + for (num=i=0; i < 4; i++) + num += getreal(type) * pow (wbtemp/100.0, i); + cam_mul[c] = 2048 / (num * mul[c]); + } +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (tag == 2317) linear_table (len); + if (tag == 6020) iso_speed = getint(type); + fseek (ifp, save, SEEK_SET); + } +} + +#line 5922 "dcraw/dcraw.c" +int CLASS parse_tiff_ifd (int base) +{ + unsigned entries, tag, type, len, plen=16, save; + int ifd, use_cm=0, cfa, i, j, c, ima_len=0; + char software[64], *cbuf, *cp; + uchar cfa_pat[16], cfa_pc[] = { 0,1,2,3 }, tab[256]; + double dblack, cc[4][4], cm[4][3], cam_xyz[4][3], num; + double ab[]={ 1,1,1,1 }, asn[] = { 0,0,0,0 }, xyz[] = { 1,1,1 }; + unsigned sony_curve[] = { 0,0,0,0,0,4095 }; + unsigned *buf, sony_offset=0, sony_length=0, sony_key=0; + struct jhead jh; + FILE *sfp; + + if (tiff_nifds >= sizeof tiff_ifd / sizeof tiff_ifd[0]) + return 1; + ifd = tiff_nifds++; + for (j=0; j < 4; j++) + for (i=0; i < 4; i++) + cc[j][i] = i == j; + entries = get2(); + if (entries > 512) return 1; + while (entries--) { + tiff_get (base, &tag, &type, &len, &save); + switch (tag) { + case 17: case 18: + if (type == 3 && len == 1) + { + cam_mul[(tag-17)*2] = get2() / 256.0; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + break; + case 23: + if (type == 3) iso_speed = get2(); + break; + case 36: case 37: case 38: + cam_mul[tag-0x24] = get2(); + break; + case 39: + if (len < 50 || cam_mul[0]) break; + fseek (ifp, 12, SEEK_CUR); + FORC3 cam_mul[c] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 46: + if (type != 7 || fgetc(ifp) != 0xff || fgetc(ifp) != 0xd8) break; + thumb_offset = ftell(ifp) - 2; + thumb_length = len; + break; + case 2: case 256: /* ImageWidth */ + tiff_ifd[ifd].t_width = getint(type); + break; + case 3: case 257: /* ImageHeight */ + tiff_ifd[ifd].t_height = getint(type); + break; + case 258: /* BitsPerSample */ + tiff_ifd[ifd].samples = len & 7; + tiff_ifd[ifd].bps = get2(); + break; + case 259: /* Compression */ + tiff_ifd[ifd].comp = get2(); + break; + case 262: /* PhotometricInterpretation */ + tiff_ifd[ifd].phint = get2(); + break; + case 270: /* ImageDescription */ + fread (desc, 512, 1, ifp); + break; + case 271: /* Make */ + fgets (make, 64, ifp); + break; + case 272: /* Model */ + fgets (model, 64, ifp); + break; + case 280: /* Panasonic RW2 offset */ + if (type != 4) break; + load_raw = &CLASS panasonic_load_raw; + load_flags = 0x2008; + case 273: /* StripOffset */ + case 513: + tiff_ifd[ifd].offset = get4()+base; + if (!tiff_ifd[ifd].bps) { + fseek (ifp, tiff_ifd[ifd].offset, SEEK_SET); + if (ljpeg_start (&jh, 1)) { + tiff_ifd[ifd].comp = 6; + tiff_ifd[ifd].t_width = jh.wide << (jh.clrs == 2); + tiff_ifd[ifd].t_height = jh.high; + tiff_ifd[ifd].bps = jh.bits; + tiff_ifd[ifd].samples = jh.clrs; + } + } + break; + case 274: /* Orientation */ + tiff_ifd[ifd].t_flip = "50132467"[get2() & 7]-'0'; + break; + case 277: /* SamplesPerPixel */ + tiff_ifd[ifd].samples = getint(type) & 7; + break; + case 279: /* StripByteCounts */ + case 514: + tiff_ifd[ifd].bytes = get4(); + break; + case 305: case 11: /* Software */ + fgets (software, 64, ifp); + if (!strncmp(software,"Adobe",5) || + !strncmp(software,"dcraw",5) || + !strncmp(software,"UFRaw",5) || + !strncmp(software,"Bibble",6) || + !strncmp(software,"Nikon Scan",10) || + !strcmp (software,"Digital Photo Professional")) + is_raw = 0; + break; + case 306: /* DateTime */ + get_timestamp(0); + break; + case 315: /* Artist */ + fread (artist, 64, 1, ifp); + break; + case 322: /* TileWidth */ + tile_width = getint(type); + break; + case 323: /* TileLength */ + tile_length = getint(type); + break; + case 324: /* TileOffsets */ + tiff_ifd[ifd].offset = len > 1 ? ftell(ifp) : get4(); + if (len == 4) { + load_raw = &CLASS sinar_4shot_load_raw; + is_raw = 5; + } + break; + case 330: /* SubIFDs */ + if (!strcmp(model,"DSLR-A100") && tiff_ifd[ifd].t_width == 3872) { + load_raw = &CLASS sony_arw_load_raw; + data_offset = get4()+base; + ifd++; break; + } + while (len--) { + i = ftell(ifp); + fseek (ifp, get4()+base, SEEK_SET); + if (parse_tiff_ifd (base)) break; + fseek (ifp, i+4, SEEK_SET); + } + break; + case 400: + strcpy (make, "Sarnoff"); + maximum = 0xfff; + break; + case 28688: + FORC4 sony_curve[c+1] = get2() >> 2 & 0xfff; + for (i=0; i < 5; i++) + for (j = sony_curve[i]+1; j <= sony_curve[i+1]; j++) + curve[j] = curve[j-1] + (1 << i); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 29184: sony_offset = get4(); break; + case 29185: sony_length = get4(); break; + case 29217: sony_key = get4(); break; + case 29264: + parse_minolta (ftell(ifp)); + raw_width = 0; + break; + case 29443: + FORC4 cam_mul[c ^ (c < 2)] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 29459: + FORC4 cam_mul[c ^ (c >> 1)] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 33405: /* Model2 */ + fgets (model2, 64, ifp); + break; + case 33422: /* CFAPattern */ + case 64777: /* Kodak P-series */ + if ((plen=len) > 16) plen = 16; + fread (cfa_pat, 1, plen, ifp); + for (colors=cfa=i=0; i < plen; i++) { + colors += !(cfa & (1 << cfa_pat[i])); + cfa |= 1 << cfa_pat[i]; + } + if (cfa == 070) memcpy (cfa_pc,"\003\004\005",3); /* CMY */ + if (cfa == 072) memcpy (cfa_pc,"\005\003\004\001",4); /* GMCY */ + goto guess_cfa_pc; + case 33424: + fseek (ifp, get4()+base, SEEK_SET); + parse_kodak_ifd (base); + break; + case 33434: /* ExposureTime */ + shutter = getreal(type); + break; + case 33437: /* FNumber */ + aperture = getreal(type); + break; + case 34306: /* Leaf white balance */ + FORC4 cam_mul[c ^ 1] = 4096.0 / get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 34307: /* Leaf CatchLight color matrix */ + fread (software, 1, 7, ifp); + if (strncmp(software,"MATRIX",6)) break; + colors = 4; + for (raw_color = i=0; i < 3; i++) { + FORC4 fscanf (ifp, "%f", &rgb_cam[i][c^1]); + if (!use_camera_wb) continue; + num = 0; + FORC4 num += rgb_cam[i][c]; + FORC4 rgb_cam[i][c] /= num; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + break; + case 34310: /* Leaf metadata */ + parse_mos (ftell(ifp)); + case 34303: + strcpy (make, "Leaf"); + break; + case 34665: /* EXIF tag */ + fseek (ifp, get4()+base, SEEK_SET); + parse_exif (base); + break; + case 34853: /* GPSInfo tag */ + fseek (ifp, get4()+base, SEEK_SET); + parse_gps (base); + break; + case 34675: /* InterColorProfile */ + case 50831: /* AsShotICCProfile */ + profile_offset = ftell(ifp); + profile_length = len; + break; + case 37122: /* CompressedBitsPerPixel */ + kodak_cbpp = get4(); + break; + case 37386: /* FocalLength */ + focal_len = getreal(type); + break; + case 37393: /* ImageNumber */ + shot_order = getint(type); + break; + case 37400: /* old Kodak KDC tag */ + for (raw_color = i=0; i < 3; i++) { + getreal(type); + FORC3 rgb_cam[i][c] = getreal(type); + } +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 46275: /* Imacon tags */ + strcpy (make, "Imacon"); + data_offset = ftell(ifp); + ima_len = len; + break; + case 46279: + if (!ima_len) break; + fseek (ifp, 78, SEEK_CUR); + raw_width = get4(); + raw_height = get4(); + left_margin = get4() & 7; + width = raw_width - left_margin - (get4() & 7); + top_margin = get4() & 7; + height = raw_height - top_margin - (get4() & 7); + if (raw_width == 7262) { + height = 5444; + width = 7244; + left_margin = 7; + } + fseek (ifp, 52, SEEK_CUR); + FORC3 cam_mul[c] = getreal(11); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + fseek (ifp, 114, SEEK_CUR); + flip = (get2() >> 7) * 90; + if (width * height * 6 == ima_len) { + if (flip % 180 == 90) SWAP(width,height); + filters = flip = 0; + } + sprintf (model, "Ixpress %d-Mp", height*width/1000000); + load_raw = &CLASS imacon_full_load_raw; + if (filters) { + if (left_margin & 1) filters = 0x61616161; + load_raw = &CLASS unpacked_load_raw; + } + maximum = 0xffff; + break; + case 50454: /* Sinar tag */ + case 50455: + if (!(cbuf = (char *) malloc(len))) break; + fread (cbuf, 1, len, ifp); + for (cp = cbuf-1; cp && cp < cbuf+len; cp = strchr(cp,'\n')) + if (!strncmp (++cp,"Neutral ",8)) + { + sscanf (cp+8, "%f %f %f", cam_mul, cam_mul+1, cam_mul+2); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + free (cbuf); + break; + case 50458: + if (!make[0]) strcpy (make, "Hasselblad"); + break; + case 50459: /* Hasselblad tag */ + i = order; + j = ftell(ifp); + c = tiff_nifds; + order = get2(); + fseek (ifp, j+(get2(),get4()), SEEK_SET); + parse_tiff_ifd (j); + maximum = 0xffff; + tiff_nifds = c; + order = i; + break; + case 50706: /* DNGVersion */ + FORC4 dng_version = (dng_version << 8) + fgetc(ifp); + if (!make[0]) strcpy (make, "DNG"); + is_raw = 1; + break; + case 50710: /* CFAPlaneColor */ + if (len > 4) len = 4; + colors = len; + fread (cfa_pc, 1, colors, ifp); +guess_cfa_pc: + FORCC tab[cfa_pc[c]] = c; + cdesc[c] = 0; + for (i=16; i--; ) + filters = filters << 2 | tab[cfa_pat[i % plen]]; + break; + case 50711: /* CFALayout */ + if (get2() == 2) { + fuji_width = 1; + filters = 0x49494949; + } + break; + case 291: + case 50712: /* LinearizationTable */ + linear_table (len); + break; + case 50714: /* BlackLevel */ + case 50715: /* BlackLevelDeltaH */ + case 50716: /* BlackLevelDeltaV */ + for (dblack=i=0; i < len; i++) + dblack += getreal(type); + black += dblack/len + 0.5; + break; + case 50717: /* WhiteLevel */ + maximum = getint(type); + break; + case 50718: /* DefaultScale */ + pixel_aspect = getreal(type); + pixel_aspect /= getreal(type); + break; + case 50721: /* ColorMatrix1 */ + case 50722: /* ColorMatrix2 */ + FORCC for (j=0; j < 3; j++) + cm[c][j] = getreal(type); + use_cm = 1; + break; + case 50723: /* CameraCalibration1 */ + case 50724: /* CameraCalibration2 */ + for (i=0; i < colors; i++) + FORCC cc[i][c] = getreal(type); + case 50727: /* AnalogBalance */ + FORCC ab[c] = getreal(type); + break; + case 50728: /* AsShotNeutral */ + FORCC asn[c] = getreal(type); + break; + case 50729: /* AsShotWhiteXY */ + xyz[0] = getreal(type); + xyz[1] = getreal(type); + xyz[2] = 1 - xyz[0] - xyz[1]; + FORC3 xyz[c] /= d65_white[c]; + break; + case 50740: /* DNGPrivateData */ + if (dng_version) break; + parse_minolta (j = get4()+base); + fseek (ifp, j, SEEK_SET); + parse_tiff_ifd (base); + break; + case 50752: + read_shorts (cr2_slice, 3); + break; + case 50829: /* ActiveArea */ + top_margin = getint(type); + left_margin = getint(type); + height = getint(type) - top_margin; + width = getint(type) - left_margin; + break; + case 64772: /* Kodak P-series */ + fseek (ifp, 16, SEEK_CUR); + data_offset = get4(); + fseek (ifp, 28, SEEK_CUR); + data_offset += get4(); + load_raw = &CLASS packed_12_load_raw; + } + fseek (ifp, save, SEEK_SET); + } + if (sony_length && (buf = (unsigned *) malloc(sony_length))) { + fseek (ifp, sony_offset, SEEK_SET); + fread (buf, sony_length, 1, ifp); + sony_decrypt (buf, sony_length/4, 1, sony_key); +#ifndef LIBRAW_LIBRARY_BUILD + sfp = ifp; + if ((ifp = tmpfile())) { + fwrite (buf, sony_length, 1, ifp); + fseek (ifp, 0, SEEK_SET); + parse_tiff_ifd (-sony_offset); + fclose (ifp); + } + ifp = sfp; +#else + if( !ifp->tempbuffer_open(buf,sony_length)) + { + parse_tiff_ifd(-sony_offset); + ifp->tempbuffer_close(); + } +#endif + free (buf); + } + for (i=0; i < colors; i++) + FORCC cc[i][c] *= ab[i]; + if (use_cm) { + FORCC for (i=0; i < 3; i++) + for (cam_xyz[c][i]=j=0; j < colors; j++) + cam_xyz[c][i] += cc[c][j] * cm[j][i] * xyz[i]; + cam_xyz_coeff (cam_xyz); + } + if (asn[0]) { + cam_mul[3] = 0; + FORCC cam_mul[c] = 1 / asn[c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (!use_cm) + { + FORCC pre_mul[c] /= cc[c][c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + + return 0; +} + +void CLASS parse_tiff (int base) +{ + int doff, max_samp=0, raw=-1, thm=-1, i; + struct jhead jh; + + fseek (ifp, base, SEEK_SET); + order = get2(); + if (order != 0x4949 && order != 0x4d4d) return; + get2(); + memset (tiff_ifd, 0, sizeof tiff_ifd); + tiff_nifds = 0; + while ((doff = get4())) { + fseek (ifp, doff+base, SEEK_SET); + if (parse_tiff_ifd (base)) break; + } + thumb_misc = 16; + if (thumb_offset) { + fseek (ifp, thumb_offset, SEEK_SET); + if (ljpeg_start (&jh, 1)) { + thumb_misc = jh.bits; + thumb_width = jh.wide; + thumb_height = jh.high; + } + } + for (i=0; i < tiff_nifds; i++) { + if (max_samp < tiff_ifd[i].samples) + max_samp = tiff_ifd[i].samples; + if (max_samp > 3) max_samp = 3; + if ((tiff_ifd[i].comp != 6 || tiff_ifd[i].samples != 3) && + tiff_ifd[i].t_width*tiff_ifd[i].t_height > raw_width*raw_height) { + raw_width = tiff_ifd[i].t_width; + raw_height = tiff_ifd[i].t_height; + tiff_bps = tiff_ifd[i].bps; + tiff_compress = tiff_ifd[i].comp; + data_offset = tiff_ifd[i].offset; + tiff_flip = tiff_ifd[i].t_flip; + tiff_samples = tiff_ifd[i].samples; + raw = i; + } + } + fuji_width *= (raw_width+1)/2; + if (tiff_ifd[0].t_flip) tiff_flip = tiff_ifd[0].t_flip; + if (raw >= 0 && !load_raw) + switch (tiff_compress) { + case 0: case 1: + switch (tiff_bps) { + case 8: load_raw = &CLASS eight_bit_load_raw; break; + case 12: load_raw = &CLASS packed_12_load_raw; + if (tiff_ifd[raw].phint == 2) + load_flags = 6; + if (strncmp(make,"PENTAX",6)) break; + case 14: + case 16: load_raw = &CLASS unpacked_load_raw; break; + } + if (tiff_ifd[raw].bytes*5 == raw_width*raw_height*8) + load_raw = &CLASS olympus_e300_load_raw; + break; + case 6: case 7: case 99: + load_raw = &CLASS lossless_jpeg_load_raw; break; + case 262: + load_raw = &CLASS kodak_262_load_raw; break; + case 32767: + load_raw = &CLASS sony_arw2_load_raw; + if (tiff_ifd[raw].bytes*8 == raw_width*raw_height*tiff_bps) + break; + raw_height += 8; + load_raw = &CLASS sony_arw_load_raw; break; + case 32769: + load_flags = 8; + case 32773: + load_raw = &CLASS packed_12_load_raw; break; + case 34713: + load_raw = &CLASS nikon_compressed_load_raw; break; + case 65535: + load_raw = &CLASS pentax_k10_load_raw; break; + case 65000: + switch (tiff_ifd[raw].phint) { + case 2: load_raw = &CLASS kodak_rgb_load_raw; filters = 0; break; + case 6: load_raw = &CLASS kodak_ycbcr_load_raw; filters = 0; break; + case 32803: load_raw = &CLASS kodak_65000_load_raw; + } + case 32867: break; + default: is_raw = 0; + } + if (!dng_version && tiff_samples == 3) + if (tiff_ifd[raw].bytes && tiff_bps != 14 && tiff_bps != 2048) + is_raw = 0; + if (!dng_version && tiff_bps == 8 && tiff_compress == 1 && + tiff_ifd[raw].phint == 1) is_raw = 0; + if (tiff_bps == 8 && tiff_samples == 4) is_raw = 0; + for (i=0; i < tiff_nifds; i++) + if (i != raw && tiff_ifd[i].samples == max_samp && + tiff_ifd[i].t_width * tiff_ifd[i].t_height / SQR(tiff_ifd[i].bps+1) > + thumb_width * thumb_height / SQR(thumb_misc+1)) { + thumb_width = tiff_ifd[i].t_width; + thumb_height = tiff_ifd[i].t_height; + thumb_offset = tiff_ifd[i].offset; + thumb_length = tiff_ifd[i].bytes; + thumb_misc = tiff_ifd[i].bps; + thm = i; + } + if (thm >= 0) { + thumb_misc |= tiff_ifd[thm].samples << 5; + switch (tiff_ifd[thm].comp) { + case 0: + write_thumb = &CLASS layer_thumb; + break; + case 1: + if (tiff_ifd[thm].bps > 8) + thumb_load_raw = &CLASS kodak_thumb_load_raw; + else + write_thumb = &CLASS ppm_thumb; + break; + case 65000: + thumb_load_raw = tiff_ifd[thm].phint == 6 ? + &CLASS kodak_ycbcr_load_raw : &CLASS kodak_rgb_load_raw; + } + } +} + +void CLASS parse_minolta (int base) +{ + int save, tag, len, offset, high=0, wide=0, i, c; + short sorder=order; + + fseek (ifp, base, SEEK_SET); + if (fgetc(ifp) || fgetc(ifp)-'M' || fgetc(ifp)-'R') return; + order = fgetc(ifp) * 0x101; + offset = base + get4() + 8; + while ((save=ftell(ifp)) < offset) { + for (tag=i=0; i < 4; i++) + tag = tag << 8 | fgetc(ifp); + len = get4(); + switch (tag) { + case 0x505244: /* PRD */ + fseek (ifp, 8, SEEK_CUR); + high = get2(); + wide = get2(); + break; + case 0x574247: /* WBG */ + get4(); + i = strcmp(model,"DiMAGE A200") ? 0:3; + FORC4 cam_mul[c ^ (c >> 1) ^ i] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 0x545457: /* TTW */ + parse_tiff (ftell(ifp)); + data_offset = offset; + } + fseek (ifp, save+len+8, SEEK_SET); + } + raw_height = high; + raw_width = wide; + order = sorder; +} + +/* + Many cameras have a "debug mode" that writes JPEG and raw + at the same time. The raw file has no header, so try to + to open the matching JPEG file and read its metadata. + */ +void CLASS parse_external_jpeg() +{ + char *file, *ext, *jname, *jfile, *jext; +#ifndef LIBRAW_LIBRARY_BUILD + FILE *save=ifp; +#else + if(!ifp->fname()) + { + imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ; + return; + } +#endif + + ext = strrchr (ifname, '.'); + file = strrchr (ifname, '/'); + if (!file) file = strrchr (ifname, '\\'); +#ifndef LIBRAW_LIBRARY_BUILD + if (!file) file = ifname-1; +#else + if (!file) file = (char*)ifname-1; +#endif + file++; + if (!ext || strlen(ext) != 4 || ext-file != 8) return; + jname = (char *) malloc (strlen(ifname) + 1); + merror (jname, "parse_external_jpeg()"); + strcpy (jname, ifname); + jfile = file - ifname + jname; + jext = ext - ifname + jname; + if (strcasecmp (ext, ".jpg")) { + strcpy (jext, isupper(ext[1]) ? ".JPG":".jpg"); + if (isdigit(*file)) { + memcpy (jfile, file+4, 4); + memcpy (jfile+4, file, 4); + } + } else + while (isdigit(*--jext)) { + if (*jext != '9') { + (*jext)++; + break; + } + *jext = '0'; + } +#ifndef LIBRAW_LIBRARY_BUILD + if (strcmp (jname, ifname)) { + if ((ifp = fopen (jname, "rb"))) { +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr,_("Reading metadata from %s ...\n"), jname); +#endif + parse_tiff (12); + thumb_offset = 0; + is_raw = 1; + fclose (ifp); + } + } +#else + if (strcmp (jname, ifname)) + { + if(!ifp->subfile_open(jname)) + { + parse_tiff (12); + thumb_offset = 0; + is_raw = 1; + ifp->subfile_close(); + } + else + imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ; + } +#endif + if (!timestamp) + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA ; +#endif +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("Failed to read metadata from %s\n"), jname); +#endif + } + free (jname); +#ifndef LIBRAW_LIBRARY_BUILD + ifp = save; +#endif +} + +/* + CIFF block 0x1030 contains an 8x8 white sample. + Load this into white[][] for use in scale_colors(). + */ +void CLASS ciff_block_1030() +{ + static const ushort key[] = { 0x410, 0x45f3 }; + int i, bpp, row, col, vbits=0; + unsigned long bitbuf=0; + + if ((get2(),get4()) != 0x80008 || !get4()) return; + bpp = get2(); + if (bpp != 10 && bpp != 12) return; + for (i=row=0; row < 8; row++) + for (col=0; col < 8; col++) { + if (vbits < bpp) { + bitbuf = bitbuf << 16 | (get2() ^ key[i++ & 1]); + vbits += 16; + } + white[row][col] = + bitbuf << (LONG_BIT - vbits) >> (LONG_BIT - bpp); + vbits -= bpp; + } +} + +/* + Parse a CIFF file, better known as Canon CRW format. + */ +void CLASS parse_ciff (int offset, int length) +{ + int tboff, nrecs, c, type, len, save, wbi=-1; + ushort key[] = { 0x410, 0x45f3 }; + + fseek (ifp, offset+length-4, SEEK_SET); + tboff = get4() + offset; + fseek (ifp, tboff, SEEK_SET); + nrecs = get2(); + if (nrecs > 100) return; + while (nrecs--) { + type = get2(); + len = get4(); + save = ftell(ifp) + 4; + fseek (ifp, offset+get4(), SEEK_SET); + if ((((type >> 8) + 8) | 8) == 0x38) + parse_ciff (ftell(ifp), len); /* Parse a sub-table */ + + if (type == 0x0810) + fread (artist, 64, 1, ifp); + if (type == 0x080a) { + fread (make, 64, 1, ifp); + fseek (ifp, strlen(make) - 63, SEEK_CUR); + fread (model, 64, 1, ifp); + } + if (type == 0x1810) { + fseek (ifp, 12, SEEK_CUR); + flip = get4(); + } + if (type == 0x1835) /* Get the decoder table */ + tiff_compress = get4(); + if (type == 0x2007) { + thumb_offset = ftell(ifp); + thumb_length = len; + } + if (type == 0x1818) { + shutter = pow (2.0f, -int_to_float((get4(),get4()))); + aperture = pow (2.0f, int_to_float(get4())/2); + } + if (type == 0x102a) { + iso_speed = pow (2, (get4(),get2())/32.0 - 4) * 50; + aperture = pow (2, (get2(),(short)get2())/64.0); + shutter = pow (2,-((short)get2())/32.0); + wbi = (get2(),get2()); + if (wbi > 17) wbi = 0; + fseek (ifp, 32, SEEK_CUR); + if (shutter > 1e6) shutter = get2()/10.0; + } + if (type == 0x102c) { + if (get2() > 512) { /* Pro90, G1 */ + fseek (ifp, 118, SEEK_CUR); + FORC4 cam_mul[c ^ 2] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } else { /* G2, S30, S40 */ + fseek (ifp, 98, SEEK_CUR); + FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + } + if (type == 0x0032) { + if (len == 768) { /* EOS D30 */ + fseek (ifp, 72, SEEK_CUR); + FORC4 cam_mul[c ^ (c >> 1)] = 1024.0 / get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + if (!wbi) cam_mul[0] = -1; /* use my auto white balance */ + } else if (!cam_mul[0]) { + if (get2() == key[0]) /* Pro1, G6, S60, S70 */ + c = (strstr(model,"Pro1") ? + "012346000000000000":"01345:000000006008")[wbi]-'0'+ 2; + else { /* G3, G5, S45, S50 */ + c = "023457000000006000"[wbi]-'0'; + key[0] = key[1] = 0; + } + fseek (ifp, 78 + c*8, SEEK_CUR); + FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2() ^ key[c & 1]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + if (!wbi) cam_mul[0] = -1; + } + } + if (type == 0x10a9) { /* D60, 10D, 300D, and clones */ + if (len > 66) wbi = "0134567028"[wbi]-'0'; + fseek (ifp, 2 + wbi*8, SEEK_CUR); + FORC4 cam_mul[c ^ (c >> 1)] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + if (type == 0x1030 && (0x18040 >> wbi & 1)) + ciff_block_1030(); /* all that don't have 0x10a9 */ + if (type == 0x1031) { + raw_width = (get2(),get2()); + raw_height = get2(); + } + if (type == 0x5029) { + focal_len = len >> 16; + if ((len & 0xffff) == 2) focal_len /= 32; + } + if (type == 0x5813) flash_used = int_to_float(len); + if (type == 0x5814) canon_ev = int_to_float(len); + if (type == 0x5817) shot_order = len; + if (type == 0x5834) unique_id = len; + if (type == 0x580e) timestamp = len; + if (type == 0x180e) timestamp = get4(); +#ifdef LOCALTIME + if ((type | 0x4000) == 0x580e) + timestamp = mktime (gmtime (×tamp)); +#endif + fseek (ifp, save, SEEK_SET); + } +} + +void CLASS parse_rollei() +{ + char line[128], *val; + struct tm t; + + fseek (ifp, 0, SEEK_SET); + memset (&t, 0, sizeof t); + do { + fgets (line, 128, ifp); + if ((val = strchr(line,'='))) + *val++ = 0; + else + val = line + strlen(line); + if (!strcmp(line,"DAT")) + sscanf (val, "%d.%d.%d", &t.tm_mday, &t.tm_mon, &t.tm_year); + if (!strcmp(line,"TIM")) + sscanf (val, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec); + if (!strcmp(line,"HDR")) + thumb_offset = atoi(val); + if (!strcmp(line,"X ")) + raw_width = atoi(val); + if (!strcmp(line,"Y ")) + raw_height = atoi(val); + if (!strcmp(line,"TX ")) + thumb_width = atoi(val); + if (!strcmp(line,"TY ")) + thumb_height = atoi(val); + } while (strncmp(line,"EOHD",4)); + data_offset = thumb_offset + thumb_width * thumb_height * 2; + t.tm_year -= 1900; + t.tm_mon -= 1; + if (mktime(&t) > 0) + timestamp = mktime(&t); + strcpy (make, "Rollei"); + strcpy (model,"d530flex"); + write_thumb = &CLASS rollei_thumb; +} + +void CLASS parse_sinar_ia() +{ + int entries, off; + char str[8], *cp; + + order = 0x4949; + fseek (ifp, 4, SEEK_SET); + entries = get4(); + fseek (ifp, get4(), SEEK_SET); + while (entries--) { + off = get4(); get4(); + fread (str, 8, 1, ifp); + if (!strcmp(str,"META")) meta_offset = off; + if (!strcmp(str,"THUMB")) thumb_offset = off; + if (!strcmp(str,"RAW0")) data_offset = off; + } + fseek (ifp, meta_offset+20, SEEK_SET); + fread (make, 64, 1, ifp); + make[63] = 0; + if ((cp = strchr(make,' '))) { + strcpy (model, cp+1); + *cp = 0; + } + raw_width = get2(); + raw_height = get2(); + load_raw = &CLASS unpacked_load_raw; + thumb_width = (get4(),get2()); + thumb_height = get2(); + write_thumb = &CLASS ppm_thumb; + maximum = 0x3fff; +} + +void CLASS parse_phase_one (int base) +{ + unsigned entries, tag, type, len, data, save, i, c; + float romm_cam[3][3]; + char *cp; + + memset (&ph1, 0, sizeof ph1); + fseek (ifp, base, SEEK_SET); + order = get4() & 0xffff; + if (get4() >> 8 != 0x526177) return; /* "Raw" */ + fseek (ifp, get4()+base, SEEK_SET); + entries = get4(); + get4(); + while (entries--) { + tag = get4(); + type = get4(); + len = get4(); + data = get4(); + save = ftell(ifp); + fseek (ifp, base+data, SEEK_SET); + switch (tag) { + case 0x100: flip = "0653"[data & 3]-'0'; break; + case 0x106: + for (i=0; i < 9; i++) + romm_cam[0][i] = getreal(11); + romm_coeff (romm_cam); + break; + case 0x107: + FORC3 cam_mul[c] = getreal(11); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + break; + case 0x108: raw_width = data; break; + case 0x109: raw_height = data; break; + case 0x10a: left_margin = data; break; + case 0x10b: top_margin = data; break; + case 0x10c: width = data; break; + case 0x10d: height = data; break; + case 0x10e: ph1.format = data; break; + case 0x10f: data_offset = data+base; break; + case 0x110: meta_offset = data+base; + meta_length = len; break; + case 0x112: ph1.key_off = save - 4; break; + case 0x210: ph1.tag_210 = int_to_float(data); break; + case 0x21a: ph1.tag_21a = data; break; + case 0x21c: strip_offset = data+base; break; + case 0x21d: ph1.t_black = data; break; + case 0x222: ph1.split_col = data - left_margin; break; + case 0x223: ph1.black_off = data+base; break; + case 0x301: + model[63] = 0; + fread (model, 1, 63, ifp); + if ((cp = strstr(model," camera"))) *cp = 0; + } + fseek (ifp, save, SEEK_SET); + } + load_raw = ph1.format < 3 ? + &CLASS phase_one_load_raw : &CLASS phase_one_load_raw_c; + maximum = 0xffff; + strcpy (make, "Phase One"); + if (model[0]) return; + switch (raw_height) { + case 2060: strcpy (model,"LightPhase"); break; + case 2682: strcpy (model,"H 10"); break; + case 4128: strcpy (model,"H 20"); break; + case 5488: strcpy (model,"H 25"); break; + } +} + +void CLASS parse_fuji (int offset) +{ + unsigned entries, tag, len, save, c; + + fseek (ifp, offset, SEEK_SET); + entries = get4(); + if (entries > 255) return; + while (entries--) { + tag = get2(); + len = get2(); + save = ftell(ifp); + if (tag == 0x100) { + raw_height = get2(); + raw_width = get2(); + } else if (tag == 0x121) { + height = get2(); + if ((width = get2()) == 4284) width += 3; + } else if (tag == 0x130) + fuji_layout = fgetc(ifp) >> 7; + if (tag == 0x2ff0) + { + FORC4 cam_mul[c ^ 1] = get2(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } + fseek (ifp, save+len, SEEK_SET); + } + height <<= fuji_layout; + width >>= fuji_layout; +} + +int CLASS parse_jpeg (int offset) +{ + int len, save, hlen, mark; + + fseek (ifp, offset, SEEK_SET); + if (fgetc(ifp) != 0xff || fgetc(ifp) != 0xd8) return 0; + + while (fgetc(ifp) == 0xff && (mark = fgetc(ifp)) != 0xda) { + order = 0x4d4d; + len = get2() - 2; + save = ftell(ifp); + if (mark == 0xc0 || mark == 0xc3) { + fgetc(ifp); + raw_height = get2(); + raw_width = get2(); + } + order = get2(); + hlen = get4(); + if (get4() == 0x48454150) /* "HEAP" */ + parse_ciff (save+hlen, len-hlen); + parse_tiff (save+6); + fseek (ifp, save+len, SEEK_SET); + } + return 1; +} + +void CLASS parse_riff() +{ + unsigned i, size, end; + char tag[4], date[64], month[64]; + static const char mon[12][4] = + { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; + struct tm t; + + order = 0x4949; + fread (tag, 4, 1, ifp); + size = get4(); + end = ftell(ifp) + size; + if (!memcmp(tag,"RIFF",4) || !memcmp(tag,"LIST",4)) { + get4(); + while (ftell(ifp) < end) + parse_riff(); + } else if (!memcmp(tag,"nctg",4)) { + while (ftell(ifp) < end) { + i = get2(); + size = get2(); + if ((i+1) >> 1 == 10 && size == 20) + get_timestamp(0); + else fseek (ifp, size, SEEK_CUR); + } + } else if (!memcmp(tag,"IDIT",4) && size < 64) { + fread (date, 64, 1, ifp); + date[size] = 0; + memset (&t, 0, sizeof t); + if (sscanf (date, "%*s %s %d %d:%d:%d %d", month, &t.tm_mday, + &t.tm_hour, &t.tm_min, &t.tm_sec, &t.tm_year) == 6) { + for (i=0; i < 12 && strcasecmp(mon[i],month); i++); + t.tm_mon = i; + t.tm_year -= 1900; + if (mktime(&t) > 0) + timestamp = mktime(&t); + } + } else + fseek (ifp, size, SEEK_CUR); +} + +void CLASS parse_smal (int offset, int fsize) +{ + int ver; + + fseek (ifp, offset+2, SEEK_SET); + order = 0x4949; + ver = fgetc(ifp); + if (ver == 6) + fseek (ifp, 5, SEEK_CUR); + if (get4() != fsize) return; + if (ver > 6) data_offset = get4(); + raw_height = height = get2(); + raw_width = width = get2(); + strcpy (make, "SMaL"); + sprintf (model, "v%d %dx%d", ver, width, height); + if (ver == 6) load_raw = &CLASS smal_v6_load_raw; + if (ver == 9) load_raw = &CLASS smal_v9_load_raw; +} + +void CLASS parse_cine() +{ + unsigned off_head, off_setup, off_image, i; + + order = 0x4949; + fseek (ifp, 4, SEEK_SET); + is_raw = get2() == 2; + fseek (ifp, 14, SEEK_CUR); + is_raw *= get4(); + off_head = get4(); + off_setup = get4(); + off_image = get4(); + timestamp = get4(); + if ((i = get4())) timestamp = i; + fseek (ifp, off_head+4, SEEK_SET); + raw_width = get4(); + raw_height = get4(); + switch (get2(),get2()) { + case 8: load_raw = &CLASS eight_bit_load_raw; break; + case 16: load_raw = &CLASS unpacked_load_raw; + } + fseek (ifp, off_setup+792, SEEK_SET); + strcpy (make, "CINE"); + sprintf (model, "%d", get4()); + fseek (ifp, 12, SEEK_CUR); + switch ((i=get4()) & 0xffffff) { + case 3: filters = 0x94949494; break; + case 4: filters = 0x49494949; break; + default: is_raw = 0; + } + fseek (ifp, 72, SEEK_CUR); + switch ((get4()+3600) % 360) { + case 270: flip = 4; break; + case 180: flip = 1; break; + case 90: flip = 7; break; + case 0: flip = 2; + } + cam_mul[0] = getreal(11); + cam_mul[2] = getreal(11); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + maximum = ~(-1 << get4()); + fseek (ifp, 668, SEEK_CUR); + shutter = get4()/1000000000.0; + fseek (ifp, off_image, SEEK_SET); + if (shot_select < is_raw) + fseek (ifp, shot_select*8, SEEK_CUR); + data_offset = (INT64) get4() + 8; + data_offset += (INT64) get4() << 32; +} +#line 7187 "dcraw/dcraw.c" +#ifdef LIBRAW_LIBRARY_BUILD +void CLASS adobe_coeff (const char *p_make, const char *p_model) +#else +void CLASS adobe_coeff (char *p_make, char *p_model) +#endif +{ + static const struct { + const char *prefix; + short t_black, t_maximum, trans[12]; + } table[] = { + { "Apple QuickTake", 0, 0, /* DJC */ + { 17576,-3191,-3318,5210,6733,-1942,9031,1280,-124 } }, + { "Canon EOS D2000", 0, 0, + { 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } }, + { "Canon EOS D6000", 0, 0, + { 20482,-7172,-3125,-1033,10410,-285,2542,226,3136 } }, + { "Canon EOS D30", 0, 0, + { 9805,-2689,-1312,-5803,13064,3068,-2438,3075,8775 } }, + { "Canon EOS D60", 0, 0xfa0, + { 6188,-1341,-890,-7168,14489,2937,-2640,3228,8483 } }, + { "Canon EOS 5D Mark II", 0, 0x3cf0, + { 4716,603,-830,-7798,15474,2480,-1496,1937,6651 } }, + { "Canon EOS 5D", 0, 0xe6c, + { 6347,-479,-972,-8297,15954,2480,-1968,2131,7649 } }, + { "Canon EOS 10D", 0, 0xfa0, + { 8197,-2000,-1118,-6714,14335,2592,-2536,3178,8266 } }, + { "Canon EOS 20Da", 0, 0, + { 14155,-5065,-1382,-6550,14633,2039,-1623,1824,6561 } }, + { "Canon EOS 20D", 0, 0xfff, + { 6599,-537,-891,-8071,15783,2424,-1983,2234,7462 } }, + { "Canon EOS 30D", 0, 0, + { 6257,-303,-1000,-7880,15621,2396,-1714,1904,7046 } }, + { "Canon EOS 40D", 0, 0x3f60, + { 6071,-747,-856,-7653,15365,2441,-2025,2553,7315 } }, + { "Canon EOS 50D", 0, 0x3d93, + { 4920,616,-593,-6493,13964,2784,-1774,3178,7005 } }, + { "Canon EOS 300D", 0, 0xfa0, + { 8197,-2000,-1118,-6714,14335,2592,-2536,3178,8266 } }, + { "Canon EOS 350D", 0, 0xfff, + { 6018,-617,-965,-8645,15881,2975,-1530,1719,7642 } }, + { "Canon EOS 400D", 0, 0xe8e, + { 7054,-1501,-990,-8156,15544,2812,-1278,1414,7796 } }, + { "Canon EOS 450D", 0, 0x390d, + { 5784,-262,-821,-7539,15064,2672,-1982,2681,7427 } }, + { "Canon EOS 1000D", 0, 0xe43, + { 6771,-1139,-977,-7818,15123,2928,-1244,1437,7533 } }, + { "Canon EOS-1Ds Mark III", 0, 0x3bb0, + { 5859,-211,-930,-8255,16017,2353,-1732,1887,7448 } }, + { "Canon EOS-1Ds Mark II", 0, 0xe80, + { 6517,-602,-867,-8180,15926,2378,-1618,1771,7633 } }, + { "Canon EOS-1D Mark II N", 0, 0xe80, + { 6240,-466,-822,-8180,15825,2500,-1801,1938,8042 } }, + { "Canon EOS-1D Mark III", 0, 0x3bb0, + { 6291,-540,-976,-8350,16145,2311,-1714,1858,7326 } }, + { "Canon EOS-1D Mark II", 0, 0xe80, + { 6264,-582,-724,-8312,15948,2504,-1744,1919,8664 } }, + { "Canon EOS-1DS", 0, 0xe20, + { 4374,3631,-1743,-7520,15212,2472,-2892,3632,8161 } }, + { "Canon EOS-1D", 0, 0xe20, + { 6806,-179,-1020,-8097,16415,1687,-3267,4236,7690 } }, + { "Canon EOS", 0, 0, + { 8197,-2000,-1118,-6714,14335,2592,-2536,3178,8266 } }, + { "Canon PowerShot A50", 0, 0, + { -5300,9846,1776,3436,684,3939,-5540,9879,6200,-1404,11175,217 } }, + { "Canon PowerShot A5", 0, 0, + { -4801,9475,1952,2926,1611,4094,-5259,10164,5947,-1554,10883,547 } }, + { "Canon PowerShot G10", 0, 0, + { 11093,-3906,-1028,-5047,12492,2879,-1003,1750,5561 } }, + { "Canon PowerShot G1", 0, 0, + { -4778,9467,2172,4743,-1141,4344,-5146,9908,6077,-1566,11051,557 } }, + { "Canon PowerShot G2", 0, 0, + { 9087,-2693,-1049,-6715,14382,2537,-2291,2819,7790 } }, + { "Canon PowerShot G3", 0, 0, + { 9212,-2781,-1073,-6573,14189,2605,-2300,2844,7664 } }, + { "Canon PowerShot G5", 0, 0, + { 9757,-2872,-933,-5972,13861,2301,-1622,2328,7212 } }, + { "Canon PowerShot G6", 0, 0, + { 9877,-3775,-871,-7613,14807,3072,-1448,1305,7485 } }, + { "Canon PowerShot G9", 0, 0, + { 7368,-2141,-598,-5621,13254,2625,-1418,1696,5743 } }, + { "Canon PowerShot Pro1", 0, 0, + { 10062,-3522,-999,-7643,15117,2730,-765,817,7323 } }, + { "Canon PowerShot Pro70", 34, 0, + { -4155,9818,1529,3939,-25,4522,-5521,9870,6610,-2238,10873,1342 } }, + { "Canon PowerShot Pro90", 0, 0, + { -4963,9896,2235,4642,-987,4294,-5162,10011,5859,-1770,11230,577 } }, + { "Canon PowerShot S30", 0, 0, + { 10566,-3652,-1129,-6552,14662,2006,-2197,2581,7670 } }, + { "Canon PowerShot S40", 0, 0, + { 8510,-2487,-940,-6869,14231,2900,-2318,2829,9013 } }, + { "Canon PowerShot S45", 0, 0, + { 8163,-2333,-955,-6682,14174,2751,-2077,2597,8041 } }, + { "Canon PowerShot S50", 0, 0, + { 8882,-2571,-863,-6348,14234,2288,-1516,2172,6569 } }, + { "Canon PowerShot S60", 0, 0, + { 8795,-2482,-797,-7804,15403,2573,-1422,1996,7082 } }, + { "Canon PowerShot S70", 0, 0, + { 9976,-3810,-832,-7115,14463,2906,-901,989,7889 } }, + { "Canon PowerShot A610", 0, 0, /* DJC */ + { 15591,-6402,-1592,-5365,13198,2168,-1300,1824,5075 } }, + { "Canon PowerShot A620", 0, 0, /* DJC */ + { 15265,-6193,-1558,-4125,12116,2010,-888,1639,5220 } }, + { "Canon PowerShot A630", 0, 0, /* DJC */ + { 14201,-5308,-1757,-6087,14472,1617,-2191,3105,5348 } }, + { "Canon PowerShot A640", 0, 0, /* DJC */ + { 13124,-5329,-1390,-3602,11658,1944,-1612,2863,4885 } }, + { "Canon PowerShot A650", 0, 0, /* DJC */ + { 9427,-3036,-959,-2581,10671,1911,-1039,1982,4430 } }, + { "Canon PowerShot A720", 0, 0, /* DJC */ + { 14573,-5482,-1546,-1266,9799,1468,-1040,1912,3810 } }, + { "Canon PowerShot S3 IS", 0, 0, /* DJC */ + { 14062,-5199,-1446,-4712,12470,2243,-1286,2028,4836 } }, + { "CINE 650", 0, 0, + { 3390,480,-500,-800,3610,340,-550,2336,1192 } }, + { "CINE 660", 0, 0, + { 3390,480,-500,-800,3610,340,-550,2336,1192 } }, + { "CINE", 0, 0, + { 20183,-4295,-423,-3940,15330,3985,-280,4870,9800 } }, + { "Contax N Digital", 0, 0xf1e, + { 7777,1285,-1053,-9280,16543,2916,-3677,5679,7060 } }, + { "EPSON R-D1", 0, 0, + { 6827,-1878,-732,-8429,16012,2564,-704,592,7145 } }, + { "FUJIFILM FinePix E550", 0, 0, + { 11044,-3888,-1120,-7248,15168,2208,-1531,2277,8069 } }, + { "FUJIFILM FinePix E900", 0, 0, + { 9183,-2526,-1078,-7461,15071,2574,-2022,2440,8639 } }, + { "FUJIFILM FinePix F8", 0, 0, + { 11044,-3888,-1120,-7248,15168,2208,-1531,2277,8069 } }, + { "FUJIFILM FinePix F7", 0, 0, + { 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } }, + { "FUJIFILM FinePix S100FS", 514, 0, + { 11521,-4355,-1065,-6524,13767,3058,-1466,1984,6045 } }, + { "FUJIFILM FinePix S20Pro", 0, 0, + { 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } }, + { "FUJIFILM FinePix S2Pro", 128, 0, + { 12492,-4690,-1402,-7033,15423,1647,-1507,2111,7697 } }, + { "FUJIFILM FinePix S3Pro", 0, 0, + { 11807,-4612,-1294,-8927,16968,1988,-2120,2741,8006 } }, + { "FUJIFILM FinePix S5Pro", 0, 0, + { 12300,-5110,-1304,-9117,17143,1998,-1947,2448,8100 } }, + { "FUJIFILM FinePix S5000", 0, 0, + { 8754,-2732,-1019,-7204,15069,2276,-1702,2334,6982 } }, + { "FUJIFILM FinePix S5100", 0, 0x3e00, + { 11940,-4431,-1255,-6766,14428,2542,-993,1165,7421 } }, + { "FUJIFILM FinePix S5500", 0, 0x3e00, + { 11940,-4431,-1255,-6766,14428,2542,-993,1165,7421 } }, + { "FUJIFILM FinePix S5200", 0, 0, + { 9636,-2804,-988,-7442,15040,2589,-1803,2311,8621 } }, + { "FUJIFILM FinePix S5600", 0, 0, + { 9636,-2804,-988,-7442,15040,2589,-1803,2311,8621 } }, + { "FUJIFILM FinePix S6", 0, 0, + { 12628,-4887,-1401,-6861,14996,1962,-2198,2782,7091 } }, + { "FUJIFILM FinePix S7000", 0, 0, + { 10190,-3506,-1312,-7153,15051,2238,-2003,2399,7505 } }, + { "FUJIFILM FinePix S9000", 0, 0, + { 10491,-3423,-1145,-7385,15027,2538,-1809,2275,8692 } }, + { "FUJIFILM FinePix S9500", 0, 0, + { 10491,-3423,-1145,-7385,15027,2538,-1809,2275,8692 } }, + { "FUJIFILM FinePix S9100", 0, 0, + { 12343,-4515,-1285,-7165,14899,2435,-1895,2496,8800 } }, + { "FUJIFILM FinePix S9600", 0, 0, + { 12343,-4515,-1285,-7165,14899,2435,-1895,2496,8800 } }, + { "FUJIFILM IS-1", 0, 0, + { 21461,-10807,-1441,-2332,10599,1999,289,875,7703 } }, + { "FUJIFILM IS Pro", 0, 0, + { 12300,-5110,-1304,-9117,17143,1998,-1947,2448,8100 } }, + { "Imacon Ixpress", 0, 0, /* DJC */ + { 7025,-1415,-704,-5188,13765,1424,-1248,2742,6038 } }, + { "KODAK NC2000", 0, 0, + { 13891,-6055,-803,-465,9919,642,2121,82,1291 } }, + { "Kodak DCS315C", 8, 0, + { 17523,-4827,-2510,756,8546,-137,6113,1649,2250 } }, + { "Kodak DCS330C", 8, 0, + { 20620,-7572,-2801,-103,10073,-396,3551,-233,2220 } }, + { "KODAK DCS420", 0, 0, + { 10868,-1852,-644,-1537,11083,484,2343,628,2216 } }, + { "KODAK DCS460", 0, 0, + { 10592,-2206,-967,-1944,11685,230,2206,670,1273 } }, + { "KODAK EOSDCS1", 0, 0, + { 10592,-2206,-967,-1944,11685,230,2206,670,1273 } }, + { "KODAK EOSDCS3B", 0, 0, + { 9898,-2700,-940,-2478,12219,206,1985,634,1031 } }, + { "Kodak DCS520C", 180, 0, + { 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } }, + { "Kodak DCS560C", 188, 0, + { 20482,-7172,-3125,-1033,10410,-285,2542,226,3136 } }, + { "Kodak DCS620C", 180, 0, + { 23617,-10175,-3149,-2054,11749,-272,2586,-489,3453 } }, + { "Kodak DCS620X", 185, 0, + { 13095,-6231,154,12221,-21,-2137,895,4602,2258 } }, + { "Kodak DCS660C", 214, 0, + { 18244,-6351,-2739,-791,11193,-521,3711,-129,2802 } }, + { "Kodak DCS720X", 0, 0, + { 11775,-5884,950,9556,1846,-1286,-1019,6221,2728 } }, + { "Kodak DCS760C", 0, 0, + { 16623,-6309,-1411,-4344,13923,323,2285,274,2926 } }, + { "Kodak DCS Pro SLR", 0, 0, + { 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } }, + { "Kodak DCS Pro 14nx", 0, 0, + { 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } }, + { "Kodak DCS Pro 14", 0, 0, + { 7791,3128,-776,-8588,16458,2039,-2455,4006,6198 } }, + { "Kodak ProBack645", 0, 0, + { 16414,-6060,-1470,-3555,13037,473,2545,122,4948 } }, + { "Kodak ProBack", 0, 0, + { 21179,-8316,-2918,-915,11019,-165,3477,-180,4210 } }, + { "KODAK P712", 0, 0, + { 9658,-3314,-823,-5163,12695,2768,-1342,1843,6044 } }, + { "KODAK P850", 0, 0xf7c, + { 10511,-3836,-1102,-6946,14587,2558,-1481,1792,6246 } }, + { "KODAK P880", 0, 0xfff, + { 12805,-4662,-1376,-7480,15267,2360,-1626,2194,7904 } }, + { "Leaf CMost", 0, 0, + { 3952,2189,449,-6701,14585,2275,-4536,7349,6536 } }, + { "Leaf Valeo 6", 0, 0, + { 3952,2189,449,-6701,14585,2275,-4536,7349,6536 } }, + { "Leaf Aptus 54S", 0, 0, + { 8236,1746,-1314,-8251,15953,2428,-3673,5786,5771 } }, + { "Leaf Aptus 65", 0, 0, + { 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } }, + { "Leaf Aptus 75", 0, 0, + { 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } }, + { "Leaf", 0, 0, + { 8236,1746,-1314,-8251,15953,2428,-3673,5786,5771 } }, + { "Mamiya ZD", 0, 0, + { 7645,2579,-1363,-8689,16717,2015,-3712,5941,5961 } }, + { "Micron 2010", 110, 0, /* DJC */ + { 16695,-3761,-2151,155,9682,163,3433,951,4904 } }, + { "Minolta DiMAGE 5", 0, 0xf7d, + { 8983,-2942,-963,-6556,14476,2237,-2426,2887,8014 } }, + { "Minolta DiMAGE 7Hi", 0, 0xf7d, + { 11368,-3894,-1242,-6521,14358,2339,-2475,3056,7285 } }, + { "Minolta DiMAGE 7", 0, 0xf7d, + { 9144,-2777,-998,-6676,14556,2281,-2470,3019,7744 } }, + { "Minolta DiMAGE A1", 0, 0xf8b, + { 9274,-2547,-1167,-8220,16323,1943,-2273,2720,8340 } }, + { "MINOLTA DiMAGE A200", 0, 0, + { 8560,-2487,-986,-8112,15535,2771,-1209,1324,7743 } }, + { "Minolta DiMAGE A2", 0, 0xf8f, + { 9097,-2726,-1053,-8073,15506,2762,-966,981,7763 } }, + { "Minolta DiMAGE Z2", 0, 0, /* DJC */ + { 11280,-3564,-1370,-4655,12374,2282,-1423,2168,5396 } }, + { "MINOLTA DYNAX 5", 0, 0xffb, + { 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } }, + { "MINOLTA DYNAX 7", 0, 0xffb, + { 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } }, + { "NIKON D100", 0, 0, + { 5902,-933,-782,-8983,16719,2354,-1402,1455,6464 } }, + { "NIKON D1H", 0, 0, + { 7577,-2166,-926,-7454,15592,1934,-2377,2808,8606 } }, + { "NIKON D1X", 0, 0, + { 7702,-2245,-975,-9114,17242,1875,-2679,3055,8521 } }, + { "NIKON D1", 0, 0, /* multiplied by 2.218750, 1.0, 1.148438 */ + { 16772,-4726,-2141,-7611,15713,1972,-2846,3494,9521 } }, + { "NIKON D2H", 0, 0, + { 5710,-901,-615,-8594,16617,2024,-2975,4120,6830 } }, + { "NIKON D2X", 0, 0, + { 10231,-2769,-1255,-8301,15900,2552,-797,680,7148 } }, + { "NIKON D40X", 0, 0, + { 8819,-2543,-911,-9025,16928,2151,-1329,1213,8449 } }, + { "NIKON D40", 0, 0, + { 6992,-1668,-806,-8138,15748,2543,-874,850,7897 } }, + { "NIKON D50", 0, 0, + { 7732,-2422,-789,-8238,15884,2498,-859,783,7330 } }, + { "NIKON D60", 0, 0, + { 8736,-2458,-935,-9075,16894,2251,-1354,1242,8263 } }, + { "NIKON D700", 0, 0, + { 8139,-2171,-663,-8747,16541,2295,-1925,2008,8093 } }, + { "NIKON D70", 0, 0, + { 7732,-2422,-789,-8238,15884,2498,-859,783,7330 } }, + { "NIKON D80", 0, 0, + { 8629,-2410,-883,-9055,16940,2171,-1490,1363,8520 } }, + { "NIKON D90", 0, 0xf00, + { 7309,-1403,-519,-8474,16008,2622,-2434,2826,8064 } }, + { "NIKON D200", 0, 0xfbc, + { 8367,-2248,-763,-8758,16447,2422,-1527,1550,8053 } }, + { "NIKON D300", 0, 0, + { 9030,-1992,-715,-8465,16302,2255,-2689,3217,8069 } }, + { "NIKON D3", 0, 0, + { 8139,-2171,-663,-8747,16541,2295,-1925,2008,8093 } }, + { "NIKON E950", 0, 0x3dd, /* DJC */ + { -3746,10611,1665,9621,-1734,2114,-2389,7082,3064,3406,6116,-244 } }, + { "NIKON E995", 0, 0, /* copied from E5000 */ + { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } }, + { "NIKON E2100", 0, 0, /* copied from Z2, new white balance */ + { 13142,-4152,-1596,-4655,12374,2282,-1769,2696,6711} }, + { "NIKON E2500", 0, 0, + { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } }, + { "NIKON E4300", 0, 0, /* copied from Minolta DiMAGE Z2 */ + { 11280,-3564,-1370,-4655,12374,2282,-1423,2168,5396 } }, + { "NIKON E4500", 0, 0, + { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } }, + { "NIKON E5000", 0, 0, + { -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } }, + { "NIKON E5400", 0, 0, + { 9349,-2987,-1001,-7919,15766,2266,-2098,2680,6839 } }, + { "NIKON E5700", 0, 0, + { -5368,11478,2368,5537,-113,3148,-4969,10021,5782,778,9028,211 } }, + { "NIKON E8400", 0, 0, + { 7842,-2320,-992,-8154,15718,2599,-1098,1342,7560 } }, + { "NIKON E8700", 0, 0, + { 8489,-2583,-1036,-8051,15583,2643,-1307,1407,7354 } }, + { "NIKON E8800", 0, 0, + { 7971,-2314,-913,-8451,15762,2894,-1442,1520,7610 } }, + { "NIKON COOLPIX P6000", 0, 0, + { 9698,-3367,-914,-4706,12584,2368,-837,968,5801 } }, + { "OLYMPUS C5050", 0, 0, + { 10508,-3124,-1273,-6079,14294,1901,-1653,2306,6237 } }, + { "OLYMPUS C5060", 0, 0, + { 10445,-3362,-1307,-7662,15690,2058,-1135,1176,7602 } }, + { "OLYMPUS C7070", 0, 0, + { 10252,-3531,-1095,-7114,14850,2436,-1451,1723,6365 } }, + { "OLYMPUS C70", 0, 0, + { 10793,-3791,-1146,-7498,15177,2488,-1390,1577,7321 } }, + { "OLYMPUS C80", 0, 0, + { 8606,-2509,-1014,-8238,15714,2703,-942,979,7760 } }, + { "OLYMPUS E-10", 0, 0xffc0, + { 12745,-4500,-1416,-6062,14542,1580,-1934,2256,6603 } }, + { "OLYMPUS E-1", 0, 0xfff0, + { 11846,-4767,-945,-7027,15878,1089,-2699,4122,8311 } }, + { "OLYMPUS E-20", 0, 0xffc0, + { 13173,-4732,-1499,-5807,14036,1895,-2045,2452,7142 } }, + { "OLYMPUS E-300", 0, 0, + { 7828,-1761,-348,-5788,14071,1830,-2853,4518,6557 } }, + { "OLYMPUS E-330", 0, 0, + { 8961,-2473,-1084,-7979,15990,2067,-2319,3035,8249 } }, + { "OLYMPUS E-3", 0, 0xf99, + { 9487,-2875,-1115,-7533,15606,2010,-1618,2100,7389 } }, + { "OLYMPUS E-400", 0, 0xfff0, + { 6169,-1483,-21,-7107,14761,2536,-2904,3580,8568 } }, + { "OLYMPUS E-410", 0, 0xf6a, + { 8856,-2582,-1026,-7761,15766,2082,-2009,2575,7469 } }, + { "OLYMPUS E-420", 0, 0xfd7, + { 8746,-2425,-1095,-7594,15612,2073,-1780,2309,7416 } }, + { "OLYMPUS E-500", 0, 0, + { 8136,-1968,-299,-5481,13742,1871,-2556,4205,6630 } }, + { "OLYMPUS E-510", 0, 0xf6a, + { 8785,-2529,-1033,-7639,15624,2112,-1783,2300,7817 } }, + { "OLYMPUS E-520", 0, 0xfd2, + { 8344,-2322,-1020,-7596,15635,2048,-1748,2269,7287 } }, + { "OLYMPUS SP350", 0, 0, + { 12078,-4836,-1069,-6671,14306,2578,-786,939,7418 } }, + { "OLYMPUS SP3", 0, 0, + { 11766,-4445,-1067,-6901,14421,2707,-1029,1217,7572 } }, + { "OLYMPUS SP500UZ", 0, 0xfff, + { 9493,-3415,-666,-5211,12334,3260,-1548,2262,6482 } }, + { "OLYMPUS SP510UZ", 0, 0xffe, + { 10593,-3607,-1010,-5881,13127,3084,-1200,1805,6721 } }, + { "OLYMPUS SP550UZ", 0, 0xffe, + { 11597,-4006,-1049,-5432,12799,2957,-1029,1750,6516 } }, + { "OLYMPUS SP560UZ", 0, 0xff9, + { 10915,-3677,-982,-5587,12986,2911,-1168,1968,6223 } }, + { "OLYMPUS SP570UZ", 0, 0, + { 11522,-4044,-1146,-4736,12172,2904,-988,1829,6039 } }, + { "PENTAX *ist DL2", 0, 0, + { 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } }, + { "PENTAX *ist DL", 0, 0, + { 10829,-2838,-1115,-8339,15817,2696,-837,680,11939 } }, + { "PENTAX *ist DS2", 0, 0, + { 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } }, + { "PENTAX *ist DS", 0, 0, + { 10371,-2333,-1206,-8688,16231,2602,-1230,1116,11282 } }, + { "PENTAX *ist D", 0, 0, + { 9651,-2059,-1189,-8881,16512,2487,-1460,1345,10687 } }, + { "PENTAX K10D", 0, 0, + { 9566,-2863,-803,-7170,15172,2112,-818,803,9705 } }, + { "PENTAX K1", 0, 0, + { 11095,-3157,-1324,-8377,15834,2720,-1108,947,11688 } }, + { "PENTAX K20D", 0, 0, + { 9427,-2714,-868,-7493,16092,1373,-2199,3264,7180 } }, + { "PENTAX K200D", 0, 0, + { 9186,-2678,-907,-8693,16517,2260,-1129,1094,8524 } }, + { "PENTAX K2000", 0, 0, + { 11057,-3604,-1155,-5152,13046,2329,-282,375,8104 } }, + { "Panasonic DMC-FZ8", 0, 0xf7f0, + { 8986,-2755,-802,-6341,13575,3077,-1476,2144,6379 } }, + { "Panasonic DMC-FZ18", 0, 0, + { 9932,-3060,-935,-5809,13331,2753,-1267,2155,5575 } }, + { "Panasonic DMC-FZ28", 15, 0xfff, + { 10109,-3488,-993,-5412,12812,2916,-1305,2140,5543 } }, + { "Panasonic DMC-FZ30", 0, 0xf94c, + { 10976,-4029,-1141,-7918,15491,2600,-1670,2071,8246 } }, + { "Panasonic DMC-FZ50", 0, 0xfff0, /* aka "LEICA V-LUX1" */ + { 7906,-2709,-594,-6231,13351,3220,-1922,2631,6537 } }, + { "Panasonic DMC-L10", 15, 0xf96, + { 8025,-1942,-1050,-7920,15904,2100,-2456,3005,7039 } }, + { "Panasonic DMC-L1", 0, 0xf7fc, /* aka "LEICA DIGILUX 3" */ + { 8054,-1885,-1025,-8349,16367,2040,-2805,3542,7629 } }, + { "Panasonic DMC-LC1", 0, 0, /* aka "LEICA DIGILUX 2" */ + { 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } }, + { "Panasonic DMC-LX1", 0, 0xf7f0, /* aka "LEICA D-LUX2" */ + { 10704,-4187,-1230,-8314,15952,2501,-920,945,8927 } }, + { "Panasonic DMC-LX2", 0, 0, /* aka "LEICA D-LUX3" */ + { 8048,-2810,-623,-6450,13519,3272,-1700,2146,7049 } }, + { "Panasonic DMC-LX3", 15, 0xfff, /* aka "LEICA D-LUX4" */ + { 8128,-2668,-655,-6134,13307,3161,-1782,2568,6083 } }, + { "Panasonic DMC-FX150", 15, 0xfff, + { 9082,-2907,-925,-6119,13377,3058,-1797,2641,5609 } }, + { "Panasonic DMC-G1", 15, 0xfff, + { 8199,-2065,-1056,-8124,16156,2033,-2458,3022,7220 } }, + { "Phase One H 20", 0, 0, /* DJC */ + { 1313,1855,-109,-6715,15908,808,-327,1840,6020 } }, + { "Phase One P 2", 0, 0, + { 2905,732,-237,-8134,16626,1476,-3038,4253,7517 } }, + { "Phase One P 30", 0, 0, + { 4516,-245,-37,-7020,14976,2173,-3206,4671,7087 } }, + { "Phase One P 45", 0, 0, + { 5053,-24,-117,-5684,14076,1702,-2619,4492,5849 } }, + { "SAMSUNG GX-1", 0, 0, + { 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } }, + { "Sinar", 0, 0, /* DJC */ + { 16442,-2956,-2422,-2877,12128,750,-1136,6066,4559 } }, + { "SONY DSC-F828", 491, 0, + { 7924,-1910,-777,-8226,15459,2998,-1517,2199,6818,-7242,11401,3481 } }, + { "SONY DSC-R1", 512, 0, + { 8512,-2641,-694,-8042,15670,2526,-1821,2117,7414 } }, + { "SONY DSC-V3", 0, 0, + { 7511,-2571,-692,-7894,15088,3060,-948,1111,8128 } }, + { "SONY DSLR-A100", 0, 0xfeb, + { 9437,-2811,-774,-8405,16215,2290,-710,596,7181 } }, + { "SONY DSLR-A200", 0, 0, + { 9847,-3091,-928,-8485,16345,2225,-715,595,7103 } }, + { "SONY DSLR-A300", 0, 0, + { 9847,-3091,-928,-8485,16345,2225,-715,595,7103 } }, + { "SONY DSLR-A350", 0, 0xffc, + { 6038,-1484,-578,-9146,16746,2513,-875,746,7217 } }, + { "SONY DSLR-A700", 254, 0x1ffe, + { 5775,-805,-359,-8574,16295,2391,-1943,2341,7249 } }, + { "SONY DSLR-A900", 254, 0x1ffe, + { 5209,-1072,-397,-8845,16120,2919,-1618,1803,8654 } } + }; + double cam_xyz[4][3]; + char name[130]; + int i, j; + + sprintf (name, "%s %s", p_make, p_model); + for (i=0; i < sizeof table / sizeof *table; i++) + if (!strncmp (name, table[i].prefix, strlen(table[i].prefix))) { + if (table[i].t_black) black = (ushort) table[i].t_black; + if (table[i].t_maximum) maximum = (ushort) table[i].t_maximum; + for (j=0; j < 12; j++) +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.color.cam_xyz[0][j] = +#endif + cam_xyz[0][j] = table[i].trans[j] / 10000.0; + cam_xyz_coeff (cam_xyz); + break; + } +} + +void CLASS simple_coeff (int index) +{ + static const float table[][12] = { + /* index 0 -- all Foveon cameras */ + { 1.4032,-0.2231,-0.1016,-0.5263,1.4816,0.017,-0.0112,0.0183,0.9113 }, + /* index 1 -- Kodak DC20 and DC25 */ + { 2.25,0.75,-1.75,-0.25,-0.25,0.75,0.75,-0.25,-0.25,-1.75,0.75,2.25 }, + /* index 2 -- Logitech Fotoman Pixtura */ + { 1.893,-0.418,-0.476,-0.495,1.773,-0.278,-1.017,-0.655,2.672 }, + /* index 3 -- Nikon E880, E900, and E990 */ + { -1.936280, 1.800443, -1.448486, 2.584324, + 1.405365, -0.524955, -0.289090, 0.408680, + -1.204965, 1.082304, 2.941367, -1.818705 } + }; + int i, c; + + for (raw_color = i=0; i < 3; i++) + FORCC rgb_cam[i][c] = table[index][i*colors+c]; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_CALCULATED; +#endif +} + +short CLASS guess_byte_order (int words) +{ + uchar test[4][2]; + int t=2, msb; + double diff, sum[2] = {0,0}; + + fread (test[0], 2, 2, ifp); + for (words-=2; words--; ) { + fread (test[t], 2, 1, ifp); + for (msb=0; msb < 2; msb++) { + diff = (test[t^2][msb] << 8 | test[t^2][!msb]) + - (test[t ][msb] << 8 | test[t ][!msb]); + sum[msb] += diff*diff; + } + t = (t+1) & 3; + } + return sum[0] < sum[1] ? 0x4d4d : 0x4949; +} + +/* + Identify which camera created this file, and set global variables + accordingly. + */ +void CLASS identify() +{ + char head[32], *cp; + unsigned hlen, fsize, i, c, is_canon; + struct jhead jh; + static const struct { + int fsize; + char t_make[12], t_model[19], withjpeg; + } table[] = { + { 62464, "Kodak", "DC20" ,0 }, + { 124928, "Kodak", "DC20" ,0 }, + { 1652736, "Kodak", "DCS200" ,0 }, + { 4159302, "Kodak", "C330" ,0 }, + { 4162462, "Kodak", "C330" ,0 }, + { 460800, "Kodak", "C603v" ,0 }, + { 614400, "Kodak", "C603v" ,0 }, + { 6163328, "Kodak", "C603" ,0 }, + { 6166488, "Kodak", "C603" ,0 }, + { 9116448, "Kodak", "C603y" ,0 }, + { 311696, "ST Micro", "STV680 VGA" ,0 }, /* SPYz */ + { 614400, "Kodak", "KAI-0340" ,0 }, + { 787456, "Creative", "PC-CAM 600" ,0 }, + { 1138688, "Minolta", "RD175" ,0 }, + { 3840000, "Foculus", "531C" ,0 }, + { 786432, "AVT", "F-080C" ,0 }, + { 1447680, "AVT", "F-145C" ,0 }, + { 1920000, "AVT", "F-201C" ,0 }, + { 5067304, "AVT", "F-510C" ,0 }, + { 10134608, "AVT", "F-510C" ,0 }, + { 16157136, "AVT", "F-810C" ,0 }, + { 1409024, "Sony", "XCD-SX910CR" ,0 }, + { 2818048, "Sony", "XCD-SX910CR" ,0 }, + { 3884928, "Micron", "2010" ,0 }, + { 6624000, "Pixelink", "A782" ,0 }, + { 13248000, "Pixelink", "A782" ,0 }, + { 6291456, "RoverShot","3320AF" ,0 }, + { 6553440, "Canon", "PowerShot A460" ,0 }, + { 6653280, "Canon", "PowerShot A530" ,0 }, + { 6573120, "Canon", "PowerShot A610" ,0 }, + { 9219600, "Canon", "PowerShot A620" ,0 }, + { 10341600, "Canon", "PowerShot A720" ,0 }, + { 10383120, "Canon", "PowerShot A630" ,0 }, + { 12945240, "Canon", "PowerShot A640" ,0 }, + { 15636240, "Canon", "PowerShot A650" ,0 }, + { 5298000, "Canon", "PowerShot SD300" ,0 }, + { 7710960, "Canon", "PowerShot S3 IS" ,0 }, + { 5939200, "OLYMPUS", "C770UZ" ,0 }, + { 1581060, "NIKON", "E900" ,1 }, /* or E900s,E910 */ + { 2465792, "NIKON", "E950" ,1 }, /* or E800,E700 */ + { 2940928, "NIKON", "E2100" ,1 }, /* or E2500 */ + { 4771840, "NIKON", "E990" ,1 }, /* or E995, Oly C3030Z */ + { 4775936, "NIKON", "E3700" ,1 }, /* or Optio 33WR */ + { 5869568, "NIKON", "E4300" ,1 }, /* or DiMAGE Z2 */ + { 5865472, "NIKON", "E4500" ,1 }, + { 7438336, "NIKON", "E5000" ,1 }, /* or E5700 */ + { 8998912, "NIKON", "COOLPIX S6" ,1 }, + { 1976352, "CASIO", "QV-2000UX" ,1 }, + { 3217760, "CASIO", "QV-3*00EX" ,1 }, + { 6218368, "CASIO", "QV-5700" ,1 }, + { 6054400, "CASIO", "QV-R41" ,1 }, + { 7530816, "CASIO", "QV-R51" ,1 }, + { 7684000, "CASIO", "QV-4000" ,1 }, + { 4948608, "CASIO", "EX-S100" ,1 }, + { 7542528, "CASIO", "EX-Z50" ,1 }, + { 7753344, "CASIO", "EX-Z55" ,1 }, + { 7426656, "CASIO", "EX-P505" ,1 }, + { 9313536, "CASIO", "EX-P600" ,1 }, + { 10979200, "CASIO", "EX-P700" ,1 }, + { 3178560, "PENTAX", "Optio S" ,1 }, + { 4841984, "PENTAX", "Optio S" ,1 }, + { 6114240, "PENTAX", "Optio S4" ,1 }, /* or S4i, CASIO EX-Z4 */ + { 10702848, "PENTAX", "Optio 750Z" ,1 }, + { 16098048, "SAMSUNG", "S85" ,1 }, + { 16215552, "SAMSUNG", "S85" ,1 }, + { 12582980, "Sinar", "" ,0 }, + { 33292868, "Sinar", "" ,0 }, + { 44390468, "Sinar", "" ,0 } }; + static const char *corp[] = + { "Canon", "NIKON", "EPSON", "KODAK", "Kodak", "OLYMPUS", "PENTAX", + "MINOLTA", "Minolta", "Konica", "CASIO", "Sinar", "Phase One", + "SAMSUNG", "Mamiya" }; + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_IDENTIFY,0,2); +#endif + + tiff_flip = flip = filters = -1; /* 0 is valid, so -1 is unknown */ + raw_height = raw_width = fuji_width = fuji_layout = cr2_slice[0] = 0; + maximum = height = width = top_margin = left_margin = 0; + cdesc[0] = desc[0] = artist[0] = make[0] = model[0] = model2[0] = 0; + iso_speed = shutter = aperture = focal_len = unique_id = 0; + memset (gpsdata, 0, sizeof gpsdata); + memset (white, 0, sizeof white); + thumb_offset = thumb_length = thumb_width = thumb_height = 0; + load_raw = thumb_load_raw = 0; + write_thumb = &CLASS jpeg_thumb; + data_offset = meta_length = tiff_bps = tiff_compress = 0; + kodak_cbpp = zero_after_ff = dng_version = load_flags = 0; + timestamp = shot_order = tiff_samples = black = is_foveon = 0; + mix_green = profile_length = data_error = zero_is_bad = 0; + pixel_aspect = is_raw = raw_color = use_gamma = 1; + tile_width = tile_length = INT_MAX; + for (i=0; i < 4; i++) { + cam_mul[i] = i == 1; + pre_mul[i] = i < 3; + FORC3 cmatrix[c][i] = 0; + FORC3 rgb_cam[c][i] = c == i; + } +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cmatrix_state = LIBRAW_COLORSTATE_INIT; + color_flags.rgb_cam_state = LIBRAW_COLORSTATE_INIT; + color_flags.pre_mul_state = LIBRAW_COLORSTATE_INIT; + color_flags.cam_mul_state = LIBRAW_COLORSTATE_INIT; +#endif + colors = 3; + tiff_bps = 12; + for (i=0; i < 0x4000; i++) curve[i] = i; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_INIT; +#endif + + order = get2(); + hlen = get4(); + fseek (ifp, 0, SEEK_SET); + fread (head, 1, 32, ifp); + fseek (ifp, 0, SEEK_END); + fsize = ftell(ifp); + if ((cp = (char *) memmem (head, 32, "MMMM", 4)) || + (cp = (char *) memmem (head, 32, "IIII", 4))) { + parse_phase_one (cp-head); + if (cp-head) parse_tiff(0); + } else if (order == 0x4949 || order == 0x4d4d) { + if (!memcmp (head+6,"HEAPCCDR",8)) { + data_offset = hlen; + parse_ciff (hlen, fsize - hlen); + } else { + parse_tiff(0); + } + } else if (!memcmp (head,"\xff\xd8\xff\xe1",4) && + !memcmp (head+6,"Exif",4)) { + fseek (ifp, 4, SEEK_SET); + data_offset = 4 + get2(); + fseek (ifp, data_offset, SEEK_SET); + if (fgetc(ifp) != 0xff) + parse_tiff(12); + thumb_offset = 0; + } else if (!memcmp (head+25,"ARECOYK",7)) { + strcpy (make, "Contax"); + strcpy (model,"N Digital"); + fseek (ifp, 33, SEEK_SET); + get_timestamp(1); + fseek (ifp, 60, SEEK_SET); + FORC4 cam_mul[c ^ (c >> 1)] = get4(); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.cam_mul_state = LIBRAW_COLORSTATE_LOADED; +#endif + } else if (!strcmp (head, "PXN")) { + strcpy (make, "Logitech"); + strcpy (model,"Fotoman Pixtura"); + } else if (!strcmp (head, "qktk")) { + strcpy (make, "Apple"); + strcpy (model,"QuickTake 100"); + } else if (!strcmp (head, "qktn")) { + strcpy (make, "Apple"); + strcpy (model,"QuickTake 150"); + } else if (!memcmp (head,"FUJIFILM",8)) { + fseek (ifp, 84, SEEK_SET); + thumb_offset = get4(); + thumb_length = get4(); + fseek (ifp, 92, SEEK_SET); + parse_fuji (get4()); + if (thumb_offset > 120) { + fseek (ifp, 120, SEEK_SET); + is_raw += (i = get4()) && 1; + if (is_raw == 2 && shot_select) + parse_fuji (i); + } + fseek (ifp, 100, SEEK_SET); + data_offset = get4(); + parse_tiff (thumb_offset+12); + } else if (!memcmp (head,"RIFF",4)) { + fseek (ifp, 0, SEEK_SET); + parse_riff(); + } else if (!memcmp (head,"\0\001\0\001\0@",6)) { + fseek (ifp, 6, SEEK_SET); + fread (make, 1, 8, ifp); + fread (model, 1, 8, ifp); + fread (model2, 1, 16, ifp); + data_offset = get2(); + get2(); + raw_width = get2(); + raw_height = get2(); + load_raw = &CLASS nokia_load_raw; + filters = 0x61616161; + } else if (!memcmp (head,"DSC-Image",9)) + parse_rollei(); + else if (!memcmp (head,"PWAD",4)) + parse_sinar_ia(); + else if (!memcmp (head,"\0MRM",4)) + parse_minolta(0); + else if (!memcmp (head,"FOVb",4)) + parse_foveon(); + else if (!memcmp (head,"CI",2)) + parse_cine(); + else + for (i=0; i < sizeof table / sizeof *table; i++) + if (fsize == table[i].fsize) { + strcpy (make, table[i].t_make ); + strcpy (model, table[i].t_model); + if (table[i].withjpeg) + parse_external_jpeg(); + } + if (make[0] == 0) parse_smal (0, fsize); + if (make[0] == 0) parse_jpeg (is_raw = 0); + + for (i=0; i < sizeof corp / sizeof *corp; i++) + if (strstr (make, corp[i])) /* Simplify company names */ + strcpy (make, corp[i]); + if (!strncmp (make,"KODAK",5)) + make[16] = model[16] = 0; + cp = make + strlen(make); /* Remove trailing spaces */ + while (*--cp == ' ') *cp = 0; + cp = model + strlen(model); + while (*--cp == ' ') *cp = 0; + i = strlen(make); /* Remove make from model */ + if (!strncasecmp (model, make, i) && model[i++] == ' ') + memmove (model, model+i, 64-i); + if (!strncmp (model,"Digital Camera ",15)) + strcpy (model, model+15); + desc[511] = artist[63] = make[63] = model[63] = model2[63] = 0; + if (!is_raw) goto notraw; + + if (!maximum) maximum = (1 << tiff_bps) - 1; + if (!height) height = raw_height; + if (!width) width = raw_width; + if (fuji_width) { + width = height + fuji_width; + height = width - 1; + pixel_aspect = 1; + } + if (height == 2624 && width == 3936) /* Pentax K10D and Samsung GX10 */ + { height = 2616; width = 3896; } + if (height == 3136 && width == 4864) /* Pentax K20D */ + { height = 3124; width = 4688; } + if (height == 3014 && width == 4096) /* Ricoh GX200 */ + width = 4014; + if (dng_version) { + if (filters == UINT_MAX) filters = 0; + if (filters) is_raw = tiff_samples; + else colors = tiff_samples; + if (tiff_compress == 1) + load_raw = &CLASS adobe_dng_load_raw_nc; + if (tiff_compress == 7) + load_raw = &CLASS adobe_dng_load_raw_lj; + goto dng_skip; + } + if ((is_canon = !strcmp(make,"Canon"))) + load_raw = memcmp (head+6,"HEAPCCDR",8) ? + &CLASS lossless_jpeg_load_raw : &CLASS canon_compressed_load_raw; + if (!strcmp(make,"NIKON")) { + if (!load_raw) + load_raw = &CLASS packed_12_load_raw; + if (model[0] == 'E') + load_flags |= !data_offset << 2 | 2; + } + if (!strcmp(make,"CASIO")) { + load_raw = &CLASS packed_12_load_raw; + maximum = 0xf7f; + } + +/* Set parameters based on camera name (for non-DNG files). */ + + if (is_foveon) { + if (height*2 < width) pixel_aspect = 0.5; + if (height > width) pixel_aspect = 2; + filters = 0; + load_raw = &CLASS foveon_load_raw; + simple_coeff(0); + } else if (is_canon && tiff_bps == 15) { + switch (width) { + case 3344: width -= 66; + case 3872: width -= 6; + } + filters = 0; + load_raw = &CLASS canon_sraw_load_raw; + } else if (!strcmp(model,"PowerShot 600")) { + height = 613; + width = 854; + raw_width = 896; + pixel_aspect = 607/628.0; + colors = 4; + filters = 0xe1e4e1e4; + load_raw = &CLASS canon_600_load_raw; + } else if (!strcmp(model,"PowerShot A5") || + !strcmp(model,"PowerShot A5 Zoom")) { + height = 773; + width = 960; + raw_width = 992; + pixel_aspect = 256/235.0; + colors = 4; + filters = 0x1e4e1e4e; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A50")) { + height = 968; + width = 1290; + raw_width = 1320; + colors = 4; + filters = 0x1b4e4b1e; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot Pro70")) { + height = 1024; + width = 1552; + colors = 4; + filters = 0x1e4b4e1b; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot SD300")) { + height = 1752; + width = 2344; + raw_height = 1766; + raw_width = 2400; + top_margin = 12; + left_margin = 12; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A460")) { + height = 1960; + width = 2616; + raw_height = 1968; + raw_width = 2664; + top_margin = 4; + left_margin = 4; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A530")) { + height = 1984; + width = 2620; + raw_height = 1992; + raw_width = 2672; + top_margin = 6; + left_margin = 10; + load_raw = &CLASS canon_a5_load_raw; + raw_color = 0; + } else if (!strcmp(model,"PowerShot A610")) { + if (canon_s2is()) strcpy (model+10, "S2 IS"); + height = 1960; + width = 2616; + raw_height = 1968; + raw_width = 2672; + top_margin = 8; + left_margin = 12; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A620")) { + height = 2328; + width = 3112; + raw_height = 2340; + raw_width = 3152; + top_margin = 12; + left_margin = 36; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A720")) { + height = 2472; + width = 3298; + raw_height = 2480; + raw_width = 3336; + top_margin = 5; + left_margin = 6; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A630")) { + height = 2472; + width = 3288; + raw_height = 2484; + raw_width = 3344; + top_margin = 6; + left_margin = 12; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A640")) { + height = 2760; + width = 3672; + raw_height = 2772; + raw_width = 3736; + top_margin = 6; + left_margin = 12; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot A650")) { + height = 3024; + width = 4032; + raw_height = 3048; + raw_width = 4104; + top_margin = 12; + left_margin = 48; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot S3 IS")) { + height = 2128; + width = 2840; + raw_height = 2136; + raw_width = 2888; + top_margin = 8; + left_margin = 44; + load_raw = &CLASS canon_a5_load_raw; + } else if (!strcmp(model,"PowerShot Pro90 IS")) { + width = 1896; + colors = 4; + filters = 0xb4b4b4b4; + } else if (is_canon && raw_width == 2144) { + height = 1550; + width = 2088; + top_margin = 8; + left_margin = 4; + if (!strcmp(model,"PowerShot G1")) { + colors = 4; + filters = 0xb4b4b4b4; + } + } else if (is_canon && raw_width == 2224) { + height = 1448; + width = 2176; + top_margin = 6; + left_margin = 48; + } else if (is_canon && raw_width == 2376) { + height = 1720; + width = 2312; + top_margin = 6; + left_margin = 12; + } else if (is_canon && raw_width == 2672) { + height = 1960; + width = 2616; + top_margin = 6; + left_margin = 12; + } else if (is_canon && raw_width == 3152) { + height = 2056; + width = 3088; + top_margin = 12; + left_margin = 64; + if (unique_id == 0x80000170) + adobe_coeff ("Canon","EOS 300D"); + } else if (is_canon && raw_width == 3160) { + height = 2328; + width = 3112; + top_margin = 12; + left_margin = 44; + } else if (is_canon && raw_width == 3344) { + height = 2472; + width = 3288; + top_margin = 6; + left_margin = 4; + } else if (!strcmp(model,"EOS D2000C")) { + filters = 0x61616161; + black = curve[200]; + } else if (is_canon && raw_width == 3516) { + top_margin = 14; + left_margin = 42; + if (unique_id == 0x80000189) + adobe_coeff ("Canon","EOS 350D"); + goto canon_cr2; + } else if (is_canon && raw_width == 3596) { + top_margin = 12; + left_margin = 74; + goto canon_cr2; + } else if (is_canon && raw_width == 3944) { + height = 2602; + width = 3908; + top_margin = 18; + left_margin = 30; + } else if (is_canon && raw_width == 3948) { + top_margin = 18; + left_margin = 42; + height -= 2; + if (unique_id == 0x80000236) + adobe_coeff ("Canon","EOS 400D"); + if (unique_id == 0x80000254) + adobe_coeff ("Canon","EOS 1000D"); + goto canon_cr2; + } else if (is_canon && raw_width == 3984) { + top_margin = 20; + left_margin = 76; + height -= 2; + goto canon_cr2; + } else if (is_canon && raw_width == 4104) { + height = 3024; + width = 4032; + top_margin = 12; + left_margin = 48; + } else if (is_canon && raw_width == 4312) { + top_margin = 18; + left_margin = 22; + height -= 2; + if (unique_id == 0x80000176) + adobe_coeff ("Canon","EOS 450D"); + goto canon_cr2; + } else if (is_canon && raw_width == 4476) { + top_margin = 34; + left_margin = 90; + goto canon_cr2; + } else if (is_canon && raw_width == 4480) { + height = 3326; + width = 4432; + top_margin = 10; + left_margin = 12; + filters = 0x49494949; + } else if (is_canon && raw_width == 1208) { + top_margin = 51; + left_margin = 62; + raw_width = width *= 4; + goto canon_cr2; + } else if (is_canon && raw_width == 1448) { + top_margin = 51; + left_margin = 158; + raw_width = width *= 4; + goto canon_cr2; + } else if (is_canon && raw_width == 5108) { + top_margin = 13; + left_margin = 98; +canon_cr2: + height -= top_margin; + width -= left_margin; + } else if (is_canon && raw_width == 5712) { + height = 3752; + width = 5640; + top_margin = 20; + left_margin = 62; + } else if (!strcmp(model,"D1")) { + cam_mul[0] *= 256/527.0; + cam_mul[2] *= 256/317.0; + } else if (!strcmp(model,"D1X")) { + width -= 4; + pixel_aspect = 0.5; + } else if (!strcmp(model,"D40X") || + !strcmp(model,"D60") || + !strcmp(model,"D80")) { + height -= 3; + width -= 4; + } else if (!strcmp(model,"D3") || + !strcmp(model,"D700")) { + width -= 4; + left_margin = 2; + } else if (!strncmp(model,"D40",3) || + !strncmp(model,"D50",3) || + !strncmp(model,"D70",3)) { + width--; + } else if (!strcmp(model,"D90")) { + width -= 42; + } else if (!strcmp(model,"D100")) { + if (tiff_compress == 34713 && !nikon_is_compressed()) { + load_raw = &CLASS packed_12_load_raw; + load_flags |= 8; + raw_width = (width += 3) + 3; + } + } else if (!strcmp(model,"D200")) { + left_margin = 1; + width -= 4; + filters = 0x94949494; + } else if (!strncmp(model,"D2H",3)) { + left_margin = 6; + width -= 14; + } else if (!strncmp(model,"D2X",3)) { + if (width == 3264) width -= 32; + else width -= 8; + } else if (!strcmp(model,"D300")) { + width -= 32; + } else if (!strcmp(model,"COOLPIX P6000")) { + load_flags = 1; + filters = 0x94949494; + } else if (fsize == 1581060) { + height = 963; + width = 1287; + raw_width = 1632; + load_raw = &CLASS nikon_e900_load_raw; + maximum = 0x3f4; + colors = 4; + filters = 0x1e1e1e1e; + simple_coeff(3); + pre_mul[0] = 1.2085; + pre_mul[1] = 1.0943; + pre_mul[3] = 1.1103; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (fsize == 2465792) { + height = 1203; + width = 1616; + raw_width = 2048; + load_raw = &CLASS nikon_e900_load_raw; + colors = 4; + filters = 0x4b4b4b4b; + adobe_coeff ("NIKON","E950"); + } else if (fsize == 4771840) { + height = 1540; + width = 2064; + colors = 4; + filters = 0xe1e1e1e1; + load_raw = &CLASS packed_12_load_raw; + load_flags = 6; + if (!timestamp && nikon_e995()) + strcpy (model, "E995"); + if (strcmp(model,"E995")) { + filters = 0xb4b4b4b4; + simple_coeff(3); + pre_mul[0] = 1.196; + pre_mul[1] = 1.246; + pre_mul[2] = 1.018; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } + } else if (!strcmp(model,"E2100")) { + if (!timestamp && !nikon_e2100()) goto cp_e2500; + height = 1206; + width = 1616; + load_flags = 7; + } else if (!strcmp(model,"E2500")) { +cp_e2500: + strcpy (model, "E2500"); + height = 1204; + width = 1616; + colors = 4; + filters = 0x4b4b4b4b; + } else if (fsize == 4775936) { + height = 1542; + width = 2064; + load_raw = &CLASS packed_12_load_raw; + load_flags = 7; + pre_mul[0] = 1.818; + pre_mul[2] = 1.618; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + if (!timestamp) nikon_3700(); + if (model[0] == 'E' && atoi(model+1) < 3700) + filters = 0x49494949; + if (!strcmp(model,"Optio 33WR")) { + flip = 1; + filters = 0x16161616; + pre_mul[0] = 1.331; + pre_mul[2] = 1.820; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } + } else if (fsize == 5869568) { + height = 1710; + width = 2288; + filters = 0x16161616; + if (!timestamp && minolta_z2()) { + strcpy (make, "Minolta"); + strcpy (model,"DiMAGE Z2"); + } + load_raw = &CLASS packed_12_load_raw; + load_flags = 6 + (make[0] == 'M'); + } else if (!strcmp(model,"E4500")) { + height = 1708; + width = 2288; + colors = 4; + filters = 0xb4b4b4b4; + } else if (fsize == 7438336) { + height = 1924; + width = 2576; + colors = 4; + filters = 0xb4b4b4b4; + } else if (fsize == 8998912) { + height = 2118; + width = 2832; + maximum = 0xf83; + load_raw = &CLASS packed_12_load_raw; + load_flags = 7; + } else if (!strcmp(model,"FinePix S5100") || + !strcmp(model,"FinePix S5500")) { + load_raw = &CLASS unpacked_load_raw; + } else if (!strcmp(make,"FUJIFILM")) { + if (!strcmp(model+7,"S2Pro")) { + strcpy (model+7," S2Pro"); + height = 2144; + width = 2880; + flip = 6; + } else + maximum = 0x3e00; + if (is_raw == 2 && shot_select) + maximum = 0x2f00; + top_margin = (raw_height - height)/2; + left_margin = (raw_width - width )/2; + if (is_raw == 2) + data_offset += (shot_select > 0) * ( fuji_layout ? + (raw_width *= 2) : raw_height*raw_width*2 ); + fuji_width = width >> !fuji_layout; + width = (height >> fuji_layout) + fuji_width; + raw_height = height; + height = width - 1; + load_raw = &CLASS fuji_load_raw; + if (!(fuji_width & 1)) filters = 0x49494949; + } else if (!strcmp(model,"RD175")) { + height = 986; + width = 1534; + data_offset = 513; + filters = 0x61616161; + load_raw = &CLASS minolta_rd175_load_raw; + } else if (!strcmp(model,"KD-400Z")) { + height = 1712; + width = 2312; + raw_width = 2336; + goto konica_400z; + } else if (!strcmp(model,"KD-510Z")) { + goto konica_510z; + } else if (!strcasecmp(make,"MINOLTA")) { + load_raw = &CLASS unpacked_load_raw; + maximum = 0xfff; + if (!strncmp(model,"DiMAGE A",8)) { + if (!strcmp(model,"DiMAGE A200")) + filters = 0x49494949; + load_raw = &CLASS packed_12_load_raw; + } else if (!strncmp(model,"ALPHA",5) || + !strncmp(model,"DYNAX",5) || + !strncmp(model,"MAXXUM",6)) { + sprintf (model+20, "DYNAX %-10s", model+6+(model[0]=='M')); + adobe_coeff (make, model+20); + load_raw = &CLASS packed_12_load_raw; + } else if (!strncmp(model,"DiMAGE G",8)) { + if (model[8] == '4') { + height = 1716; + width = 2304; + } else if (model[8] == '5') { +konica_510z: + height = 1956; + width = 2607; + raw_width = 2624; + } else if (model[8] == '6') { + height = 2136; + width = 2848; + } + data_offset += 14; + filters = 0x61616161; +konica_400z: + load_raw = &CLASS unpacked_load_raw; + maximum = 0x3df; + order = 0x4d4d; + } + } else if (!strcmp(model,"*ist DS")) { + height -= 2; + } else if (!strcmp(model,"K20D")) { + filters = 0x16161616; + } else if (!strcmp(model,"Optio S")) { + if (fsize == 3178560) { + height = 1540; + width = 2064; + load_raw = &CLASS eight_bit_load_raw; + cam_mul[0] *= 4; + cam_mul[2] *= 4; + pre_mul[0] = 1.391; + pre_mul[2] = 1.188; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else { + height = 1544; + width = 2068; + raw_width = 3136; + load_raw = &CLASS packed_12_load_raw; + maximum = 0xf7c; + pre_mul[0] = 1.137; + pre_mul[2] = 1.453; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } + } else if (fsize == 6114240) { + height = 1737; + width = 2324; + raw_width = 3520; + load_raw = &CLASS packed_12_load_raw; + maximum = 0xf7a; + pre_mul[0] = 1.980; + pre_mul[2] = 1.570; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"Optio 750Z")) { + height = 2302; + width = 3072; + load_raw = &CLASS packed_12_load_raw; + load_flags = 7; + } else if (!strcmp(model,"S85")) { + height = 2448; + width = 3264; + raw_width = fsize/height/2; + order = 0x4d4d; + load_raw = &CLASS unpacked_load_raw; + maximum = 0xffff; + } else if (!strcmp(model,"STV680 VGA")) { + height = 484; + width = 644; + load_raw = &CLASS eight_bit_load_raw; + flip = 2; + filters = 0x16161616; + black = 16; + pre_mul[0] = 1.097; + pre_mul[2] = 1.128; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"KAI-0340")) { + height = 477; + width = 640; + order = 0x4949; + data_offset = 3840; + load_raw = &CLASS unpacked_load_raw; + pre_mul[0] = 1.561; + pre_mul[2] = 2.454; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"N95")) { + height = raw_height - (top_margin = 2); + } else if (!strcmp(model,"531C")) { + height = 1200; + width = 1600; + load_raw = &CLASS unpacked_load_raw; + filters = 0x49494949; + pre_mul[1] = 1.218; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"F-080C")) { + height = 768; + width = 1024; + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcmp(model,"F-145C")) { + height = 1040; + width = 1392; + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcmp(model,"F-201C")) { + height = 1200; + width = 1600; + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcmp(model,"F-510C")) { + height = 1958; + width = 2588; + load_raw = fsize < 7500000 ? + &CLASS eight_bit_load_raw : &CLASS unpacked_load_raw; + maximum = 0xfff0; + } else if (!strcmp(model,"F-810C")) { + height = 2469; + width = 3272; + load_raw = &CLASS unpacked_load_raw; + maximum = 0xfff0; + } else if (!strcmp(model,"XCD-SX910CR")) { + height = 1024; + width = 1375; + raw_width = 1376; + filters = 0x49494949; + maximum = 0x3ff; + load_raw = fsize < 2000000 ? + &CLASS eight_bit_load_raw : &CLASS unpacked_load_raw; + } else if (!strcmp(model,"2010")) { + height = 1207; + width = 1608; + order = 0x4949; + filters = 0x16161616; + data_offset = 3212; + maximum = 0x3ff; + load_raw = &CLASS unpacked_load_raw; + } else if (!strcmp(model,"A782")) { + height = 3000; + width = 2208; + filters = 0x61616161; + load_raw = fsize < 10000000 ? + &CLASS eight_bit_load_raw : &CLASS unpacked_load_raw; + maximum = 0xffc0; + } else if (!strcmp(model,"3320AF")) { + height = 1536; + raw_width = width = 2048; + filters = 0x61616161; + load_raw = &CLASS unpacked_load_raw; + maximum = 0x3ff; + pre_mul[0] = 1.717; + pre_mul[2] = 1.138; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + fseek (ifp, 0x300000, SEEK_SET); + if ((order = guess_byte_order(0x10000)) == 0x4d4d) { + height -= (top_margin = 16); + width -= (left_margin = 28); + maximum = 0xf5c0; + strcpy (make, "ISG"); + model[0] = 0; + } + } else if (!strcmp(make,"Hasselblad")) { + if (load_raw == &CLASS lossless_jpeg_load_raw) + load_raw = &CLASS hasselblad_load_raw; + if (raw_width == 7262) { + height = 5444; + width = 7248; + top_margin = 4; + left_margin = 7; + filters = 0x61616161; + } else if (raw_width == 4090) { + strcpy (model, "V96C"); + height -= (top_margin = 6); + width -= (left_margin = 3) + 7; + filters = 0x61616161; + } + } else if (!strcmp(make,"Sinar")) { + if (!memcmp(head,"8BPS",4)) { + fseek (ifp, 14, SEEK_SET); + height = get4(); + width = get4(); + filters = 0x61616161; + data_offset = 68; + } + if (!load_raw) load_raw = &CLASS unpacked_load_raw; + maximum = 0x3fff; + } else if (!strcmp(make,"Leaf")) { + maximum = 0x3fff; + fseek (ifp, data_offset, SEEK_SET); + if (ljpeg_start (&jh, 1) && jh.bits == 15) + maximum = 0x1fff; + if (tiff_samples > 1) filters = 0; + if (tiff_samples > 1 || tile_length < raw_height) + load_raw = &CLASS leaf_hdr_load_raw; + if ((width | height) == 2048) { + if (tiff_samples == 1) { + filters = 1; + strcpy (cdesc, "RBTG"); + strcpy (model, "CatchLight"); + top_margin = 8; left_margin = 18; height = 2032; width = 2016; + } else { + strcpy (model, "DCB2"); + top_margin = 10; left_margin = 16; height = 2028; width = 2022; + } + } else if (width+height == 3144+2060) { + if (!model[0]) strcpy (model, "Cantare"); + if (width > height) { + top_margin = 6; left_margin = 32; height = 2048; width = 3072; + filters = 0x61616161; + } else { + left_margin = 6; top_margin = 32; width = 2048; height = 3072; + filters = 0x16161616; + } + if (!cam_mul[0] || model[0] == 'V') filters = 0; + else is_raw = tiff_samples; + } else if (width == 2116) { + strcpy (model, "Valeo 6"); + height -= 2 * (top_margin = 30); + width -= 2 * (left_margin = 55); + filters = 0x49494949; + } else if (width == 3171) { + strcpy (model, "Valeo 6"); + height -= 2 * (top_margin = 24); + width -= 2 * (left_margin = 24); + filters = 0x16161616; + } + } else if (!strcmp(make,"LEICA") || !strcmp(make,"Panasonic")) { + maximum = 0xfff0; + if ((fsize-data_offset) / (width*8/7) == height) + load_raw = &CLASS panasonic_load_raw; + if (!load_raw) load_raw = &CLASS unpacked_load_raw; + switch (width) { + case 2568: + adobe_coeff ("Panasonic","DMC-LC1"); break; + case 3130: + left_margin = -14; + case 3170: + left_margin += 18; + width = 3096; + if (height > 2326) { + height = 2326; + top_margin = 13; + filters = 0x49494949; + } + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-FZ8"); break; + case 3213: + width -= 27; + case 3177: + width -= 10; + filters = 0x49494949; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-L1"); break; + case 3304: + width -= 17; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-FZ30"); break; + case 3330: + width += 43; + left_margin = -6; + maximum = 0xf7f0; + case 3370: + width -= 82; + left_margin += 15; + if (height > 2480) + height = 2480 - (top_margin = 10); + filters = 0x49494949; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-FZ18"); break; + case 3690: + height -= 2; + left_margin = -14; + maximum = 0xf7f0; + case 3770: + width = 3672; + if (--height == 2798 && (height = 2760)) + top_margin = 15; + else filters = 0x49494949; + left_margin += 17; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-FZ50"); break; + case 3710: + width = 3682; + filters = 0x49494949; + adobe_coeff ("Panasonic","DMC-L10"); break; + case 3724: + width -= 14; + case 3836: + width -= 42; +lx3: filters = 0x16161616; + if (make[0] != 'P') + adobe_coeff ("Panasonic","DMC-LX3"); + break; + case 3880: + width -= 22; + left_margin = 6; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-LX1"); break; + case 4060: + width = 3982; + if (height == 2250) goto lx3; + width = 4018; + filters = 0x49494949; + zero_is_bad = 1; + adobe_coeff ("Panasonic","DMC-G1"); break; + case 4290: + height += 38; + left_margin = -14; + filters = 0x49494949; + case 4330: + width = 4248; + if ((height -= 39) == 2400) + top_margin = 15; + left_margin += 17; + adobe_coeff ("Panasonic","DMC-LX2"); break; + case 4508: + height -= 6; + width = 4429; + filters = 0x16161616; + adobe_coeff ("Panasonic","DMC-FX150"); break; + } + } else if (!strcmp(model,"C770UZ")) { + height = 1718; + width = 2304; + filters = 0x16161616; + load_raw = &CLASS packed_12_load_raw; + load_flags = 7; + } else if (!strcmp(make,"OLYMPUS")) { + height += height & 1; + filters = exif_cfa; + if (load_raw == &CLASS olympus_e410_load_raw) { + black >>= 4; + } else if (!strcmp(model,"E-10") || + !strncmp(model,"E-20",4)) { + black <<= 2; + } else if (!strcmp(model,"E-300") || + !strcmp(model,"E-500")) { + width -= 20; + if (load_raw == &CLASS unpacked_load_raw) { + maximum = 0xfc30; + black = 0; + } + } else if (!strcmp(model,"E-330")) { + width -= 30; + if (load_raw == &CLASS unpacked_load_raw) + maximum = 0xf790; + } else if (!strcmp(model,"SP550UZ")) { + thumb_length = fsize - (thumb_offset = 0xa39800); + thumb_height = 480; + thumb_width = 640; + } + } else if (!strcmp(model,"N Digital")) { + height = 2047; + width = 3072; + filters = 0x61616161; + data_offset = 0x1a00; + load_raw = &CLASS packed_12_load_raw; + } else if (!strcmp(model,"DSC-F828")) { + width = 3288; + left_margin = 5; + data_offset = 862144; + load_raw = &CLASS sony_load_raw; + filters = 0x9c9c9c9c; + colors = 4; + strcpy (cdesc, "RGBE"); + } else if (!strcmp(model,"DSC-V3")) { + width = 3109; + left_margin = 59; + data_offset = 787392; + load_raw = &CLASS sony_load_raw; + } else if (!strcmp(make,"SONY") && raw_width == 3984) { + adobe_coeff ("SONY","DSC-R1"); + width = 3925; + order = 0x4d4d; + } else if (!strcmp(model,"DSLR-A100")) { + height--; + } else if (!strcmp(model,"DSLR-A350")) { + height -= 4; + } else if (!strcmp(model,"C603v")) { + height = 480; + width = 640; + goto c603v; + } else if (!strcmp(model,"C603y")) { + height = 2134; + width = 2848; +c603v: + filters = 0; + load_raw = &CLASS kodak_yrgb_load_raw; + } else if (!strcmp(model,"C603")) { + raw_height = height = 2152; + raw_width = width = 2864; + goto c603; + } else if (!strcmp(model,"C330")) { + height = 1744; + width = 2336; + raw_height = 1779; + raw_width = 2338; + top_margin = 33; + left_margin = 1; +c603: + order = 0x4949; + if ((data_offset = fsize - raw_height*raw_width)) { + fseek (ifp, 168, SEEK_SET); + read_shorts (curve, 256); +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.curve_state = LIBRAW_COLORSTATE_LOADED; +#endif + } else use_gamma = 0; + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcasecmp(make,"KODAK")) { + if (filters == UINT_MAX) filters = 0x61616161; + if (!strncmp(model,"NC2000",6)) { + width -= 4; + left_margin = 2; + } else if (!strcmp(model,"EOSDCS3B")) { + width -= 4; + left_margin = 2; + } else if (!strcmp(model,"EOSDCS1")) { + width -= 4; + left_margin = 2; + } else if (!strcmp(model,"DCS420")) { + width -= 4; + left_margin = 2; + } else if (!strcmp(model,"DCS460")) { + width -= 4; + left_margin = 2; + } else if (!strcmp(model,"DCS460A")) { + width -= 4; + left_margin = 2; + colors = 1; + filters = 0; + } else if (!strcmp(model,"DCS660M")) { + black = 214; + colors = 1; + filters = 0; + } else if (!strcmp(model,"DCS760M")) { + colors = 1; + filters = 0; + } + if (strstr(model,"DC25")) { + strcpy (model, "DC25"); + data_offset = 15424; + } + if (!strncmp(model,"DC2",3)) { + height = 242; + if (fsize < 100000) { + raw_width = 256; width = 249; + pixel_aspect = (4.0*height) / (3.0*width); + } else { + raw_width = 512; width = 501; + pixel_aspect = (493.0*height) / (373.0*width); + } + data_offset += raw_width + 1; + colors = 4; + filters = 0x8d8d8d8d; + simple_coeff(1); + pre_mul[1] = 1.179; + pre_mul[2] = 1.209; + pre_mul[3] = 1.036; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcmp(model,"40")) { + strcpy (model, "DC40"); + height = 512; + width = 768; + data_offset = 1152; + load_raw = &CLASS kodak_radc_load_raw; + } else if (strstr(model,"DC50")) { + strcpy (model, "DC50"); + height = 512; + width = 768; + data_offset = 19712; + load_raw = &CLASS kodak_radc_load_raw; + } else if (strstr(model,"DC120")) { + strcpy (model, "DC120"); + height = 976; + width = 848; + pixel_aspect = height/0.75/width; + load_raw = tiff_compress == 7 ? + &CLASS kodak_jpeg_load_raw : &CLASS kodak_dc120_load_raw; + } else if (!strcmp(model,"DCS200")) { + thumb_height = 128; + thumb_width = 192; + thumb_offset = 6144; + thumb_misc = 360; + write_thumb = &CLASS layer_thumb; + height = 1024; + width = 1536; + data_offset = 79872; + load_raw = &CLASS eight_bit_load_raw; + black = 17; + } + } else if (!strcmp(model,"Fotoman Pixtura")) { + height = 512; + width = 768; + data_offset = 3632; + load_raw = &CLASS kodak_radc_load_raw; + filters = 0x61616161; + simple_coeff(2); + } else if (!strcmp(model,"QuickTake 100")) { + fseek (ifp, 544, SEEK_SET); + height = get2(); + width = get2(); + data_offset = (get4(),get2()) == 30 ? 738:736; + if (height > width) { + SWAP(height,width); + fseek (ifp, data_offset-6, SEEK_SET); + flip = ~get2() & 3 ? 5:6; + } + load_raw = &CLASS quicktake_100_load_raw; + filters = 0x61616161; + } else if (!strcmp(model,"QuickTake 150")) { + data_offset = 738 - head[5]; + if (head[5]) strcpy (model+10, "200"); + load_raw = &CLASS kodak_radc_load_raw; + height = 480; + width = 640; + filters = 0x61616161; + } else if (!strcmp(make,"Rollei") && !load_raw) { + switch (raw_width) { + case 1316: + height = 1030; + width = 1300; + top_margin = 1; + left_margin = 6; + break; + case 2568: + height = 1960; + width = 2560; + top_margin = 2; + left_margin = 8; + } + filters = 0x16161616; + load_raw = &CLASS rollei_load_raw; + pre_mul[0] = 1.8; + pre_mul[2] = 1.3; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"PC-CAM 600")) { + height = 768; + data_offset = width = 1024; + filters = 0x49494949; + load_raw = &CLASS eight_bit_load_raw; + pre_mul[0] = 1.14; + pre_mul[2] = 2.73; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"QV-2000UX")) { + height = 1208; + width = 1632; + data_offset = width * 2; + load_raw = &CLASS eight_bit_load_raw; + } else if (fsize == 3217760) { + height = 1546; + width = 2070; + raw_width = 2080; + load_raw = &CLASS eight_bit_load_raw; + } else if (!strcmp(model,"QV-4000")) { + height = 1700; + width = 2260; + load_raw = &CLASS unpacked_load_raw; + maximum = 0xffff; + } else if (!strcmp(model,"QV-5700")) { + height = 1924; + width = 2576; + load_raw = &CLASS casio_qv5700_load_raw; + } else if (!strcmp(model,"QV-R41")) { + height = 1720; + width = 2312; + raw_width = 3520; + left_margin = 2; + } else if (!strcmp(model,"QV-R51")) { + height = 1926; + width = 2580; + raw_width = 3904; + pre_mul[0] = 1.340; + pre_mul[2] = 1.672; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"EX-S100")) { + height = 1544; + width = 2058; + raw_width = 3136; + pre_mul[0] = 1.631; + pre_mul[2] = 1.106; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"EX-Z50")) { + height = 1931; + width = 2570; + raw_width = 3904; + pre_mul[0] = 2.529; + pre_mul[2] = 1.185; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"EX-Z55")) { + height = 1960; + width = 2570; + raw_width = 3904; + pre_mul[0] = 1.520; + pre_mul[2] = 1.316; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"EX-P505")) { + height = 1928; + width = 2568; + raw_width = 3852; + maximum = 0xfff; + pre_mul[0] = 2.07; + pre_mul[2] = 1.88; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (fsize == 9313536) { /* EX-P600 or QV-R61 */ + height = 2142; + width = 2844; + raw_width = 4288; + pre_mul[0] = 1.797; + pre_mul[2] = 1.219; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } else if (!strcmp(model,"EX-P700")) { + height = 2318; + width = 3082; + raw_width = 4672; + pre_mul[0] = 1.758; + pre_mul[2] = 1.504; +#ifdef LIBRAW_LIBRARY_BUILD + color_flags.pre_mul_state = LIBRAW_COLORSTATE_CONST; +#endif + } + if (!model[0]) + sprintf (model, "%dx%d", width, height); + if (filters == UINT_MAX) filters = 0x94949494; + if (raw_color) adobe_coeff (make, model); + if (thumb_offset && !thumb_height) { + fseek (ifp, thumb_offset, SEEK_SET); + if (ljpeg_start (&jh, 1)) { + thumb_width = jh.wide; + thumb_height = jh.high; + } + } +dng_skip: + if (!load_raw || height < 22) is_raw = 0; +#ifdef NO_JPEG + if (load_raw == &CLASS kodak_jpeg_load_raw) { +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s: You must link dcraw with libjpeg!!\n"), ifname); +#endif + is_raw = 0; +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_NO_JPEGLIB; +#endif + } +#endif + if (!cdesc[0]) + strcpy (cdesc, colors == 3 ? "RGB":"GMCY"); + if (!raw_height) raw_height = height; + if (!raw_width ) raw_width = width; + if (filters && colors == 3) + for (i=0; i < 32; i+=4) { + if ((filters >> i & 15) == 9) + filters |= 2 << i; + if ((filters >> i & 15) == 6) + filters |= 8 << i; + } +notraw: + if (flip == -1) flip = tiff_flip; + if (flip == -1) flip = 0; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_IDENTIFY,1,2); +#endif +} +#line 9177 "dcraw/dcraw.c" +void CLASS convert_to_rgb() +{ + int row, col, c, i, j, k; + ushort *img; + float out[3], out_cam[3][4]; + double num, inverse[3][3]; + static const double xyzd50_srgb[3][3] = + { { 0.436083, 0.385083, 0.143055 }, + { 0.222507, 0.716888, 0.060608 }, + { 0.013930, 0.097097, 0.714022 } }; + static const double rgb_rgb[3][3] = + { { 1,0,0 }, { 0,1,0 }, { 0,0,1 } }; + static const double adobe_rgb[3][3] = + { { 0.715146, 0.284856, 0.000000 }, + { 0.000000, 1.000000, 0.000000 }, + { 0.000000, 0.041166, 0.958839 } }; + static const double wide_rgb[3][3] = + { { 0.593087, 0.404710, 0.002206 }, + { 0.095413, 0.843149, 0.061439 }, + { 0.011621, 0.069091, 0.919288 } }; + static const double prophoto_rgb[3][3] = + { { 0.529317, 0.330092, 0.140588 }, + { 0.098368, 0.873465, 0.028169 }, + { 0.016879, 0.117663, 0.865457 } }; + static const double (*out_rgb[])[3] = + { rgb_rgb, adobe_rgb, wide_rgb, prophoto_rgb, xyz_rgb }; + static const char *name[] = + { "sRGB", "Adobe RGB (1998)", "WideGamut D65", "ProPhoto D65", "XYZ" }; + static const unsigned phead[] = + { 1024, 0, 0x2100000, 0x6d6e7472, 0x52474220, 0x58595a20, 0, 0, 0, + 0x61637370, 0, 0, 0x6e6f6e65, 0, 0, 0, 0, 0xf6d6, 0x10000, 0xd32d }; + unsigned pbody[] = + { 10, 0x63707274, 0, 36, /* cprt */ + 0x64657363, 0, 40, /* desc */ + 0x77747074, 0, 20, /* wtpt */ + 0x626b7074, 0, 20, /* bkpt */ + 0x72545243, 0, 14, /* rTRC */ + 0x67545243, 0, 14, /* gTRC */ + 0x62545243, 0, 14, /* bTRC */ + 0x7258595a, 0, 20, /* rXYZ */ + 0x6758595a, 0, 20, /* gXYZ */ + 0x6258595a, 0, 20 }; /* bXYZ */ + static const unsigned pwhite[] = { 0xf351, 0x10000, 0x116cc }; + unsigned pcurve[] = { 0x63757276, 0, 1, 0x1000000 }; + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB,0,2); +#endif + + memcpy (out_cam, rgb_cam, sizeof out_cam); + raw_color |= colors == 1 || document_mode || + output_color < 1 || output_color > 5; + if (!raw_color) { + oprof = (unsigned *) calloc (phead[0], 1); + merror (oprof, "convert_to_rgb()"); + memcpy (oprof, phead, sizeof phead); + if (output_color == 5) oprof[4] = oprof[5]; + oprof[0] = 132 + 12*pbody[0]; + for (i=0; i < pbody[0]; i++) { + oprof[oprof[0]/4] = i ? (i > 1 ? 0x58595a20 : 0x64657363) : 0x74657874; + pbody[i*3+2] = oprof[0]; + oprof[0] += (pbody[i*3+3] + 3) & -4; + } + memcpy (oprof+32, pbody, sizeof pbody); + oprof[pbody[5]/4+2] = strlen(name[output_color-1]) + 1; + memcpy ((char *)oprof+pbody[8]+8, pwhite, sizeof pwhite); + if (output_bps == 8 || gamma_16bit) +#ifdef SRGB_GAMMA + pcurve[3] = 0x2330000; +#else + pcurve[3] = 0x1f00000; +#endif + for (i=4; i < 7; i++) + memcpy ((char *)oprof+pbody[i*3+2], pcurve, sizeof pcurve); + pseudoinverse ((double (*)[3]) out_rgb[output_color-1], inverse, 3); + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) { + for (num = k=0; k < 3; k++) + num += xyzd50_srgb[i][k] * inverse[j][k]; + oprof[pbody[j*3+23]/4+i+2] = num * 0x10000 + 0.5; + } + for (i=0; i < phead[0]/4; i++) + oprof[i] = htonl(oprof[i]); + strcpy ((char *)oprof+pbody[2]+8, "auto-generated by dcraw"); + strcpy ((char *)oprof+pbody[5]+12, name[output_color-1]); + for (i=0; i < 3; i++) + for (j=0; j < colors; j++) + for (out_cam[i][j] = k=0; k < 3; k++) + out_cam[i][j] += out_rgb[output_color-1][i][k] * rgb_cam[k][j]; + } +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr, raw_color ? _("Building histograms...\n") : + _("Converting to %s colorspace...\n"), name[output_color-1]); + +#endif +#ifdef LIBRAW_LIBRARY_BUILD + memset(histogram,0,sizeof(int)*LIBRAW_HISTOGRAM_SIZE*4); +#else + memset (histogram, 0, sizeof histogram); +#endif + for (img=image[0], row=0; row < height; row++) + for (col=0; col < width; col++, img+=4) { + if (!raw_color) { + out[0] = out[1] = out[2] = 0; + FORCC { + out[0] += out_cam[0][c] * img[c]; + out[1] += out_cam[1][c] * img[c]; + out[2] += out_cam[2][c] * img[c]; + } + FORC3 img[c] = CLIP((int) out[c]); + } + else if (document_mode) + img[0] = img[FC(row,col)]; + FORCC histogram[c][img[c] >> 3]++; + } + if (colors == 4 && output_color) colors = 3; + if (document_mode && filters) colors = 1; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB,1,2); +#endif +} + +void CLASS fuji_rotate() +{ + int i, row, col; + double step; + float r, c, fr, fc; + unsigned ur, uc; + ushort wide, high, (*img)[4], (*pix)[4]; + + if (!fuji_width) return; +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr,_("Rotating image 45 degrees...\n")); +#endif + fuji_width = (fuji_width - 1 + shrink) >> shrink; + step = sqrt(0.5); + wide = fuji_width / step; + high = (height - fuji_width) / step; + img = (ushort (*)[4]) calloc (wide*high, sizeof *img); + merror (img, "fuji_rotate()"); + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE,0,2); +#endif + + for (row=0; row < high; row++) + for (col=0; col < wide; col++) { + ur = r = fuji_width + (row-col)*step; + uc = c = (row+col)*step; + if (ur > height-2 || uc > width-2) continue; + fr = r - ur; + fc = c - uc; + pix = image + ur*width + uc; + for (i=0; i < colors; i++) + img[row*wide+col][i] = + (pix[ 0][i]*(1-fc) + pix[ 1][i]*fc) * (1-fr) + + (pix[width][i]*(1-fc) + pix[width+1][i]*fc) * fr; + } + free (image); + width = wide; + height = high; + image = img; + fuji_width = 0; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE,1,2); +#endif +} + +void CLASS stretch() +{ + ushort newdim, (*img)[4], *pix0, *pix1; + int row, col, c; + double rc, frac; + + if (pixel_aspect == 1) return; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_STRETCH,0,2); +#endif +#ifdef DCRAW_VERBOSE + if (verbose) fprintf (stderr,_("Stretching the image...\n")); +#endif + if (pixel_aspect < 1) { + newdim = height / pixel_aspect + 0.5; + img = (ushort (*)[4]) calloc (width*newdim, sizeof *img); + merror (img, "stretch()"); + for (rc=row=0; row < newdim; row++, rc+=pixel_aspect) { + frac = rc - (c = rc); + pix0 = pix1 = image[c*width]; + if (c+1 < height) pix1 += width*4; + for (col=0; col < width; col++, pix0+=4, pix1+=4) + FORCC img[row*width+col][c] = pix0[c]*(1-frac) + pix1[c]*frac + 0.5; + } + height = newdim; + } else { + newdim = width * pixel_aspect + 0.5; + img = (ushort (*)[4]) calloc (height*newdim, sizeof *img); + merror (img, "stretch()"); + for (rc=col=0; col < newdim; col++, rc+=1/pixel_aspect) { + frac = rc - (c = rc); + pix0 = pix1 = image[c]; + if (c+1 < width) pix1 += 4; + for (row=0; row < height; row++, pix0+=width*4, pix1+=width*4) + FORCC img[row*newdim+col][c] = pix0[c]*(1-frac) + pix1[c]*frac + 0.5; + } + width = newdim; + } + free (image); + image = img; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_STRETCH,1,2); +#endif +} + +int CLASS flip_index (int row, int col) +{ + if (flip & 4) SWAP(row,col); + if (flip & 2) row = iheight - 1 - row; + if (flip & 1) col = iwidth - 1 - col; + return row * iwidth + col; +} + +void CLASS gamma_lut (ushort lut[0x10000]) +{ + int perc, c, val, total, i; + float t_white=0, r; + + perc = width * height * 0.01; /* 99th percentile white level */ + if (fuji_width) perc /= 2; + if ((highlight & ~2) || no_auto_bright) perc = -1; + FORCC { + for (val=0x2000, total=0; --val > 32; ) + if ((total += histogram[c][val]) > perc) break; + if (t_white < val) t_white = val; + } + t_white *= 8 / bright; + for (i=0; i < 0x10000; i++) { + r = i / t_white; + val = 65536 * ( !use_gamma ? r : +#ifdef SRGB_GAMMA + r <= 0.00304 ? r*12.92 : pow((double)r,2.5/6)*1.055-0.055 ); +#else + r <= 0.018 ? r*4.5 : pow((double)r,0.45)*1.099-0.099 ); +#endif + if (val > 65535) val = 65535; + lut[i] = val; + } +} + + +#line 9453 "dcraw/dcraw.c" +void CLASS tiff_set (ushort *ntag, + ushort tag, ushort type, int count, int val) +{ + struct tiff_tag *tt; + int c; + + tt = (struct tiff_tag *)(ntag+1) + (*ntag)++; + tt->tag = tag; + tt->type = type; + tt->count = count; + if (type < 3 && count <= 4) + FORC(4) tt->val.c[c] = val >> (c << 3); + else if (type == 3 && count <= 2) + FORC(2) tt->val.s[c] = val >> (c << 4); + else tt->val.i = val; +} + +#define TOFF(ptr) ((char *)(&(ptr)) - (char *)th) + +void CLASS tiff_head (struct tiff_hdr *th, int full) +{ + int c, psize=0; + struct tm *t; + + memset (th, 0, sizeof *th); + th->t_order = htonl(0x4d4d4949) >> 16; + th->magic = 42; + th->ifd = 10; + if (full) { + tiff_set (&th->ntag, 254, 4, 1, 0); + tiff_set (&th->ntag, 256, 4, 1, width); + tiff_set (&th->ntag, 257, 4, 1, height); + tiff_set (&th->ntag, 258, 3, colors, output_bps); + if (colors > 2) + th->tag[th->ntag-1].val.i = TOFF(th->bps); + FORC4 th->bps[c] = output_bps; + tiff_set (&th->ntag, 259, 3, 1, 1); + tiff_set (&th->ntag, 262, 3, 1, 1 + (colors > 1)); + } + tiff_set (&th->ntag, 270, 2, 512, TOFF(th->t_desc)); + tiff_set (&th->ntag, 271, 2, 64, TOFF(th->t_make)); + tiff_set (&th->ntag, 272, 2, 64, TOFF(th->t_model)); + if (full) { + if (oprof) psize = ntohl(oprof[0]); + tiff_set (&th->ntag, 273, 4, 1, sizeof *th + psize); + tiff_set (&th->ntag, 277, 3, 1, colors); + tiff_set (&th->ntag, 278, 4, 1, height); + tiff_set (&th->ntag, 279, 4, 1, height*width*colors*output_bps/8); + } else + tiff_set (&th->ntag, 274, 3, 1, "12435867"[flip]-'0'); + tiff_set (&th->ntag, 282, 5, 1, TOFF(th->rat[0])); + tiff_set (&th->ntag, 283, 5, 1, TOFF(th->rat[2])); + tiff_set (&th->ntag, 284, 3, 1, 1); + tiff_set (&th->ntag, 296, 3, 1, 2); + tiff_set (&th->ntag, 305, 2, 32, TOFF(th->soft)); + tiff_set (&th->ntag, 306, 2, 20, TOFF(th->date)); + tiff_set (&th->ntag, 315, 2, 64, TOFF(th->t_artist)); + tiff_set (&th->ntag, 34665, 4, 1, TOFF(th->nexif)); + if (psize) tiff_set (&th->ntag, 34675, 7, psize, sizeof *th); + tiff_set (&th->nexif, 33434, 5, 1, TOFF(th->rat[4])); + tiff_set (&th->nexif, 33437, 5, 1, TOFF(th->rat[6])); + tiff_set (&th->nexif, 34855, 3, 1, iso_speed); + tiff_set (&th->nexif, 37386, 5, 1, TOFF(th->rat[8])); + if (gpsdata[1]) { + tiff_set (&th->ntag, 34853, 4, 1, TOFF(th->ngps)); + tiff_set (&th->ngps, 0, 1, 4, 0x202); + tiff_set (&th->ngps, 1, 2, 2, gpsdata[29]); + tiff_set (&th->ngps, 2, 5, 3, TOFF(th->gps[0])); + tiff_set (&th->ngps, 3, 2, 2, gpsdata[30]); + tiff_set (&th->ngps, 4, 5, 3, TOFF(th->gps[6])); + tiff_set (&th->ngps, 5, 1, 1, gpsdata[31]); + tiff_set (&th->ngps, 6, 5, 1, TOFF(th->gps[18])); + tiff_set (&th->ngps, 7, 5, 3, TOFF(th->gps[12])); + tiff_set (&th->ngps, 18, 2, 12, TOFF(th->gps[20])); + tiff_set (&th->ngps, 29, 2, 12, TOFF(th->gps[23])); + memcpy (th->gps, gpsdata, sizeof th->gps); + } + th->rat[0] = th->rat[2] = 300; + th->rat[1] = th->rat[3] = 1; + FORC(6) th->rat[4+c] = 1000000; + th->rat[4] *= shutter; + th->rat[6] *= aperture; + th->rat[8] *= focal_len; + strncpy (th->t_desc, desc, 512); + strncpy (th->t_make, make, 64); + strncpy (th->t_model, model, 64); + strcpy (th->soft, "dcraw v"VERSION); + t = gmtime (×tamp); + sprintf (th->date, "%04d:%02d:%02d %02d:%02d:%02d", + t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec); + strncpy (th->t_artist, artist, 64); +} + +void CLASS jpeg_thumb_writer (FILE *tfp,char *t_humb,int t_humb_length) +{ + ushort exif[5]; + struct tiff_hdr th; + fputc (0xff, tfp); + fputc (0xd8, tfp); + if (strcmp (t_humb+6, "Exif")) { + memcpy (exif, "\xff\xe1 Exif\0\0", 10); + exif[1] = htons (8 + sizeof th); + fwrite (exif, 1, sizeof exif, tfp); + tiff_head (&th, 0); + fwrite (&th, 1, sizeof th, tfp); + } + fwrite (t_humb+2, 1, t_humb_length-2, tfp); +} + + +void CLASS jpeg_thumb (FILE *tfp) +{ + char *thumb; + ushort exif[5]; + struct tiff_hdr th; + + thumb = (char *) malloc (thumb_length); + merror (thumb, "jpeg_thumb()"); + fread (thumb, 1, thumb_length, ifp); +#if 0 + fputc (0xff, tfp); + fputc (0xd8, tfp); + if (strcmp (thumb+6, "Exif")) { + memcpy (exif, "\xff\xe1 Exif\0\0", 10); + exif[1] = htons (8 + sizeof th); + fwrite (exif, 1, sizeof exif, tfp); + tiff_head (&th, 0); + fwrite (&th, 1, sizeof th, tfp); + } + fwrite (thumb+2, 1, thumb_length-2, tfp); +#else + jpeg_thumb_writer(tfp,thumb,thumb_length); +#endif + free (thumb); +} + +void CLASS write_ppm_tiff (FILE *ofp) +{ + struct tiff_hdr th; + uchar *ppm; + ushort *ppm2,lut16[0x10000]; + int c, row, col, soff, rstep, cstep; + + iheight = height; + iwidth = width; + if (flip & 4) SWAP(height,width); + ppm = (uchar *) calloc (width, colors*output_bps/8); + ppm2 = (ushort *) ppm; + merror (ppm, "write_ppm_tiff()"); + if (output_tiff) { + tiff_head (&th, 1); + fwrite (&th, sizeof th, 1, ofp); + if (oprof) + fwrite (oprof, ntohl(oprof[0]), 1, ofp); + } else if (colors > 3) + fprintf (ofp, + "P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\nTUPLTYPE %s\nENDHDR\n", + width, height, colors, (1 << output_bps)-1, cdesc); + else + fprintf (ofp, "P%d\n%d %d\n%d\n", + colors/2+5, width, height, (1 << output_bps)-1); + + if (output_bps == 8 || gamma_16bit ) gamma_lut (lut16); + + soff = flip_index (0, 0); + cstep = flip_index (0, 1) - soff; + rstep = flip_index (1, 0) - flip_index (0, width); + for (row=0; row < height; row++, soff += rstep) { + for (col=0; col < width; col++, soff += cstep) + if (output_bps == 8) + FORCC ppm [col*colors+c] = lut16[image[soff][c]]/256; + else if(gamma_16bit) FORCC ppm2[col*colors+c] = lut16[image[soff][c]]; + else FORCC ppm2[col*colors+c] = image[soff][c]; + if (output_bps == 16 && !output_tiff && htons(0x55aa) != 0x55aa) + swab ((char*)ppm2, (char*)ppm2, width*colors*2); + fwrite (ppm, colors*output_bps/8, width, ofp); + } + free (ppm); +} diff --git a/src/LibRaw/internal/dcraw_fileio.cpp b/src/LibRaw/internal/dcraw_fileio.cpp new file mode 100644 index 000000000000..284ac31a77af --- /dev/null +++ b/src/LibRaw/internal/dcraw_fileio.cpp @@ -0,0 +1,215 @@ +/* + GENERATED FILE, DO NOT EDIT + Generated from dcraw/dcraw.c at Sat Feb 7 20:23:45 2009 + Look into original file (probably http://cybercom.net/~dcoffin/dcraw/dcraw.c) + for copyright information. +*/ + +#line 3971 "dcraw/dcraw.c" +#define CLASS LibRaw:: +#include "libraw/libraw_types.h" +#define LIBRAW_LIBRARY_BUILD +#include "libraw/libraw.h" +#include "internal/defines.h" +#include "internal/var_defines.h" +#line 3981 "dcraw/dcraw.c" + +/* + Seach from the current directory up to the root looking for + a ".badpixels" file, and fix those pixels now. + */ +void CLASS bad_pixels (char *fname) +{ + FILE *fp=0; + char *cp, line[128]; + int len, time, row, col, r, c, rad, tot, n, fixed=0; + + if (!filters) return; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,0,2); +#endif + if (fname) + fp = fopen (fname, "r"); +#line 4024 "dcraw/dcraw.c" + if (!fp) + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_NO_BADPIXELMAP; +#endif + return; + } + while (fgets (line, 128, fp)) { + cp = strchr (line, '#'); + if (cp) *cp = 0; + if (sscanf (line, "%d %d %d", &col, &row, &time) != 3) continue; + if ((unsigned) col >= width || (unsigned) row >= height) continue; + if (time > timestamp) continue; + for (tot=n=0, rad=1; rad < 3 && n==0; rad++) + for (r = row-rad; r <= row+rad; r++) + for (c = col-rad; c <= col+rad; c++) + if ((unsigned) r < height && (unsigned) c < width && + (r != row || c != col) && fc(r,c) == fc(row,col)) { + tot += BAYER2(r,c); + n++; + } + BAYER2(row,col) = tot/n; +#ifdef DCRAW_VERBOSE + if (verbose) { + if (!fixed++) + fprintf (stderr,_("Fixed dead pixels at:")); + fprintf (stderr, " %d,%d", col, row); + } +#endif + } +#ifdef DCRAW_VERBOSE + if (fixed) fputc ('\n', stderr); +#endif + fclose (fp); +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_BAD_PIXELS,1,2); +#endif +} + +void CLASS subtract (char *fname) +{ + FILE *fp; + int dim[3]={0,0,0}, comment=0, number=0, error=0, nd=0, c, row, col; + ushort *pixel; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_DARK_FRAME,0,2); +#endif + + if (!(fp = fopen (fname, "rb"))) { +#ifdef DCRAW_VERBOSE + perror (fname); +#endif +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_BAD_DARKFRAME_FILE; +#endif + return; + } + if (fgetc(fp) != 'P' || fgetc(fp) != '5') error = 1; + while (!error && nd < 3 && (c = fgetc(fp)) != EOF) { + if (c == '#') comment = 1; + if (c == '\n') comment = 0; + if (comment) continue; + if (isdigit(c)) number = 1; + if (number) { + if (isdigit(c)) dim[nd] = dim[nd]*10 + c -'0'; + else if (isspace(c)) { + number = 0; nd++; + } else error = 1; + } + } + if (error || nd < 3) { + fprintf (stderr,_("%s is not a valid PGM file!\n"), fname); + fclose (fp); return; + } else if (dim[0] != width || dim[1] != height || dim[2] != 65535) { +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s has the wrong dimensions!\n"), fname); +#endif +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_BAD_DARKFRAME_DIM; +#endif + fclose (fp); return; + } + pixel = (ushort *) calloc (width, sizeof *pixel); + merror (pixel, "subtract()"); + for (row=0; row < height; row++) { + fread (pixel, 2, width, fp); + for (col=0; col < width; col++) + BAYER(row,col) = MAX (BAYER(row,col) - ntohs(pixel[col]), 0); + } + free (pixel); + black = 0; +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_DARK_FRAME,1,2); +#endif +} +#line 9088 "dcraw/dcraw.c" + +#ifndef NO_LCMS +void CLASS apply_profile (char *input, char *output) +{ + char *prof; + cmsHPROFILE hInProfile=0, hOutProfile=0; + cmsHTRANSFORM hTransform; + FILE *fp; + unsigned size; + + cmsErrorAction (LCMS_ERROR_SHOW); + if (strcmp (input, "embed")) + hInProfile = cmsOpenProfileFromFile (input, "r"); + else if (profile_length) { +#ifndef LIBRAW_LIBRARY_BUILD + prof = (char *) malloc (profile_length); + merror (prof, "apply_profile()"); + fseek (ifp, profile_offset, SEEK_SET); + fread (prof, 1, profile_length, ifp); + hInProfile = cmsOpenProfileFromMem (prof, profile_length); + free (prof); +#else + hInProfile = cmsOpenProfileFromMem (imgdata.color.profile, profile_length); +#endif + } else + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_NO_EMBEDDED_PROFILE; +#endif +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s has no embedded profile.\n"), ifname); +#endif + } + if (!hInProfile) + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_NO_INPUT_PROFILE; +#endif + return; + } + if (!output) + hOutProfile = cmsCreate_sRGBProfile(); + else if ((fp = fopen (output, "rb"))) { + fread (&size, 4, 1, fp); + fseek (fp, 0, SEEK_SET); + oprof = (unsigned *) malloc (size = ntohl(size)); + merror (oprof, "apply_profile()"); + fread (oprof, 1, size, fp); + fclose (fp); + if (!(hOutProfile = cmsOpenProfileFromMem (oprof, size))) { + free (oprof); + oprof = 0; + } +#ifdef DCRAW_VERBOSE + } else + fprintf (stderr,_("Cannot open file %s!\n"), output); +#else +} +#endif + if (!hOutProfile) + { +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_BAD_OUTPUT_PROFILE; +#endif + goto quit; + } +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf (stderr,_("Applying color profile...\n")); +#endif +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_APPLY_PROFILE,0,2); +#endif + hTransform = cmsCreateTransform (hInProfile, TYPE_RGBA_16, + hOutProfile, TYPE_RGBA_16, INTENT_PERCEPTUAL, 0); + cmsDoTransform (hTransform, image, image, width*height); + raw_color = 1; /* Don't use rgb_cam with a profile */ + cmsDeleteTransform (hTransform); + cmsCloseProfile (hOutProfile); +quit: + cmsCloseProfile (hInProfile); +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_APPLY_PROFILE,1,2); +#endif +} +#endif diff --git a/src/LibRaw/internal/defines.h b/src/LibRaw/internal/defines.h new file mode 100644 index 000000000000..ff5def5edd44 --- /dev/null +++ b/src/LibRaw/internal/defines.h @@ -0,0 +1,134 @@ +/* + GENERATED FILE, DO NOT EDIT + Generated from dcraw/dcraw.c at Sat Feb 7 20:23:43 2009 + Look into original file (probably http://cybercom.net/~dcoffin/dcraw/dcraw.c) + for copyright information. +*/ + +#line 27 "dcraw/dcraw.c" +#define NO_JPEG +#line 32 "dcraw/dcraw.c" +#define VERSION "8.91" + +#define _GNU_SOURCE +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _OPENMP +#include +#endif +/* + NO_JPEG disables decoding of compressed Kodak DC120 files. + NO_LCMS disables the "-p" option. + */ +#ifndef NO_JPEG +#include +#endif +#ifndef NO_LCMS +#include +#endif +#ifdef LOCALEDIR +#include +#define _(String) gettext(String) +#else +#define _(String) (String) +#endif +#line 75 "dcraw/dcraw.c" +#ifdef __CYGWIN__ +#include +#endif +#ifdef WIN32 +#include +#include +#pragma comment(lib, "ws2_32.lib") +#define snprintf _snprintf +#define strcasecmp _stricmp +#define strncasecmp strnicmp +typedef __int64 INT64; +typedef unsigned __int64 UINT64; +#else +#include +#include +#include +typedef long long INT64; +typedef unsigned long long UINT64; +#endif + +#ifdef LJPEG_DECODE +#error Please compile dcraw.c by itself. +#error Do not link it with ljpeg_decode. +#endif + +#ifndef LONG_BIT +#define LONG_BIT (8 * sizeof (long)) +#endif +#line 169 "dcraw/dcraw.c" +#define FORC(cnt) for (c=0; c < cnt; c++) +#define FORC3 FORC(3) +#define FORC4 FORC(4) +#define FORCC FORC(colors) + +#define SQR(x) ((x)*(x)) +#define ABS(x) (((int)(x) ^ ((int)(x) >> 31)) - ((int)(x) >> 31)) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define LIM(x,min,max) MAX(min,MIN(x,max)) +#define ULIM(x,y,z) ((y) < (z) ? LIM(x,y,z) : LIM(x,z,y)) +#define CLIP(x) LIM(x,0,65535) +#define SWAP(a,b) { a ^= b; a ^= (b ^= a); } + +/* + In order to inline this calculation, I make the risky + assumption that all filter patterns can be described + by a repeating pattern of eight rows and two columns + + Do not use the FC or BAYER macros with the Leaf CatchLight, + because its pattern is 16x16, not 2x8. + + Return values are either 0/1/2/3 = G/M/C/Y or 0/1/2/3 = R/G1/B/G2 + + PowerShot 600 PowerShot A50 PowerShot Pro70 Pro90 & G1 + 0xe1e4e1e4: 0x1b4e4b1e: 0x1e4b4e1b: 0xb4b4b4b4: + + 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 + 0 G M G M G M 0 C Y C Y C Y 0 Y C Y C Y C 0 G M G M G M + 1 C Y C Y C Y 1 M G M G M G 1 M G M G M G 1 Y C Y C Y C + 2 M G M G M G 2 Y C Y C Y C 2 C Y C Y C Y + 3 C Y C Y C Y 3 G M G M G M 3 G M G M G M + 4 C Y C Y C Y 4 Y C Y C Y C + PowerShot A5 5 G M G M G M 5 G M G M G M + 0x1e4e1e4e: 6 Y C Y C Y C 6 C Y C Y C Y + 7 M G M G M G 7 M G M G M G + 0 1 2 3 4 5 + 0 C Y C Y C Y + 1 G M G M G M + 2 C Y C Y C Y + 3 M G M G M G + + All RGB cameras use one of these Bayer grids: + + 0x16161616: 0x61616161: 0x49494949: 0x94949494: + + 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 + 0 B G B G B G 0 G R G R G R 0 G B G B G B 0 R G R G R G + 1 G R G R G R 1 B G B G B G 1 R G R G R G 1 G B G B G B + 2 B G B G B G 2 G R G R G R 2 G B G B G B 2 R G R G R G + 3 G R G R G R 3 B G B G B G 3 R G R G R G 3 G B G B G B + */ + +#line 227 "dcraw/dcraw.c" +#define BAYER(row,col) \ + image[((row) >> shrink)*iwidth + ((col) >> shrink)][FC(row,col)] + +#define BAYER2(row,col) \ + image[((row) >> shrink)*iwidth + ((col) >> shrink)][fc(row,col)] diff --git a/src/LibRaw/internal/foveon.cpp b/src/LibRaw/internal/foveon.cpp new file mode 100644 index 000000000000..521d28a00b9e --- /dev/null +++ b/src/LibRaw/internal/foveon.cpp @@ -0,0 +1,817 @@ +/* + GENERATED FILE, DO NOT EDIT + Generated from dcraw/dcraw.c at Sat Feb 7 20:23:43 2009 + Look into original file (probably http://cybercom.net/~dcoffin/dcraw/dcraw.c) + for copyright information. +*/ + +#line 3250 "dcraw/dcraw.c" +#define CLASS LibRaw:: +#include "libraw/libraw_types.h" +#define LIBRAW_IO_REDEFINED +#define LIBRAW_LIBRARY_BUILD +#include "libraw/libraw.h" +#include "internal/defines.h" +#define SRC_USES_SHRINK +#define SRC_USES_BLACK +#define SRC_USES_CURVE +#include "internal/var_defines.h" +#define sget4(s) sget4((uchar *)s) +#line 3263 "dcraw/dcraw.c" + +/* RESTRICTED code starts here */ + +void CLASS foveon_decoder (unsigned size, unsigned code) +{ +#ifndef LIBRAW_NOTHREADS +#define huff tls->foveon_decoder_huff +#else + static unsigned huff[1024]; +#endif + struct decode *cur; + int i, len; + + if (!code) { + for (i=0; i < size; i++) + huff[i] = get4(); + init_decoder(); + } + cur = free_decode++; + if (free_decode > first_decode+2048) { +#ifdef LIBRAW_LIBRARY_BUILD + throw LIBRAW_EXCEPTION_DECODE_RAW; +#else + fprintf (stderr,_("%s: decoder table overflow\n"), ifname); + longjmp (failure, 2); +#endif + } + if (code) + for (i=0; i < size; i++) + if (huff[i] == code) { + cur->leaf = i; + return; + } + if ((len = code >> 27) > 26) return; + code = (len+1) << 27 | (code & 0x3ffffff) << 1; + + cur->branch[0] = free_decode; + foveon_decoder (size, code); + cur->branch[1] = free_decode; + foveon_decoder (size, code+1); +#ifndef LIBRAW_NOTHREADS +#undef huff +#endif +} + +void CLASS foveon_thumb (FILE *tfp) +{ + unsigned bwide, row, col, bitbuf=0, bit=1, c, i; + char *buf; + struct decode *dindex; + short pred[3]; + + bwide = get4(); + fprintf (tfp, "P6\n%d %d\n255\n", thumb_width, thumb_height); + if (bwide > 0) { + if (bwide < thumb_width*3) return; + buf = (char *) malloc (bwide); + merror (buf, "foveon_thumb()"); + for (row=0; row < thumb_height; row++) { + fread (buf, 1, bwide, ifp); + fwrite (buf, 3, thumb_width, tfp); + } + free (buf); + return; + } + foveon_decoder (256, 0); + + for (row=0; row < thumb_height; row++) { + memset (pred, 0, sizeof pred); + if (!bit) get4(); + for (bit=col=0; col < thumb_width; col++) + FORC3 { + for (dindex=first_decode; dindex->branch[0]; ) { + if ((bit = (bit-1) & 31) == 31) + for (i=0; i < 4; i++) + bitbuf = (bitbuf << 8) + fgetc(ifp); + dindex = dindex->branch[bitbuf >> bit & 1]; + } + pred[c] += dindex->leaf; + fputc (pred[c], tfp); + } + } +} + +void CLASS foveon_load_camf() +{ + unsigned key, i, val; + + fseek (ifp, meta_offset, SEEK_SET); + key = get4(); + fread (meta_data, 1, meta_length, ifp); + for (i=0; i < meta_length; i++) { + key = (key * 1597 + 51749) % 244944; + val = key * (INT64) 301593171 >> 24; + meta_data[i] ^= ((((key << 8) - val) >> 1) + val) >> 17; + } +} + +void CLASS foveon_load_raw() +{ + struct decode *dindex; + short diff[1024]; + unsigned bitbuf=0; + int pred[3], fixed, row, col, bit=-1, c, i; + + fixed = get4(); + read_shorts ((ushort *) diff, 1024); + if (!fixed) foveon_decoder (1024, 0); + + for (row=0; row < height; row++) { + memset (pred, 0, sizeof pred); + if (!bit && !fixed && atoi(model+2) < 14) get4(); + for (col=bit=0; col < width; col++) { + if (fixed) { + bitbuf = get4(); + FORC3 pred[2-c] += diff[bitbuf >> c*10 & 0x3ff]; + } + else FORC3 { + for (dindex=first_decode; dindex->branch[0]; ) { + if ((bit = (bit-1) & 31) == 31) + for (i=0; i < 4; i++) + bitbuf = (bitbuf << 8) + fgetc(ifp); + dindex = dindex->branch[bitbuf >> bit & 1]; + } + pred[c] += diff[dindex->leaf]; + if (pred[c] >> 16 && ~pred[c] >> 16) derror(); + } + FORC3 image[row*width+col][c] = pred[c]; + } + } + if (document_mode) + for (i=0; i < height*width*4; i++) + if ((short) image[0][i] < 0) image[0][i] = 0; + foveon_load_camf(); +} + +const char * CLASS foveon_camf_param (const char *block, const char *param) +{ + unsigned idx, num; + char *pos, *cp, *dp; + + for (idx=0; idx < meta_length; idx += sget4(pos+8)) { + pos = meta_data + idx; + if (strncmp (pos, "CMb", 3)) break; + if (pos[3] != 'P') continue; + if (strcmp (block, pos+sget4(pos+12))) continue; + cp = pos + sget4(pos+16); + num = sget4(cp); + dp = pos + sget4(cp+4); + while (num--) { + cp += 8; + if (!strcmp (param, dp+sget4(cp))) + return dp+sget4(cp+4); + } + } + return 0; +} + +void * CLASS foveon_camf_matrix (unsigned dim[3], const char *name) +{ + unsigned i, idx, type, ndim, size, *mat; + char *pos, *cp, *dp; + double dsize; + + for (idx=0; idx < meta_length; idx += sget4(pos+8)) { + pos = meta_data + idx; + if (strncmp (pos, "CMb", 3)) break; + if (pos[3] != 'M') continue; + if (strcmp (name, pos+sget4(pos+12))) continue; + dim[0] = dim[1] = dim[2] = 1; + cp = pos + sget4(pos+16); + type = sget4(cp); + if ((ndim = sget4(cp+4)) > 3) break; + dp = pos + sget4(cp+8); + for (i=ndim; i--; ) { + cp += 12; + dim[i] = sget4(cp); + } + if ((dsize = (double) dim[0]*dim[1]*dim[2]) > meta_length/4) break; + mat = (unsigned *) malloc ((size = dsize) * 4); + merror (mat, "foveon_camf_matrix()"); + for (i=0; i < size; i++) + if (type && type != 6) + mat[i] = sget4(dp + i*4); + else + mat[i] = sget4(dp + i*2) & 0xffff; + return mat; + } +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_FOVEON_NOMATRIX; +#endif +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s: \"%s\" matrix not found!\n"), ifname, name); +#endif + return 0; +} + +int CLASS foveon_fixed (void *ptr, int size, const char *name) +{ + void *dp; + unsigned dim[3]; + + dp = foveon_camf_matrix (dim, name); + if (!dp) return 0; + memcpy (ptr, dp, size*4); + free (dp); + return 1; +} + +float CLASS foveon_avg (short *pix, int range[2], float cfilt) +{ + int i; + float val, min=FLT_MAX, max=-FLT_MAX, sum=0; + + for (i=range[0]; i <= range[1]; i++) { + sum += val = pix[i*4] + (pix[i*4]-pix[(i-1)*4]) * cfilt; + if (min > val) min = val; + if (max < val) max = val; + } + if (range[1] - range[0] == 1) return sum/2; + return (sum - min - max) / (range[1] - range[0] - 1); +} + +short * CLASS foveon_make_curve (double max, double mul, double filt) +{ + short *curve; + unsigned i, size; + double x; + + if (!filt) filt = 0.8; + size = 4*M_PI*max / filt; + if (size == UINT_MAX) size--; + curve = (short *) calloc (size+1, sizeof *curve); + merror (curve, "foveon_make_curve()"); + curve[0] = size; + for (i=0; i < size; i++) { + x = i*filt/max/4; + curve[i+1] = (cos(x)+1)/2 * tanh(i*filt/mul) * mul + 0.5; + } + return curve; +} + +void CLASS foveon_make_curves + (short **curvep, float dq[3], float div[3], float filt) +{ + double mul[3], max=0; + int c; + + FORC3 mul[c] = dq[c]/div[c]; + FORC3 if (max < mul[c]) max = mul[c]; + FORC3 curvep[c] = foveon_make_curve (max, mul[c], filt); +} + +int CLASS foveon_apply_curve (short *curve, int i) +{ + if (abs(i) >= curve[0]) return 0; + return i < 0 ? -curve[1-i] : curve[1+i]; +} + +#line 3525 "dcraw/dcraw.c" +#ifdef image +#undef image +#endif +#define image ((short(*)[4]) imgdata.image) +#line 3532 "dcraw/dcraw.c" + +void CLASS foveon_interpolate() +{ + static const short hood[] = { -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 }; + short *pix, prev[3], *curve[8], (*shrink)[3]; + float cfilt=0, ddft[3][3][2], ppm[3][3][3]; + float cam_xyz[3][3], correct[3][3], last[3][3], trans[3][3]; + float chroma_dq[3], color_dq[3], diag[3][3], div[3]; + float (*black)[3], (*sgain)[3], (*sgrow)[3]; + float fsum[3], val, frow, num; + int row, col, c, i, j, diff, sgx, irow, sum, min, max, limit; + int dscr[2][2], dstb[4], (*smrow[7])[3], total[4], ipix[3]; + int work[3][3], smlast, smred, smred_p=0, dev[3]; + int satlev[3], keep[4], active[4]; + unsigned dim[3], *badpix; + double dsum=0, trsum[3]; + char str[128]; + const char* cp; + + +#ifdef DCRAW_VERBOSE + if (verbose) + fprintf(stderr,_("Foveon interpolation...\n")); +#endif +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,0,9); +#endif + + foveon_fixed (dscr, 4, "DarkShieldColRange"); + foveon_fixed (ppm[0][0], 27, "PostPolyMatrix"); + foveon_fixed (satlev, 3, "SaturationLevel"); + foveon_fixed (keep, 4, "KeepImageArea"); + foveon_fixed (active, 4, "ActiveImageArea"); + foveon_fixed (chroma_dq, 3, "ChromaDQ"); + foveon_fixed (color_dq, 3, + foveon_camf_param ("IncludeBlocks", "ColorDQ") ? + "ColorDQ" : "ColorDQCamRGB"); + if (foveon_camf_param ("IncludeBlocks", "ColumnFilter")) + foveon_fixed (&cfilt, 1, "ColumnFilter"); + + memset (ddft, 0, sizeof ddft); + if (!foveon_camf_param ("IncludeBlocks", "DarkDrift") + || !foveon_fixed (ddft[1][0], 12, "DarkDrift")) + for (i=0; i < 2; i++) { + foveon_fixed (dstb, 4, i ? "DarkShieldBottom":"DarkShieldTop"); + for (row = dstb[1]; row <= dstb[3]; row++) + for (col = dstb[0]; col <= dstb[2]; col++) + FORC3 ddft[i+1][c][1] += (short) image[row*width+col][c]; + FORC3 ddft[i+1][c][1] /= (dstb[3]-dstb[1]+1) * (dstb[2]-dstb[0]+1); + } + + if (!(cp = foveon_camf_param ("WhiteBalanceIlluminants", model2))) + { +#ifdef DCRAW_VERBOSE + fprintf (stderr,_("%s: Invalid white balance \"%s\"\n"), ifname, model2); +#endif +#ifdef LIBRAW_LIBRARY_BUILD + imgdata.process_warnings |= LIBRAW_WARN_FOVEON_INVALIDWB; +#endif + return; + } + foveon_fixed (cam_xyz, 9, cp); + foveon_fixed (correct, 9, + foveon_camf_param ("WhiteBalanceCorrections", model2)); + memset (last, 0, sizeof last); + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) + FORC3 last[i][j] += correct[i][c] * cam_xyz[c][j]; + + #define LAST(x,y) last[(i+x)%3][(c+y)%3] + for (i=0; i < 3; i++) + FORC3 diag[c][i] = LAST(1,1)*LAST(2,2) - LAST(1,2)*LAST(2,1); + #undef LAST + FORC3 div[c] = diag[c][0]*0.3127 + diag[c][1]*0.329 + diag[c][2]*0.3583; + sprintf (str, "%sRGBNeutral", model2); + if (foveon_camf_param ("IncludeBlocks", str)) + foveon_fixed (div, 3, str); + num = 0; + FORC3 if (num < div[c]) num = div[c]; + FORC3 div[c] /= num; + + memset (trans, 0, sizeof trans); + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) + FORC3 trans[i][j] += rgb_cam[i][c] * last[c][j] * div[j]; + FORC3 trsum[c] = trans[c][0] + trans[c][1] + trans[c][2]; + dsum = (6*trsum[0] + 11*trsum[1] + 3*trsum[2]) / 20; + for (i=0; i < 3; i++) + FORC3 last[i][c] = trans[i][c] * dsum / trsum[i]; + memset (trans, 0, sizeof trans); + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) + FORC3 trans[i][j] += (i==c ? 32 : -1) * last[c][j] / 30; + + foveon_make_curves (curve, color_dq, div, cfilt); + FORC3 chroma_dq[c] /= 3; + foveon_make_curves (curve+3, chroma_dq, div, cfilt); + FORC3 dsum += chroma_dq[c] / div[c]; + curve[6] = foveon_make_curve (dsum, dsum, cfilt); + curve[7] = foveon_make_curve (dsum*2, dsum*2, cfilt); + + sgain = (float (*)[3]) foveon_camf_matrix (dim, "SpatialGain"); + if (!sgain) return; + sgrow = (float (*)[3]) calloc (dim[1], sizeof *sgrow); + sgx = (width + dim[1]-2) / (dim[1]-1); + + black = (float (*)[3]) calloc (height, sizeof *black); +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,1,9); +#endif + for (row=0; row < height; row++) { + for (i=0; i < 6; i++) + ddft[0][0][i] = ddft[1][0][i] + + row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]); + FORC3 black[row][c] = + ( foveon_avg (image[row*width]+c, dscr[0], cfilt) + + foveon_avg (image[row*width]+c, dscr[1], cfilt) * 3 + - ddft[0][c][0] ) / 4 - ddft[0][c][1]; + } + memcpy (black, black+8, sizeof *black*8); + memcpy (black+height-11, black+height-22, 11*sizeof *black); + memcpy (last, black, sizeof last); + + for (row=1; row < height-1; row++) { + FORC3 if (last[1][c] > last[0][c]) { + if (last[1][c] > last[2][c]) + black[row][c] = (last[0][c] > last[2][c]) ? last[0][c]:last[2][c]; + } else + if (last[1][c] < last[2][c]) + black[row][c] = (last[0][c] < last[2][c]) ? last[0][c]:last[2][c]; + memmove (last, last+1, 2*sizeof last[0]); + memcpy (last[2], black[row+1], sizeof last[2]); + } + FORC3 black[row][c] = (last[0][c] + last[1][c])/2; + FORC3 black[0][c] = (black[1][c] + black[3][c])/2; + + val = 1 - exp(-1/24.0); + memcpy (fsum, black, sizeof fsum); + for (row=1; row < height; row++) + FORC3 fsum[c] += black[row][c] = + (black[row][c] - black[row-1][c])*val + black[row-1][c]; + memcpy (last[0], black[height-1], sizeof last[0]); + FORC3 fsum[c] /= height; + for (row = height; row--; ) + FORC3 last[0][c] = black[row][c] = + (black[row][c] - fsum[c] - last[0][c])*val + last[0][c]; + + memset (total, 0, sizeof total); + for (row=2; row < height; row+=4) + for (col=2; col < width; col+=4) { + FORC3 total[c] += (short) image[row*width+col][c]; + total[3]++; + } + for (row=0; row < height; row++) + FORC3 black[row][c] += fsum[c]/2 + total[c]/(total[3]*100.0); + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,2,9); +#endif + for (row=0; row < height; row++) { + for (i=0; i < 6; i++) + ddft[0][0][i] = ddft[1][0][i] + + row / (height-1.0) * (ddft[2][0][i] - ddft[1][0][i]); + pix = image[row*width]; + memcpy (prev, pix, sizeof prev); + frow = row / (height-1.0) * (dim[2]-1); + if ((irow = frow) == dim[2]-1) irow--; + frow -= irow; + for (i=0; i < dim[1]; i++) + FORC3 sgrow[i][c] = sgain[ irow *dim[1]+i][c] * (1-frow) + + sgain[(irow+1)*dim[1]+i][c] * frow; + for (col=0; col < width; col++) { + FORC3 { + diff = pix[c] - prev[c]; + prev[c] = pix[c]; + ipix[c] = pix[c] + floor ((diff + (diff*diff >> 14)) * cfilt + - ddft[0][c][1] - ddft[0][c][0] * ((float) col/width - 0.5) + - black[row][c] ); + } + FORC3 { + work[0][c] = ipix[c] * ipix[c] >> 14; + work[2][c] = ipix[c] * work[0][c] >> 14; + work[1][2-c] = ipix[(c+1) % 3] * ipix[(c+2) % 3] >> 14; + } + FORC3 { + for (val=i=0; i < 3; i++) + for ( j=0; j < 3; j++) + val += ppm[c][i][j] * work[i][j]; + ipix[c] = floor ((ipix[c] + floor(val)) * + ( sgrow[col/sgx ][c] * (sgx - col%sgx) + + sgrow[col/sgx+1][c] * (col%sgx) ) / sgx / div[c]); + if (ipix[c] > 32000) ipix[c] = 32000; + pix[c] = ipix[c]; + } + pix += 4; + } + } + free (black); + free (sgrow); + free (sgain); + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,3,9); +#endif + if ((badpix = (unsigned int *) foveon_camf_matrix (dim, "BadPixels"))) { + for (i=0; i < dim[0]; i++) { + col = (badpix[i] >> 8 & 0xfff) - keep[0]; + row = (badpix[i] >> 20 ) - keep[1]; + if ((unsigned)(row-1) > height-3 || (unsigned)(col-1) > width-3) + continue; + memset (fsum, 0, sizeof fsum); + for (sum=j=0; j < 8; j++) + if (badpix[i] & (1 << j)) { + FORC3 fsum[c] += (short) + image[(row+hood[j*2])*width+col+hood[j*2+1]][c]; + sum++; + } + if (sum) FORC3 image[row*width+col][c] = fsum[c]/sum; + } + free (badpix); + } + + /* Array for 5x5 Gaussian averaging of red values */ + smrow[6] = (int (*)[3]) calloc (width*5, sizeof **smrow); + merror (smrow[6], "foveon_interpolate()"); + for (i=0; i < 5; i++) + smrow[i] = smrow[6] + i*width; + + /* Sharpen the reds against these Gaussian averages */ + for (smlast=-1, row=2; row < height-2; row++) { + while (smlast < row+2) { + for (i=0; i < 6; i++) + smrow[(i+5) % 6] = smrow[i]; + pix = image[++smlast*width+2]; + for (col=2; col < width-2; col++) { + smrow[4][col][0] = + (pix[0]*6 + (pix[-4]+pix[4])*4 + pix[-8]+pix[8] + 8) >> 4; + pix += 4; + } + } + pix = image[row*width+2]; + for (col=2; col < width-2; col++) { + smred = ( 6 * smrow[2][col][0] + + 4 * (smrow[1][col][0] + smrow[3][col][0]) + + smrow[0][col][0] + smrow[4][col][0] + 8 ) >> 4; + if (col == 2) + smred_p = smred; + i = pix[0] + ((pix[0] - ((smred*7 + smred_p) >> 3)) >> 3); + if (i > 32000) i = 32000; + pix[0] = i; + smred_p = smred; + pix += 4; + } + } + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,4,9); +#endif + /* Adjust the brighter pixels for better linearity */ + min = 0xffff; + FORC3 { + i = satlev[c] / div[c]; + if (min > i) min = i; + } + limit = min * 9 >> 4; + for (pix=image[0]; pix < image[height*width]; pix+=4) { + if (pix[0] <= limit || pix[1] <= limit || pix[2] <= limit) + continue; + min = max = pix[0]; + for (c=1; c < 3; c++) { + if (min > pix[c]) min = pix[c]; + if (max < pix[c]) max = pix[c]; + } + if (min >= limit*2) { + pix[0] = pix[1] = pix[2] = max; + } else { + i = 0x4000 - ((min - limit) << 14) / limit; + i = 0x4000 - (i*i >> 14); + i = i*i >> 14; + FORC3 pix[c] += (max - pix[c]) * i >> 14; + } + } +/* + Because photons that miss one detector often hit another, + the sum R+G+B is much less noisy than the individual colors. + So smooth the hues without smoothing the total. + */ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,5,9); +#endif + for (smlast=-1, row=2; row < height-2; row++) { + while (smlast < row+2) { + for (i=0; i < 6; i++) + smrow[(i+5) % 6] = smrow[i]; + pix = image[++smlast*width+2]; + for (col=2; col < width-2; col++) { + FORC3 smrow[4][col][c] = (pix[c-4]+2*pix[c]+pix[c+4]+2) >> 2; + pix += 4; + } + } + pix = image[row*width+2]; + for (col=2; col < width-2; col++) { + FORC3 dev[c] = -foveon_apply_curve (curve[7], pix[c] - + ((smrow[1][col][c] + 2*smrow[2][col][c] + smrow[3][col][c]) >> 2)); + sum = (dev[0] + dev[1] + dev[2]) >> 3; + FORC3 pix[c] += dev[c] - sum; + pix += 4; + } + } + for (smlast=-1, row=2; row < height-2; row++) { + while (smlast < row+2) { + for (i=0; i < 6; i++) + smrow[(i+5) % 6] = smrow[i]; + pix = image[++smlast*width+2]; + for (col=2; col < width-2; col++) { + FORC3 smrow[4][col][c] = + (pix[c-8]+pix[c-4]+pix[c]+pix[c+4]+pix[c+8]+2) >> 2; + pix += 4; + } + } + pix = image[row*width+2]; + for (col=2; col < width-2; col++) { + for (total[3]=375, sum=60, c=0; c < 3; c++) { + for (total[c]=i=0; i < 5; i++) + total[c] += smrow[i][col][c]; + total[3] += total[c]; + sum += pix[c]; + } + if (sum < 0) sum = 0; + j = total[3] > 375 ? (sum << 16) / total[3] : sum * 174; + FORC3 pix[c] += foveon_apply_curve (curve[6], + ((j*total[c] + 0x8000) >> 16) - pix[c]); + pix += 4; + } + } + +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,6,9); +#endif + /* Transform the image to a different colorspace */ + for (pix=image[0]; pix < image[height*width]; pix+=4) { + FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]); + sum = (pix[0]+pix[1]+pix[1]+pix[2]) >> 2; + FORC3 pix[c] -= foveon_apply_curve (curve[c], pix[c]-sum); + FORC3 { + for (dsum=i=0; i < 3; i++) + dsum += trans[c][i] * pix[i]; + if (dsum < 0) dsum = 0; + if (dsum > 24000) dsum = 24000; + ipix[c] = dsum + 0.5; + } + FORC3 pix[c] = ipix[c]; + } + + /* Smooth the image bottom-to-top and save at 1/4 scale */ + shrink = (short (*)[3]) calloc ((width/4) * (height/4), sizeof *shrink); + merror (shrink, "foveon_interpolate()"); + for (row = height/4; row--; ) + for (col=0; col < width/4; col++) { + ipix[0] = ipix[1] = ipix[2] = 0; + for (i=0; i < 4; i++) + for (j=0; j < 4; j++) + FORC3 ipix[c] += image[(row*4+i)*width+col*4+j][c]; + FORC3 + if (row+2 > height/4) + shrink[row*(width/4)+col][c] = ipix[c] >> 4; + else + shrink[row*(width/4)+col][c] = + (shrink[(row+1)*(width/4)+col][c]*1840 + ipix[c]*141 + 2048) >> 12; + } + /* From the 1/4-scale image, smooth right-to-left */ +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,7,9); +#endif + for (row=0; row < (height & ~3); row++) { + ipix[0] = ipix[1] = ipix[2] = 0; + if ((row & 3) == 0) + for (col = width & ~3 ; col--; ) + FORC3 smrow[0][col][c] = ipix[c] = + (shrink[(row/4)*(width/4)+col/4][c]*1485 + ipix[c]*6707 + 4096) >> 13; + + /* Then smooth left-to-right */ + ipix[0] = ipix[1] = ipix[2] = 0; + for (col=0; col < (width & ~3); col++) + FORC3 smrow[1][col][c] = ipix[c] = + (smrow[0][col][c]*1485 + ipix[c]*6707 + 4096) >> 13; + + /* Smooth top-to-bottom */ + if (row == 0) + memcpy (smrow[2], smrow[1], sizeof **smrow * width); + else + for (col=0; col < (width & ~3); col++) + FORC3 smrow[2][col][c] = + (smrow[2][col][c]*6707 + smrow[1][col][c]*1485 + 4096) >> 13; + + /* Adjust the chroma toward the smooth values */ + for (col=0; col < (width & ~3); col++) { + for (i=j=30, c=0; c < 3; c++) { + i += smrow[2][col][c]; + j += image[row*width+col][c]; + } + j = (j << 16) / i; + for (sum=c=0; c < 3; c++) { + ipix[c] = foveon_apply_curve (curve[c+3], + ((smrow[2][col][c] * j + 0x8000) >> 16) - image[row*width+col][c]); + sum += ipix[c]; + } + sum >>= 3; + FORC3 { + i = image[row*width+col][c] + ipix[c] - sum; + if (i < 0) i = 0; + image[row*width+col][c] = i; + } + } + } + free (shrink); + free (smrow[6]); + for (i=0; i < 8; i++) + free (curve[i]); +#ifdef LIBRAW_LIBRARY_BUILD + RUN_CALLBACK(LIBRAW_PROGRESS_FOVEON_INTERPOLATE,8,9); +#endif + + /* Trim off the black border */ + active[1] -= keep[1]; + active[3] -= 2; + i = active[2] - active[0]; + for (row=0; row < active[3]-active[1]; row++) + memcpy (image[row*i], image[(row+active[1])*width+active[0]], + i * sizeof *image); + width = i; + height = row; +} +#undef image + +/* RESTRICTED code ends here */ +#line 7086 "dcraw/dcraw.c" +char * CLASS foveon_gets (int offset, char *str, int len) +{ + int i; + fseek (ifp, offset, SEEK_SET); + for (i=0; i < len-1; i++) + if ((str[i] = get2()) == 0) break; + str[i] = 0; + return str; +} + +void CLASS parse_foveon() +{ + int entries, img=0, off, len, tag, save, i, wide, high, pent, poff[256][2]; + char name[64], value[64]; + + order = 0x4949; /* Little-endian */ + fseek (ifp, 36, SEEK_SET); + flip = get4(); + fseek (ifp, -4, SEEK_END); + fseek (ifp, get4(), SEEK_SET); + if (get4() != 0x64434553) return; /* SECd */ + entries = (get4(),get4()); + while (entries--) { + off = get4(); + len = get4(); + tag = get4(); + save = ftell(ifp); + fseek (ifp, off, SEEK_SET); + if (get4() != (0x20434553 | (tag << 24))) return; + switch (tag) { + case 0x47414d49: /* IMAG */ + case 0x32414d49: /* IMA2 */ + fseek (ifp, 12, SEEK_CUR); + wide = get4(); + high = get4(); + if (wide > raw_width && high > raw_height) { + raw_width = wide; + raw_height = high; + data_offset = off+24; + } + fseek (ifp, off+28, SEEK_SET); + if (fgetc(ifp) == 0xff && fgetc(ifp) == 0xd8 + && thumb_length < len-28) { + thumb_offset = off+28; + thumb_length = len-28; + write_thumb = &CLASS jpeg_thumb; + } + if (++img == 2 && !thumb_length) { + thumb_offset = off+24; + thumb_width = wide; + thumb_height = high; + write_thumb = &CLASS foveon_thumb; + } + break; + case 0x464d4143: /* CAMF */ + meta_offset = off+24; + meta_length = len-28; + if (meta_length > 0x20000) + meta_length = 0x20000; + break; + case 0x504f5250: /* PROP */ + pent = (get4(),get4()); + fseek (ifp, 12, SEEK_CUR); + off += pent*8 + 24; + if ((unsigned) pent > 256) pent=256; + for (i=0; i < pent*2; i++) + poff[0][i] = off + get4()*2; + for (i=0; i < pent; i++) { + foveon_gets (poff[i][0], name, 64); + foveon_gets (poff[i][1], value, 64); + if (!strcmp (name, "ISO")) + iso_speed = atoi(value); + if (!strcmp (name, "CAMMANUF")) + strcpy (make, value); + if (!strcmp (name, "CAMMODEL")) + strcpy (model, value); + if (!strcmp (name, "WB_DESC")) + strcpy (model2, value); + if (!strcmp (name, "TIME")) + timestamp = atoi(value); + if (!strcmp (name, "EXPTIME")) + shutter = atoi(value) / 1000000.0; + if (!strcmp (name, "APERTURE")) + aperture = atof(value); + if (!strcmp (name, "FLENGTH")) + focal_len = atof(value); + } +#ifdef LOCALTIME + timestamp = mktime (gmtime (×tamp)); +#endif + } + fseek (ifp, save, SEEK_SET); + } + is_foveon = 1; +} diff --git a/src/LibRaw/internal/libraw_internal_funcs.h b/src/LibRaw/internal/libraw_internal_funcs.h new file mode 100644 index 000000000000..33d11ffbfc10 --- /dev/null +++ b/src/LibRaw/internal/libraw_internal_funcs.h @@ -0,0 +1,200 @@ +/* -*- C++ -*- + * File: libraw_internal_funcs.h + * Copyright 2008 Alex Tutubalin + * Created: Sat Mar 14, 2008 + * + * LibRaw internal data structures (not visible outside) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _LIBRAW_INTERNAL_FUNCS_H +#define _LIBRAW_INTERNAL_FUNCS_H + +#ifndef LIBRAW_LIBRARY_BUILD +#error "This file should be used only for libraw library build" +#else +// inline functions + ushort sget2 (uchar *s); + ushort get2(); + unsigned sget4 (uchar *s); + unsigned getint (int type); + float int_to_float (int i); + double getreal (int type); + void read_shorts (ushort *pixel, int count); + +// Canon P&S cameras + void canon_600_fixed_wb (int temp); + int canon_600_color (int ratio[2], int mar); + void canon_600_auto_wb(); + void canon_600_coeff(); + void canon_600_load_raw(); + int canon_s2is(); + void canon_a5_load_raw(); + void parse_ciff (int offset, int length); + void ciff_block_1030(); + +// LJPEG decoder + unsigned getbits (int nbits); + void init_decoder(); + uchar * make_decoder (const uchar *source, int level); + int ljpeg_start (struct jhead *jh, int info_only); + int ljpeg_diff (struct decode *dindex); + ushort * ljpeg_row (int jrow, struct jhead *jh); + +// Canon DSLRs + void crw_init_tables (unsigned table); + int canon_has_lowbits(); + void canon_compressed_load_raw(); + void lossless_jpeg_load_raw(); + void canon_sraw_load_raw(); + void canon_black(double *); +// Adobe DNG + void adobe_copy_pixel (int row, int col, ushort **rp); + void adobe_dng_load_raw_lj(); + void adobe_dng_load_raw_nc(); + +// Pentax + void pentax_k10_load_raw(); + +// Nikon (and Minolta Z2) + void nikon_compressed_load_raw(); + void nikon_load_raw(); + int nikon_is_compressed(); + int nikon_e995(); + int nikon_e2100(); + void nikon_3700(); + int minolta_z2(); + void nikon_e900_load_raw(); + void nikon_e2100_load_raw(); + +// Fuji + void fuji_load_raw(); + void parse_fuji (int offset); + + + + +// Rollei + void rollei_load_raw(); + void parse_rollei(); + +// MF backs + int bayer (unsigned row, unsigned col); + void phase_one_flat_field (int is_float, int nc); + void phase_one_correct(); + void phase_one_load_raw(); + unsigned ph1_bits (int nbits); + void phase_one_load_raw_c(); + void hasselblad_load_raw(); + void leaf_hdr_load_raw(); + void sinar_4shot_load_raw(); + void imacon_full_load_raw(); + void packed_12_load_raw(); + void unpacked_load_raw(); + void parse_sinar_ia(); + void parse_phase_one (int base); + +// Misc P&S cameras + void nokia_load_raw(); + unsigned pana_bits (int nbits); + void panasonic_load_raw(); + void olympus_e300_load_raw(); + void olympus_e410_load_raw(); + void olympus_cseries_load_raw(); + void minolta_rd175_load_raw(); + void casio_qv5700_load_raw(); + void quicktake_100_load_raw(); + const int* make_decoder_int (const int *source, int level); + int radc_token (int tree); + void kodak_radc_load_raw(); + void kodak_jpeg_load_raw(); + void kodak_dc120_load_raw(); + void eight_bit_load_raw(); + void smal_decode_segment (unsigned seg[2][2], int holes); + void smal_v6_load_raw(); + int median4 (int *p); + void fill_holes (int holes); + void smal_v9_load_raw(); + void parse_riff(); + void parse_cine(); + void parse_smal (int offset, int fsize); + int parse_jpeg (int offset); + +// Kodak + void kodak_262_load_raw(); + int kodak_65000_decode (short *out, int bsize); + void kodak_65000_load_raw(); + void kodak_rgb_load_raw(); + void kodak_yrgb_load_raw(); + +// It's a Sony (and K&M) + void sony_decrypt (unsigned *data, int len, int start, int key); + void sony_load_raw(); + void sony_arw_load_raw(); + void sony_arw2_load_raw(); + void parse_minolta (int base); + +#ifndef NO_FOVEON +// Foveon/Sigma + void foveon_load_camf(); + void foveon_load_raw(); + const char* foveon_camf_param (const char *block, const char *param); + void * foveon_camf_matrix (unsigned dim[3], const char *name); + int foveon_fixed (void *ptr, int size, const char *name); + float foveon_avg (short *pix, int range[2], float cfilt); + short * foveon_make_curve (double max, double mul, double filt); + void foveon_make_curves(short **curvep, float dq[3], float div[3], float filt); + int foveon_apply_curve (short *curve, int i); + void foveon_interpolate(); + char * foveon_gets (int offset, char *str, int len); + void parse_foveon(); +#endif + + +// CAM/RGB + void pseudoinverse (double (*in)[3], double (*out)[3], int size); + void cam_xyz_coeff (double cam_xyz[4][3]); + void adobe_coeff (const char *, const char *); + void simple_coeff (int index); + + +// Tiff/Exif parsers + void tiff_get (unsigned base,unsigned *tag, unsigned *type, unsigned *len, unsigned *save); + void parse_thumb_note (int base, unsigned toff, unsigned tlen); + void parse_makernote (int base, int uptag); + void parse_exif (int base); + void linear_table (unsigned len); + void parse_kodak_ifd (int base); + int parse_tiff_ifd (int base); + void parse_tiff (int base); + void parse_gps (int base); + void romm_coeff (float romm_cam[3][3]); + void parse_mos (int offset); + void get_timestamp (int reversed); + +// External JPEGs, what cameras uses it ? + void parse_external_jpeg(); + +// The identify + short guess_byte_order (int words); + +// Tiff writer + void tiff_set (ushort *ntag, ushort tag, ushort type, int count, int val); + void tiff_head (struct tiff_hdr *th, int full); +#endif + +#endif diff --git a/src/LibRaw/internal/var_defines.h b/src/LibRaw/internal/var_defines.h new file mode 100644 index 000000000000..8adc954956d7 --- /dev/null +++ b/src/LibRaw/internal/var_defines.h @@ -0,0 +1,180 @@ +/* -*- C++ -*- + * File: var_defines.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8, 2008 + * + * LibRaw redefinitions of dcraw internal variables + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef VAR_DEFINES_H +#define VAR_DEFINES_H + +// imgdata.idata +#define make (imgdata.idata.make) +#define model (imgdata.idata.model) +#define is_raw (imgdata.idata.raw_count) +#define dng_version (imgdata.idata.dng_version) +#define is_foveon (imgdata.idata.is_foveon) +#define colors (imgdata.idata.colors) +#define cdesc (imgdata.idata.cdesc) +#define filters (imgdata.idata.filters) + +//imgdata image +#define image (imgdata.image) + +// imgdata.sizes +#define raw_height (imgdata.sizes.raw_height) +#define raw_width (imgdata.sizes.raw_width) +#define height (imgdata.sizes.height) +#define width (imgdata.sizes.width) +#define top_margin (imgdata.sizes.top_margin) +#define left_margin (imgdata.sizes.left_margin) +#define bottom_margin (imgdata.sizes.bottom_margin) +#define right_margin (imgdata.sizes.right_margin) +#define iheight (imgdata.sizes.iheight) +#define iwidth (imgdata.sizes.iwidth) +#define pixel_aspect (imgdata.sizes.pixel_aspect) +#define flip (imgdata.sizes.flip) + +//imgdata.color +#define white (imgdata.color.white) +#define cam_mul (imgdata.color.cam_mul) +#define pre_mul (imgdata.color.pre_mul) +#define cmatrix (imgdata.color.cmatrix) +#define rgb_cam (imgdata.color.rgb_cam) +#ifndef SRC_USES_CURVE +#define curve (imgdata.color.curve) +#endif +#ifndef SRC_USES_BLACK +#define black (imgdata.color.black) +#endif +#define maximum (imgdata.color.maximum) +#define profile_length (imgdata.color.profile_length) +#define color_flags (imgdata.color.color_flags) +#define ph1 (imgdata.color.phase_one_data) +#define flash_used (imgdata.color.flash_used) +#define canon_ev (imgdata.color.canon_ev) +#define model2 (imgdata.color.model2) + +//imgdata.thumbnail + +#define thumb_width (imgdata.thumbnail.twidth) +#define thumb_height (imgdata.thumbnail.theight) +#define thumb_length (imgdata.thumbnail.tlength) + + +//imgdata.others +#define iso_speed (imgdata.other.iso_speed) +#define shutter (imgdata.other.shutter) +#define aperture (imgdata.other.aperture) +#define focal_len (imgdata.other.focal_len) +#define timestamp (imgdata.other.timestamp) +#define shot_order (imgdata.other.shot_order) +#define gpsdata (imgdata.other.gpsdata) +#define desc (imgdata.other.desc) +#define artist (imgdata.other.artist) + +//imgdata.output +#define greybox (imgdata.params.greybox) +#define aber (imgdata.params.aber) +#define user_mul (imgdata.params.user_mul) +#define shot_select (imgdata.params.shot_select) +#define bright (imgdata.params.bright) +#define threshold (imgdata.params.threshold) +#define half_size (imgdata.params.half_size) +#define four_color_rgb (imgdata.params.four_color_rgb) +#define document_mode (imgdata.params.document_mode) +#define highlight (imgdata.params.highlight) +//#define verbose (imgdata.params.verbose) +#define use_auto_wb (imgdata.params.use_auto_wb) +#define use_camera_wb (imgdata.params.use_camera_wb) +#define use_camera_matrix (imgdata.params.use_camera_matrix) +#define output_color (imgdata.params.output_color) +#define output_bps (imgdata.params.output_bps) +#define gamma_16bit (imgdata.params.gamma_16bit) +#define output_tiff (imgdata.params.output_tiff) +#define med_passes (imgdata.params.med_passes) +#define no_auto_bright (imgdata.params.no_auto_bright) +#define use_fuji_rotate (imgdata.params.use_fuji_rotate) +#define filtering_mode (imgdata.params.filtering_mode) + +//rgb_constants +#define xyz_rgb (rgb_constants.xyz_rgb) +#define d65_white (rgb_constants.d65_white) + +//libraw_internal_data.internal_data +#define meta_data (libraw_internal_data.internal_data.meta_data) +#define ifp libraw_internal_data.internal_data.input +#define ifname ((char*)libraw_internal_data.internal_data.input->fname()) +#define profile_offset (libraw_internal_data.internal_data.profile_offset) +#define thumb_offset (libraw_internal_data.internal_data.toffset) + +//libraw_internal_data.internal_output_params +#define mix_green (libraw_internal_data.internal_output_params.mix_green) +#define raw_color (libraw_internal_data.internal_output_params.raw_color) +#define use_gamma (libraw_internal_data.internal_output_params.use_gamma) +#define zero_is_bad (libraw_internal_data.internal_output_params.zero_is_bad) +#ifndef SRC_USES_SHRINK +#define shrink (libraw_internal_data.internal_output_params.shrink) +#endif +#define fuji_width (libraw_internal_data.internal_output_params.fuji_width) + + +//libraw_internal_data.output_data +#define histogram (libraw_internal_data.output_data.histogram) +#define oprof (libraw_internal_data.output_data.oprof) + +//libraw_internal_data.identify_data +#define exif_cfa (libraw_internal_data.identify_data.olympus_exif_cfa) +#define unique_id (libraw_internal_data.identify_data.unique_id) +#define tiff_nifds (libraw_internal_data.identify_data.tiff_nifds) +#define tiff_flip (libraw_internal_data.identify_data.tiff_flip) + +//libraw_internal_data.unpacker_data +#define order (libraw_internal_data.unpacker_data.order) +#define data_error (libraw_internal_data.unpacker_data.data_error) +#define cr2_slice (libraw_internal_data.unpacker_data.cr2_slice) +#define sraw_mul (libraw_internal_data.unpacker_data.sraw_mul) +#define kodak_cbpp (libraw_internal_data.unpacker_data.kodak_cbpp) +#define strip_offset (libraw_internal_data.unpacker_data.strip_offset) +#define data_offset (libraw_internal_data.unpacker_data.data_offset) +#define meta_offset (libraw_internal_data.unpacker_data.meta_offset) +#define meta_length (libraw_internal_data.unpacker_data.meta_length) +#define thumb_misc (libraw_internal_data.unpacker_data.thumb_misc) +#define fuji_layout (libraw_internal_data.unpacker_data.fuji_layout) +#define tiff_samples (libraw_internal_data.unpacker_data.tiff_samples) +#define tiff_bps (libraw_internal_data.unpacker_data.tiff_bps) +#define tiff_compress (libraw_internal_data.unpacker_data.tiff_compress) +#define zero_after_ff (libraw_internal_data.unpacker_data.zero_after_ff) +#define tile_width (libraw_internal_data.unpacker_data.tile_width) +#define tile_length (libraw_internal_data.unpacker_data.tile_length) +#define load_flags (libraw_internal_data.unpacker_data.load_flags) + +#ifdef LIBRAW_IO_REDEFINED +#define fread(ptr,size,n,stream) stream->read(ptr,size,n) +#define fseek(stream,o,w) stream->seek(o,w) +#define fseeko(stream,o,w) stream->seek(o,w) +#define ftell(stream) stream->tell() +#define ftello(stream) stream->tell() +#define getc(stream) stream->get_char() +#define fgetc(stream) stream->get_char() +#define fgets(str,n,stream) stream->gets(str,n) +#define fscanf(stream,fmt,ptr) stream->scanf_one(fmt,ptr) +#endif + +#endif diff --git a/src/LibRaw/libraw/libraw.h b/src/LibRaw/libraw/libraw.h new file mode 100644 index 000000000000..6bd12d6a27b0 --- /dev/null +++ b/src/LibRaw/libraw/libraw.h @@ -0,0 +1,229 @@ +/* -*- C++ -*- + * File: libraw.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8, 2008 + * + * LibRaw C++ interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _LIBRAW_CLASS_H +#define _LIBRAW_CLASS_H + +#include +#include +#include +#include + + +#include "libraw_datastream.h" +#include "libraw_types.h" +#include "libraw_const.h" +#include "libraw_internal.h" +#include "libraw_alloc.h" + +//#define DCRAW_VERBOSE + +#ifdef __cplusplus +extern "C" +{ +#endif +DllDef const char *libraw_strerror(int errorcode); +DllDef const char *libraw_strprogress(enum LibRaw_progress); + // LibRaw C API +DllDef libraw_data_t *libraw_init(unsigned int flags); +DllDef int libraw_open_file(libraw_data_t*, const char *); +DllDef int libraw_open_buffer(libraw_data_t*, void * buffer, size_t size); +DllDef int libraw_unpack(libraw_data_t*); +DllDef int libraw_unpack_thumb(libraw_data_t*); +DllDef void libraw_recycle(libraw_data_t*); +DllDef void libraw_close(libraw_data_t*); + // version helpers +DllDef const char* libraw_version(); +DllDef int libraw_versionNumber(); + // Camera list +DllDef const char** libraw_cameraList(); +DllDef int libraw_cameraCount(); + +DllDef void libraw_set_memerror_handler(libraw_data_t*, memory_callback cb, void *datap); +DllDef void libraw_set_dataerror_handler(libraw_data_t*,data_callback func,void *datap); +DllDef void libraw_set_progress_handler(libraw_data_t*,progress_callback cb,void *datap); +DllDef int libraw_add_masked_borders_to_bitmap(libraw_data_t* lr); +DllDef const char * libraw_unpack_function_name(libraw_data_t* lr); +DllDef int libraw_rotate_fuji_raw(libraw_data_t* lr); + + // DCRAW compatibility +DllDef int libraw_adjust_sizes_info_only(libraw_data_t*); +DllDef int libraw_dcraw_document_mode_processing(libraw_data_t*); +DllDef int libraw_dcraw_ppm_tiff_writer(libraw_data_t* lr,const char *filename); +DllDef int libraw_dcraw_thumb_writer(libraw_data_t* lr,const char *fname); +DllDef int libraw_dcraw_process(libraw_data_t* lr); +DllDef libraw_processed_image_t* dcraw_make_mem_image(libraw_data_t* lr, int *errc); +DllDef libraw_processed_image_t* dcraw_make_mem_thumb(libraw_data_t* lr, int *errc); + +#ifdef __cplusplus +} +#endif + + +#ifdef __cplusplus + +class DllDef LibRaw +{ + public: + libraw_data_t imgdata; + int verbose; + + LibRaw(unsigned int flags = LIBRAW_OPTIONS_NONE); + + libraw_output_params_t* output_params_ptr() { return &imgdata.params;} + int open_file(const char *fname); + int open_buffer(void *buffer, size_t size); + int open_datastream(LibRaw_abstract_datastream *); + int unpack(void); + int unpack_thumb(void); + + int adjust_sizes_info_only(void); + void set_memerror_handler( memory_callback cb,void *data) {callbacks.memcb_data = data; callbacks.mem_cb = cb; } + void set_dataerror_handler(data_callback func, void *data) { callbacks.datacb_data = data; callbacks.data_cb = func;} + void set_progress_handler(progress_callback pcb, void *data) { callbacks.progresscb_data = data; callbacks.progress_cb = pcb;} + + // helpers + static const char* version() { return LIBRAW_VERSION_STR;} + static int versionNumber() { return LIBRAW_VERSION; } + static const char** cameraList(); + static int cameraCount(); + static const char* strprogress(enum LibRaw_progress); + static const char* strerror(int p) { return libraw_strerror(p);} + // dcraw emulation + int dcraw_document_mode_processing(); + int dcraw_ppm_tiff_writer(const char *filename); + int dcraw_thumb_writer(const char *fname); + int dcraw_process(void); + // memory writers + libraw_processed_image_t* dcraw_make_mem_image(int *errcode=NULL); + libraw_processed_image_t* dcraw_make_mem_thumb(int *errcode=NULL); + + // free all internal data structures + void recycle(); + ~LibRaw(void) { recycle(); delete tls; } + + int FC(int row,int col) { return (imgdata.idata.filters >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3);} + int fc (int row, int col); + int add_masked_borders_to_bitmap(); + + const char *unpack_function_name(); + int rotate_fuji_raw(); + + private: + void* malloc(size_t t); + void* calloc(size_t n,size_t t); + void free(void *p); + void merror (void *ptr, const char *where); + void derror(); + +// data + + LibRaw_TLS *tls; + libraw_internal_data_t libraw_internal_data; + decode first_decode[2048], *second_decode, *free_decode; + tiff_ifd_t tiff_ifd[10]; + libraw_memmgr memmgr; + libraw_callbacks_t callbacks; + + LibRaw_constants rgb_constants; + void (LibRaw:: *write_thumb)(FILE *), + (LibRaw:: *write_fun)(FILE *); + void (LibRaw:: *load_raw)(), + (LibRaw:: *thumb_load_raw)(); + + void kodak_thumb_loader(); + void write_thumb_ppm_tiff(FILE *); // kodak + void foveon_thumb_loader (void); //Sigma + + + // moved from implementation level to private: visibility + void init_masked_ptrs(); + ushort *get_masked_pointer(int row, int col); + + int own_filtering_supported(){ return 0;} + void identify(); + void write_ppm_tiff (FILE *ofp); + void convert_to_rgb(); + void kodak_ycbcr_load_raw(); + void remove_zeroes(); +#ifndef NO_LCMS + void apply_profile(char*,char*); +#endif +// Iterpolators + void pre_interpolate(); + void border_interpolate (int border); + void lin_interpolate(); + void vng_interpolate(); + void ppg_interpolate(); + void ahd_interpolate(); + +// Image filters + void bad_pixels(char*); + void subtract(char*); + void hat_transform (float *temp, float *base, int st, int size, int sc); + void wavelet_denoise(); + void scale_colors(); + void median_filter (); + void blend_highlights(); + void recover_highlights(); + + void fuji_rotate(); + void stretch(); + +// Thmbnail functions + void foveon_thumb (FILE *tfp); + void jpeg_thumb_writer (FILE *tfp,char *thumb,int thumb_length); + void jpeg_thumb (FILE *tfp); + void ppm_thumb (FILE *tfp); + void layer_thumb (FILE *tfp); + void rollei_thumb (FILE *tfp); + void kodak_thumb_load_raw(); + + // utility for cut'n'pasted code + void foveon_decoder (unsigned size, unsigned code); + unsigned get4(); + + int flip_index (int row, int col); + void gamma_lut(ushort lut[0x10000]); + + +// == internal functions + +#ifdef LIBRAW_LIBRARY_BUILD +#include "internal/libraw_internal_funcs.h" +#endif + +}; + +#ifdef LIBRAW_LIBRARY_BUILD +#define RUN_CALLBACK(stage,iter,expect) if(callbacks.progress_cb) { \ + int rr = (*callbacks.progress_cb)(callbacks.progresscb_data,stage,iter,expect); \ + if(rr!=0) throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK; \ + } +#endif + + +#endif // __cplusplus + + +#endif // _LIBRAW_CLASS_H diff --git a/src/LibRaw/libraw/libraw_alloc.h b/src/LibRaw/libraw/libraw_alloc.h new file mode 100644 index 000000000000..84970d2f601a --- /dev/null +++ b/src/LibRaw/libraw/libraw_alloc.h @@ -0,0 +1,98 @@ +/* -*- C++ -*- + * File: libraw_alloc.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 22, 2008 + * + * LibRaw C++ interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __LIBRAW_ALLOC_H +#define __LIBRAW_ALLOC_H + +#include +#include +#ifdef WIN32 +#define bzero(p,sz) memset(p,0,sz) +#endif + +#ifdef __cplusplus + +#define MSIZE 32 + +class libraw_memmgr +{ + public: + libraw_memmgr() + { + bzero(mems,sizeof(mems)); + calloc_cnt=0; + } + void *malloc(size_t sz) + { + void *ptr = ::malloc(sz); + mem_ptr(ptr); + return ptr; + } + void *calloc(size_t n, size_t sz) + { + void *ptr = ::calloc(n,sz); + mem_ptr(ptr); + return ptr; + } + void free(void *ptr) + { + ::free(ptr); + forget_ptr(ptr); + } + void cleanup(void) + { + for(int i = 0; i< MSIZE; i++) + if(mems[i]) + { +// fprintf(stderr,"Found lost fragment at 0x%x\n",mems[i]); + free(mems[i]); + mems[i] = NULL; + } + } + + private: + void *mems[MSIZE]; + int calloc_cnt; + void mem_ptr(void *ptr) + { + if(ptr) + for(int i=0;i < MSIZE; i++) + if(!mems[i]) + { + mems[i] = ptr; + break; + } + } + void forget_ptr(void *ptr) + { + if(ptr) + for(int i=0;i < MSIZE; i++) + if(mems[i] == ptr) + mems[i] = NULL; + } + +}; + +#endif //C++ + +#endif diff --git a/src/LibRaw/libraw/libraw_const.h b/src/LibRaw/libraw/libraw_const.h new file mode 100644 index 000000000000..1c4b32c72311 --- /dev/null +++ b/src/LibRaw/libraw/libraw_const.h @@ -0,0 +1,160 @@ +/* -*- C++ -*- + * File: libraw_const.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8 , 2008 + * + * LibRaw error codes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _LIBRAW_ERRORS_H +#define _LIBRAW_ERRORS_H + +enum LibRaw_constructor_flags +{ + LIBRAW_OPTIONS_NONE =0, + LIBRAW_OPIONS_NO_MEMERR_CALLBACK=1, + LIBRAW_OPIONS_NO_DATAERR_CALLBACK=1<<1 +}; + +enum LibRaw_warnings +{ + LIBRAW_WARN_NONE =0, + LIBRAW_WARN_FOVEON_NOMATRIX =1, + LIBRAW_WARN_FOVEON_INVALIDWB =1<<1, + LIBRAW_WARN_BAD_CAMERA_WB =1<<2, + LIBRAW_WARN_NO_METADATA =1<<3, + LIBRAW_WARN_NO_JPEGLIB = 1<<4, + LIBRAW_WARN_NO_EMBEDDED_PROFILE = 1<<5, + LIBRAW_WARN_NO_INPUT_PROFILE = 1<<6, + LIBRAW_WARN_BAD_OUTPUT_PROFILE= 1<<7, + LIBRAW_WARN_NO_BADPIXELMAP=1<<8, + LIBRAW_WARN_BAD_DARKFRAME_FILE=1<<9, + LIBRAW_WARN_BAD_DARKFRAME_DIM=1<<10 +}; + +enum LibRaw_exceptions +{ + LIBRAW_EXCEPTION_NONE =0, + LIBRAW_EXCEPTION_ALLOC =1, + LIBRAW_EXCEPTION_DECODE_RAW =2, + LIBRAW_EXCEPTION_DECODE_JPEG=3, + LIBRAW_EXCEPTION_IO_EOF =4, + LIBRAW_EXCEPTION_IO_CORRUPT =5, + LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK=6 +}; + + +enum LibRaw_colorstate +{ + LIBRAW_COLORSTATE_UNKNOWN =0, + LIBRAW_COLORSTATE_INIT =1, + LIBRAW_COLORSTATE_CONST =2, + LIBRAW_COLORSTATE_LOADED =3, + LIBRAW_COLORSTATE_CALCULATED=4, + LIBRAW_COLORSTATE_RESERVED1 =5, + LIBRAW_COLORSTATE_RESERVED2 =6, + LIBRAW_COLORSTATE_RESERVED3 =7 +}; + +enum LibRaw_filtering +{ + LIBRAW_FILTERING_DEFAULT =0, + LIBRAW_FILTERING_NOZEROES =1, // no remove zeroes + LIBRAW_FILTERING_NOBLACKS =2, // no black subtraction + LIBRAW_FILTERING_NORAWCURVE =4, // no raw data postprocessing (e.g. PhaseOne corrections etc) + LIBRAW_FILTERING_NONE =7, // (_NOZEROES | _NOBLACKS | _NORAWCURVE) + LIBRAW_FILTERING_LIBRAWOWN =(8 | LIBRAW_FILTERING_NONE), // NONE + 8 + LIBRAW_FILTERING_AUTOMATIC_BIT =16, // - restore automatic mode after processing + LIBRAW_FILTERING_AUTOMATIC = (LIBRAW_FILTERING_LIBRAWOWN | LIBRAW_FILTERING_AUTOMATIC_BIT) +}; + + +enum LibRaw_progress +{ + LIBRAW_PROGRESS_START = 0, + LIBRAW_PROGRESS_OPEN = 1, + LIBRAW_PROGRESS_IDENTIFY = 1<<1, + LIBRAW_PROGRESS_SIZE_ADJUST = 1<<2, + LIBRAW_PROGRESS_LOAD_RAW = 1<<3, + LIBRAW_PROGRESS_REMOVE_ZEROES = 1<<4, + LIBRAW_PROGRESS_BAD_PIXELS = 1<<5, + LIBRAW_PROGRESS_DARK_FRAME = 1<<6, + LIBRAW_PROGRESS_FOVEON_INTERPOLATE = 1<<7, + LIBRAW_PROGRESS_SCALE_COLORS = 1<<8, + LIBRAW_PROGRESS_PRE_INTERPOLATE = 1<<9, + LIBRAW_PROGRESS_INTERPOLATE = 1<<10, + LIBRAW_PROGRESS_MIX_GREEN = 1<<11, + LIBRAW_PROGRESS_MEDIAN_FILTER = 1<<12, + LIBRAW_PROGRESS_HIGHLIGHTS = 1<<13, + LIBRAW_PROGRESS_FUJI_ROTATE = 1<<14, + LIBRAW_PROGRESS_FLIP = 1<<15, + LIBRAW_PROGRESS_APPLY_PROFILE = 1<<16, + LIBRAW_PROGRESS_CONVERT_RGB = 1<<17, + LIBRAW_PROGRESS_STRETCH = 1<<18, +// reserved + LIBRAW_PROGRESS_STAGE19 = 1<<19, + LIBRAW_PROGRESS_STAGE20 = 1<<20, + LIBRAW_PROGRESS_STAGE21 = 1<<21, + LIBRAW_PROGRESS_STAGE22 = 1<<22, + LIBRAW_PROGRESS_STAGE23 = 1<<23, + LIBRAW_PROGRESS_STAGE24 = 1<<24, + LIBRAW_PROGRESS_STAGE25 = 1<<25, + LIBRAW_PROGRESS_STAGE26 = 1<<26, + LIBRAW_PROGRESS_STAGE27 = 1<<27, + + LIBRAW_PROGRESS_THUMB_LOAD = 1<<28, + LIBRAW_PROGRESS_TRESERVED1 = 1<<29, + LIBRAW_PROGRESS_TRESERVED2 = 1<<30, + LIBRAW_PROGRESS_TRESERVED3 = 1<<31 +}; +#define LIBRAW_PROGRESS_THUMB_MASK 0x0fffffff + +enum LibRaw_errors +{ + LIBRAW_SUCCESS = 0, + LIBRAW_UNSPECIFIED_ERROR=-1, + LIBRAW_FILE_UNSUPPORTED = -2, + LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE=-3, + LIBRAW_OUT_OF_ORDER_CALL=-4, + LIBRAW_NO_THUMBNAIL=-5, + LIBRAW_UNSUPPORTED_THUMBNAIL=-6, + LIBRAW_CANNOT_ADDMASK=-7, + LIBRAW_UNSUFFICIENT_MEMORY=-100007, + LIBRAW_DATA_ERROR=-100008, + LIBRAW_IO_ERROR=-100009, + LIBRAW_CANCELLED_BY_CALLBACK=-100010 +}; + +#define LIBRAW_FATAL_ERROR(ec) ((ec)<-100000) + +enum LibRaw_thumbnail_formats +{ + LIBRAW_THUMBNAIL_UNKNOWN=0, + LIBRAW_THUMBNAIL_JPEG=1, + LIBRAW_THUMBNAIL_BITMAP=2, + LIBRAW_THUMBNAIL_LAYER=4, + LIBRAW_THUMBNAIL_ROLLEI=5 +}; + +enum LibRaw_image_formats +{ + LIBRAW_IMAGE_BITMAP=1, + LIBRAW_IMAGE_JPEG=2 +}; + +#endif diff --git a/src/LibRaw/libraw/libraw_datastream.h b/src/LibRaw/libraw/libraw_datastream.h new file mode 100644 index 000000000000..2f3989409aa4 --- /dev/null +++ b/src/LibRaw/libraw/libraw_datastream.h @@ -0,0 +1,296 @@ +/* -*- C -*- + * File: libraw_datastream.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sun Jan 18 13:07:35 2009 + * + * LibRaw Data stream interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __LIBRAW_DATASTREAM_H +#define __LIBRAW_DATASTREAM_H + +#include +#include +#include +#include + +#ifndef __cplusplus + +struct LibRaw_abstract_datastream; + +#else // __cplusplus + +#include "libraw_const.h" + +class LibRaw_buffer_datastream; + +class LibRaw_abstract_datastream +{ + public: + LibRaw_abstract_datastream(){substream=0;}; + virtual ~LibRaw_abstract_datastream(void){if(substream) delete substream;} + virtual int valid(){return 0;} + // file input emulation + virtual int read(void *,size_t, size_t ){ return -1;} + virtual int seek(off_t o, int whence){return -1;} + virtual int tell(){return -1;} + virtual int get_char(){return -1;} + virtual char* gets(char *, int){ return NULL;} + virtual int scanf_one(const char *, void *){return -1;} + virtual int eof(){return -1;} + + virtual const char* fname(){ return NULL;}; + virtual int subfile_open(const char*){ return EINVAL;} + virtual void subfile_close(){} + virtual int tempbuffer_open(void*, size_t); + virtual void tempbuffer_close() + { + if(substream) delete substream; + substream = NULL; + } + + protected: + LibRaw_abstract_datastream *substream; +}; + + +class LibRaw_file_datastream : public LibRaw_abstract_datastream +{ + public: + LibRaw_file_datastream(const char *fname) + { + if(fname) + {filename = fname; f = fopen(fname,"rb");} + else + {filename=0;f=0;} + sav=0; + } + + virtual ~LibRaw_file_datastream() {if(f)fclose(f); if(sav)fclose(sav);} + + virtual int valid() { return f?1:0;} + +#define CHK() do {if(!f) throw LIBRAW_EXCEPTION_IO_EOF;}while(0) + virtual int read(void * ptr,size_t size, size_t nmemb) + { + CHK(); + return substream?substream->read(ptr,size,nmemb):fread(ptr,size,nmemb,f); + } + virtual int eof() + { + CHK(); + return substream?substream->eof():feof(f); + } + virtual int seek(off_t o, int whence) + { + CHK(); + return substream?substream->seek(o,whence):fseek(f,o,whence); + } + virtual int tell() + { + CHK(); + return substream?substream->tell():ftell(f); + } + virtual int get_char() + { + CHK(); + return substream?substream->get_char():fgetc(f); + } + virtual char* gets(char *str, int sz) + { + CHK(); + return substream?substream->gets(str,sz):fgets(str,sz,f); + } + virtual int scanf_one(const char *fmt, void*val) + { + CHK(); + return substream?substream->scanf_one(fmt,val):fscanf(f,fmt,val); + } + + virtual const char *fname() { return filename; } + + // secondary + virtual int subfile_open(const char *fn) + { + if(sav) return EBUSY; + sav = f; + f = fopen(fn,"rb"); + if(!f) + { + f = sav; + sav = NULL; + return ENOENT; + } + else + return 0; + } + virtual void subfile_close() + { + if(!sav) return; + fclose(f); + f = sav; + sav = 0; + } + + private: + FILE *f,*sav; + const char *filename; +}; +#undef CHK + +class LibRaw_buffer_datastream : public LibRaw_abstract_datastream +{ + public: + LibRaw_buffer_datastream(void *buffer, size_t bsize) + { + buf = (unsigned char*)buffer; streampos = 0; streamsize = bsize; + } + virtual ~LibRaw_buffer_datastream(){} + virtual int valid() { return buf?1:0;} + virtual int read(void * ptr,size_t sz, size_t nmemb) + { + if(substream) return substream->read(ptr,sz,nmemb); + size_t to_read = sz*nmemb; + if(to_read > streamsize - streampos) + to_read = streamsize-streampos; + if(to_read<1) + return 0; + memmove(ptr,buf+streampos,to_read); + streampos+=to_read; + return (to_read+sz-1)/sz; + } + + virtual int eof() + { + if(substream) return substream->eof(); + return streampos >= streamsize; + } + + virtual int seek(off_t o, int whence) + { + if(substream) return substream->seek(o,whence); + switch(whence) + { + case SEEK_SET: + streampos = o > streamsize ? streamsize: o; + return 0; + case SEEK_CUR: + if(o<0) + { + if (o < - streampos) + o = - streampos; + streampos += o; + } + else if (o>0) + { + if(o> streamsize-streampos) + o = streamsize - streampos; + streampos += o; + } + return 0; + case SEEK_END: + if(o>0) + streampos = streamsize; + else if ( -o > streamsize) + streampos = 0; + else + streampos = streamsize+o; + return 0; + default: + return 0; + } + } + + virtual int tell() + { + if(substream) return substream->tell(); + return streampos; + } + + virtual int get_char() + { + if(substream) return substream->get_char(); + if(streampos>=streamsize) + return -1; + return buf[streampos++]; + } + virtual char* gets(char *s, int sz) + { + if (substream) return substream->gets(s,sz); + unsigned char *psrc,*pdest,*str; + str = (unsigned char *)s; + psrc = buf+streampos; + pdest = str; + while ( ((psrc - buf) < streamsize) + && + ((pdest-str)scanf_one(fmt,val); + int scanf_res; + if(streampos>streamsize) return 0; + scanf_res = sscanf((char*)(buf+streampos),fmt,val); + if(scanf_res>0) + { + int xcnt=0; + while(streampos24) + break; + } + } + return scanf_res; + } + private: + unsigned char *buf; + size_t streampos,streamsize; +}; + +inline int LibRaw_abstract_datastream::tempbuffer_open(void *buf, size_t size) +{ + if(substream) return EBUSY; + substream = new LibRaw_buffer_datastream(buf,size); + return substream?0:EINVAL; +} + + +#endif + +#endif + diff --git a/src/LibRaw/libraw/libraw_internal.h b/src/LibRaw/libraw/libraw_internal.h new file mode 100644 index 000000000000..109b3a83ea76 --- /dev/null +++ b/src/LibRaw/libraw/libraw_internal.h @@ -0,0 +1,241 @@ +/* -*- C++ -*- + * File: libraw_internal.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8 , 2008 + * + * LibRaw internal data structures (not visible outside) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _LIBRAW_INTERNAL_TYPES_H +#define _LIBRAW_INTERNAL_TYPES_H + +#include +#ifdef __cplusplus + + +#ifndef CLASS +#define CLASS LibRaw:: +#endif + +#else +// C build +#ifndef CLASS +#define CLASS +#endif +#endif + + +#ifdef __cplusplus + +#include "libraw_datastream.h" + +class LibRaw_TLS +{ +public: + struct + { + unsigned bitbuf; + int vbits, reset; + }getbits; + struct + { + UINT64 bitbuf; + int vbits; + + }ph1_bits; + int make_decoder_leaf; + struct + { + struct decode *dstart[18], *dindex; + const int *s; + }radc_token; + struct + { + unsigned pad[128], p; + }sony_decrypt; + unsigned foveon_decoder_huff[1024]; + uchar jpeg_buffer[4096]; + struct + { + uchar buf[0x4000]; + int vbits, padding; + }pana_bits; + + // init - should use in constructor/recycle + void init() + { + getbits.bitbuf = 0; getbits.vbits = getbits.reset = 0; + ph1_bits.bitbuf = 0; ph1_bits.vbits = 0; + pana_bits.vbits = 0; + } +}; + + +class LibRaw_constants +{ + public: + static const float d65_white[3]; + static const double xyz_rgb[3][3]; +}; +#endif // __cplusplus + +#ifdef WIN32 +typedef long off_t; +#endif + +typedef struct +{ +#ifndef __cplusplus + struct +#endif + LibRaw_abstract_datastream *input; + int input_internal; +// char *ifname; + char *meta_data; + off_t profile_offset; + off_t toffset; + +} internal_data_t; + +typedef struct +{ + unsigned mix_green; + unsigned raw_color; + unsigned use_gamma; + unsigned zero_is_bad; + ushort shrink; + ushort fuji_width; + ushort fwidth,fheight; +} internal_output_params_t; + +#define LIBRAW_HISTOGRAM_SIZE 0x2000 +typedef struct +{ + int (*histogram)[LIBRAW_HISTOGRAM_SIZE]; + unsigned *oprof; +} output_data_t; + +typedef struct +{ + unsigned olympus_exif_cfa; + unsigned unique_id; + unsigned tiff_nifds; + int tiff_flip; +}identify_data_t; + +typedef struct +{ + short order; // II* / MM* - file word byte order + ushort sraw_mul[4],cr2_slice[3]; + unsigned kodak_cbpp; + off_t strip_offset, data_offset; + off_t meta_offset; + unsigned meta_length; + unsigned thumb_misc; + unsigned fuji_layout; + unsigned tiff_samples; + unsigned tiff_bps; + unsigned tiff_compress; + unsigned zero_after_ff; + unsigned tile_width, tile_length,load_flags; + unsigned data_error; +}unpacker_data_t; + + + +typedef struct +{ + internal_data_t internal_data; + internal_output_params_t internal_output_params; + output_data_t output_data; + identify_data_t identify_data; + unpacker_data_t unpacker_data; +// callbacks_t callbacks; +} libraw_internal_data_t; + + +struct decode +{ + struct decode *branch[2]; + int leaf; +}; + +struct tiff_ifd_t +{ + int t_width, t_height, bps, comp, phint, offset, t_flip, samples, bytes; +}; + + +struct jhead { + int bits, high, wide, clrs, sraw, psv, restart, vpred[6]; + struct decode *huff[6]; + ushort *row; +}; +struct tiff_tag { + ushort tag, type; + int count; + union { char c[4]; short s[2]; int i; } val; +}; + +struct tiff_hdr { + ushort t_order, magic; + int ifd; + ushort pad, ntag; + struct tiff_tag tag[23]; + int nextifd; + ushort pad2, nexif; + struct tiff_tag exif[4]; + ushort pad3, ngps; + struct tiff_tag gpst[10]; + short bps[4]; + int rat[10]; + unsigned gps[26]; + char t_desc[512], t_make[64], t_model[64], soft[32], date[20], t_artist[64]; +}; + + + +#ifdef DEBUG_STAGE_CHECKS +#define CHECK_ORDER_HIGH(expected_stage) \ + do { if((imgdata.progress_flags & LIBRAW_PROGRESS_THUMB_MASK) >= expected_stage) {fprintf(stderr,"CHECK_HIGH: check %d >= %d\n",imgdata.progress_flags & LIBRAW_PROGRESS_THUMB_MASK,expected_stage);return LIBRAW_OUT_OF_ORDER_CALL;} } while(0) + +#define CHECK_ORDER_LOW(expected_stage) \ + do { printf("Checking LOW %d/%d : %d\n",imgdata.progress_flags,expected_stage,imgdata.progress_flags= expected_stage) \ + {return LIBRAW_OUT_OF_ORDER_CALL;} } while(0) + +#define CHECK_ORDER_LOW(expected_stage) \ + do { if((imgdata.progress_flags&LIBRAW_PROGRESS_THUMB_MASK) < expected_stage) \ + return LIBRAW_OUT_OF_ORDER_CALL; } while(0) + +#define CHECK_ORDER_BIT(expected_stage) \ + do { if(imgdata.progress_flags & expected_stage) return LIBRAW_OUT_OF_ORDER_CALL; } while(0) + +#define SET_PROC_FLAG(stage) do {imgdata.progress_flags |= stage;} while (0) + +#endif + +#endif diff --git a/src/LibRaw/libraw/libraw_types.h b/src/LibRaw/libraw/libraw_types.h new file mode 100644 index 000000000000..0520cab55688 --- /dev/null +++ b/src/LibRaw/libraw/libraw_types.h @@ -0,0 +1,291 @@ +/* -*- C++ -*- + * File: libraw_types.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8 , 2008 + * + * LibRaw C data structures + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _LIBRAW_TYPES_H +#define _LIBRAW_TYPES_H + +#ifndef WIN32 +#include +#endif +#include +#ifdef _OPENMP +#include +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef USE_LCMS +#define NO_LCMS +#endif + +#include "libraw_const.h" +#include "libraw_version.h" + +typedef long long INT64; +typedef unsigned long long UINT64; +//#define ushort UshORt +typedef unsigned char uchar; +typedef unsigned short ushort; + +#ifdef WIN32 +#ifdef LIBRAW_NODLL +# define DllDef +#else +# ifdef LIBRAW_BUILDLIB +# define DllDef __declspec( dllexport ) +# else +# define DllDef __declspec( dllimport ) +# endif +#endif +// NO Win32 +#else +# define DllDef +#endif + + +//class LibRaw; + +typedef void (* memory_callback)(void * data, const char *file, const char *where); + +DllDef void default_memory_callback(void *data,const char *file, const char *where); + +typedef void (*data_callback)(void *data,const char *file, const int offset); + +DllDef void default_data_callback(void *data,const char *file, const int offset); + +typedef int (* progress_callback) (void *data,enum LibRaw_progress stage, int iteration,int expected); + +typedef struct +{ + memory_callback mem_cb; + void* memcb_data; + + data_callback data_cb; + void* datacb_data; + + progress_callback progress_cb; + void *progresscb_data; +} libraw_callbacks_t; + +// Output bitmap type + +typedef struct +{ + enum LibRaw_image_formats type; + ushort height, + width, + colors, + bits, + gamma_corrected; +#if 0//def _OPENMP +#pragma omp firstprivate(colors,height,width) +#endif + unsigned int data_size; // ðàçìåð ïîëÿ äàííûõ â áàéòàõ + unsigned char data[1]; // we'll allocate more! +}libraw_processed_image_t; + + +//Decoded from exif and used in calculations +typedef struct +{ + char make[64]; + char model[64]; + + unsigned raw_count; + unsigned dng_version; + unsigned is_foveon; + int colors; + + unsigned filters; // camera CFA pattern mask + char cdesc[5]; + +}libraw_iparams_t; + +typedef struct +{ + ushort raw_height, + raw_width, + height, + width, + top_margin, + left_margin; + ushort iheight, + iwidth; +#if 0//def _OPENMP +#pragma omp firstprivate(iheight,iwidth) +#endif + double pixel_aspect; + int flip; + + // masked border sizes + ushort right_margin,bottom_margin; // right masked width and bottom height, inited after idendify() + +} libraw_image_sizes_t; + +//Phase One data +struct ph1_t +{ + int format, key_off, t_black, black_off, split_col, tag_21a; + float tag_210; +}; + + +typedef struct +{ + // 32 bits total + unsigned curve_state : 3; + unsigned rgb_cam_state : 3; + unsigned cmatrix_state : 3; + unsigned pre_mul_state : 3; + unsigned cam_mul_state : 3; + unsigned filler : 17; +} color_data_state_t; + +typedef struct +{ + color_data_state_t color_flags; + ushort white[8][8]; // white block extracted from ciff/CRW + float cam_mul[4]; // camera white balance (from RAW) + float pre_mul[4]; // either set in identify() or calculated. Used on output + float cmatrix[3][4]; // camera color matrix + float rgb_cam[3][4]; // another way to set color matrix + float cam_xyz[4][3]; // Camera to XYZ matrix (DNG coeffs) + ushort curve[0x4001]; // camera tone curve/ljpeg curve + unsigned black; + unsigned maximum; + struct ph1_t phase_one_data; + float flash_used; // canon/CRW only + float canon_ev; // canon/CRW only + char model2[64]; + // profile + void *profile; + unsigned profile_length; +}libraw_colordata_t; + +typedef struct +{ + enum LibRaw_thumbnail_formats tformat; + ushort twidth, + theight; + unsigned tlength; + int tcolors; + + // thumbnail buffer + char *thumb; +}libraw_thumbnail_t; + +// Decoded from exif/raw, but not used in real calculations +typedef struct +{ + float iso_speed; + float shutter; + float aperture; + float focal_len; + time_t timestamp; + unsigned shot_order; + unsigned gpsdata[32]; + // string variables + char desc[512], + artist[64]; +} libraw_imgother_t; + +typedef struct +{ + unsigned greybox[4]; /* -A x1 y1 x2 y2 */ + double aber[4]; /* -C */ + float user_mul[4]; /* -r mul0 mul1 mul2 mul3 */ + unsigned shot_select; /* -s */ + float bright; /* -b */ + float threshold; /* -n */ +#if 0//def _OPENMP +#pragma omp firstprivate(threshold) +#endif + int half_size; /* -h */ + int four_color_rgb; /* -f */ + int document_mode; /* -d/-D */ + int highlight; /* -H */ +// int verbose; /* -v */ + int use_auto_wb; /* -a */ + int use_camera_wb; /* -w */ + int use_camera_matrix; /* +M/-M */ + int output_color; /* -o */ + char *output_profile; /* -o */ + char *camera_profile; /* -p */ + char *bad_pixels; /* -P */ + char *dark_frame; /* -K */ + int output_bps; /* -4 */ + int gamma_16bit; /* -1 */ + int output_tiff; /* -T */ + int user_flip; /* -t */ + int user_qual; /* -q */ + int user_black; /* -k */ + int user_sat; /* -S */ + + int med_passes; /* -m */ + int no_auto_bright; /* -W */ + int use_fuji_rotate;/* -j */ + enum LibRaw_filtering filtering_mode; +}libraw_output_params_t; + +typedef struct +{ + ushort *buffer; // actual pixel buffer size=(raw_width*raw_height - width*height) + ushort *tl; // top left size=(top_margin*left_margin) + ushort *top; // top size=(top_margin*width) + ushort *tr; // top right size=((raw_width-width-left_margin)*top_margin) + ushort *left; // left size=(left_margin*height) + ushort *right; // right size=(raw_width-width-left_margin)*height; + ushort *bl; // bottom left size=(raw_height-height-top_margin)*left_margin + ushort *bottom; // bottom size=(raw_height-height-top_margin)*width + ushort *br; // bottom right size=(raw_height-height-top_margin)* + ushort (*ph1_black)[2]; // Phase One black +}libraw_masked_t; + +typedef struct +{ + unsigned int progress_flags; + unsigned int process_warnings; + libraw_iparams_t idata; + libraw_image_sizes_t sizes; + libraw_colordata_t color; + libraw_imgother_t other; + libraw_thumbnail_t thumbnail; + libraw_masked_t masked_pixels; + ushort (*image)[4] ; +#if 0//def _OPENMP +#pragma omp shared(image) +#endif + libraw_output_params_t params; + // pointer to LibRaw class for use in C calls + void *parent_class; +} libraw_data_t; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/LibRaw/libraw/libraw_version.h b/src/LibRaw/libraw/libraw_version.h new file mode 100644 index 000000000000..7a4844cf3a90 --- /dev/null +++ b/src/LibRaw/libraw/libraw_version.h @@ -0,0 +1,47 @@ +/* -*- C++ -*- + * File: version.h + * Copyright 2008-2009 Alex Tutubalin + * Created: Mon Sept 8, 2008 + * + * LibRaw C++ interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __VERSION_H +#define __VERSION_H + +#define LIBRAW_MAJOR_VERSION 0 +#define LIBRAW_MINOR_VERSION 7 +#define LIBRAW_PATCH_VERSION 0 +#define LIBRAW_VERSION_TAIL Beta1 + +#define _LIBRAW_VERSION_MAKE(a,b,c,d) #a"."#b"."#c"-"#d +#define LIBRAW_VERSION_MAKE(a,b,c,d) _LIBRAW_VERSION_MAKE(a,b,c,d) + +#define LIBRAW_VERSION_STR LIBRAW_VERSION_MAKE(LIBRAW_MAJOR_VERSION,LIBRAW_MINOR_VERSION,LIBRAW_PATCH_VERSION,LIBRAW_VERSION_TAIL) + +#define LIBRAW_MAKE_VERSION(major,minor,patch) \ + (((major) << 16) | ((minor) << 8) | (patch)) + +#define LIBRAW_VERSION \ + LIBRAW_MAKE_VERSION(LIBRAW_MAJOR_VERSION,LIBRAW_MINOR_VERSION,LIBRAW_PATCH_VERSION) + +#define LIBRAW_CHECK_VERSION(major,minor,patch) \ + ( LibRaw::versionNumber() >= LIBRAW_MAKE_VERSION(major,minor,patch) ) + + +#endif diff --git a/src/LibRaw/src/libraw_c_api.cpp b/src/LibRaw/src/libraw_c_api.cpp new file mode 100644 index 000000000000..3770ac31745b --- /dev/null +++ b/src/LibRaw/src/libraw_c_api.cpp @@ -0,0 +1,149 @@ +/* -*- C++ -*- + * File: libraw_c_api.cpp + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8 , 2008 + * + * LibRaw C++ interface (implementation) + */ +#include +#include "libraw/libraw.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + libraw_data_t *libraw_init(unsigned int flags) + { + LibRaw *ret = new LibRaw(flags); + return &(ret->imgdata); + } + + const char* libraw_version() { return LibRaw::version();} + const char* libraw_strprogress(enum LibRaw_progress p) { return LibRaw::strprogress(p);} + int libraw_versionNumber() { return LibRaw::versionNumber();} + const char** libraw_cameraList() { return LibRaw::cameraList();} + int libraw_cameraCount() { return LibRaw::cameraCount(); } + const char* libraw_unpack_function_name(libraw_data_t* lr) + { + if(!lr) return "NULL parameter passed"; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->unpack_function_name(); + } + int libraw_rotate_fuji_raw(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->rotate_fuji_raw(); + } + + int libraw_add_masked_borders_to_bitmap(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->add_masked_borders_to_bitmap(); + } + + int libraw_open_file(libraw_data_t* lr, const char *file) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->open_file(file); + } + int libraw_open_buffer(libraw_data_t* lr, void *buffer, size_t size) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->open_buffer(buffer,size); + } + int libraw_unpack(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->unpack(); + } + int libraw_unpack_thumb(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->unpack_thumb(); + } + void libraw_recycle(libraw_data_t* lr) + { + if(!lr) return; + LibRaw *ip = (LibRaw*) lr->parent_class; + ip->recycle(); + } + void libraw_close(libraw_data_t* lr) + { + if(!lr) return; + LibRaw *ip = (LibRaw*) lr->parent_class; + delete ip; + } + + void libraw_set_memerror_handler(libraw_data_t* lr, memory_callback cb,void *data) + { + if(!lr) return; + LibRaw *ip = (LibRaw*) lr->parent_class; + ip->set_memerror_handler(cb,data); + + } + void libraw_set_dataerror_handler(libraw_data_t* lr,data_callback func,void *data) + { + if(!lr) return; + LibRaw *ip = (LibRaw*) lr->parent_class; + ip->set_dataerror_handler(func,data); + + } + void libraw_set_progress_handler(libraw_data_t* lr, progress_callback cb,void *data) + { + if(!lr) return; + LibRaw *ip = (LibRaw*) lr->parent_class; + ip->set_progress_handler(cb,data); + + } + + // DCRAW + int libraw_adjust_sizes_info_only(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->adjust_sizes_info_only(); + } + + int libraw_dcraw_document_mode_processing(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->dcraw_document_mode_processing(); + + } + int libraw_dcraw_ppm_tiff_writer(libraw_data_t* lr,const char *filename) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->dcraw_ppm_tiff_writer(filename); + } + int libraw_dcraw_thumb_writer(libraw_data_t* lr,const char *fname) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->dcraw_thumb_writer(fname); + + } + int libraw_dcraw_process(libraw_data_t* lr) + { + if(!lr) return EINVAL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->dcraw_process(); + } + libraw_processed_image_t *dcraw_make_mem_image(libraw_data_t* lr, int *errorcode) + { + if(!lr) return NULL; + LibRaw *ip = (LibRaw*) lr->parent_class; + return ip->dcraw_make_mem_image(errorcode); + } + +#ifdef __cplusplus +} +#endif diff --git a/src/LibRaw/src/libraw_cxx.cpp b/src/LibRaw/src/libraw_cxx.cpp new file mode 100644 index 000000000000..75053b549fe4 --- /dev/null +++ b/src/LibRaw/src/libraw_cxx.cpp @@ -0,0 +1,1935 @@ +/* -*- C++ -*- + * File: libraw_cxx.cpp + * Copyright 2008-2009 Alex Tutubalin + * Created: Sat Mar 8 , 2008 + * + * LibRaw C++ interface (implementation) + */ + +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#endif +#define LIBRAW_LIBRARY_BUILD +#include "libraw/libraw.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + void default_memory_callback(void *,const char *file,const char *where) + { + fprintf (stderr,"%s: Out of memory in %s\n", file?file:"unknown file", where); + } + + void default_data_callback(void*,const char *file, const int offset) + { + if(offset < 0) + fprintf (stderr,"%s: Unexpected end of file\n", file?file:"unknown file"); + else + fprintf (stderr,"%s: data corrupted at %d\n",file?file:"unknown file",offset); + } + const char *libraw_strerror(int e) + { + enum LibRaw_errors errorcode = (LibRaw_errors)e; + switch(errorcode) + { + case LIBRAW_SUCCESS: + return "No error"; + case LIBRAW_UNSPECIFIED_ERROR: + return "Unspecified error"; + case LIBRAW_FILE_UNSUPPORTED: + return "Unsupported file format or not RAW file"; + case LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE: + return "Request for nonexisting image number"; + case LIBRAW_OUT_OF_ORDER_CALL: + return "Out of order call of libraw function"; + case LIBRAW_NO_THUMBNAIL: + return "No thumbnail in file"; + case LIBRAW_UNSUPPORTED_THUMBNAIL: + return "Unsupported thumbnail format"; + case LIBRAW_CANNOT_ADDMASK: + return "Cannot add masked pixels to resized image"; + case LIBRAW_UNSUFFICIENT_MEMORY: + return "Unsufficient memory"; + case LIBRAW_DATA_ERROR: + return "Corrupted data or unexpected EOF"; + case LIBRAW_IO_ERROR: + return "Input/output error"; + case LIBRAW_CANCELLED_BY_CALLBACK: + return "Cancelled by user callback"; + default: + return "Unknown error code"; + } + } + +#ifdef __cplusplus +} +#endif + + +const double LibRaw_constants::xyz_rgb[3][3] = +{ + { 0.412453, 0.357580, 0.180423 }, + { 0.212671, 0.715160, 0.072169 }, + { 0.019334, 0.119193, 0.950227 } +}; + +const float LibRaw_constants::d65_white[3] = { 0.950456, 1, 1.088754 }; + +#define P1 imgdata.idata +#define S imgdata.sizes +#define O imgdata.params +#define C imgdata.color +#define M imgdata.masked_pixels +#define T imgdata.thumbnail +#define IO libraw_internal_data.internal_output_params +#define ID libraw_internal_data.internal_data + +#define EXCEPTION_HANDLER(e) do{ \ + fprintf(stderr,"Exception %d caught\n",e); \ + switch(e) \ + { \ + case LIBRAW_EXCEPTION_ALLOC: \ + recycle(); \ + return LIBRAW_UNSUFFICIENT_MEMORY; \ + case LIBRAW_EXCEPTION_DECODE_RAW: \ + case LIBRAW_EXCEPTION_DECODE_JPEG: \ + recycle(); \ + return LIBRAW_DATA_ERROR; \ + case LIBRAW_EXCEPTION_IO_EOF: \ + case LIBRAW_EXCEPTION_IO_CORRUPT: \ + recycle(); \ + return LIBRAW_IO_ERROR; \ + case LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK:\ + recycle(); \ + return LIBRAW_CANCELLED_BY_CALLBACK; \ + default: \ + return LIBRAW_UNSPECIFIED_ERROR; \ + } \ + }while(0) + +void LibRaw::derror() +{ + if (!libraw_internal_data.unpacker_data.data_error && libraw_internal_data.internal_data.input) + { + if (libraw_internal_data.internal_data.input->eof()) + { + if(callbacks.data_cb)(*callbacks.data_cb)(callbacks.datacb_data, + libraw_internal_data.internal_data.input->fname(),-1); + throw LIBRAW_EXCEPTION_IO_EOF; + } + else + { + if(callbacks.data_cb)(*callbacks.data_cb)(callbacks.datacb_data, + libraw_internal_data.internal_data.input->fname(), + libraw_internal_data.internal_data.input->tell()); + throw LIBRAW_EXCEPTION_IO_CORRUPT; + } + } + libraw_internal_data.unpacker_data.data_error = 1; +} +LibRaw:: LibRaw(unsigned int flags) +{ + double aber[4] = {1,1,1,1}; + unsigned greybox[4] = { 0, 0, UINT_MAX, UINT_MAX }; +#ifdef DCRAW_VERBOSE + verbose = 1; +#else + verbose = 0; +#endif + bzero(&imgdata,sizeof(imgdata)); + bzero(&libraw_internal_data,sizeof(libraw_internal_data)); + bzero(&callbacks,sizeof(callbacks)); + callbacks.mem_cb = (flags & LIBRAW_OPIONS_NO_MEMERR_CALLBACK) ? NULL: &default_memory_callback; + callbacks.data_cb = (flags & LIBRAW_OPIONS_NO_DATAERR_CALLBACK)? NULL : &default_data_callback; + memmove(&imgdata.params.aber,&aber,sizeof(aber)); + memmove(&imgdata.params.greybox,&greybox,sizeof(greybox)); + + imgdata.params.bright=1; + imgdata.params.use_camera_matrix=-1; + imgdata.params.user_flip=-1; + imgdata.params.user_black=-1; + imgdata.params.user_sat=-1; + imgdata.params.user_qual=-1; + imgdata.params.output_color=1; + imgdata.params.output_bps=8; + imgdata.params.use_fuji_rotate=1; + imgdata.parent_class = this; + imgdata.progress_flags = 0; + tls = new LibRaw_TLS; + tls->init(); +} + + +void* LibRaw:: malloc(size_t t) +{ + void *p = memmgr.malloc(t); + return p; +} +void* LibRaw:: calloc(size_t n,size_t t) +{ + void *p = memmgr.calloc(n,t); + return p; +} +void LibRaw:: free(void *p) +{ + memmgr.free(p); +} + + +int LibRaw:: fc (int row, int col) +{ + static const char filter[16][16] = + { { 2,1,1,3,2,3,2,0,3,2,3,0,1,2,1,0 }, + { 0,3,0,2,0,1,3,1,0,1,1,2,0,3,3,2 }, + { 2,3,3,2,3,1,1,3,3,1,2,1,2,0,0,3 }, + { 0,1,0,1,0,2,0,2,2,0,3,0,1,3,2,1 }, + { 3,1,1,2,0,1,0,2,1,3,1,3,0,1,3,0 }, + { 2,0,0,3,3,2,3,1,2,0,2,0,3,2,2,1 }, + { 2,3,3,1,2,1,2,1,2,1,1,2,3,0,0,1 }, + { 1,0,0,2,3,0,0,3,0,3,0,3,2,1,2,3 }, + { 2,3,3,1,1,2,1,0,3,2,3,0,2,3,1,3 }, + { 1,0,2,0,3,0,3,2,0,1,1,2,0,1,0,2 }, + { 0,1,1,3,3,2,2,1,1,3,3,0,2,1,3,2 }, + { 2,3,2,0,0,1,3,0,2,0,1,2,3,0,1,0 }, + { 1,3,1,2,3,2,3,2,0,2,0,1,1,0,3,0 }, + { 0,2,0,3,1,0,0,1,1,3,3,2,3,2,2,1 }, + { 2,1,3,2,3,1,2,1,0,3,0,2,0,2,0,2 }, + { 0,3,1,0,0,2,0,3,2,1,3,1,1,3,1,3 } }; + + if (imgdata.idata.filters != 1) return FC(row,col); + return filter[(row+imgdata.sizes.top_margin) & 15][(col+imgdata.sizes.left_margin) & 15]; +} + +void LibRaw:: recycle() +{ + if(libraw_internal_data.internal_data.input && libraw_internal_data.internal_data.input_internal) + { + delete libraw_internal_data.internal_data.input; + libraw_internal_data.internal_data.input = NULL; + } + libraw_internal_data.internal_data.input_internal = 0; +#define FREE(a) do { if(a) { free(a); a = NULL;} }while(0) + + FREE(imgdata.image); + FREE(imgdata.thumbnail.thumb); + FREE(libraw_internal_data.internal_data.meta_data); + FREE(libraw_internal_data.output_data.histogram); + FREE(libraw_internal_data.output_data.oprof); + FREE(imgdata.color.profile); + FREE(imgdata.masked_pixels.buffer); + FREE(imgdata.masked_pixels.ph1_black); +#undef FREE +#define ZERO(a) bzero(&a,sizeof(a)) + ZERO(imgdata.masked_pixels); + ZERO(imgdata.sizes); + ZERO(libraw_internal_data.internal_output_params); +#undef ZERO + memmgr.cleanup(); + imgdata.thumbnail.tformat = LIBRAW_THUMBNAIL_UNKNOWN; + imgdata.progress_flags = 0; + + tls->init(); +} + +const char * LibRaw::unpack_function_name() +{ + if(!load_raw) return "Function not set"; + + // sorted names order + if (load_raw == &LibRaw::adobe_dng_load_raw_lj) return "adobe_dng_load_raw_lj()"; + if (load_raw == &LibRaw::adobe_dng_load_raw_nc) return "adobe_dng_load_raw_nc()"; + if (load_raw == &LibRaw::canon_600_load_raw) return "canon_600_load_raw()"; + + if (load_raw == &LibRaw::canon_a5_load_raw) return "canon_a5_load_raw()"; + if (load_raw == &LibRaw::canon_compressed_load_raw) return "canon_compressed_load_raw()"; + if (load_raw == &LibRaw::canon_sraw_load_raw) return "canon_sraw_load_raw()"; + + if (load_raw == &LibRaw::casio_qv5700_load_raw ) return "casio_qv5700_load_raw()"; + if (load_raw == &LibRaw::eight_bit_load_raw ) return "eight_bit_load_raw()"; + if (load_raw == &LibRaw::foveon_load_raw ) return "foveon_load_raw()"; + if (load_raw == &LibRaw::fuji_load_raw ) return "fuji_load_raw()"; + // 10 + if (load_raw == &LibRaw::hasselblad_load_raw ) return "hasselblad_load_raw()"; + if (load_raw == &LibRaw::imacon_full_load_raw ) return "imacon_full_load_raw()"; + if (load_raw == &LibRaw::kodak_262_load_raw ) return "kodak_262_load_raw()"; + + if (load_raw == &LibRaw::kodak_65000_load_raw ) return "kodak_65000_load_raw()"; + if (load_raw == &LibRaw::kodak_dc120_load_raw ) return "kodak_dc120_load_raw()"; + if (load_raw == &LibRaw::kodak_jpeg_load_raw ) return "kodak_jpeg_load_raw()"; + + if (load_raw == &LibRaw::kodak_radc_load_raw ) return "kodak_radc_load_raw()"; + if (load_raw == &LibRaw::kodak_rgb_load_raw ) return "kodak_rgb_load_raw()"; + if (load_raw == &LibRaw::kodak_yrgb_load_raw ) return "kodak_yrgb_load_raw()"; + if (load_raw == &LibRaw::kodak_ycbcr_load_raw ) return "kodak_ycbcr_load_raw()"; + // 20 + if (load_raw == &LibRaw::leaf_hdr_load_raw ) return "leaf_hdr_load_raw()"; + if (load_raw == &LibRaw::lossless_jpeg_load_raw) return "lossless_jpeg_load_raw()"; + if (load_raw == &LibRaw::minolta_rd175_load_raw ) return "minolta_rd175_load_raw()"; + + if (load_raw == &LibRaw::nikon_compressed_load_raw) return "nikon_compressed_load_raw()"; + if (load_raw == &LibRaw::nikon_e900_load_raw ) return "nikon_e900_load_raw()"; + if (load_raw == &LibRaw::nokia_load_raw ) return "nokia_load_raw()"; + + if (load_raw == &LibRaw::olympus_e300_load_raw ) return "olympus_e300_load_raw()"; + if (load_raw == &LibRaw::olympus_e410_load_raw ) return "olympus_e410_load_raw()"; + if (load_raw == &LibRaw::packed_12_load_raw ) return "packed_12_load_raw()"; + if (load_raw == &LibRaw::panasonic_load_raw ) return "panasonic_load_raw()"; + // 30 + if (load_raw == &LibRaw::pentax_k10_load_raw ) return "pentax_k10_load_raw()"; + if (load_raw == &LibRaw::phase_one_load_raw ) return "phase_one_load_raw()"; + if (load_raw == &LibRaw::phase_one_load_raw_c ) return "phase_one_load_raw_c()"; + + if (load_raw == &LibRaw::quicktake_100_load_raw ) return "quicktake_100_load_raw()"; + if (load_raw == &LibRaw::rollei_load_raw ) return "rollei_load_raw()"; + if (load_raw == &LibRaw::sinar_4shot_load_raw ) return "sinar_4shot_load_raw()"; + + if (load_raw == &LibRaw::smal_v6_load_raw ) return "smal_v6_load_raw()"; + if (load_raw == &LibRaw::smal_v9_load_raw ) return "smal_v9_load_raw()"; + if (load_raw == &LibRaw::sony_load_raw ) return "sony_load_raw()"; + if (load_raw == &LibRaw::sony_arw_load_raw ) return "sony_arw_load_raw()"; + // 40 + if (load_raw == &LibRaw::sony_arw2_load_raw ) return "sony_arw2_load_raw()"; + if (load_raw == &LibRaw::unpacked_load_raw ) return "unpacked_load_raw()"; + // 42 total + + return "Unknown unpack function"; +} + + +void LibRaw:: merror (void *ptr, const char *where) +{ + if (ptr) return; + if(callbacks.mem_cb)(*callbacks.mem_cb)(callbacks.memcb_data, + libraw_internal_data.internal_data.input + ?libraw_internal_data.internal_data.input->fname() + :NULL, + where); + throw LIBRAW_EXCEPTION_ALLOC; +} + +ushort * LibRaw::get_masked_pointer(int row, int col) +{ + if(row<0 || col < 0) return NULL; + if(!M.buffer) return NULL; + if(row < S.top_margin) + { + // top band + if(col < S.left_margin) + { + return &(M.tl[row*S.left_margin+col]); + } + else if (col < S.left_margin + S.width) + { + int icol = col - S.left_margin; + return &(M.top[row*S.width+icol]); + } + else if (col < S.raw_width) + { + int icol = col - S.left_margin - S.width; + return &(M.tr[row*S.right_margin+icol]); + } + else + return NULL; // out of bounds + } + else if (row < S.top_margin + S.height) + { + //normal image height + int irow = row - S.top_margin; + if(col < S.left_margin) + { + return &M.left[irow*S.left_margin + col]; + } + else if (col < S.left_margin + S.width) + { + // central image + return NULL; + } + else if (col < S.raw_width) + { + int icol = col - S.left_margin - S.width; + return &M.right[irow*S.right_margin+icol]; + } + else + return NULL; // out of bounds + } + else if (row < S.raw_height) + { + int irow = row - S.top_margin - S.height; + // bottom band + if(col < S.left_margin) + { + return &M.bl[irow*S.left_margin+col]; + } + else if (col < S.left_margin + S.width) + { + int icol = col - S.left_margin; + return &M.bottom[irow*S.width + icol]; + } + else if (col < S.raw_width) + { + int icol = col - S.left_margin - S.width; + return &M.br[irow*S.right_margin + icol]; + } + else + return NULL; // out of bounds + } + else + { + // out of bounds + return NULL; + } + // fallback + return NULL; +} + +void LibRaw:: init_masked_ptrs() +{ + if(!M.buffer) return; + + // top band + M.tl = M.buffer; + M.top = M.tl +(S.top_margin*S.left_margin); + M.tr = M.top + (S.top_margin*S.width); + + // left-right + M.left = M.tr + (S.top_margin * S.right_margin); + M.right = M.left + (S.left_margin * S.height); + + // bottom band + M.bl = M.right + (S.right_margin * S.height); + M.bottom = M.bl + (S.left_margin * S.bottom_margin); + M.br = M.bottom + (S.width * S.bottom_margin); + +} + +int LibRaw::add_masked_borders_to_bitmap() +{ + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_PRE_INTERPOLATE); + CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW); + + if(S.width != S.iwidth || S.height!=S.iheight) + return LIBRAW_CANNOT_ADDMASK; + + if(P1.is_foveon || !P1.filters) + return LIBRAW_CANNOT_ADDMASK; + + if(!imgdata.image) + return LIBRAW_OUT_OF_ORDER_CALL; + + if(S.raw_width < S.width || S.raw_height < S.height) + return LIBRAW_SUCCESS; // nothing to do or already called + + if(S.width == S.raw_width && S.height == S.raw_height) + return LIBRAW_SUCCESS; // nothing to do or already called + + ushort (*newimage)[4]; + + newimage = (ushort (*)[4]) calloc (S.raw_height*S.raw_width, sizeof (*newimage)); + merror (newimage, "add_masked_borders_to_bitmap()"); + + int r,c; + // top rows + for (r=0; rvalid()) + { + delete stream; + return LIBRAW_IO_ERROR; + } + ID.input_internal = 0; // preserve from deletion on error + int ret = open_datastream(stream); + if (ret == LIBRAW_SUCCESS) + { + ID.input_internal =1 ; // flag to delete datastream on recycle + } + else + { + delete stream; + ID.input_internal = 0; + } + return ret; +} + +int LibRaw::open_buffer(void *buffer, size_t size) +{ + // this stream will close on recycle() + if(!buffer || buffer==(void*)-1) + return LIBRAW_IO_ERROR; + + LibRaw_buffer_datastream *stream = new LibRaw_buffer_datastream(buffer,size); + if(!stream->valid()) + { + delete stream; + return LIBRAW_IO_ERROR; + } + ID.input_internal = 0; // preserve from deletion on error + int ret = open_datastream(stream); + if (ret == LIBRAW_SUCCESS) + { + ID.input_internal =1 ; // flag to delete datastream on recycle + } + else + { + delete stream; + ID.input_internal = 0; + } + return ret; +} + + +int LibRaw::open_datastream(LibRaw_abstract_datastream *stream) +{ + + if(!stream) + return ENOENT; + if(!stream->valid()) + return LIBRAW_IO_ERROR; + recycle(); + + try { + ID.input = stream; + SET_PROC_FLAG(LIBRAW_PROGRESS_OPEN); + + if (O.use_camera_matrix < 0) + O.use_camera_matrix = O.use_camera_wb; + + identify(); + + if(IO.fuji_width) + { + IO.fwidth = S.width; + IO.fheight = S.height; + S.iwidth = S.width = IO.fuji_width << !libraw_internal_data.unpacker_data.fuji_layout; + S.iheight = S.height = S.raw_height; + S.raw_height += 2*S.top_margin; + } + + int saved_raw_width = S.raw_width; + int saved_width = S.width; + // from packed_12_load_raw + if ((load_raw == &LibRaw:: packed_12_load_raw) && (S.raw_width * 2 >= S.width * 3)) + { + // raw_width is in bytes! + S.raw_width = S.raw_width * 2 / 3; + } + else if (S.pixel_aspect < 0.95 || S.pixel_aspect > 1.05) + { + S.width*=S.pixel_aspect; + } + + if(S.raw_width>S.width + S.left_margin) + S.right_margin = S.raw_width - S.width - S.left_margin; + + if(S.raw_height > S.height + S.top_margin) + S.bottom_margin = S.raw_height - S.height - S.top_margin; + + S.raw_width = saved_raw_width; + S.width = saved_width; + + if(C.profile_length) + { + if(C.profile) free(C.profile); + C.profile = malloc(C.profile_length); + merror(C.profile,"LibRaw::open_file()"); + ID.input->seek(ID.profile_offset,SEEK_SET); + ID.input->read(C.profile,C.profile_length,1); + } + + SET_PROC_FLAG(LIBRAW_PROGRESS_IDENTIFY); + } + catch ( LibRaw_exceptions err) { + EXCEPTION_HANDLER(err); + } + + if(P1.raw_count < 1) + return LIBRAW_FILE_UNSUPPORTED; + + if (O.user_flip >= 0) + S.flip = O.user_flip; + + switch ((S.flip+3600) % 360) + { + case 270: S.flip = 5; break; + case 180: S.flip = 3; break; + case 90: S.flip = 6; break; + } + + write_fun = &LibRaw::write_ppm_tiff; + + if (load_raw == &LibRaw::kodak_ycbcr_load_raw) + { + S.height += S.height & 1; + S.width += S.width & 1; + } + + IO.shrink = P1.filters && (O.half_size || O.threshold || O.aber[0] != 1 || O.aber[2] != 1); + S.iheight = (S.height + IO.shrink) >> IO.shrink; + S.iwidth = (S.width + IO.shrink) >> IO.shrink; + + SET_PROC_FLAG(LIBRAW_PROGRESS_SIZE_ADJUST); + + + return LIBRAW_SUCCESS; +} + +int LibRaw::unpack(void) +{ + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_LOAD_RAW); + CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY); + try { + + RUN_CALLBACK(LIBRAW_PROGRESS_LOAD_RAW,0,2); + if (O.shot_select >= P1.raw_count) + return LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE; + + if(!load_raw) + return LIBRAW_UNSPECIFIED_ERROR; + + if (O.use_camera_matrix && C.cmatrix[0][0] > 0.25) + { + memcpy (C.rgb_cam, C.cmatrix, sizeof (C.cmatrix)); + IO.raw_color = 0; + } + // already allocated ? + if(imgdata.image) free(imgdata.image); + + imgdata.image = (ushort (*)[4]) calloc (S.iheight*S.iwidth, sizeof (*imgdata.image)); + merror (imgdata.image, "unpack()"); + + + if(S.top_margin || S.left_margin || S.right_margin || S.bottom_margin) + { + unsigned sz = S.raw_height*(S.left_margin+S.right_margin) + + S.width*(S.top_margin+S.bottom_margin); + imgdata.masked_pixels.buffer = (ushort*) calloc(sz, sizeof(ushort)); + merror (imgdata.masked_pixels.buffer, "unpack()"); + init_masked_ptrs(); + } + if (libraw_internal_data.unpacker_data.meta_length) + { + libraw_internal_data.internal_data.meta_data = + (char *) malloc (libraw_internal_data.unpacker_data.meta_length); + merror (libraw_internal_data.internal_data.meta_data, "LibRaw::unpack()"); + } + ID.input->seek(libraw_internal_data.unpacker_data.data_offset, SEEK_SET); + // foveon_load_raw produces different data for document_mode, we'll + // deal with it in dcraw_document_mode_processing + int save_document_mode = O.document_mode; + O.document_mode = 0; + + if(!own_filtering_supported() && (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT)) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC_BIT; // turn on black and zeroes filtering + + (this->*load_raw)(); + + O.document_mode = save_document_mode; + + if (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC; // restore automated mode + + SET_PROC_FLAG(LIBRAW_PROGRESS_LOAD_RAW); + RUN_CALLBACK(LIBRAW_PROGRESS_LOAD_RAW,1,2); + + return 0; + } + catch ( LibRaw_exceptions err) { + EXCEPTION_HANDLER(err); + } +} + +int LibRaw::dcraw_document_mode_processing(void) +{ + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_PRE_INTERPOLATE); + CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW); + + try { + + if(IO.fwidth) + rotate_fuji_raw(); + + if(!own_filtering_supported() && (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT)) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC_BIT; // turn on black and zeroes filtering + + O.document_mode = 2; + if(P1.is_foveon) + { + // filter image data for foveon document mode + short *iptr = (short *)imgdata.image; + for (int i=0; i < S.height*S.width*4; i++) + { + if ((short) iptr[i] < 0) + iptr[i] = 0; + } + SET_PROC_FLAG(LIBRAW_PROGRESS_FOVEON_INTERPOLATE); + } + + O.use_fuji_rotate = 0; + if (!(O.filtering_mode & LIBRAW_FILTERING_NOZEROES) && IO.zero_is_bad) + { + remove_zeroes(); + SET_PROC_FLAG(LIBRAW_PROGRESS_REMOVE_ZEROES); + } + if(O.bad_pixels) + { + bad_pixels(O.bad_pixels); + SET_PROC_FLAG(LIBRAW_PROGRESS_BAD_PIXELS); + } + if (O.dark_frame) + { + subtract (O.dark_frame); + SET_PROC_FLAG(LIBRAW_PROGRESS_DARK_FRAME); + } + if(O.filtering_mode & LIBRAW_FILTERING_NOBLACKS) + C.black=0; + + if (O.user_black >= 0) + C.black = O.user_black; + + if (O.user_sat > 0) + C.maximum = O.user_sat; + + pre_interpolate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_PRE_INTERPOLATE); + + if (libraw_internal_data.internal_output_params.mix_green) + { + int i; + for (P1.colors=3, i=0; i < S.height*S.width; i++) + imgdata.image[i][1] = (imgdata.image[i][1] + imgdata.image[i][3]) >> 1; + } + SET_PROC_FLAG(LIBRAW_PROGRESS_MIX_GREEN); + + if (!P1.is_foveon && P1.colors == 3) + median_filter(); + SET_PROC_FLAG(LIBRAW_PROGRESS_MEDIAN_FILTER); + + if (!P1.is_foveon && O.highlight == 2) + blend_highlights(); + + if (!P1.is_foveon && O.highlight > 2) + recover_highlights(); + SET_PROC_FLAG(LIBRAW_PROGRESS_HIGHLIGHTS); + + if (O.use_fuji_rotate) + fuji_rotate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_FUJI_ROTATE); +#ifndef NO_LCMS + if(O.camera_profile) + { + apply_profile(O.camera_profile,O.output_profile); + SET_PROC_FLAG(LIBRAW_PROGRESS_APPLY_PROFILE); + } +#endif + if(!libraw_internal_data.output_data.histogram) + { + libraw_internal_data.output_data.histogram = (int (*)[LIBRAW_HISTOGRAM_SIZE]) malloc(sizeof(*libraw_internal_data.output_data.histogram)*4); + merror(libraw_internal_data.output_data.histogram,"LibRaw::dcraw_document_mode_processing()"); + } + convert_to_rgb(); + SET_PROC_FLAG(LIBRAW_PROGRESS_CONVERT_RGB); + + if (O.use_fuji_rotate) + stretch(); + SET_PROC_FLAG(LIBRAW_PROGRESS_STRETCH); + + if (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC; // restore automated mode + + return 0; + } + catch ( LibRaw_exceptions err) { + EXCEPTION_HANDLER(err); + } + +} + +#if 1 +#define FORC(cnt) for (c=0; c < cnt; c++) +#define FORCC FORC(ret->colors) +#define SWAP(a,b) { a ^= b; a ^= (b ^= a); } + +libraw_processed_image_t * LibRaw::dcraw_make_mem_thumb(int *errcode) +{ + if(!T.thumb) + { + if ( !ID.toffset) + { + if(errcode) *errcode= LIBRAW_NO_THUMBNAIL; + } + else + { + if(errcode) *errcode= LIBRAW_OUT_OF_ORDER_CALL; + } + return NULL; + } + + if (T.tformat == LIBRAW_THUMBNAIL_BITMAP) + { + libraw_processed_image_t * ret = + (libraw_processed_image_t *)::malloc(sizeof(libraw_processed_image_t)+T.tlength); + + if(!ret) + { + if(errcode) *errcode= ENOMEM; + return NULL; + } + + bzero(ret,sizeof(libraw_processed_image_t)); + ret->type = LIBRAW_IMAGE_BITMAP; + ret->height = T.theight; + ret->width = T.twidth; + ret->colors = 3; + ret->bits = 8; + ret->gamma_corrected = 1; + ret->data_size = T.tlength; + memmove(ret->data,T.thumb,T.tlength); + if(errcode) *errcode= 0; + return ret; + } + else if (T.tformat == LIBRAW_THUMBNAIL_JPEG) + { + ushort exif[5]; + int mk_exif = 0; + if(strcmp(T.thumb+6,"Exif")) mk_exif = 1; + + int dsize = T.tlength + mk_exif * (sizeof(exif)+sizeof(tiff_hdr)); + + libraw_processed_image_t * ret = + (libraw_processed_image_t *)::malloc(sizeof(libraw_processed_image_t)+dsize); + + if(!ret) + { + if(errcode) *errcode= ENOMEM; + return NULL; + } + + bzero(ret,sizeof(libraw_processed_image_t)); + + ret->type = LIBRAW_IMAGE_JPEG; + ret->data_size = dsize; + + ret->data[0] = 0xff; + ret->data[1] = 0xd8; + if(mk_exif) + { + struct tiff_hdr th; + memcpy (exif, "\xff\xe1 Exif\0\0", 10); + exif[1] = htons (8 + sizeof th); + memmove(ret->data+2,exif,sizeof(exif)); + tiff_head (&th, 0); + memmove(ret->data+(2+sizeof(exif)),&th,sizeof(th)); + memmove(ret->data+(2+sizeof(exif)+sizeof(th)),T.thumb+2,T.tlength-2); + } + else + { + memmove(ret->data+2,T.thumb+2,T.tlength-2); + } + if(errcode) *errcode= 0; + return ret; + + } + else + { + if(errcode) *errcode= LIBRAW_UNSUPPORTED_THUMBNAIL; + return NULL; + + } +} + + + +libraw_processed_image_t *LibRaw::dcraw_make_mem_image(int *errcode) +{ + if((imgdata.progress_flags & LIBRAW_PROGRESS_THUMB_MASK) < LIBRAW_PROGRESS_PRE_INTERPOLATE) + { + if(errcode) *errcode= LIBRAW_OUT_OF_ORDER_CALL; + return NULL; + } + + if(!libraw_internal_data.output_data.histogram) + { + libraw_internal_data.output_data.histogram = + (int (*)[LIBRAW_HISTOGRAM_SIZE]) malloc(sizeof(*libraw_internal_data.output_data.histogram)*4); + merror(libraw_internal_data.output_data.histogram,"LibRaw::dcraw_make_mem_image()"); + } + + unsigned ds = S.height * S.width * (O.output_bps/8) * P1.colors; + libraw_processed_image_t *ret = (libraw_processed_image_t*)::malloc(sizeof(libraw_processed_image_t)+ds); + if(!ret) + { + if(errcode) *errcode= ENOMEM; + return NULL; + } + bzero(ret,sizeof(libraw_processed_image_t)); + // metadata init + + int s_iheight = S.iheight; + int s_iwidth = S.iwidth; + int s_width = S.width; + int s_hwight = S.height; + + S.iheight = S.height; + S.iwidth = S.width; + + + if (S.flip & 4) SWAP(S.height,S.width); + + + ret->type = LIBRAW_IMAGE_BITMAP; + ret->height = S.height; + ret->width = S.width; + ret->colors = P1.colors; + ret->bits = O.output_bps; + ret->gamma_corrected = (O.output_bps == 8)?1:O.gamma_16bit; + + ret->data_size = ds; + + // Cut'n'paste from write_tiff_ppm, should be generalized later + uchar *bufp = ret->data; + uchar *ppm; + ushort *ppm2,lut16[0x10000]; + int c, row, col, soff, rstep, cstep; + + + if (ret->bits == 8 || ret->gamma_corrected ) gamma_lut (lut16); + soff = flip_index (0, 0); + cstep = flip_index (0, 1) - soff; + rstep = flip_index (1, 0) - flip_index (0, S.width); + + + for (row=0; row < ret->height; row++, soff += rstep) + { + ppm2 = (ushort*) (ppm = bufp); + for (col=0; col < ret->width; col++, soff += cstep) + if (ret->bits == 8) + FORCC ppm [col*ret->colors+c] = lut16[imgdata.image[soff][c]]/256; + else if(ret->gamma_corrected) + FORCC ppm2[col*ret->colors+c] = lut16[imgdata.image[soff][c]]; + else + FORCC ppm2[col*ret->colors+c] = imgdata.image[soff][c]; + bufp+=ret->colors*(ret->bits/8)*ret->width; + } + if(errcode) *errcode= 0; + + S.iheight = s_iheight; + S.iwidth = s_iwidth; + S.width = s_width; + S.height = s_hwight; + + return ret; +} + +#undef FORC +#undef FORCC +#undef SWAP +#endif + + +int LibRaw::dcraw_ppm_tiff_writer(const char *filename) +{ + CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW); + + if(!imgdata.image) + return LIBRAW_OUT_OF_ORDER_CALL; + + if(!filename) + return ENOENT; + FILE *f = fopen(filename,"wb"); + + if(!f) + return errno; + + try { + if(!libraw_internal_data.output_data.histogram) + { + libraw_internal_data.output_data.histogram = + (int (*)[LIBRAW_HISTOGRAM_SIZE]) malloc(sizeof(*libraw_internal_data.output_data.histogram)*4); + merror(libraw_internal_data.output_data.histogram,"LibRaw::dcraw_ppm_tiff_writer()"); + } + write_ppm_tiff(f); + SET_PROC_FLAG(LIBRAW_PROGRESS_FLIP); + fclose(f); + return 0; + } + catch ( LibRaw_exceptions err) { + fclose(f); + EXCEPTION_HANDLER(err); + } +} + +void LibRaw::kodak_thumb_loader() +{ + // some kodak cameras + ushort s_height = S.height, s_width = S.width,s_iwidth = S.iwidth,s_iheight=S.iheight; + int s_colors = P1.colors; + unsigned s_filters = P1.filters; + ushort (*s_image)[4] = imgdata.image; + + + S.height = T.theight; + S.width = T.twidth; + P1.filters = 0; + + if (thumb_load_raw == &CLASS kodak_ycbcr_load_raw) + { + S.height += S.height & 1; + S.width += S.width & 1; + } + + imgdata.image = (ushort (*)[4]) calloc (S.iheight*S.iwidth, sizeof (*imgdata.image)); + merror (imgdata.image, "LibRaw::kodak_thumb_loader()"); + + ID.input->seek(ID.toffset, SEEK_SET); + // read kodak thumbnail into T.image[] + (this->*thumb_load_raw)(); + + // copy-n-paste from image pipe +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define LIM(x,min,max) MAX(min,MIN(x,max)) +#define CLIP(x) LIM(x,0,65535) +#define SWAP(a,b) { a ^= b; a ^= (b ^= a); } + + // from scale_colors + { + double dmax; + float scale_mul[4]; + int c,val; + for (dmax=DBL_MAX, c=0; c < 3; c++) + if (dmax > C.pre_mul[c]) + dmax = C.pre_mul[c]; + + for( c=0; c< 3; c++) + scale_mul[c] = (C.pre_mul[c] / dmax) * 65535.0 / C.maximum; + scale_mul[3] = scale_mul[1]; + + size_t size = S.height * S.width; + for (int i=0; i < size*4 ; i++) + { + val = imgdata.image[0][i]; + if(!val) continue; + val *= scale_mul[i & 3]; + imgdata.image[0][i] = CLIP(val); + } + } + + // from convert_to_rgb + ushort *img; + int row,col; + + int (*t_hist)[LIBRAW_HISTOGRAM_SIZE] = (int (*)[LIBRAW_HISTOGRAM_SIZE]) calloc(sizeof(*t_hist),4); + merror (t_hist, "LibRaw::kodak_thumb_loader()"); + + float out[3], + out_cam[3][4] = + { + {2.81761312, -1.98369181, 0.166078627, 0}, + {-0.111855984, 1.73688626, -0.625030339, 0}, + {-0.0379119813, -0.891268849, 1.92918086, 0} + }; + + for (img=imgdata.image[0], row=0; row < S.height; row++) + for (col=0; col < S.width; col++, img+=4) + { + out[0] = out[1] = out[2] = 0; + for(int c=0;c<3;c++) + { + out[0] += out_cam[0][c] * img[c]; + out[1] += out_cam[1][c] * img[c]; + out[2] += out_cam[2][c] * img[c]; + } + for(int c=0; c<3; c++) + img[c] = CLIP((int) out[c]); + for(int c=0; c> 3]++; + + } + + // from gamma_lut + int (*save_hist)[LIBRAW_HISTOGRAM_SIZE] = libraw_internal_data.output_data.histogram; + libraw_internal_data.output_data.histogram = t_hist; + + ushort *lut16 = (ushort*)calloc(0x10000,sizeof(ushort)); + merror(lut16,"LibRaw::kodak_thumb_loader()"); + gamma_lut(lut16); + + libraw_internal_data.output_data.histogram = save_hist; + + free(t_hist); + + // from write_ppm_tiff - copy pixels into bitmap + + S.iheight = S.height; + S.iwidth = S.width; + if (S.flip & 4) SWAP(S.height,S.width); + + if(T.thumb) free(T.thumb); + T.thumb = (char*) calloc (S.width * S.height, P1.colors); + merror (T.thumb, "LibRaw::kodak_thumb_loader()"); + T.tlength = S.width * S.height * P1.colors; + + // from write_tiff_ppm + { + int soff = flip_index (0, 0); + int cstep = flip_index (0, 1) - soff; + int rstep = flip_index (1, 0) - flip_index (0, S.width); + + for (int row=0; row < S.height; row++, soff += rstep) + { + char *ppm = T.thumb + row*S.width*P1.colors; + for (int col=0; col < S.width; col++, soff += cstep) + for(int c = 0; c < P1.colors; c++) + ppm [col*P1.colors+c] = lut16[imgdata.image[soff][c]]/256; + } + } + free(lut16); + // restore variables + free(imgdata.image); + imgdata.image = s_image; + + T.twidth = S.width; + S.width = s_width; + + S.iwidth = s_iwidth; + S.iheight = s_iheight; + + T.theight = S.height; + S.height = s_height; + + T.tcolors = P1.colors; + P1.colors = s_colors; + + P1.filters = s_filters; +} +#undef MIN +#undef MAX +#undef LIM +#undef CLIP +#undef SWAP + + +void LibRaw::foveon_thumb_loader (void) +{ + unsigned bwide, row, col, bitbuf=0, bit=1, c, i; + struct decode *dindex; + short pred[3]; + + if(T.thumb) free(T.thumb); + T.thumb = NULL; + + bwide = get4(); + if (bwide > 0) + { + if (bwide < T.twidth*3) return; + T.thumb = (char*)malloc(3*T.twidth * T.theight); + merror (T.thumb, "foveon_thumb()"); + char *buf = (char*)malloc(bwide); + merror (buf, "foveon_thumb()"); + for (row=0; row < T.theight; row++) + { + ID.input->read(buf, 1, bwide); + memmove(T.thumb+(row*T.twidth*3),buf,T.twidth*3); + } + free(buf); + T.tlength = 3*T.twidth * T.theight; + T.tformat = LIBRAW_THUMBNAIL_BITMAP; + return; + } + else + { + foveon_decoder (256, 0); + T.thumb = (char*)malloc(3*T.twidth * T.theight); + char *bufp = T.thumb; + merror (T.thumb, "foveon_thumb()"); + for (row=0; row < T.theight; row++) + { + memset (pred, 0, sizeof pred); + if (!bit) get4(); + for (bit=col=0; col < T.twidth; col++) + for(c=0;c<3;c++) + { + for (dindex=first_decode; dindex->branch[0]; ) + { + if ((bit = (bit-1) & 31) == 31) + for (i=0; i < 4; i++) + bitbuf = (bitbuf << 8) + ID.input->get_char(); + dindex = dindex->branch[bitbuf >> bit & 1]; + } + pred[c] += dindex->leaf; + (*bufp++)=pred[c]; + } + } + T.tformat = LIBRAW_THUMBNAIL_BITMAP; + T.tlength = 3*T.twidth * T.theight; + } + return; +} + + +// Äîñòàåò thumbnail èç ôàéëà, ñòàâèò thumb_format â ñîîòâåòñòâèè ñ ôîðìàòîì +int LibRaw::unpack_thumb(void) +{ + CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY); + CHECK_ORDER_BIT(LIBRAW_PROGRESS_THUMB_LOAD); + + try { + if ( !ID.toffset) + { + return LIBRAW_NO_THUMBNAIL; + } + else if (thumb_load_raw) + { + kodak_thumb_loader(); + T.tformat = LIBRAW_THUMBNAIL_BITMAP; + SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD); + return 0; + } + else + { + ID.input->seek(ID.toffset, SEEK_SET); + if ( write_thumb == &LibRaw::jpeg_thumb) + { + if(T.thumb) free(T.thumb); + T.thumb = (char *) malloc (T.tlength); + merror (T.thumb, "jpeg_thumb()"); + ID.input->read (T.thumb, 1, T.tlength); + T.tcolors = 3; + T.tformat = LIBRAW_THUMBNAIL_JPEG; + SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD); + return 0; + } + else if (write_thumb == &LibRaw::ppm_thumb) + { + T.tlength = T.twidth * T.theight*3; + if(T.thumb) free(T.thumb); + + T.thumb = (char *) malloc (T.tlength); + merror (T.thumb, "ppm_thumb()"); + + ID.input->read(T.thumb, 1, T.tlength); + + T.tformat = LIBRAW_THUMBNAIL_BITMAP; + SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD); + return 0; + + } + else if (write_thumb == &LibRaw::foveon_thumb) + { + foveon_thumb_loader(); + // may return with error, so format is set in + // foveon thumb loader itself + SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD); + return 0; + } + // else if -- all other write_thumb cases! + else + { + return LIBRAW_UNSUPPORTED_THUMBNAIL; + } + } + // last resort + return LIBRAW_UNSUPPORTED_THUMBNAIL; + } + catch ( LibRaw_exceptions err) { + EXCEPTION_HANDLER(err); + } + +} + +int LibRaw::dcraw_thumb_writer(const char *fname) +{ +// CHECK_ORDER_LOW(LIBRAW_PROGRESS_THUMB_LOAD); + + if(!fname) + return ENOENT; + + FILE *tfp = fopen(fname,"wb"); + + if(!tfp) + return errno; + + if(!T.thumb) + { + fclose(tfp); + return LIBRAW_OUT_OF_ORDER_CALL; + } + + try { + switch (T.tformat) + { + case LIBRAW_THUMBNAIL_JPEG: + jpeg_thumb_writer (tfp,T.thumb,T.tlength); + break; + case LIBRAW_THUMBNAIL_BITMAP: + fprintf (tfp, "P6\n%d %d\n255\n", T.twidth, T.theight); + fwrite (T.thumb, 1, T.tlength, tfp); + break; + default: + fclose(tfp); + return LIBRAW_UNSUPPORTED_THUMBNAIL; + } + fclose(tfp); + return 0; + } + catch ( LibRaw_exceptions err) { + fclose(tfp); + EXCEPTION_HANDLER(err); + } +} + +int LibRaw::adjust_sizes_info_only(void) +{ + CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY); + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_FUJI_ROTATE); + if (O.use_fuji_rotate) + { + if (IO.fuji_width) + { + // restore saved values + if(IO.fheight) + { + S.height = IO.fheight; + S.width = IO.fwidth; + S.iheight = (S.height + IO.shrink) >> IO.shrink; + S.iwidth = (S.width + IO.shrink) >> IO.shrink; + S.raw_height -= 2*S.top_margin; + IO.fheight = IO.fwidth = 0; // prevent repeated calls + } + // dcraw code + IO.fuji_width = (IO.fuji_width - 1 + IO.shrink) >> IO.shrink; + S.iwidth = (ushort)(IO.fuji_width / sqrt(0.5)); + S.iheight = (ushort)( (S.iheight - IO.fuji_width) / sqrt(0.5)); + } + else + { + if (S.pixel_aspect < 1) S.iheight = (ushort)( S.iheight / S.pixel_aspect + 0.5); + if (S.pixel_aspect > 1) S.iwidth = (ushort) (S.iwidth * S.pixel_aspect + 0.5); + } + } + SET_PROC_FLAG(LIBRAW_PROGRESS_FUJI_ROTATE); + if (S.flip & 4) + { + unsigned short t = S.iheight; + S.iheight=S.iwidth; + S.iwidth = t; + SET_PROC_FLAG(LIBRAW_PROGRESS_FLIP); + } + return 0; +} + +int LibRaw::rotate_fuji_raw(void) +{ + CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW); + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_PRE_INTERPOLATE); + + + if(!IO.fwidth) return LIBRAW_SUCCESS; + int row,col,r,c; + ushort (*newimage)[4]; + ushort fiwidth,fiheight; + + fiheight = (IO.fheight + IO.shrink) >> IO.shrink; + fiwidth = (IO.fwidth + IO.shrink) >> IO.shrink; + + newimage = (ushort (*)[4]) calloc (fiheight*fiwidth, sizeof (*newimage)); + merror(newimage,"rotate_fuji_raw()"); + for(row=0;row> 1); + c = col + ((row+1) >> 1); + } else { + r = IO.fuji_width - 1 + row - (col >> 1); + c = row + ((col+1) >> 1); + } + newimage[((r) >> IO.shrink)*fiwidth + ((c) >> IO.shrink)][FC(r,c)] = + imgdata.image[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][FC(row,col)]; + } + } + // restore fuji sizes! + S.height = IO.fheight; + S.width = IO.fwidth; + S.iheight = (S.height + IO.shrink) >> IO.shrink; + S.iwidth = (S.width + IO.shrink) >> IO.shrink; + S.raw_height -= 2*S.top_margin; + IO.fheight = IO.fwidth = 0; // prevent repeated calls + + free(imgdata.image); + imgdata.image = newimage; + return LIBRAW_SUCCESS; + +} + + +int LibRaw::dcraw_process(void) +{ + int quality,i; + + + CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW); + CHECK_ORDER_HIGH(LIBRAW_PROGRESS_PRE_INTERPOLATE); + + try { + + if(IO.fwidth) + rotate_fuji_raw(); + + + if(!own_filtering_supported() && (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT)) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC_BIT; // turn on black and zeroes filtering + + if(O.half_size) + O.four_color_rgb = 1; + + if (!(O.filtering_mode & LIBRAW_FILTERING_NOZEROES) && IO.zero_is_bad) + { + remove_zeroes(); + SET_PROC_FLAG(LIBRAW_PROGRESS_REMOVE_ZEROES); + } + if(O.bad_pixels) + { + bad_pixels(O.bad_pixels); + SET_PROC_FLAG(LIBRAW_PROGRESS_BAD_PIXELS); + } + if (O.dark_frame) + { + subtract (O.dark_frame); + SET_PROC_FLAG(LIBRAW_PROGRESS_DARK_FRAME); + } + + quality = 2 + !IO.fuji_width; + + if(O.filtering_mode & LIBRAW_FILTERING_NOBLACKS) + C.black=0; + + if (O.user_qual >= 0) quality = O.user_qual; + if (O.user_black >= 0) C.black = O.user_black; + if (O.user_sat > 0) C.maximum = O.user_sat; + + if (P1.is_foveon && !O.document_mode) + { + foveon_interpolate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_FOVEON_INTERPOLATE); + } + + if (!P1.is_foveon && O.document_mode < 2) + { + scale_colors(); + SET_PROC_FLAG(LIBRAW_PROGRESS_SCALE_COLORS); + } + + pre_interpolate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_PRE_INTERPOLATE); + + if (P1.filters && !O.document_mode) + { + if (quality == 0) + lin_interpolate(); + else if (quality == 1 || P1.colors > 3) + vng_interpolate(); + else if (quality == 2) + ppg_interpolate(); + else + ahd_interpolate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_INTERPOLATE); + } + if (IO.mix_green) + { + for (P1.colors=3, i=0; i < S.height * S.width; i++) + imgdata.image[i][1] = (imgdata.image[i][1] + imgdata.image[i][3]) >> 1; + SET_PROC_FLAG(LIBRAW_PROGRESS_MIX_GREEN); + } + + if(!P1.is_foveon) + { + if (P1.colors == 3) + { + median_filter(); + SET_PROC_FLAG(LIBRAW_PROGRESS_MEDIAN_FILTER); + } + + if (O.highlight == 2) + { + blend_highlights(); + SET_PROC_FLAG(LIBRAW_PROGRESS_HIGHLIGHTS); + } + + if (O.highlight > 2) + { + recover_highlights(); + SET_PROC_FLAG(LIBRAW_PROGRESS_HIGHLIGHTS); + } + } + if (O.use_fuji_rotate) + { + fuji_rotate(); + SET_PROC_FLAG(LIBRAW_PROGRESS_FUJI_ROTATE); + } + + if(!libraw_internal_data.output_data.histogram) + { + libraw_internal_data.output_data.histogram = (int (*)[LIBRAW_HISTOGRAM_SIZE]) malloc(sizeof(*libraw_internal_data.output_data.histogram)*4); + merror(libraw_internal_data.output_data.histogram,"LibRaw::dcraw_process()"); + } +#ifndef NO_LCMS + if(O.camera_profile) + { + apply_profile(O.camera_profile,O.output_profile); + SET_PROC_FLAG(LIBRAW_PROGRESS_APPLY_PROFILE); + } +#endif + + convert_to_rgb(); + SET_PROC_FLAG(LIBRAW_PROGRESS_CONVERT_RGB); + + if (O.use_fuji_rotate) + { + stretch(); + SET_PROC_FLAG(LIBRAW_PROGRESS_STRETCH); + } + if (O.filtering_mode & LIBRAW_FILTERING_AUTOMATIC_BIT) + O.filtering_mode = LIBRAW_FILTERING_AUTOMATIC; // restore automated mode + return 0; + } + catch ( LibRaw_exceptions err) { + EXCEPTION_HANDLER(err); + } +} + +// Supported cameras: +static const char *static_camera_list[] = +{ +"Adobe Digital Negative (DNG)", +"Apple QuickTake 100", +"Apple QuickTake 150", +"Apple QuickTake 200", +"AVT F-080C", +"AVT F-145C", +"AVT F-201C", +"AVT F-510C", +"AVT F-810C", +"Canon PowerShot 600", +"Canon PowerShot A5", +"Canon PowerShot A5 Zoom", +"Canon PowerShot A50", +"Canon PowerShot A460 (CHDK hack)", +"Canon PowerShot A530 (CHDK hack)", +"Canon PowerShot A610 (CHDK hack)", +"Canon PowerShot A620 (CHDK hack)", +"Canon PowerShot A630 (CHDK hack)", +"Canon PowerShot A640 (CHDK hack)", +"Canon PowerShot A650 (CHDK hack)", +"Canon PowerShot A710 IS (CHDK hack)", +"Canon PowerShot A720 IS (CHDK hack)", +"Canon PowerShot Pro70", +"Canon PowerShot Pro90 IS", +"Canon PowerShot G1", +"Canon PowerShot G2", +"Canon PowerShot G3", +"Canon PowerShot G5", +"Canon PowerShot G6", +"Canon PowerShot G7 (CHDK hack)", +"Canon PowerShot G9", +"Canon PowerShot G10", +"Canon PowerShot S2 IS (CHDK hack)", +"Canon PowerShot S3 IS (CHDK hack)", +"Canon PowerShot S5 IS (CHDK hack)", +"Canon PowerShot SD300 (CHDK hack)", +"Canon PowerShot S30", +"Canon PowerShot S40", +"Canon PowerShot S45", +"Canon PowerShot S50", +"Canon PowerShot S60", +"Canon PowerShot S70", +"Canon PowerShot Pro1", +"Canon EOS D30", +"Canon EOS D60", +"Canon EOS 5D", +"Canon EOS 5D Mark II", +"Canon EOS 10D", +"Canon EOS 20D", +"Canon EOS 30D", +"Canon EOS 40D", +"Canon EOS 50D", +"Canon EOS 300D / Digital Rebel / Kiss Digital", +"Canon EOS 350D / Digital Rebel XT / Kiss Digital N", +"Canon EOS 400D / Digital Rebel XTi / Kiss Digital X", +"Canon EOS 450D / Digital Rebel XSi / Kiss Digital X2", +"Canon EOS 1000D / Digital Rebel XS / Kiss Digital F", +"Canon EOS D2000C", +"Canon EOS-1D", +"Canon EOS-1DS", +"Canon EOS-1D Mark II", +"Canon EOS-1D Mark III", +"Canon EOS-1D Mark II N", +"Canon EOS-1Ds Mark II", +"Canon EOS-1Ds Mark III", +"Casio QV-2000UX", +"Casio QV-3000EX", +"Casio QV-3500EX", +"Casio QV-4000", +"Casio QV-5700", +"Casio QV-R41", +"Casio QV-R51", +"Casio QV-R61", +"Casio EX-S100", +"Casio EX-Z4", +"Casio EX-Z50", +"Casio EX-Z55", +"Casio Exlim Pro 505", +"Casio Exlim Pro 600", +"Casio Exlim Pro 700", +"Contax N Digital", +"Creative PC-CAM 600", +"Epson R-D1", +"Foculus 531C", +"Fuji FinePix E550", +"Fuji FinePix E900", +"Fuji FinePix F700", +"Fuji FinePix F710", +"Fuji FinePix F800", +"Fuji FinePix F810", +"Fuji FinePix S2Pro", +"Fuji FinePix S3Pro", +"Fuji FinePix S5Pro", +"Fuji FinePix S20Pro", +"Fuji FinePix S100FS", +"Fuji FinePix S5000", +"Fuji FinePix S5100/S5500", +"Fuji FinePix S5200/S5600", +"Fuji FinePix S6000fd", +"Fuji FinePix S7000", +"Fuji FinePix S9000/S9500", +"Fuji FinePix S9100/S9600", +"Fuji IS-1", +"Hasselblad CFV", +"Hasselblad H3D", +"Hasselblad V96C", +"Imacon Ixpress 16-megapixel", +"Imacon Ixpress 22-megapixel", +"Imacon Ixpress 39-megapixel", +"ISG 2020x1520", +"Kodak DC20 (see Oliver Hartman's page)", +"Kodak DC25 (see Jun-ichiro Itoh's page)", +"Kodak DC40", +"Kodak DC50", +"Kodak DC120 (also try kdc2tiff)", +"Kodak DCS200", +"Kodak DCS315C", +"Kodak DCS330C", +"Kodak DCS420", +"Kodak DCS460", +"Kodak DCS460A", +"Kodak DCS520C", +"Kodak DCS560C", +"Kodak DCS620C", +"Kodak DCS620X", +"Kodak DCS660C", +"Kodak DCS660M", +"Kodak DCS720X", +"Kodak DCS760C", +"Kodak DCS760M", +"Kodak EOSDCS1", +"Kodak EOSDCS3B", +"Kodak NC2000F", +"Kodak ProBack", +"Kodak PB645C", +"Kodak PB645H", +"Kodak PB645M", +"Kodak DCS Pro 14n", +"Kodak DCS Pro 14nx", +"Kodak DCS Pro SLR/c", +"Kodak DCS Pro SLR/n", +"Kodak C330", +"Kodak C603", +"Kodak P850", +"Kodak P880", +"Kodak KAI-0340", +"Konica KD-400Z", +"Konica KD-510Z", +"Leaf AFi 7", +"Leaf Aptus 17", +"Leaf Aptus 22", +"Leaf Aptus 54S", +"Leaf Aptus 65", +"Leaf Aptus 75", +"Leaf Aptus 75S", +"Leaf Cantare", +"Leaf CatchLight", +"Leaf CMost", +"Leaf DCB2", +"Leaf Valeo 6", +"Leaf Valeo 11", +"Leaf Valeo 17", +"Leaf Valeo 22", +"Leaf Volare", +"Leica Digilux 2", +"Leica Digilux 3", +"Leica D-LUX2", +"Leica D-LUX3", +"Leica D-LUX4", +"Leica V-LUX1", +"Logitech Fotoman Pixtura", +"Mamiya ZD", +"Micron 2010", +"Minolta RD175", +"Minolta DiMAGE 5", +"Minolta DiMAGE 7", +"Minolta DiMAGE 7i", +"Minolta DiMAGE 7Hi", +"Minolta DiMAGE A1", +"Minolta DiMAGE A2", +"Minolta DiMAGE A200", +"Minolta DiMAGE G400", +"Minolta DiMAGE G500", +"Minolta DiMAGE G530", +"Minolta DiMAGE G600", +"Minolta DiMAGE Z2", +"Minolta Alpha/Dynax/Maxxum 5D", +"Minolta Alpha/Dynax/Maxxum 7D", +"Nikon D1", +"Nikon D1H", +"Nikon D1X", +"Nikon D2H", +"Nikon D2Hs", +"Nikon D2X", +"Nikon D2Xs", +"Nikon D3", +"Nikon D40", +"Nikon D40X", +"Nikon D50", +"Nikon D60", +"Nikon D70", +"Nikon D70s", +"Nikon D80", +"Nikon D90", +"Nikon D100", +"Nikon D200", +"Nikon D300", +"Nikon D700", +"Nikon E700 (\"DIAG RAW\" hack)", +"Nikon E800 (\"DIAG RAW\" hack)", +"Nikon E880 (\"DIAG RAW\" hack)", +"Nikon E900 (\"DIAG RAW\" hack)", +"Nikon E950 (\"DIAG RAW\" hack)", +"Nikon E990 (\"DIAG RAW\" hack)", +"Nikon E995 (\"DIAG RAW\" hack)", +"Nikon E2100 (\"DIAG RAW\" hack)", +"Nikon E2500 (\"DIAG RAW\" hack)", +"Nikon E3200 (\"DIAG RAW\" hack)", +"Nikon E3700 (\"DIAG RAW\" hack)", +"Nikon E4300 (\"DIAG RAW\" hack)", +"Nikon E4500 (\"DIAG RAW\" hack)", +"Nikon E5000", +"Nikon E5400", +"Nikon E5700", +"Nikon E8400", +"Nikon E8700", +"Nikon E8800", +"Nikon Coolpix P6000", +"Nikon Coolpix S6 (\"DIAG RAW\" hack)", +"Nokia N95", +"Olympus C3030Z", +"Olympus C5050Z", +"Olympus C5060WZ", +"Olympus C7070WZ", +"Olympus C70Z,C7000Z", +"Olympus C740UZ", +"Olympus C770UZ", +"Olympus C8080WZ", +"Olympus E-1", +"Olympus E-3", +"Olympus E-10", +"Olympus E-20", +"Olympus E-300", +"Olympus E-330", +"Olympus E-400", +"Olympus E-410", +"Olympus E-420", +"Olympus E-500", +"Olympus E-510", +"Olympus E-520", +"Olympus SP310", +"Olympus SP320", +"Olympus SP350", +"Olympus SP500UZ", +"Olympus SP510UZ", +"Olympus SP550UZ", +"Olympus SP560UZ", +"Olympus SP570UZ", +"Panasonic DMC-FZ8", +"Panasonic DMC-FZ18", +"Panasonic DMC-FZ28", +"Panasonic DMC-FZ30", +"Panasonic DMC-FZ50", +"Panasonic DMC-FX150", +"Panasonic DMC-G1", +"Panasonic DMC-L1", +"Panasonic DMC-L10", +"Panasonic DMC-LC1", +"Panasonic DMC-LX1", +"Panasonic DMC-LX2", +"Panasonic DMC-LX3", +"Pentax *ist D", +"Pentax *ist DL", +"Pentax *ist DL2", +"Pentax *ist DS", +"Pentax *ist DS2", +"Pentax K10D", +"Pentax K20D", +"Pentax K100D", +"Pentax K100D Super", +"Pentax K200D", +"Pentax Optio S", +"Pentax Optio S4", +"Pentax Optio 33WR", +"Pentax Optio 750Z", +"Phase One LightPhase", +"Phase One H 10", +"Phase One H 20", +"Phase One H 25", +"Phase One P 20", +"Phase One P 25", +"Phase One P 30", +"Phase One P 45", +"Pixelink A782", +"Polaroid x530", +"Rollei d530flex", +"RoverShot 3320af", +"Samsung GX-1S", +"Samsung GX-10", +"Samsung S85 (hacked)", +"Sarnoff 4096x5440", +"Sigma SD9", +"Sigma SD10", +"Sigma SD14", +"Sinar 3072x2048", +"Sinar 4080x4080", +"Sinar 4080x5440", +"Sinar STI format", +"SMaL Ultra-Pocket 3", +"SMaL Ultra-Pocket 4", +"SMaL Ultra-Pocket 5", +"Sony DSC-F828", +"Sony DSC-R1", +"Sony DSC-V3", +"Sony DSLR-A100", +"Sony DSLR-A200", +"Sony DSLR-A300", +"Sony DSLR-A350", +"Sony DSLR-A700", +"Sony DSLR-A900", +"Sony XCD-SX910CR", +"STV680 VGA", + NULL +}; + +const char** LibRaw::cameraList() { return static_camera_list;} +int LibRaw::cameraCount() { return (sizeof(static_camera_list)/sizeof(static_camera_list[0]))-1; } + + +const char * LibRaw::strprogress(enum LibRaw_progress p) +{ + switch(p) + { + case LIBRAW_PROGRESS_START: + return "Starting"; + case LIBRAW_PROGRESS_OPEN : + return "Opening file"; + case LIBRAW_PROGRESS_IDENTIFY : + return "Reading metadata"; + case LIBRAW_PROGRESS_SIZE_ADJUST: + return "Adjusting size"; + case LIBRAW_PROGRESS_LOAD_RAW: + return "Reading RAW data"; + case LIBRAW_PROGRESS_REMOVE_ZEROES: + return "Clearing zero values"; + case LIBRAW_PROGRESS_BAD_PIXELS : + return "Removing dead pixels"; + case LIBRAW_PROGRESS_DARK_FRAME: + return "Subtracting dark frame data"; + case LIBRAW_PROGRESS_FOVEON_INTERPOLATE: + return "Interpolating Foveon sensor data"; + case LIBRAW_PROGRESS_SCALE_COLORS: + return "Scaling colors"; + case LIBRAW_PROGRESS_PRE_INTERPOLATE: + return "Pre-interpolating"; + case LIBRAW_PROGRESS_INTERPOLATE: + return "Interpolating"; + case LIBRAW_PROGRESS_MIX_GREEN : + return "Mixing green channels"; + case LIBRAW_PROGRESS_MEDIAN_FILTER : + return "Median filter"; + case LIBRAW_PROGRESS_HIGHLIGHTS: + return "Highlight recovery"; + case LIBRAW_PROGRESS_FUJI_ROTATE : + return "Rotating Fuji diagonal data"; + case LIBRAW_PROGRESS_FLIP : + return "Flipping image"; + case LIBRAW_PROGRESS_APPLY_PROFILE: + return "ICC conversion"; + case LIBRAW_PROGRESS_CONVERT_RGB: + return "Converting to RGB"; + case LIBRAW_PROGRESS_STRETCH: + return "Stretching image"; + case LIBRAW_PROGRESS_THUMB_LOAD: + return "Loading thumbnail"; + default: + return "Some strange things"; + } +} diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000000..e37caff8397f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS=LibRaw +include shared.am +noinst_LIBRARIES=libdarktable.a +libdarktable_a_SOURCES=control/jobs.c library/film.c library/library.c gui/gtk.c gui/navigation.c gui/histogram.c gui/curveeditor.c develop/develop.c develop/imageop.c common/imageio.c common/darktable.c common/nikon_curve.c control/control.c develop/iop_hsb.c gui/develop_view.c common/image.c common/image_cache.c common/image_compression.c +bin_PROGRAMS=darktable +darktable_SOURCES=main.c +darktable_LDADD=-lgomp $(MAGICK_LIBS) $(CAIRO_LIBS) $(GLADE_LIBS) $(GTK_LIBS) $(GTHREAD_LIBS) $(SQLITE_LIBS) libdarktable.a LibRaw/libraw_r.a -lstdc++ +noinst_HEADERS=library/library.h gui/curveeditor.h gui/gtk.h gui/histogram.h gui/navigation.h develop/develop.h develop/imageop.h common/nikon_curve.h common/darktable.h common/imageio.h control/settings.h control/control.h control/jobs.h develop/iop_hsb.h common/image.h common/image_cache.h common/image_compression.h diff --git a/src/common/darktable.c b/src/common/darktable.c new file mode 100644 index 000000000000..9e422e428d82 --- /dev/null +++ b/src/common/darktable.c @@ -0,0 +1,131 @@ +#ifdef HAVE_CONFIG_H + #include "../config.h" +#endif +#include "common/darktable.h" +#include "common/image.h" +#include "common/image_cache.h" +#include "library/library.h" +#include "develop/develop.h" +#include "control/control.h" +#include "gui/gtk.h" +#include +#include +#ifdef HAVE_MAGICK + #include +#endif +#include +#ifdef _OPENMP +# include +#endif + +darktable_t darktable; + +int dt_init(int argc, char *argv[]) +{ +#ifdef _OPENMP + omp_set_num_threads(omp_get_num_procs()); +#endif + darktable.unmuted = 0; + for(int k=0;k k+1) + { + if(!strcmp(argv[k+1], "cache")) darktable.unmuted |= DT_DEBUG_CACHE; // enable debugging for lib/film/cache module + if(!strcmp(argv[k+1], "control")) darktable.unmuted |= DT_DEBUG_CONTROL; // enable debugging for scheduler module + if(!strcmp(argv[k+1], "dev")) darktable.unmuted |= DT_DEBUG_DEV; // develop module + } + } + +#ifdef HAVE_MAGICK + MagickCoreGenesis(*argv, MagickTrue); +#endif + char *homedir = getenv("HOME"); + char filename[512], *c = NULL; + snprintf(filename, 512, "%s/.darktablerc", homedir); + FILE *f = fopen(filename, "rb"); + if(f) + { + c = fgets(filename, 512, f); + fclose(f); + } + if(!c) snprintf(filename, 512, "%s/.darktabledb", homedir); + if(sqlite3_open(filename, &(darktable.db))) + { + fprintf(stderr, "[init] could not open database %s!\n", filename); + sqlite3_close(darktable.db); + exit(1); + } + pthread_mutex_init(&(darktable.db_insert), NULL); + + // has to go first for settings needed by all the others. + darktable.control = (dt_control_t *)malloc(sizeof(dt_control_t)); + dt_control_init(darktable.control); + + darktable.mipmap_cache = (dt_mipmap_cache_t *)malloc(sizeof(dt_mipmap_cache_t)); + dt_mipmap_cache_init(darktable.mipmap_cache, 500); + + darktable.image_cache = (dt_image_cache_t *)malloc(sizeof(dt_image_cache_t)); + dt_image_cache_init(darktable.image_cache, 500); + + darktable.library = (dt_library_t *)malloc(sizeof(dt_library_t)); + dt_library_init(darktable.library); + + darktable.develop = (dt_develop_t *)malloc(sizeof(dt_develop_t)); + dt_dev_init(darktable.develop, 1); + + darktable.gui = (dt_gui_gtk_t *)malloc(sizeof(dt_gui_gtk_t)); + dt_gui_gtk_init(darktable.gui, argc, argv); + + dt_control_load_config(darktable.control); + + return 0; +} + +void dt_cleanup() +{ + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + if(gui == DT_DEVELOP) dt_dev_leave(); + char *homedir = getenv("HOME"); + char filename[512]; + snprintf(filename, 512, "%s/.darktablerc", homedir); + FILE *f = fopen(filename, "wb"); + if(f) + { + if(fputs(darktable.control->global_settings.dbname, f) == EOF) fprintf(stderr, "[cleanup] could not write to %s!\n", filename); + fclose(f); + } + else fprintf(stderr, "[cleanup] could not write to %s!\n", filename); + dt_control_write_config(darktable.control); + + dt_control_cleanup(darktable.control); + free(darktable.control); + dt_dev_cleanup(darktable.develop); + free(darktable.develop); + dt_library_cleanup(darktable.library); + free(darktable.library); + dt_gui_gtk_cleanup(darktable.gui); + free(darktable.gui); + dt_image_cache_cleanup(darktable.image_cache); + free(darktable.image_cache); + dt_mipmap_cache_cleanup(darktable.mipmap_cache); + free(darktable.mipmap_cache); + + sqlite3_close(darktable.db); + pthread_mutex_destroy(&(darktable.db_insert)); + +#ifdef HAVE_MAGICK + MagickCoreTerminus(); +#endif +} + +void dt_print(dt_debug_thread_t thread, const char *msg, ...) +{ + if(darktable.unmuted & thread) + { + va_list ap; + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + } +} diff --git a/src/common/darktable.h b/src/common/darktable.h new file mode 100644 index 000000000000..1160e880c167 --- /dev/null +++ b/src/common/darktable.h @@ -0,0 +1,53 @@ +#ifndef DARKROOM_H +#define DARKROOM_H + +#include +#include +#include + +#define DT_VERSION 9 + +#define HANDLE_SQLITE_ERR(rc) \ + if(rc != SQLITE_OK) \ + { \ + fprintf(stderr, "sqlite3 error: %s\n", sqlite3_errmsg(darktable.db)); \ + return 1; \ + } \ + + +struct dt_library_t; +struct dt_gui_gtk_t; +struct dt_control_t; +struct dt_develop_t; +struct dt_mipmap_cache_t; +struct dt_image_cache_t; + +typedef enum dt_debug_thread_t +{ + DT_DEBUG_CACHE = 1, + DT_DEBUG_CONTROL = 2, + DT_DEBUG_DEV = 4 // powers of two, masking +} +dt_debug_thread_t; + +typedef struct darktable_t +{ + int32_t unmuted; + struct dt_library_t *library; + struct dt_develop_t *develop; + struct dt_control_t *control; + struct dt_gui_gtk_t *gui; + struct dt_mipmap_cache_t *mipmap_cache; + struct dt_image_cache_t *image_cache; + sqlite3 *db; + pthread_mutex_t db_insert; +} +darktable_t; + +extern darktable_t darktable; + +int dt_init(int argc, char *argv[]); +void dt_cleanup(); +void dt_print(dt_debug_thread_t thread, const char *msg, ...); + +#endif diff --git a/src/common/image.c b/src/common/image.c new file mode 100644 index 000000000000..f1d11eb3eece --- /dev/null +++ b/src/common/image.c @@ -0,0 +1,517 @@ + +#include "common/darktable.h" +#include "common/image.h" +#include "common/image_cache.h" +#include "common/imageio.h" +#include "control/control.h" +#include "control/jobs.h" +#include +#include +#include +#include +#include +#include + +// how large would the average screen be (largest mip map size) ? +#define DT_IMAGE_WINDOW_SIZE 1200 + +dt_image_buffer_t dt_image_get_matching_mip_size(const dt_image_t *img, const int32_t width, const int32_t height, int32_t *w, int32_t *h) +{ + const float scale = fminf(DT_IMAGE_WINDOW_SIZE/(float)(img->width), DT_IMAGE_WINDOW_SIZE/(float)(img->height)); + int32_t wd = MIN(img->width, (int)(scale*img->width)), ht = MIN(img->height, (int)(scale*img->height)); + if(wd & 0xf) wd = (wd & ~0xf) + 0x10; + if(ht & 0xf) ht = (ht & ~0xf) + 0x10; + dt_image_buffer_t mip = DT_IMAGE_MIP4; + while((int)mip > (int)DT_IMAGE_MIP0 && wd > width && ht > height) + { + mip--; + if(wd > 32 && ht > 32) + { // only if it's not vanishing completely :) + wd >>= 1; + ht >>= 1; + } + } + *w = wd; + *h = ht; + return mip; +} + +void dt_image_get_exact_mip_size(const dt_image_t *img, dt_image_buffer_t mip, float *w, float *h) +{ + float wd = img->width, ht = img->height; + if((int)mip < (int)DT_IMAGE_FULL) + { + const float scale = fminf(1.0, fminf(DT_IMAGE_WINDOW_SIZE/(float)img->width, DT_IMAGE_WINDOW_SIZE/(float)img->height)); + wd *= scale; ht *= scale; + while((int)mip < (int)DT_IMAGE_MIP4) + { + mip++; + if(wd > 32 && ht > 32) + { // only if it's not vanishing completely :) + wd *= .5; + ht *= .5; + } + } + } + *w = wd; + *h = ht; +} + +void dt_image_get_mip_size(const dt_image_t *img, dt_image_buffer_t mip, int32_t *w, int32_t *h) +{ + int32_t wd = img->width, ht = img->height; + if((int)mip < (int)DT_IMAGE_FULL) + { + const float scale = fminf(1.0, fminf(DT_IMAGE_WINDOW_SIZE/(float)img->width, DT_IMAGE_WINDOW_SIZE/(float)img->height)); + wd *= scale; ht *= scale; + // make exact mip possible (almost power of two) + if(wd & 0xf) wd = (wd & ~0xf) + 0x10; + if(ht & 0xf) ht = (ht & ~0xf) + 0x10; + while((int)mip < (int)DT_IMAGE_MIP4) + { + mip++; + if(wd > 32 && ht > 32) + { // only if it's not vanishing completely :) + wd >>= 1; + ht >>= 1; + } + } + } + *w = wd; + *h = ht; +} + +int dt_image_raw_to_preview(dt_image_t *img) +{ + const int raw_wd = img->width >> img->shrink; + const int raw_ht = img->height >> img->shrink; + int p_wd, p_ht; + float f_wd, f_ht; + dt_image_get_mip_size(img, DT_IMAGE_MIPF, &p_wd, &p_ht); + dt_image_get_exact_mip_size(img, DT_IMAGE_MIPF, &f_wd, &f_ht); + + if(dt_image_alloc(img, DT_IMAGE_MIPF)) return 1; + dt_image_check_buffer(img, DT_IMAGE_MIPF, 3*p_wd*p_ht*sizeof(float)); + + if(raw_wd == p_wd && raw_ht == p_ht) + { // use 1:1 + for(int j=0;jpixels + 3*(j*raw_wd + i); + for(int k=0;k<3;k++) img->mipf[3*(j*p_wd + i) + k] = cam[k]; + } + } + else + { // scale to fit + bzero(img->mipf, 3*p_wd*p_ht*sizeof(float)); + const float scale = fmaxf(raw_wd/f_wd, raw_ht/f_ht); + for(int j=0;jpixels + 3*((int)(scale*j)*raw_wd + (int)(scale*i)); + for(int k=0;k<3;k++) img->mipf[3*(j*p_wd + i) + k] = cam[k]; + } + } + // store in db. + dt_imageio_preview_write(img, DT_IMAGE_MIPF); + + if(dt_image_alloc(img, DT_IMAGE_MIP4)) + { + dt_image_release(img, DT_IMAGE_MIPF, 'w'); + dt_image_release(img, DT_IMAGE_MIPF, 'r'); + return 1; + } + dt_image_check_buffer(img, DT_IMAGE_MIP4, 4*p_wd*p_ht*sizeof(uint8_t)); + dt_image_check_buffer(img, DT_IMAGE_MIPF, 3*p_wd*p_ht*sizeof(float)); + int ret = 0; + dt_imageio_preview_f_to_8(p_wd, p_ht, img->mipf, img->mip[DT_IMAGE_MIP4]); + dt_imageio_preview_write(img, DT_IMAGE_MIP4); + if(dt_image_update_mipmaps(img)) ret = 1; + dt_image_release(img, DT_IMAGE_MIPF, 'w'); + dt_image_release(img, DT_IMAGE_MIPF, 'r'); + dt_image_release(img, DT_IMAGE_MIP4, 'w'); + dt_image_release(img, DT_IMAGE_MIP4, 'r'); + return ret; +} + +int dt_image_import(const int32_t film_id, const char *filename) +{ + int rc; + int ret = 0, id = -1; + // select from images; if found => return + gchar *imgfname = g_path_get_basename((const gchar*)filename); + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select id from images where film_id = ?1 and filename = ?2", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, film_id); + rc = sqlite3_bind_text(stmt, 2, imgfname, strlen(imgfname), SQLITE_STATIC); + if(sqlite3_step(stmt) == SQLITE_ROW) + { + id = sqlite3_column_int(stmt, 0); + g_free(imgfname); + rc = sqlite3_finalize(stmt); + return dt_image_open(id); // image already in db, open this. + } + rc = sqlite3_finalize(stmt); + + // insert dummy image entry in database + rc = sqlite3_prepare_v2(darktable.db, "insert into images (id, film_id, filename) values (null, -1, ?1)", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_text(stmt, 1, imgfname, strlen(imgfname), SQLITE_STATIC); + pthread_mutex_lock(&(darktable.db_insert)); + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) fprintf(stderr, "sqlite3 error %d\n", rc); + id = sqlite3_last_insert_rowid(darktable.db); + pthread_mutex_unlock(&(darktable.db_insert)); + rc = sqlite3_finalize(stmt); + + // insert dummy (zeroblob) entries in database! + for(dt_image_buffer_t mip=DT_IMAGE_MIP0;(int)mip<=(int)DT_IMAGE_MIPF;mip++) + { + rc = sqlite3_prepare_v2(darktable.db, "insert into mipmaps (imgid, level) values (?1, ?2)", -1, &stmt, NULL); + rc = sqlite3_bind_int(stmt, 1, id); + rc = sqlite3_bind_int(stmt, 2, mip); + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) fprintf(stderr, "[image_import] could not insert mipmap: %s\n", sqlite3_errmsg(darktable.db)); + rc = sqlite3_finalize(stmt); + } + + dt_image_t *img = dt_image_cache_use(id, 'w'); + strncpy(img->filename, imgfname, 256); + g_free(imgfname); + + // load small raw (try libraw then magick) + img->shrink = 1; + if(dt_imageio_open(img, filename)) + { + dt_image_cleanup(img); + fprintf(stderr, "[image_import] could not open %s\n", filename); + dt_image_cache_release(img, 'w'); + rc = sqlite3_prepare_v2(darktable.db, "delete from images where id = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, id); + rc = sqlite3_step(stmt); + rc = sqlite3_prepare_v2(darktable.db, "delete from mipmaps where imgid = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, id); + rc = sqlite3_step(stmt); + return 1; + } + + // update image data + rc = sqlite3_prepare_v2(darktable.db, "update images set width = ?1, height = ?2, maker = ?3, model = ?4, exposure = ?5, aperture = ?6, iso = ?7, focal_length = ?8, film_id = ?9, datetime_taken = ?10 where id = ?11", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, img->width); + rc = sqlite3_bind_int (stmt, 2, img->height); + rc = sqlite3_bind_text(stmt, 3, img->exif_maker, strlen(img->exif_maker), SQLITE_STATIC); + rc = sqlite3_bind_text(stmt, 4, img->exif_model, strlen(img->exif_model), SQLITE_STATIC); + rc = sqlite3_bind_double(stmt, 5, img->exif_exposure); + rc = sqlite3_bind_double(stmt, 6, img->exif_aperture); + rc = sqlite3_bind_double(stmt, 7, img->exif_iso); + rc = sqlite3_bind_double(stmt, 8, img->exif_focal_length); + rc = sqlite3_bind_int (stmt, 9, film_id); + rc = sqlite3_bind_text(stmt, 10, img->exif_datetime_taken, strlen(img->exif_datetime_taken), SQLITE_STATIC); + rc = sqlite3_bind_int (stmt, 11, img->id); + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) fprintf(stderr, "sqlite3 error %d\n", rc); + rc = sqlite3_finalize(stmt); + + // create preview images + if(dt_image_raw_to_preview(img)) ret = 2; + dt_image_release(img, DT_IMAGE_FULL, 'r'); + dt_image_cache_release(img, 'w'); + return ret; +} + +int dt_image_update_mipmaps(dt_image_t *img) +{ + int oldwd, oldht; + dt_image_get_mip_size(img, DT_IMAGE_MIP4, &oldwd, &oldht); + // create 8-bit mip maps: + for(dt_image_buffer_t l=DT_IMAGE_MIP3;(int)l>=(int)DT_IMAGE_MIP0;l--) + { + int p_wd, p_ht; + dt_image_get_mip_size(img, l, &p_wd, &p_ht); + if(dt_image_alloc(img, l)) return 1; + + // printf("creating mipmap %d for img %s: %d x %d\n", l, img->filename, p_wd, p_ht); + // downscale 8-bit mip + if(oldwd != p_wd) + for(int j=0;jmip[l][4*(j*p_wd + i) + k] = ((int)img->mip[l+1][8*(2*j)*p_wd + 4*(2*i) + k] + (int)img->mip[l+1][8*(2*j)*p_wd + 4*(2*i+1) + k] + + (int)img->mip[l+1][8*(2*j+1)*p_wd + 4*(2*i+1) + k] + (int)img->mip[l+1][8*(2*j+1)*p_wd + 4*(2*i) + k])/4; + else memcpy(img->mip[l], img->mip[l+1], 4*sizeof(uint8_t)*p_ht*p_wd); + + if(dt_imageio_preview_write(img, l)) + fprintf(stderr, "[update_mipmaps] could write mip level %d of image %s to database!\n", l, img->filename); + dt_image_release(img, l, 'w'); + dt_image_release(img, l+1, 'r'); + } + dt_image_release(img, DT_IMAGE_MIP0, 'r'); + return 0; +} + +void dt_image_init(dt_image_t *img) +{ + for(int k=0;(int)k<(int)DT_IMAGE_MIPF;k++) img->mip[k] = NULL; + bzero(img->lock, sizeof(dt_image_lock_t)*DT_IMAGE_NONE); + img->width = img->height = 0; + img->mipf = NULL; + img->pixels = NULL; + img->orientation = 0; + img->exposure = 0; + img->wb_auto = img->wb_cam = 1; + img->shrink = 0; + img->film_id = -1; + img->flags = 0; + img->id = -1; + img->cacheline = -1; + strncpy(img->exif_model, "unknown", 20); + strncpy(img->exif_maker, "unknown", 20); + strncpy(img->exif_datetime_taken, "0000:00:00 00:00:00", 20); + img->exif_exposure = img->exif_aperture = img->exif_iso = img->exif_focal_length = 0; +#ifdef _DEBUG + for(int k=0;(int)k<(int)DT_IMAGE_NONE;k++) img->mip_buf_size[k] = 0; +#endif +} + +int dt_image_open(const int32_t id) +{ + dt_image_t *img = dt_image_cache_use(id, 'w'); + int rc = dt_image_open2(img, id); + dt_image_cache_release(img, 'w'); + return rc; +} + +int dt_image_open2(dt_image_t *img, const int32_t id) +{ // load stuff from db and store in cache: + int rc, ret = 1; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select id, film_id, width, height, filename, maker, model, exposure, aperture, iso, focal_length from images where id = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, id); + // rc = sqlite3_bind_text(stmt, 2, img->filename, strlen(img->filename), SQLITE_STATIC); + if(sqlite3_step(stmt) == SQLITE_ROW) + { + img->id = sqlite3_column_int(stmt, 0); + img->film_id = sqlite3_column_int(stmt, 1); + img->width = sqlite3_column_int(stmt, 2); + img->height = sqlite3_column_int(stmt, 3); + strncpy(img->filename, (char *)sqlite3_column_text(stmt, 4), 512); + strncpy(img->exif_maker, (char *)sqlite3_column_text(stmt, 5), 20); + strncpy(img->exif_model, (char *)sqlite3_column_text(stmt, 6), 20); + img->exif_exposure = sqlite3_column_double(stmt, 7); + img->exif_aperture = sqlite3_column_double(stmt, 8); + img->exif_iso = sqlite3_column_double(stmt, 9); + img->exif_focal_length = sqlite3_column_double(stmt, 10); + + ret = 0; + } + else fprintf(stderr, "[image_open2] failed to open image from database: %s\n", sqlite3_errmsg(darktable.db)); + rc = sqlite3_finalize(stmt); + if(ret) return ret; + // read mip 0: + rc = dt_imageio_preview_read(img, DT_IMAGE_MIP0); + if(!rc) dt_image_release(img, DT_IMAGE_MIP0, 'r'); + return rc; +} + +void dt_image_cleanup(dt_image_t *img) +{ + pthread_mutex_lock(&(darktable.mipmap_cache->mutex)); + for(int k=0;(int)k<(int)DT_IMAGE_NONE;k++) dt_image_free(img, k); + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); +} + +int dt_image_load(dt_image_t *img, dt_image_buffer_t mip) +{ + int ret = 1, rc; + if(mip == DT_IMAGE_FULL) + { + char filename[1024]; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select folder from film_rolls where id = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, img->film_id); + if(sqlite3_step(stmt) == SQLITE_ROW) + snprintf(filename, 1024, "%s/%s", sqlite3_column_text(stmt, 0), img->filename); + rc = sqlite3_finalize(stmt); + ret = dt_imageio_open(img, filename); + if(ret) dt_image_cleanup(img); + } + else ret = dt_imageio_preview_read(img, mip); + // TODO: insert abstract hook here? + dt_control_queue_draw(); + return ret; +} + +void dt_image_prefetch(dt_image_t *img, dt_image_buffer_t mip) +{ + // TODO: alloc buf, dispatch loader job (which will release the rw lock) +} + + +// ============================= +// mipmap cache functions: +// ============================= + +void dt_mipmap_cache_init(dt_mipmap_cache_t *cache, int32_t entries) +{ + pthread_mutex_init(&(cache->mutex), NULL); + for(int k=0;k<(int)DT_IMAGE_NONE;k++) + { + if(k == DT_IMAGE_FULL) entries = 3; + dt_print(DT_DEBUG_CACHE, "mipmap cache has %d entries for mip %d.\n", entries, k); + cache->num_entries[k] = entries; + cache->mip_lru[k] = (dt_image_t **)malloc(sizeof(dt_image_t*)*entries); + bzero(cache->mip_lru[k], sizeof(dt_image_t*)*entries); + if(entries > 4) entries >>= 1; + } +} + +void dt_mipmap_cache_cleanup(dt_mipmap_cache_t *cache) +{ + // TODO: free all img bufs? + for(int k=0;k<(int)DT_IMAGE_NONE;k++) free(cache->mip_lru[k]); + pthread_mutex_destroy(&(cache->mutex)); +} + +void dt_image_check_buffer(dt_image_t *image, dt_image_buffer_t mip, int32_t size) +{ +#ifdef _DEBUG + assert(image->mip_buf_size[mip] >= size); +#endif +} + +int dt_image_alloc(dt_image_t *img, dt_image_buffer_t mip) +{ + int wd, ht; + dt_image_get_mip_size(img, mip, &wd, &ht); + size_t size = wd*ht; + pthread_mutex_lock(&(darktable.mipmap_cache->mutex)); + void *ptr = NULL; + if ((int)mip < (int)DT_IMAGE_MIPF) { size *= 4*sizeof(uint8_t); ptr = (void *)(img->mip[mip]); } + else if(mip == DT_IMAGE_MIPF) { size *= 3*sizeof(float); ptr = (void *)(img->mipf); } + else if(mip == DT_IMAGE_FULL) { size *= 3*sizeof(float); ptr = (void *)(img->pixels); } + else + { + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return 1; + } + if(ptr) + { + img->lock[mip].write = 1; // write lock + img->lock[mip].users = 1; // read lock + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return 0; // all good, already alloc'ed. + } + + // printf("allocing %d x %d x %d for %s (%d)\n", wd, ht, size/(wd*ht), img->filename, mip); + if ((int)mip < (int)DT_IMAGE_MIPF) { img->mip[mip] = (uint8_t *)malloc(size); } + else if(mip == DT_IMAGE_MIPF) { img->mipf = (float *)malloc(size); } + else if(mip == DT_IMAGE_FULL) { img->pixels = (float *)malloc(size); } + + if((mip == DT_IMAGE_FULL && img->pixels == NULL) || (mip == DT_IMAGE_MIPF && img->mipf == NULL) || ((int)mip < (int)DT_IMAGE_MIPF && img->mip[mip] == NULL)) + { + fprintf(stderr, "[image_alloc] malloc of %d x %d x %d for image %s mip %d failed!\n", wd, ht, (int)size/(wd*ht), img->filename, mip); + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return 1; + } + + // insert image in node list at newest time + for(int k=0;knum_entries[mip];k++) + { + if(darktable.mipmap_cache->mip_lru[mip][k] == NULL || darktable.mipmap_cache->mip_lru[mip][k]->lock[mip].users == 0) + { + dt_image_free(darktable.mipmap_cache->mip_lru[mip][k], mip); + memmove(darktable.mipmap_cache->mip_lru[mip] + k, darktable.mipmap_cache->mip_lru[mip] + k + 1, (darktable.mipmap_cache->num_entries[mip] - k - 1)*sizeof(dt_image_t*)); + darktable.mipmap_cache->mip_lru[mip][darktable.mipmap_cache->num_entries[mip]-1] = img; + img->lock[mip].write = 1; // write lock + img->lock[mip].users = 1; // read lock +#ifdef _DEBUG + img->mip_buf_size[mip] = size; +#if 0 + int allsize = 0; + for(int k=0;knum_entries[mip];k++) if(darktable.mipmap_cache->mip_lru[mip][k]) allsize += darktable.mipmap_cache->mip_lru[mip][k]->mip_buf_size[mip]; + printf("[image_alloc] alloc'ed additional %d bytes, now storing %f MB for mip %d\n", size, allsize/(1024.*1024.), mip); +#endif +#endif + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return 0; + } + } + fprintf(stderr, "[image_alloc] all cache slots seem to be in use! alloc of %d bytes for img id %d mip %d failed!\n", size, img->id, mip); + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return 1; +} + +void dt_image_free(dt_image_t *img, dt_image_buffer_t mip) +{ + // mutex is already locked, as only alloc is allowed to free. + if(!img) return; + // dt_image_release(img, mip, "w"); + if((int)mip < (int)DT_IMAGE_MIPF) { free(img->mip[mip]); img->mip[mip] = NULL; } + else if(mip == DT_IMAGE_MIPF) { free(img->mipf); img->mipf = NULL; } + else if(mip == DT_IMAGE_FULL) { free(img->pixels); img->pixels = NULL; } + else return; + for(int k=0;knum_entries[mip];k++) + if(darktable.mipmap_cache->mip_lru[mip][k] == img) darktable.mipmap_cache->mip_lru[mip][k] = NULL; +#ifdef _DEBUG + // printf("[image_free] freed %d bytes\n", img->mip_buf_size[mip]); + if(darktable.control->running) + assert(img->lock[mip].users == 0 && img->lock[mip].write == 0); + img->mip_buf_size[mip] = 0; +#endif +} + +dt_image_buffer_t dt_image_get(dt_image_t *img, const dt_image_buffer_t mip_in, const char mode) +{ + dt_image_buffer_t mip = mip_in; + if(mip == DT_IMAGE_NONE) return mip; + pthread_mutex_lock(&(darktable.mipmap_cache->mutex)); + // get image with no write lock set! + if((int)mip < (int)DT_IMAGE_MIPF) + { + while(mip > 0 && (img->mip[mip] == NULL || img->lock[mip].write)) mip--; // level 0 always there. + } + else if(mip == DT_IMAGE_MIPF) + { + if(img->mipf == NULL || img->lock[mip].write) mip = DT_IMAGE_NONE; + } + else if(mip == DT_IMAGE_FULL) + { + if(img->pixels == NULL || img->lock[mip].write) mip = DT_IMAGE_NONE; + } + if(mip != DT_IMAGE_NONE) + { + if(mode == 'w') + { + img->lock[mip].write = 1; + img->lock[mip].users = 1; + } + else img->lock[mip].users++; + } + + if(mip != mip_in && !img->lock[mip_in].write) + { // start job to load this buf in bg. + dt_print(DT_DEBUG_CACHE, "[image_get] reloading mip %d for image %d\n", mip_in, img->id); + img->lock[mip_in].write = 1; + dt_job_t j; + dt_image_load_job_init(&j, img, mip_in); + dt_control_add_job(darktable.control, &j); + if(mip_in == DT_IMAGE_MIP4) + { + if(!img->lock[DT_IMAGE_MIPF].write) + { // start job to load this buf in bg. + img->lock[DT_IMAGE_MIPF].write = 1; + dt_job_t j; + dt_image_load_job_init(&j, img, DT_IMAGE_MIPF); + dt_control_add_job(darktable.control, &j); + } + } + } + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); + return mip; +} + +void dt_image_release(dt_image_t *img, dt_image_buffer_t mip, const char mode) +{ + pthread_mutex_lock(&(darktable.mipmap_cache->mutex)); + if (mode == 'r' && img->lock[mip].users > 0) img->lock[mip].users --; + else if (mode == 'w') img->lock[mip].write = 0; // can only be one writing thread at a time. + pthread_mutex_unlock(&(darktable.mipmap_cache->mutex)); +} + diff --git a/src/common/image.h b/src/common/image.h new file mode 100644 index 000000000000..01d629cded92 --- /dev/null +++ b/src/common/image.h @@ -0,0 +1,112 @@ +#ifndef DT_IMAGE_H +#define DT_IMAGE_H + +#include +#include + +typedef enum +{ + DT_IMAGE_PORTRAIT = 1, + DT_IMAGE_GOOD = 2, + DT_IMAGE_BAD = 4, + DT_IMAGE_SELECTED = 8 +} +dt_image_flags_t; + +typedef enum +{ + DT_IMAGE_MIP0 = 0, + DT_IMAGE_MIP1 = 1, + DT_IMAGE_MIP2 = 2, + DT_IMAGE_MIP3 = 3, + DT_IMAGE_MIP4 = 4, + DT_IMAGE_MIPF = 5, + DT_IMAGE_FULL = 6, + DT_IMAGE_NONE = 7 +} +dt_image_buffer_t; + +typedef struct dt_image_lock_t +{ + unsigned write : 1; + unsigned users : 7; +} +dt_image_lock_t; + +typedef struct dt_image_t +{ + // common stuff + int32_t width, height; + char filename[512]; + // used by library + int32_t num, flags, film_id, id; + // cache + int32_t cacheline; // for image_cache +#ifdef _DEBUG + int32_t mip_buf_size[DT_IMAGE_NONE]; +#endif + uint8_t *mip[DT_IMAGE_MIPF]; // for mipmap_cache + float *mipf; + dt_image_lock_t lock[DT_IMAGE_NONE]; + // raw image + int32_t shrink, wb_auto, wb_cam, orientation; + float exposure; + float *pixels; + // libexif data + // ExifContent *exif; + // minimal exif data here: + char exif_maker[20], exif_model[20], exif_datetime_taken[20]; + float exif_exposure; + float exif_aperture; + float exif_iso; + float exif_focal_length; +} +dt_image_t; + +// image buffer operations: +/** inits basic values to sensible defaults. */ +void dt_image_init(dt_image_t *img); +/** opens an image with minimal storage from the data base and stores it in image cache. */ +int dt_image_open(const int32_t id); +int dt_image_open2(dt_image_t *img, const int32_t id); +/** imports a new image from raw/etc file and adds it to the data base and image cache. */ +int dt_image_import(const int32_t film_id, const char *filename); +/** cleanup. */ +void dt_image_cleanup(dt_image_t *img); +/** loads the requested buffer to cache, with read lock set. */ +int dt_image_load(dt_image_t *img, dt_image_buffer_t mip); +/** prefetches given image buffer (mip map level/float preview/full raw), without marking it as used. */ +void dt_image_prefetch(dt_image_t *img, dt_image_buffer_t mip); +/** returns appropriate mip map size for given area to paint on (width, height). */ +dt_image_buffer_t dt_image_get_matching_mip_size(const dt_image_t *img, const int32_t width, const int32_t height, int32_t *w, int32_t *h); +/** returns appropriate mip map size for given mip level. */ +void dt_image_get_mip_size(const dt_image_t *img, dt_image_buffer_t mip, int32_t *w, int32_t *h); +/** returns real image extends within mip map buffer size, in floating point. */ +void dt_image_get_exact_mip_size(const dt_image_t *img, dt_image_buffer_t mip, float *w, float *h); +/** writes mip4 through to all smaller levels. */ +int dt_image_update_mipmaps(dt_image_t *img); + +// memory management interface +typedef struct dt_mipmap_cache_t +{ + pthread_mutex_t mutex; + int32_t num_entries[DT_IMAGE_NONE]; + dt_image_t **mip_lru[DT_IMAGE_NONE]; +} +dt_mipmap_cache_t; + +void dt_mipmap_cache_init(dt_mipmap_cache_t *cache, int32_t entries); +void dt_mipmap_cache_cleanup(dt_mipmap_cache_t *cache); + +/** if in debug mode, asserts image buffer size for mip is alloc'ed this large. */ +void dt_image_check_buffer(dt_image_t *image, dt_image_buffer_t mip, int32_t size); +/** alloc new buffer for this mip map and image. also lock for writing. */ +int dt_image_alloc(dt_image_t *img, dt_image_buffer_t mip); +/** destroy buffer. */ +void dt_image_free(dt_image_t *img, dt_image_buffer_t mip); +/** gets the requested image buffer or a smaller preview if it is not available (w lock || =NULL), marking this with the read lock. returns found mip level. */ +dt_image_buffer_t dt_image_get(dt_image_t *img, const dt_image_buffer_t mip, const char mode); +/** unflags the used flag of given mip map level. these remove r and w locks, respectively. dropping the w lock will leave the r lock in place. */ +void dt_image_release(dt_image_t *img, dt_image_buffer_t mip, const char mode); + +#endif diff --git a/src/common/image_cache.c b/src/common/image_cache.c new file mode 100644 index 000000000000..7d0d716ac409 --- /dev/null +++ b/src/common/image_cache.c @@ -0,0 +1,136 @@ + +#include "common/darktable.h" +#include "common/image_cache.h" + +#include +#include + +void dt_image_cache_init(dt_image_cache_t *cache, int32_t entries) +{ + pthread_mutex_init(&(cache->mutex), NULL); + cache->num_lines = entries; + cache->line = (dt_image_cache_line_t *)malloc(sizeof(dt_image_cache_line_t)*cache->num_lines); + cache->by_id = (int16_t *)malloc(sizeof(int16_t)*cache->num_lines); + for(int k=0;knum_lines;k++) + { + cache->by_id[k] = k; + dt_image_init(&(cache->line[k].image)); + cache->line[k].lock.users = cache->line[k].lock.write = 0; + cache->line[k].image.cacheline = k; + cache->line[k].lru = k-1; + cache->line[k].mru = k+1; + } + cache->lru = 0; + cache->mru = cache->num_lines-1; +} + +void dt_image_cache_cleanup(dt_image_cache_t *cache) +{ + // free mipmap cache lines + for(int k=0;knum_lines;k++) + { + // TODO: update images set !! + dt_image_cleanup(&(cache->line[k].image)); + } + free(cache->line); + free(cache->by_id); + pthread_mutex_destroy(&(cache->mutex)); +} + +int32_t dt_image_cache_bsearch(const int32_t id) +{ + dt_image_cache_t *cache = darktable.image_cache; + unsigned int min = 0, max = cache->num_lines; + unsigned int t = max/2; + while (t != min) + { + if(cache->line[cache->by_id[t-1]].image.id < id) min = t; + else max = t; + t = (min + max)/2; + } + if(cache->line[cache->by_id[t]].image.id != id) return -1; + return cache->by_id[t]; +} + +int dt_image_cache_compare_id(const int16_t *l1, const int16_t *l2) +{ + return darktable.image_cache->line[*l1].image.id - darktable.image_cache->line[*l2].image.id; +} + +dt_image_t *dt_image_cache_get(int32_t id, const char mode) +{ + dt_image_t *img = dt_image_cache_use(id, mode); + if(img == NULL) return NULL; + if(img->film_id == -1) if(dt_image_open2(img, id)) + { + dt_image_cache_release(img, mode); + return NULL; + } + return img; +} + +dt_image_t *dt_image_cache_use(int32_t id, const char mode) +{ + dt_image_cache_t *cache = darktable.image_cache; + pthread_mutex_lock(&(cache->mutex)); + // int16_t *res = bsearch(&id, cache->by_id, cache->num_lines, sizeof(int16_t), (int(*)(const void *, const void *))&dt_image_cache_compare_id); + int32_t res = dt_image_cache_bsearch(id); + dt_image_t *ret = NULL; + int16_t k = cache->lru; + if(res < 0) + { + // get least recently used image without lock and replace it: + for(int i=0;inum_lines;i++) + { + if(cache->line[k].image.id == -1) break; + if(cache->line[k].lock.write == 0 && cache->line[k].lock.users == 0) break; + k = cache->line[k].mru; + } + if(k == cache->num_lines) + { + fprintf(stderr, "[image_cache_use] all slots are in use!\n"); + pthread_mutex_unlock(&(cache->mutex)); + return NULL; + } + // TODO: update images set !! + dt_image_cleanup(&(cache->line[k].image)); + dt_image_init(&(cache->line[k].image)); + cache->line[k].image.id = id; + cache->line[k].image.cacheline = k; + cache->line[k].image.film_id = -1; + // TODO: insertion sort faster here? + qsort(cache->by_id, cache->num_lines, sizeof(int16_t), (int(*)(const void *, const void *))&dt_image_cache_compare_id); + res = k; + } + if(cache->line[res].lock.write) + { + ret = NULL; + } + else + { + // update lock + cache->line[res].lock.users++; + if(mode == 'w') cache->line[res].lock.write = 1; + ret = &(cache->line[res].image); + } + // update least recently used/most recently used linked list: + cache->line[cache->mru].mru = res; + if(cache->line[res].lru >= 0) cache->line[cache->line[res].lru].mru = cache->line[res].mru; + if(cache->line[res].mru < cache->num_lines) cache->line[cache->line[res].mru].lru = cache->line[res].lru; + if(cache->lru == res) cache->lru = cache->line[res].mru; + cache->line[res].mru = cache->num_lines+1; + cache->line[res].lru = cache->mru; + cache->mru = res; + pthread_mutex_unlock(&(cache->mutex)); + return ret; +} + +void dt_image_cache_release(dt_image_t *img, const char mode) +{ + dt_image_cache_t *cache = darktable.image_cache; + pthread_mutex_lock(&(cache->mutex)); + cache->line[img->cacheline].lock.users--; + if(mode == 'w') cache->line[img->cacheline].lock.write = 0; + pthread_mutex_unlock(&(cache->mutex)); +} + diff --git a/src/common/image_cache.h b/src/common/image_cache.h new file mode 100644 index 000000000000..e0bcdf053e82 --- /dev/null +++ b/src/common/image_cache.h @@ -0,0 +1,48 @@ +#ifndef DT_IMAGE_CACHE_H +#define DT_IMAGE_CACHE_H + +#include "common/image.h" + +#include +#include + +/** + * image cache to hold temporary representations + * from sql queries. + * fast access by img->id via sorted index, + * which is updated each time a new image is alloc'ed or + * an old image is kicked. + * lru list maintained via linked list for fast updates. + */ + +typedef struct dt_image_cache_line_t +{ + dt_image_t image; + dt_image_lock_t lock; + int16_t mru, lru; +} +dt_image_cache_line_t; + +typedef struct dt_image_cache_t +{ + pthread_mutex_t mutex; + int32_t num_lines; + dt_image_cache_line_t *line; + int16_t *by_id; + int16_t lru, mru; +} +dt_image_cache_t; + +void dt_image_cache_init(dt_image_cache_t *cache, int32_t entries); +void dt_image_cache_cleanup(dt_image_cache_t *cache); + +/** returns alloc'ed image (newly or from cache) or NULL on failure. + * lru is freed instead. there is no explicit interface for free. + * result will have users lock incremented. */ +dt_image_t *dt_image_cache_use(int32_t id, const char mode); +/** same as use, but init image from db if it was not already loaded. */ +dt_image_t *dt_image_cache_get(int32_t id, const char mode); +/** decrements users lock. */ +void dt_image_cache_release(dt_image_t *img, const char mode); + +#endif diff --git a/src/common/image_compression.c b/src/common/image_compression.c new file mode 100644 index 000000000000..5326f1a8c5b5 --- /dev/null +++ b/src/common/image_compression.c @@ -0,0 +1,130 @@ +#include "common/image_compression.h" + +#include +#include +#include +#include + +void dt_image_uncompress(const uint8_t *in, float *out, const int32_t width, const int32_t height) +{ + float L[16], chrom[4][3]; + const float fac[3] = {4., 2., 4.}; + uint32_t *L32 = (uint32_t*)L; + uint16_t L16[16]; + int32_t n_zeroes, Lbias; + uint8_t r[4], b[4]; + const uint8_t *block = in; + for(int j=0;j> 3) << 10; + n_zeroes = block[0] & 0x7; + const int shift = 14-n_zeroes-4+1; + + for(int k=0;k<8;k++) + { + L16[2*k ] = ((int)(block[1+k]>> 4) << shift) + Lbias; + L16[2*k+1] = ((int)(block[1+k]&0xf) << shift) + Lbias; + } + for(int k=0;k<16;k++) + { + L32[k] = (((int)(L16[k]) >> 10)-(15-127)) << (23); + L32[k] |= (L16[k] & 0x3ff)<<13; + } + // chroma + r[0] = block[ 9] >> 1; + b[0] = ((block[ 9] & 0x01) << 6) | (block[10] >> 2); + r[1] = ((block[10] & 0x03) << 5) | (block[11] >> 3); + b[1] = ((block[11] & 0x07) << 4) | (block[12] >> 4); + r[2] = ((block[12] & 0x0f) << 3) | (block[13] >> 5); + b[2] = ((block[13] & 0x1f) << 2) | (block[14] >> 6); + r[3] = ((block[14] & 0x3f) << 1) | (block[15] >> 7); + b[3] = block[15] & 0x7f; + + for(int q=0;q<4;q++) + { + chrom[q][0] = r[q]*(1./127.); + chrom[q][2] = b[q]*(1./127.); + chrom[q][1] = 1. - chrom[q][0] - chrom[q][2]; + } + for(int k=0;k<16;k++) + for(int c=0;c<3;c++) + out[3*(i + (k & 3) + width*(j + (k>>2))) + c] = L[k]*fac[c]*chrom[((k>>3)<<1)|((k&3)>>1)][c]; + block += 16*sizeof(uint8_t); + } + } +} + +void dt_image_compress(const float *in, uint8_t *out, const int32_t width, const int32_t height) +{ + float L[16]; + int16_t Lmin, Lmax, n_zeroes, L16[16]; + int32_t L32; + uint8_t *block = out, r[4], b[4]; + for(int j=0;j>13)&0x3ff; + int e = ((L32 >> (23))-(127-15)); + e = e > 0 ? e : 0; + e = e > 30 ? 30 : e; + L16[io+4*jo] |= e<<10; + Lmin = Lmin < L16[io+4*jo] ? Lmin : L16[io+4*jo]; + } + } + const float norm = 1./(chrom[0] + 2*chrom[1] + chrom[2]); + r[q] = (int)(127.*(chrom[0]*norm)); + b[q] = (int)(127.*(chrom[2]*norm)); + } + // store luma + Lmin &= ~0x3ff; + block[0] = (Lmin>>10)<<3; // Lbias + Lmax = 0; + for(int k=0;k<16;k++) + { + L16[k] -= Lmin; + Lmax = Lmax > L16[k] ? Lmax : L16[k]; + } + n_zeroes = 0; + for(int k=1<<14;(k&Lmax)==0&&n_zeroes<7;k>>=1) n_zeroes++; + block[0] |= n_zeroes; + const int shift = 14-n_zeroes-4+1; + const int off = (1<>1; + for(int k=0;k<8;k++) + { + L16[2*k] = ((int)L16[2*k] + off)>>shift; + L16[2*k] = L16[2*k] > 0xf ? 0xf : L16[2*k]; + L16[2*k+1] = ((int)L16[2*k+1] + off)>>shift; + L16[2*k+1] = L16[2*k+1] > 0xf ? 0xf : L16[2*k+1]; + block[k+1] = L16[2*k+1] | (L16[2*k]<<4); + } + // store chroma + block[ 9] = (r[0] << 1) | (b[0] >> 6); + block[10] = (b[0] << 2) | (r[1] >> 5); + block[11] = (r[1] << 3) | (b[1] >> 4); + block[12] = (b[1] << 4) | (r[2] >> 3); + block[13] = (r[2] << 5) | (b[2] >> 2); + block[14] = (b[2] << 6) | (r[3] >> 1); + block[15] = (r[3] << 7) | (b[3] >> 0); + block += 16*sizeof(uint8_t); + } + } +} + diff --git a/src/common/image_compression.h b/src/common/image_compression.h new file mode 100644 index 000000000000..e6613a806cfa --- /dev/null +++ b/src/common/image_compression.h @@ -0,0 +1,8 @@ +#ifndef DT_IMAGE_COMPRESSION +#include + +/** K. Roimela, T. Aarnio and J. Itäranta. High Dynamic Range Texture Compression. Proceedings of SIGGRAPH 2006. */ +void dt_image_compress(const float *in, uint8_t *out, const int32_t width, const int32_t height); +void dt_image_uncompress(const uint8_t *in, float *out, const int32_t width, const int32_t height); + +#endif diff --git a/src/common/imageio.c b/src/common/imageio.c new file mode 100644 index 000000000000..a4ae731f2edc --- /dev/null +++ b/src/common/imageio.c @@ -0,0 +1,629 @@ +#ifdef HAVE_CONFIG_H + #include "../config.h" +#endif +#include "common/imageio.h" +#include "common/image_compression.h" +#include "common/darktable.h" +#include "library/library.h" +#include "control/control.h" +#include "develop/develop.h" +#include "develop/imageop.h" +#include "libraw/libraw.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_MAGICK + #include +#endif + +int dt_imageio_preview_write(dt_image_t *img, dt_image_buffer_t mip) +{ + if(mip == DT_IMAGE_NONE || mip == DT_IMAGE_FULL) return 1; + if(mip == DT_IMAGE_MIPF) + { + sqlite3_stmt *stmt; + int rc, wd, ht; + dt_image_get_mip_size(img, DT_IMAGE_MIPF, &wd, &ht); + dt_image_check_buffer(img, mip, 3*wd*ht*sizeof(float)); + dt_image_alloc(img, DT_IMAGE_MIP4); + dt_image_check_buffer(img, DT_IMAGE_MIP4, 4*wd*ht*sizeof(uint8_t)); + dt_image_compress(img->mipf, img->mip[DT_IMAGE_MIP4], wd, ht); + rc = sqlite3_prepare_v2(darktable.db, "update mipmaps set data = ?1 where imgid = ?2 and level = ?3", -1, &stmt, NULL); + rc = sqlite3_bind_blob(stmt, 1, img->mip[DT_IMAGE_MIP4], sizeof(uint8_t)*wd*ht, SQLITE_STATIC); + rc = sqlite3_bind_int (stmt, 2, img->id); + rc = sqlite3_bind_int (stmt, 3, mip); + rc = sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + dt_image_release(img, DT_IMAGE_MIP4, 'w'); + return 0; + } + + sqlite3_stmt *stmt; + int rc; + int wd, ht; + dt_image_get_mip_size(img, mip, &wd, &ht); + dt_image_check_buffer(img, mip, 4*wd*ht*sizeof(uint8_t)); +#ifdef HAVE_MAGICK + ExceptionInfo *exception = AcquireExceptionInfo(); + ImageInfo *image_info = CloneImageInfo((ImageInfo *) NULL); + Image *image = ConstituteImage(wd, ht, "RGBA", CharPixel, img->mip[mip], exception); + if (image == (Image *) NULL) + { + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + return 1; + } + (void)strncpy(image_info->magick, "jpeg", 4); + image_info->quality = 95; + size_t length; + uint8_t *blob = ImageToBlob(image_info, image, &length, exception); + rc = sqlite3_prepare_v2(darktable.db, "update mipmaps set data = ?1 where imgid = ?2 and level = ?3", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_blob(stmt, 1, blob, sizeof(uint8_t)*length, (void (*)(void *))RelinquishMagickMemory); +#else + uint8_t *blob = img->mip[mip]; + size_t length = 4*wd*ht*sizeof(uint8_t); + rc = sqlite3_prepare_v2(darktable.db, "update mipmaps set data = ?1 where imgid = ?2 and level = ?3", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_blob(stmt, 1, blob, sizeof(uint8_t)*length, SQLITE_STATIC); +#endif + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_int (stmt, 2, img->id); + rc = sqlite3_bind_int (stmt, 3, mip); + rc = sqlite3_step(stmt); + if(rc != SQLITE_DONE) fprintf(stderr, "[preview_write] update mipmap failed: %s\n", sqlite3_errmsg(darktable.db)); + rc = sqlite3_finalize(stmt); + +#ifdef HAVE_MAGICK + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); +#endif + return 0; +} + +int dt_imageio_preview_read(dt_image_t *img, dt_image_buffer_t mip) +{ + if(mip == DT_IMAGE_NONE || mip == DT_IMAGE_FULL) return 1; + if(img->mip[mip]) + { + dt_image_buffer_t mip2 = dt_image_get(img, mip, 'r'); + if(mip2 != mip) dt_image_release(img, mip2, 'r'); + else return 0; + } + sqlite3_stmt *stmt; + int rc, wd, ht; + size_t length = 0; + const void *blob = NULL; + dt_image_get_mip_size(img, mip, &wd, &ht); + rc = sqlite3_prepare_v2(darktable.db, "select data from mipmaps where imgid = ?1 and level = ?2", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, img->id); + rc = sqlite3_bind_int (stmt, 2, mip); + HANDLE_SQLITE_ERR(rc); + if(sqlite3_step(stmt) == SQLITE_ROW) + { + blob = sqlite3_column_blob(stmt, 0); + length = sqlite3_column_bytes(stmt, 0); + } + if(!blob) + { + fprintf(stderr, "[preview_read] could not get mipmap from database: %s\n", sqlite3_errmsg(darktable.db)); + return 1; + } + if(dt_image_alloc(img, mip)) + { + rc = sqlite3_finalize(stmt); + return 1; + } + + if(mip == DT_IMAGE_MIPF) + { + assert(length==sizeof(uint8_t)*wd*ht); + dt_image_check_buffer(img, mip, 3*wd*ht*sizeof(float)); + dt_image_uncompress((uint8_t *)blob, img->mipf, wd, ht); + } + else + { + dt_image_check_buffer(img, mip, 4*wd*ht*sizeof(uint8_t)); +#ifdef HAVE_MAGICK + ExceptionInfo *exception = AcquireExceptionInfo(); + ImageInfo *image_info = CloneImageInfo((ImageInfo *) NULL); + Image *image = BlobToImage(image_info, blob, length, exception); + if (image == (Image *) NULL) + { + CatchException(exception); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + rc = sqlite3_finalize(stmt); + dt_image_release(img, mip, 'w'); + dt_image_release(img, mip, 'r'); + fprintf(stderr, "[preview_read] could not get image from blob!\n"); + return 1; + } + const PixelPacket *p; + assert(image->rows == ht && image->columns == wd); + for (int y=0; y < image->rows; y++) + { + p = AcquireImagePixels(image,0,y,image->columns,1,exception); + if (p == (const PixelPacket *) NULL) + { + dt_image_release(img, mip, 'w'); + dt_image_release(img, mip, 'r'); + fprintf(stderr, "[preview_read] pixel read failed!\n"); + return 1; + } + for (int x=0; x < image->columns; x++) + { + if(QuantumDepth == 16) + { + img->mip[mip][4*wd*y + 4*x + 0] = (int)p->red>>8; + img->mip[mip][4*wd*y + 4*x + 1] = (int)p->green>>8; + img->mip[mip][4*wd*y + 4*x + 2] = (int)p->blue>>8; + } + else + { + img->mip[mip][4*wd*y + 4*x + 0] = (int)p->red; + img->mip[mip][4*wd*y + 4*x + 1] = (int)p->green; + img->mip[mip][4*wd*y + 4*x + 2] = (int)p->blue; + } + p++; + } + } + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); +#else + assert(length==sizeof(uint8_t)*4*wd*ht); + for(int k=0;kmip[mip][k] = ((uint8_t *)blob)[k]; +#endif + } + rc = sqlite3_finalize(stmt); + dt_image_release(img, mip, 'w'); + return 0; +} + +void dt_imageio_preview_f_to_8(int32_t p_wd, int32_t p_ht, const float *f, uint8_t *p8) +{ + for(int idx=0;idx < p_wd*p_ht; idx++) + for(int k=0;k<3;k++) p8[4*idx+2-k] = dt_dev_default_gamma[(int)CLAMP(0xffff*f[3*idx+k], 0, 0xffff)]; +} + + +// ================================================= +// begin libraw wrapper functions: +// ================================================= + +#define HANDLE_ERRORS(ret, verb) { \ + if(ret) \ + { \ + if(verb) fprintf(stderr,"[imageio] %s: %s\n", filename, libraw_strerror(ret)); \ + libraw_close(raw); \ + raw = NULL; \ + return -1; \ + } \ +} + +int dt_imageio_open_raw(dt_image_t *img, const char *filename) +{ + int ret; + libraw_data_t *raw = libraw_init(0); + libraw_processed_image_t *image; + raw->params.half_size = img->shrink; /* dcraw -h */ + raw->params.use_camera_wb = img->wb_cam; + raw->params.use_auto_wb = img->wb_auto; + raw->params.output_bps = 16; + raw->params.user_qual = 3; + // img->raw->params.output_color = 1; + raw->params.use_camera_matrix = 1; + // TODO: let this unclipped for develop, clip for preview. + raw->params.highlight = 0; //0 clip, 1 unclip, 2 blend, 3+ rebuild + // img->raw->params.user_flip = img->raw->sizes.flip; + ret = libraw_open_file(raw, filename); + HANDLE_ERRORS(ret, 0); + if(raw->idata.dng_version || (raw->sizes.iwidth <= 1200 && raw->sizes.iheight <= 800)) + { // FIXME: this is a temporary bugfix avoiding segfaults for dng images. (and to avoid shrinking on small images). + raw->params.user_qual = 0; + raw->params.half_size = img->shrink = 0; + } + ret = libraw_unpack(raw); + HANDLE_ERRORS(ret, 1); + ret = libraw_dcraw_process(raw); + HANDLE_ERRORS(ret, 1); + image = dcraw_make_mem_image(raw, &ret); + HANDLE_ERRORS(ret, 1); + + img->shrink = raw->params.half_size; + img->orientation = raw->sizes.flip; + img->width = (img->orientation & 4) ? raw->sizes.iheight : raw->sizes.iwidth; + img->height = (img->orientation & 4) ? raw->sizes.iwidth : raw->sizes.iheight; + img->exif_iso = raw->other.iso_speed; + img->exif_exposure = raw->other.shutter; + img->exif_aperture = raw->other.aperture; + img->exif_focal_length = raw->other.focal_len; + strncpy(img->exif_maker, raw->idata.make, 20); + strncpy(img->exif_model, raw->idata.model, 20); + GDate *gd = g_date_new(); + g_date_set_time_t(gd, raw->other.timestamp); + (void) g_date_strftime(img->exif_datetime_taken, 20, "%Y:%m:%d %H:%M:%S", gd); + g_date_free(gd); + + img->width <<= img->shrink; + img->height <<= img->shrink; + if(dt_image_alloc(img, DT_IMAGE_FULL)) + { + libraw_recycle(raw); + libraw_close(raw); + free(image); + return 1; + } + dt_image_check_buffer(img, DT_IMAGE_FULL, 3*(img->width>>img->shrink)*(img->height>>img->shrink)*sizeof(float)); + // img->pixels = (float *)malloc(sizeof(float)*3*img->width*img->height); + const float m = 1./0xffff; + for(int k=0;k<3*(img->width>>img->shrink)*(img->height>>img->shrink);k++) img->pixels[k] = ((uint16_t *)(image->data))[k]*m; + // TODO: wrap all exif data here: + // img->dreggn = + // clean up raw stuff. + libraw_recycle(raw); + libraw_close(raw); + free(image); + raw = NULL; + image = NULL; + dt_image_release(img, DT_IMAGE_FULL, 'w'); + return 0; +} + +#if 0 +static void MaxMidMin(int64_t p[3], int *maxc, int *midc, int *minc) +{ + if (p[0] > p[1] && p[0] > p[2]) { + *maxc = 0; + if (p[1] > p[2]) { *midc = 1; *minc = 2; } + else { *midc = 2; *minc = 1; } + } else if (p[1] > p[2]) { + *maxc = 1; + if (p[0] > p[2]) { *midc = 0; *minc = 2; } + else { *midc = 2; *minc = 0; } + } else { + *maxc = 2; + if (p[0] > p[1]) { *midc = 0; *minc = 1; } + else { *midc = 1; *minc = 0; } + } +} + +// this is stolen from develop_linear (ufraw_developer.c), because it does a great job ;) +void dt_raw_develop(uint16_t *in, uint16_t *out, dt_image_t *img) +{ + int64_t tmppix[4];//, tmp; + int64_t exposure = pow(2, img->exposure) * 0x10000; + int clipped = 0; + for(int c=0;craw->idata.colors;c++) + { + tmppix[c] = (uint64_t)in[c] * img->raw->color.cam_mul[c]/0x10000 - img->raw->color.black; + if(tmppix[c] > img->raw->color.maximum) clipped = 1; + tmppix[c] = tmppix[c] * exposure / img->raw->color.maximum; + // tmppix[c] = (tmppix[c] * 0x10000) / img->raw.color.maximum; + } + if(clipped) + { + int64_t unclipped[3], clipped[3]; + for(int cc=0;cc<3;cc++) + { + // for(int c=0,tmp=0;craw->idata.colors;c++) + // tmp += tmppix[c] * img->raw->color.cmatrix[cc][c]; + // unclipped[cc] = MAX(tmp/0x10000, 0); + unclipped[cc] = tmppix[cc]; + } + for(int c=0; c<3; c++) tmppix[c] = MIN(tmppix[c], 0xFFFF); + // for(int c=0; c<3; c++) tmppix[c] = MIN(tmppix[c], exposure); + for(int cc=0; cc<3; cc++) + { + // for(int c=0, tmp=0; craw->idata.colors; c++) + // tmp += tmppix[c] * img->raw->color.cmatrix[cc][c]; + // clipped[cc] = MAX(tmp/0x10000, 0); + clipped[cc] = tmppix[cc]; + } + int maxc, midc, minc; + MaxMidMin(unclipped, &maxc, &midc, &minc); + int64_t unclippedLum = unclipped[maxc]; + int64_t clippedLum = clipped[maxc]; + int64_t clippedSat; + if(clipped[maxc] < clipped[minc] || clipped[maxc] == 0) + clippedSat = 0; + else + clippedSat = 0x10000 - clipped[minc] * 0x10000 / clipped[maxc]; + int64_t clippedHue; + if(clipped[maxc] == clipped[minc]) clippedHue = 0; + else clippedHue = + (clipped[midc]-clipped[minc])*0x10000 / + (clipped[maxc]-clipped[minc]); + int64_t unclippedHue; + if(unclipped[maxc] == unclipped[minc]) + unclippedHue = clippedHue; + else + unclippedHue = + (unclipped[midc]-unclipped[minc])*0x10000 / + (unclipped[maxc]-unclipped[minc]); + int64_t lum = clippedLum + (unclippedLum - clippedLum) * 1/2; + int64_t sat = clippedSat; + int64_t hue = unclippedHue; + + tmppix[maxc] = lum; + tmppix[minc] = lum * (0x10000-sat) / 0x10000; + tmppix[midc] = lum * (0x10000-sat + sat*hue/0x10000) / 0x10000; + } + for(int c=0; c<3; c++) + out[c] = MIN(MAX(tmppix[c], 0), 0xFFFF); +} +#endif + +// ================================================= +// begin magickcore wrapper functions: +// ================================================= + +// transparent read method to load ldr/raw image to dt_raw_image_t with exif and so on. +int dt_imageio_open_ldr(dt_image_t *img, const char *filename) +{ +#ifdef HAVE_MAGICK + // TODO: shrink!! + // TODO: orientation!! + img->shrink = 0; + img->orientation = 0; + + ImageInfo *image_info; + ExceptionInfo *exception; + Image *image; + exception = AcquireExceptionInfo(); + image_info = CloneImageInfo((ImageInfo *) NULL); + + (void) strcpy(image_info->filename, filename); + image = ReadImage(image_info, exception); + if (image == (Image *) NULL) + { + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + return 1; + } + + img->width = image->columns; + img->height = image->rows; + if(dt_image_alloc(img, DT_IMAGE_FULL)) + { + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + return 2; + } + // img->pixels = (float *)malloc(sizeof(float)*3*img->width*img->height); + + dt_image_check_buffer(img, DT_IMAGE_FULL, 3*img->width*img->height*sizeof(uint8_t)); + const PixelPacket *p; + for (int y=0; y < image->rows; y++) + { + p = AcquireImagePixels(image,0,y,image->columns,1,exception); + if (p == (const PixelPacket *) NULL) + { + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + dt_image_release(img, DT_IMAGE_FULL, 'w'); + dt_image_release(img, DT_IMAGE_FULL, 'r'); + return 1; + } + for (int x=0; x < image->columns; x++) + { + if(QuantumDepth == 16) + { + img->pixels[3*img->width*y + 3*x + 0] = dt_dev_de_gamma[(int)p->red>>8]; + img->pixels[3*img->width*y + 3*x + 1] = dt_dev_de_gamma[(int)p->green>>8]; + img->pixels[3*img->width*y + 3*x + 2] = dt_dev_de_gamma[(int)p->blue>>8]; + } + else + { + img->pixels[3*img->width*y + 3*x + 0] = dt_dev_de_gamma[(int)p->red]; + img->pixels[3*img->width*y + 3*x + 1] = dt_dev_de_gamma[(int)p->green]; + img->pixels[3*img->width*y + 3*x + 2] = dt_dev_de_gamma[(int)p->blue]; + } + // p->opacity + p++; + } + } + + // exif:Flash: 9 + // exif:FlashPixVersion: 0100 + const char *value = NULL; + float num, den; + value = GetImageProperty(image, "exif:DateTimeOriginal"); + if (value != (const char *) NULL) strncpy(img->exif_datetime_taken, value, 20); + value = GetImageProperty(image, "exif:ExposureTime"); + if (value != (const char *) NULL) { num = g_ascii_strtod(value, (gchar**)&value); den = g_ascii_strtod(value+1, NULL); img->exif_exposure = num/den; } + value = GetImageProperty(image, "exif:FNumber"); + if (value != (const char *) NULL) { num = g_ascii_strtod(value, (gchar**)&value); den = g_ascii_strtod(value+1, NULL); img->exif_aperture = num/den; + printf("aperture for image %s is %f/%f = %f\n", img->filename, num, den, num/den);} + value = GetImageProperty(image, "exif:FocalLength"); + if (value != (const char *) NULL) { num = g_ascii_strtod(value, (gchar**)&value); den = g_ascii_strtod(value+1, NULL); img->exif_focal_length = num/den; } + value = GetImageProperty(image, "exif:ISOSpeedRatings"); + if (value != (const char *) NULL) img->exif_iso = g_ascii_strtod(value, NULL); + value = GetImageProperty(image, "exif:Make"); + if (value != (const char *) NULL) strncpy(img->exif_maker, value, 20); + value = GetImageProperty(image, "exif:Model"); + if (value != (const char *) NULL) strncpy(img->exif_model, value, 20); + // TODO: + // exif:Orientation: 1 + // http://sylvana.net/jpegcrop/exif_orientation.html + value = GetImageProperty(image, "exif:Orientation"); + if (value != (const char *) NULL) img->orientation = atol(value); + +#if 0 + (void) GetImageProperty(image,"exif:*"); + ResetImagePropertyIterator(image); + char *property=GetNextImageProperty(image); + if (property != (const char *) NULL) + { + (void) printf(" Properties:\n"); + while (property != (const char *) NULL) + { + (void) printf(" %c",*property); + if (strlen(property) > 1) + (void) printf("%s: ",property+1); + if (strlen(property) > 80) + (void) printf("\n"); + const char *value=GetImageProperty(image,property); + if (value != (const char *) NULL) + (void) printf("%s\n",value); + property=GetNextImageProperty(image); + } + } +#endif + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + dt_image_release(img, DT_IMAGE_FULL, 'w'); + return 0; +#else + fprintf(stderr, "[open_ldr] compiled without Magick support!\n"); + return 1; +#endif +} + +// batch-processing enabled write method: history stack, raw image, custom copy of gamma/tonecurves +int dt_imageio_export_f(dt_image_t *img, const char *filename) +{ + dt_develop_t dev; + dt_dev_init(&dev, 0); + dt_dev_load_image(&dev, img); + // go through stack, exec stuff (like dt_dev_load_small_cache, only operate on full buf this time) + const int wd = dev.image->width; + const int ht = dev.image->height; + for(int k=1;kpixels, dev.image->pixels, wd, ht, wd, ht, hist->operation, &(hist->op_params)); + } + + dt_image_check_buffer(dev.image, DT_IMAGE_FULL, 3*wd*ht*sizeof(float)); + int status = 1; + FILE *f = fopen(filename, "wb"); + if(f) + { + (void)fprintf(f, "PF\n%d %d\n-1.0\n", wd, ht); + float tmp[3]; + for(int i=0;ipixels[3*i+k], 0, 0xffff)]/(float)0x10000; + (void)fwrite(tmp, sizeof(float)*3, 1, f); + } + fclose(f); + status = 0; + } + + dt_dev_cleanup(&dev); + return status; +} + +int dt_imageio_export_16(dt_image_t *img, const char *filename) +{ + dt_develop_t dev; + dt_dev_init(&dev, 0); + dt_dev_load_image(&dev, img); + // go through stack, exec stuff (like dt_dev_load_small_cache, only operate on full buf this time) + const int wd = dev.image->width; + const int ht = dev.image->height; + dt_image_check_buffer(dev.image, DT_IMAGE_FULL, 3*wd*ht*sizeof(float)); + uint16_t *buf16 = (uint16_t *)malloc(sizeof(uint16_t)*3*wd*ht); + for(int k=1;kpixels, dev.image->pixels, wd, ht, wd, ht, hist->operation, &(hist->op_params)); + } + for(int i=0;ipixels[3*i+k], 0, 0xffff)]; + buf16[3*i+k] = (0xff00 & (buf16[3*i+k]<<8))|(buf16[3*i+k]>>8); + } + + int status = 1; + FILE *f = fopen(filename, "wb"); + if(f) + { + (void)fprintf(f, "P6\n%d %d\n65535\n", wd, ht); + (void)fwrite(buf16, sizeof(uint16_t)*3, wd*ht, f); + fclose(f); + status = 0; + } + + dt_dev_cleanup(&dev); + free(buf16); + return status; +} + +int dt_imageio_export_8(dt_image_t *img, const char *filename) +{ +#ifdef HAVE_MAGICK + dt_develop_t dev; + dt_dev_init(&dev, 0); + dt_dev_load_image(&dev, img); + // go through stack, exec stuff (like dt_dev_load_small_cache, only operate on full buf this time) + const int wd = dev.image->width; + const int ht = dev.image->height; + dt_image_check_buffer(dev.image, DT_IMAGE_FULL, 3*wd*ht*sizeof(float)); + uint8_t *buf8 = (uint8_t *)malloc(sizeof(uint8_t)*3*wd*ht); + for(int k=1;kpixels, dev.image->pixels, wd, ht, wd, ht, hist->operation, &(hist->op_params)); + } + for(int i=0;ipixels[3*i+k], 0, 0xffff)]]; + + // MagickCore write + ImageInfo *image_info; + ExceptionInfo *exception; + Image *image; + exception = AcquireExceptionInfo(); + image_info = CloneImageInfo((ImageInfo *) NULL); + image = ConstituteImage(wd, ht, "RGB", CharPixel, buf8, exception); + if (image == (Image *) NULL) + { + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + dt_dev_cleanup(&dev); + free(buf8); + return 1; + } + // TODO: MagickCore set image properties! + // MagickBooleanType DefineImageProperty(Image *image, const char *property) + // MagickBooleanType SetImageProperty(Image *image,const char *property, const char *value) + + image_info->quality = 97; + (void) strcpy(image->filename, filename); + WriteImage(image_info, image); + + image = DestroyImage(image); + image_info = DestroyImageInfo(image_info); + exception = DestroyExceptionInfo(exception); + free(buf8); + dt_dev_cleanup(&dev); + return 0; +#else + fprintf(stderr, "[export_8] compiled without Magick support!\n"); + return 1; +#endif +} + +// ================================================= +// combined reading +// ================================================= + +int dt_imageio_open(dt_image_t *img, const char *filename) +{ // first try raw loading + if(!dt_imageio_open_raw(img, filename)) return 0; + if(!dt_imageio_open_ldr(img, filename)) return 0; + return 1; +} + diff --git a/src/common/imageio.h b/src/common/imageio.h new file mode 100644 index 000000000000..ba7de199acc4 --- /dev/null +++ b/src/common/imageio.h @@ -0,0 +1,28 @@ +#ifndef DT_IMAGE_IO_H +#define DT_IMAGE_IO_H + +#include +#include +#include "common/image.h" + +#include + +// opens the file using libraw, doing interpolation and stuff +int dt_imageio_open_raw(dt_image_t *img, const char *filename); +// opens file using imagemagick +int dt_imageio_open_ldr(dt_image_t *img, const char *filename); +// try both, first libraw. +int dt_imageio_open(dt_image_t *img, const char *filename); + +// write cache to database, returns 0 on success. assumes buf to be locked. +int dt_imageio_preview_write(dt_image_t *img, dt_image_buffer_t mip); +// read database to cache, returns 0 on success. leaves 'w' locked buf. +int dt_imageio_preview_read(dt_image_t *img, dt_image_buffer_t mip); + +int dt_imageio_export_8 (dt_image_t *img, const char *filename); +int dt_imageio_export_16(dt_image_t *img, const char *filename); +int dt_imageio_export_f (dt_image_t *img, const char *filename); + +void dt_imageio_preview_f_to_8(int32_t wd, int32_t ht, const float *f, uint8_t *p8); + +#endif diff --git a/src/common/nikon_curve.c b/src/common/nikon_curve.c new file mode 100644 index 000000000000..127ce67194af --- /dev/null +++ b/src/common/nikon_curve.c @@ -0,0 +1,2183 @@ +/*************************************************** + nikon_curve.c - read Nikon NTC/NCV files + + Copyright 2004-2008 by Shawn Freeman, Udi Fuchs + + This program reads in a Nikon NTC/NCV file, + interperates it's tone curve, and writes out a + simple ASCII file containing a table of interpolation + values. See the header file for more information. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +****************************************************/ + +#include +#include +#include +#include +#include +#include /* For variable argument lists */ +#include +#include "nikon_curve.h" + +#if 0 +#ifdef __WITH_UFRAW__ + #include "uf_glib.h" + #include "ufraw.h" +#else + #define MAX(a,b) ((a) > (b) ? (a) : (b)) + #define MIN(a,b) ((a) < (b) ? (a) : (b)) + #define g_fopen fopen +#endif +#endif +#define g_fopen fopen + +/************************************************* + * Internal static data + *************************************************/ + +//file offsets for the different data in different file types +static const int FileOffsets[2][4] = { + {NTC_PATCH_OFFSET,NTC_BOX_DATA, NTC_NUM_ANCHOR_POINTS, NTC_ANCHOR_DATA_START}, + {NCV_PATCH_OFFSET,NCV_BOX_DATA, NCV_NUM_ANCHOR_POINTS, NCV_ANCHOR_DATA_START}, +}; + +//file header indicating ntc file +static const unsigned char NTCFileHeader[] = {0x9d,0xdc,0x7d,0x00,0x65,0xd4, + 0x11,0xd1,0x91,0x94,0x44,0x45,0x53,0x54,0x00,0x00}; + +//file header indicating an ncv file +static const unsigned char NCVFileHeader[] = {0x40,0xa9,0x86,0x7a,0x1b,0xe9, + 0xd2,0x11,0xa9,0x0a,0x00,0xaa,0x00,0xb1,0xc1,0xb7}; + +//This is an additional header chunk at the beginning of the file +//There are some similarities between the headers, but not enough to fully crack. +//This does not appear to change. +static const unsigned char NCVSecondFileHeader[] = {0x01,0x32,0xa4,0x76,0xa2, + 0x17,0xd4,0x11,0xa9,0x0a,0x00,0xaa,0x00,0xb1,0xc1, + 0xb7,0x01,0x00,0x05,0x00,0x00,0x00,0x01}; + +//This is the terminator of an NCV file. Again there are some similarites +//to other sections, but not enough for to crack what it means. However, +//it does not appear to change. +static const unsigned char NCVFileTerminator[] = {0x45,0xd3,0x0d,0x77,0xa3,0x6e, + 0x1e,0x4e,0xa4,0xbe,0xcf,0xc1,0x8e,0xb5,0xb7,0x47, + 0x01,0x00,0x05,0x00,0x00,0x00,0x01 }; + +//File section header. Only a one byte difference between this and an NTC file header +static const unsigned char FileSectionHeader[] = {0x9d,0xdc,0x7d,0x03,0x65,0xd4, + 0x11,0xd1,0x91,0x94,0x44,0x45,0x53,0x54,0x00,0x00}; +//file type header array +static const unsigned char *FileTypeHeaders[NUM_FILE_TYPES] = { + NTCFileHeader, + NCVFileHeader, +}; + +/**STANDALONE**/ +#ifdef _STAND_ALONE_ + +//filenames +char exportFilename[1024]; +char nikonFilename[1024]; + +unsigned int standalone_samplingRes = 65536; +unsigned int standalone_outputRes = 256; +unsigned int program_mode = CURVE_MODE; + +/******************************************* +ProcessArgs: + Convenient function for processing the args + for the test runner. +********************************************/ +int ProcessArgs(int num_args, char *args[]) +{ + exportFilename[0] = '\0'; + nikonFilename[0] = '\0'; + + int i; + for(i = 0; i < num_args; i++) + { + if (strcmp(args[i],"-h") == 0 || strcmp(args[i],"-H") == 0 || num_args <= 1) + { + printf("NikonCurveGenerator %s %s\n",NC_VERSION, NC_DATE); + printf("Written by Shawn Freeman\n"); + printf("Thanks go out to Udi Fuchs, UFRaw, and GIMP :)\n\n"); + printf("Usage:\n"); + printf("-o Specify output file.\n"); + printf("-sr Specify sampling resolution. Default is 65536.\n"); + printf("-or Specify output resolution. Default is 256.\n\n"); + printf("-nef Specify an NEF file to get tone curve data from.\n\n"); + printf(" The -or and -sr options are ignored for NEF files\n\n"); + printf("NOTE: If a resolution is not specified, a default one will be used.\n"); + printf(" If the -o option is not specified, default files will be used.\n\n"); + printf("Example:\n"); + printf("NikonCurveGenerator -sr 65536 -or 256 curveFile -o exportFile\n"); + + //signal that processing cannot occur + return NC_ERROR; + } + else if (strcmp(args[i],"-o") == 0 || strcmp(args[i],"-O") == 0) + { + i++; + strncpy(exportFilename, args[i], 1023); + exportFilename[1023] = '\0'; + } + else if (strcmp(args[i],"-sr") == 0) + { + i++; + standalone_samplingRes = atoi(args[i]); + + if (standalone_samplingRes < 1) + { + nc_message(NC_WARNING, "WARNING: Sampling resolution must be" + ">= 1! Using default of 65535.\n"); + } + } + else if (strcmp(args[i],"-or") == 0) + { + i++; + standalone_outputRes = atoi(args[i]); + + if (standalone_outputRes < 1) + { + nc_message(NC_WARNING, "WARNING: Output resolution must be" + ">= 1! Using default of 256.\n"); + } + } + else if (strcmp(args[i],"-nef") == 0) + { + i++; + program_mode = NEF_MODE; + strncpy(nikonFilename, args[i], 1023); + nikonFilename[1023] = '\0'; + } + //don't load argument 0 + else if (i != 0) + { + //consider this the file name to load + strncpy(nikonFilename, args[i], 1023); + nikonFilename[1023] = '\0'; + } + } + + if (strlen(exportFilename) == 0) + { + //set it to have a default output file name + strncpy(exportFilename, nikonFilename, 1023); + strncat(exportFilename, "_CURVE_OUTPUT.txt", 1023); + exportFilename[1023] = '\0'; + } + + return NC_SUCCESS; +} +#endif //End STAND_ALONE + +/************************************************************ +nc_message_handler: + The Nikon Curve message handler. Udi Fuchs created this +to make the error handling consistent acros the code. + + code - Message code + message - The message +**************************************************************/ +void nc_message(int code, char *format, ...) +{ + char message[256]; + va_list ap; + va_start(ap, format); + + vsnprintf(message, 255, format, ap); + message[255] = '\0'; + va_end(ap); + +#ifdef _STAND_ALONE_ //if we're running standalone mode + + code = code; + fprintf(stderr, "%s", message); + fflush(stderr); + +#else + +#ifdef __WITH_UFRAW__ //and if compiling with UFRAW + + if (code==NC_SET_ERROR) + { + ufraw_message(UFRAW_SET_ERROR, message); + } + else + { + ufraw_message(code, message); + } + +#else //else, just print out the errors normally + + code = code; + g_printerr("%s", message); + +#endif //End WITH_UFRAW + +#endif //End STAND_ALONE +} + +void DEBUG_PRINT(char *format, ...) +{ +#if 0//def _DEBUG + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + fflush(stderr); + va_end(ap); +#else + format = format; +#endif +} + +/* nc_merror(): Handle memory allocaltion errors */ +void nc_merror(void *ptr, char *where) +{ + if (ptr) return; +#ifdef __WITH_UFRAW__ + g_error("Out of memory in %s\n", where); +#else + fprintf(stderr, "Out of memory in %s\n", where); + exit(1); +#endif +} + +size_t nc_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t num = fread(ptr, size, nmemb, stream); + if ( num!=nmemb ) + nc_message(NC_WARNING, "WARNING: fread %d != %d\n", num, nmemb); + return num; +} + +size_t nc_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t num = nc_fwrite(ptr, size, nmemb, stream); + if ( num!=nmemb ) + nc_message(NC_WARNING, "WARNING: nc_fwrite %d != %d\n", num, nmemb); + return num; +} + +// Assert something at compile time (must use this inside a function); +// works because compilers won't let us declare negative-length arrays. +#define STATIC_ASSERT(cond) \ + { (void)((int (*)(char failed_static_assertion[(cond)?1:-1]))0); } + +/*********************************************************************** +isBigEndian: + Determines if the machine we are running on is big endian or not. +************************************************************************/ +int isBigEndian() +{ + STATIC_ASSERT(sizeof(short)==2); + short x; + unsigned char EndianTest[2] = { 1, 0 }; + + x = *(short *) EndianTest; + + return (x!=1); +} + +/*********************************************************************** +ShortVal: + Convert short int (16 bit) from little endian to machine endianess. +************************************************************************/ +short ShortVal(short s) +{ + STATIC_ASSERT(sizeof(short)==2); + if (isBigEndian()) { + unsigned char b1, b2; + + b1 = s & 255; + b2 = (s >> 8) & 255; + + return (b1 << 8) + b2; + } else + return s; +} + +/*********************************************************************** +LongVal: + Convert long int (32 bit) from little endian to machine endianess. +************************************************************************/ +int LongVal(int i) +{ + STATIC_ASSERT(sizeof(int)==4); + if (isBigEndian()) { + unsigned char b1, b2, b3, b4; + + b1 = i & 255; + b2 = ( i >> 8 ) & 255; + b3 = ( i>>16 ) & 255; + b4 = ( i>>24 ) & 255; + + return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4; + } else + return i; +} + +/*********************************************************************** +FloatVal: + Convert float from little endian to machine endianess. +************************************************************************/ +float FloatVal(float f) +{ + STATIC_ASSERT(sizeof(float)==4); + if (isBigEndian()) { + union { + float f; + unsigned char b[4]; + } dat1, dat2; + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; + } else + return f; +} + +/*********************************************************************** +DoubleVal: + Convert double from little endian to machine endianess. +************************************************************************/ +double DoubleVal(double d) +{ + STATIC_ASSERT(sizeof(double)==8); + if (isBigEndian()) { + union { + double d; + unsigned char b[8]; + } dat1, dat2; + + dat1.d = d; + dat2.b[0] = dat1.b[7]; + dat2.b[1] = dat1.b[6]; + dat2.b[2] = dat1.b[5]; + dat2.b[3] = dat1.b[4]; + dat2.b[4] = dat1.b[3]; + dat2.b[5] = dat1.b[2]; + dat2.b[6] = dat1.b[1]; + dat2.b[7] = dat1.b[0]; + return dat2.d; + } else + return d; +} + +//********************************************************************** +// +// Purpose: +// +// D3_NP_FS factors and solves a D3 system. +// +// Discussion: +// +// The D3 storage format is used for a tridiagonal matrix. +// The superdiagonal is stored in entries (1,2:N), the diagonal in +// entries (2,1:N), and the subdiagonal in (3,1:N-1). Thus, the +// original matrix is "collapsed" vertically into the array. +// +// This algorithm requires that each diagonal entry be nonzero. +// It does not use pivoting, and so can fail on systems that +// are actually nonsingular. +// +// Example: +// +// Here is how a D3 matrix of order 5 would be stored: +// +// * A12 A23 A34 A45 +// A11 A22 A33 A44 A55 +// A21 A32 A43 A54 * +// +// Modified: +// +// 07 January 2005 Shawn Freeman (pure C modifications) +// 15 November 2003 John Burkardt +// +// Author: +// +// John Burkardt +// +// Parameters: +// +// Input, int N, the order of the linear system. +// +// Input/output, double A[3*N]. +// On input, the nonzero diagonals of the linear system. +// On output, the data in these vectors has been overwritten +// by factorization information. +// +// Input, double B[N], the right hand side. +// +// Output, double D3_NP_FS[N], the solution of the linear system. +// This is NULL if there was an error because one of the diagonal +// entries was zero. +// +double *d3_np_fs ( int n, double a[], double b[] ) + +{ + int i; + double *x; + double xmult; +// +// Check. +// + for ( i = 0; i < n; i++ ) + { + if ( a[1+i*3] == 0.0E+00 ) + { + return NULL; + } + } + x = (double *)calloc(n,sizeof(double)); + nc_merror(x, "d3_np_fs"); + + for ( i = 0; i < n; i++ ) + { + x[i] = b[i]; + } + + for ( i = 1; i < n; i++ ) + { + xmult = a[2+(i-1)*3] / a[1+(i-1)*3]; + a[1+i*3] = a[1+i*3] - xmult * a[0+i*3]; + x[i] = x[i] - xmult * x[i-1]; + } + + x[n-1] = x[n-1] / a[1+(n-1)*3]; + for ( i = n-2; 0 <= i; i-- ) + { + x[i] = ( x[i] - a[0+(i+1)*3] * x[i+1] ) / a[1+i*3]; + } + + return x; +} + +//********************************************************************** +// +// Purpose: +// +// SPLINE_CUBIC_SET computes the second derivatives of a piecewise cubic spline. +// +// Discussion: +// +// For data interpolation, the user must call SPLINE_SET to determine +// the second derivative data, passing in the data to be interpolated, +// and the desired boundary conditions. +// +// The data to be interpolated, plus the SPLINE_SET output, defines +// the spline. The user may then call SPLINE_VAL to evaluate the +// spline at any point. +// +// The cubic spline is a piecewise cubic polynomial. The intervals +// are determined by the "knots" or abscissas of the data to be +// interpolated. The cubic spline has continous first and second +// derivatives over the entire interval of interpolation. +// +// For any point T in the interval T(IVAL), T(IVAL+1), the form of +// the spline is +// +// SPL(T) = A(IVAL) +// + B(IVAL) * ( T - T(IVAL) ) +// + C(IVAL) * ( T - T(IVAL) )**2 +// + D(IVAL) * ( T - T(IVAL) )**3 +// +// If we assume that we know the values Y(*) and YPP(*), which represent +// the values and second derivatives of the spline at each knot, then +// the coefficients can be computed as: +// +// A(IVAL) = Y(IVAL) +// B(IVAL) = ( Y(IVAL+1) - Y(IVAL) ) / ( T(IVAL+1) - T(IVAL) ) +// - ( YPP(IVAL+1) + 2 * YPP(IVAL) ) * ( T(IVAL+1) - T(IVAL) ) / 6 +// C(IVAL) = YPP(IVAL) / 2 +// D(IVAL) = ( YPP(IVAL+1) - YPP(IVAL) ) / ( 6 * ( T(IVAL+1) - T(IVAL) ) ) +// +// Since the first derivative of the spline is +// +// SPL'(T) = B(IVAL) +// + 2 * C(IVAL) * ( T - T(IVAL) ) +// + 3 * D(IVAL) * ( T - T(IVAL) )**2, +// +// the requirement that the first derivative be continuous at interior +// knot I results in a total of N-2 equations, of the form: +// +// B(IVAL-1) + 2 C(IVAL-1) * (T(IVAL)-T(IVAL-1)) +// + 3 * D(IVAL-1) * (T(IVAL) - T(IVAL-1))**2 = B(IVAL) +// +// or, setting H(IVAL) = T(IVAL+1) - T(IVAL) +// +// ( Y(IVAL) - Y(IVAL-1) ) / H(IVAL-1) +// - ( YPP(IVAL) + 2 * YPP(IVAL-1) ) * H(IVAL-1) / 6 +// + YPP(IVAL-1) * H(IVAL-1) +// + ( YPP(IVAL) - YPP(IVAL-1) ) * H(IVAL-1) / 2 +// = +// ( Y(IVAL+1) - Y(IVAL) ) / H(IVAL) +// - ( YPP(IVAL+1) + 2 * YPP(IVAL) ) * H(IVAL) / 6 +// +// or +// +// YPP(IVAL-1) * H(IVAL-1) + 2 * YPP(IVAL) * ( H(IVAL-1) + H(IVAL) ) +// + YPP(IVAL) * H(IVAL) +// = +// 6 * ( Y(IVAL+1) - Y(IVAL) ) / H(IVAL) +// - 6 * ( Y(IVAL) - Y(IVAL-1) ) / H(IVAL-1) +// +// Boundary conditions must be applied at the first and last knots. +// The resulting tridiagonal system can be solved for the YPP values. +// +// Modified: +// +// 07 January 2005 Shawn Freeman (pure C modifications) +// 06 February 2004 John Burkardt +// +// +// Author: +// +// John Burkardt +// +// Parameters: +// +// Input, int N, the number of data points. N must be at least 2. +// In the special case where N = 2 and IBCBEG = IBCEND = 0, the +// spline will actually be linear. +// +// Input, double T[N], the knot values, that is, the points were data is +// specified. The knot values should be distinct, and increasing. +// +// Input, double Y[N], the data values to be interpolated. +// +// Input, int IBCBEG, left boundary condition flag: +// 0: the cubic spline should be a quadratic over the first interval; +// 1: the first derivative at the left endpoint should be YBCBEG; +// 2: the second derivative at the left endpoint should be YBCBEG. +// +// Input, double YBCBEG, the values to be used in the boundary +// conditions if IBCBEG is equal to 1 or 2. +// +// Input, int IBCEND, right boundary condition flag: +// 0: the cubic spline should be a quadratic over the last interval; +// 1: the first derivative at the right endpoint should be YBCEND; +// 2: the second derivative at the right endpoint should be YBCEND. +// +// Input, double YBCEND, the values to be used in the boundary +// conditions if IBCEND is equal to 1 or 2. +// +// Output, double SPLINE_CUBIC_SET[N], the second derivatives of the cubic spline. +// +double *spline_cubic_set ( int n, double t[], double y[], int ibcbeg, + double ybcbeg, int ibcend, double ybcend ) +{ + double *a; + double *b; + int i; + double *ypp; +// +// Check. +// + if ( n <= 1 ) + { + nc_message(NC_SET_ERROR, "spline_cubic_set() error: " + "The number of data points must be at least 2.\n"); + return NULL; + } + + for ( i = 0; i < n - 1; i++ ) + { + if ( t[i+1] <= t[i] ) + { + nc_message(NC_SET_ERROR, "spline_cubic_set() error: " + "The knots must be strictly increasing, but " + "T(%u) = %e, T(%u) = %e\n",i,t[i],i+1,t[i+1]); + return NULL; + } + } + a = (double *)calloc(3*n,sizeof(double)); + nc_merror(a, "spline_cubic_set"); + b = (double *)calloc(n,sizeof(double)); + nc_merror(b, "spline_cubic_set"); +// +// Set up the first equation. +// + if ( ibcbeg == 0 ) + { + b[0] = 0.0E+00; + a[1+0*3] = 1.0E+00; + a[0+1*3] = -1.0E+00; + } + else if ( ibcbeg == 1 ) + { + b[0] = ( y[1] - y[0] ) / ( t[1] - t[0] ) - ybcbeg; + a[1+0*3] = ( t[1] - t[0] ) / 3.0E+00; + a[0+1*3] = ( t[1] - t[0] ) / 6.0E+00; + } + else if ( ibcbeg == 2 ) + { + b[0] = ybcbeg; + a[1+0*3] = 1.0E+00; + a[0+1*3] = 0.0E+00; + } + else + { + nc_message(NC_SET_ERROR, "spline_cubic_set() error: " + "IBCBEG must be 0, 1 or 2. The input value is %u.\n", ibcbeg); + free(a); + free(b); + return NULL; + } +// +// Set up the intermediate equations. +// + for ( i = 1; i < n-1; i++ ) + { + b[i] = ( y[i+1] - y[i] ) / ( t[i+1] - t[i] ) + - ( y[i] - y[i-1] ) / ( t[i] - t[i-1] ); + a[2+(i-1)*3] = ( t[i] - t[i-1] ) / 6.0E+00; + a[1+ i *3] = ( t[i+1] - t[i-1] ) / 3.0E+00; + a[0+(i+1)*3] = ( t[i+1] - t[i] ) / 6.0E+00; + } +// +// Set up the last equation. +// + if ( ibcend == 0 ) + { + b[n-1] = 0.0E+00; + a[2+(n-2)*3] = -1.0E+00; + a[1+(n-1)*3] = 1.0E+00; + } + else if ( ibcend == 1 ) + { + b[n-1] = ybcend - ( y[n-1] - y[n-2] ) / ( t[n-1] - t[n-2] ); + a[2+(n-2)*3] = ( t[n-1] - t[n-2] ) / 6.0E+00; + a[1+(n-1)*3] = ( t[n-1] - t[n-2] ) / 3.0E+00; + } + else if ( ibcend == 2 ) + { + b[n-1] = ybcend; + a[2+(n-2)*3] = 0.0E+00; + a[1+(n-1)*3] = 1.0E+00; + } + else + { + nc_message(NC_SET_ERROR, "spline_cubic_set() error: " + "IBCEND must be 0, 1 or 2. The input value is %u", ibcend); + free(a); + free(b); + return NULL; + } +// +// Solve the linear system. +// + if ( n == 2 && ibcbeg == 0 && ibcend == 0 ) + { + ypp = (double *)calloc(2,sizeof(double)); + nc_merror(ypp, "spline_cubic_set"); + + ypp[0] = 0.0E+00; + ypp[1] = 0.0E+00; + } + else + { + ypp = d3_np_fs ( n, a, b ); + + if ( !ypp ) + { + nc_message(NC_SET_ERROR, "spline_cubic_set() error: " + "The linear system could not be solved.\n"); + free(a); + free(b); + return NULL; + } + + } + + free(a); + free(b); + return ypp; +} + +//********************************************************************** +// +// Purpose: +// +// SPLINE_CUBIC_VAL evaluates a piecewise cubic spline at a point. +// +// Discussion: +// +// SPLINE_CUBIC_SET must have already been called to define the values of YPP. +// +// For any point T in the interval T(IVAL), T(IVAL+1), the form of +// the spline is +// +// SPL(T) = A +// + B * ( T - T(IVAL) ) +// + C * ( T - T(IVAL) )**2 +// + D * ( T - T(IVAL) )**3 +// +// Here: +// A = Y(IVAL) +// B = ( Y(IVAL+1) - Y(IVAL) ) / ( T(IVAL+1) - T(IVAL) ) +// - ( YPP(IVAL+1) + 2 * YPP(IVAL) ) * ( T(IVAL+1) - T(IVAL) ) / 6 +// C = YPP(IVAL) / 2 +// D = ( YPP(IVAL+1) - YPP(IVAL) ) / ( 6 * ( T(IVAL+1) - T(IVAL) ) ) +// +// Modified: +// +// 07 January 2005 Shawn Freeman (pure C modifications) +// 04 February 1999 John Burkardt +// +// Author: +// +// John Burkardt +// +// Parameters: +// +// Input, int n, the number of knots. +// +// Input, double Y[N], the data values at the knots. +// +// Input, double T[N], the knot values. +// +// Input, double TVAL, a point, typically between T[0] and T[N-1], at +// which the spline is to be evalulated. If TVAL lies outside +// this range, extrapolation is used. +// +// Input, double Y[N], the data values at the knots. +// +// Input, double YPP[N], the second derivatives of the spline at +// the knots. +// +// Output, double *YPVAL, the derivative of the spline at TVAL. +// +// Output, double *YPPVAL, the second derivative of the spline at TVAL. +// +// Output, double SPLINE_VAL, the value of the spline at TVAL. +// +double spline_cubic_val ( int n, double t[], double tval, double y[], + double ypp[], double *ypval, double *yppval ) +{ + double dt; + double h; + int i; + int ival; + double yval; +// +// Determine the interval [ T(I), T(I+1) ] that contains TVAL. +// Values below T[0] or above T[N-1] use extrapolation. +// + ival = n - 2; + + for ( i = 0; i < n-1; i++ ) + { + if ( tval < t[i+1] ) + { + ival = i; + break; + } + } +// +// In the interval I, the polynomial is in terms of a normalized +// coordinate between 0 and 1. +// + dt = tval - t[ival]; + h = t[ival+1] - t[ival]; + + yval = y[ival] + + dt * ( ( y[ival+1] - y[ival] ) / h + - ( ypp[ival+1] / 6.0E+00 + ypp[ival] / 3.0E+00 ) * h + + dt * ( 0.5E+00 * ypp[ival] + + dt * ( ( ypp[ival+1] - ypp[ival] ) / ( 6.0E+00 * h ) ) ) ); + + *ypval = ( y[ival+1] - y[ival] ) / h + - ( ypp[ival+1] / 6.0E+00 + ypp[ival] / 3.0E+00 ) * h + + dt * ( ypp[ival] + + dt * ( 0.5E+00 * ( ypp[ival+1] - ypp[ival] ) / h ) ); + + *yppval = ypp[ival] + dt * ( ypp[ival+1] - ypp[ival] ) / h; + + return yval; +} + +/********************************************* +GetNikonFileType: + Gets the nikon file type by comparing file + headers. + + file - The file to check. +**********************************************/ +int GetNikonFileType(FILE *file) +{ + unsigned char buff[HEADER_SIZE]; + int i = 0, j = 0; + int found = 1; + + nc_fread(buff,HEADER_SIZE,1,file); + + for(i = 0; i < NUM_FILE_TYPES; i++) + { + found = 1; + for(j = 0; j < HEADER_SIZE; j++) + { + if (buff[j] != FileTypeHeaders[i][j]) + { + found = 0; + break; + } + } + + if (found) + { + //return the file type + return i; + } + } + nc_message(NC_SET_ERROR, "Error, no compatible file types found!\n"); + return -1; +} + +/********************************************* +LoadNikonCurve: + Loads all curves from a Nikon ntc or ncv file. + + fileName - The filename. + curve - Pointer to curve struct to hold the data. + resolution - How many data points to sample from the curve. +**********************************************/ +int LoadNikonData(char *fileName, NikonData *data) +{ + FILE *input = NULL; + int offset = 0; + CurveData *curve = NULL; + + if (fileName == NULL || strlen(fileName) == 0) + { + nc_message(NC_SET_ERROR, + "Error, input filename cannot be NULL or empty!\n"); + return NC_ERROR; + } + + DEBUG_PRINT("DEBUG: OPENING FILE: %s\n",fileName); + + //open file for reading only! + input = g_fopen(fileName,"rb"); + + //make sure we have a valid file + if (input == NULL) + { + nc_message(NC_SET_ERROR, "Error opening '%s': %s\n", + fileName, strerror(errno)); + return NC_ERROR; + } + + //init the curve; + memset(data,0,sizeof(NikonData)); + + //get the file type + data->m_fileType = GetNikonFileType(input); + // set file seek positions for curve tones depending of file type + // todo: is it possible to find one common rule? + long curveFilePos[4][4] = { + {FileOffsets[data->m_fileType][BOX_DATA], SEEK_SET, FileOffsets[data->m_fileType][ANCHOR_DATA], SEEK_SET}, + {NEXT_SECTION_BOX_DATA_OFFSET, SEEK_CUR, NUM_POINTS_TO_ANCHOR_OFFSET, SEEK_CUR}, + {NEXT_SECTION_BOX_DATA_OFFSET, SEEK_CUR, NUM_POINTS_TO_ANCHOR_OFFSET, SEEK_CUR}, + {NEXT_SECTION_BOX_DATA_OFFSET, SEEK_CUR, NUM_POINTS_TO_ANCHOR_OFFSET, SEEK_CUR} + }; + + //make sure we have a good file type + if (data->m_fileType == -1) + return NC_ERROR; + + //advance file pointer to necessary data section + fseek(input,offset,SEEK_SET); + + //Conevenience and opt if compiler doesn't already do it + curve = &data->curves[0]; + + //set curve type + curve->m_curveType = TONE_CURVE; + + //read patch version + fseek(input,FileOffsets[data->m_fileType][PATCH_DATA],SEEK_SET); + nc_fread(&data->m_patch_version,sizeof(unsigned short),1,input); + data->m_patch_version = ShortVal(data->m_patch_version); + + // read all tone curves data follow from here + int i; + for(i = 0; i < NUM_CURVE_TYPES; i++) + { + curve = &data->curves[i]; + + //set curve type + curve->m_curveType = i; + + //get box data + fseek(input, curveFilePos[i][0], curveFilePos[i][1]); + + nc_fread(&curve->m_min_x,sizeof(double),1,input); + curve->m_min_x = DoubleVal(curve->m_min_x); + + nc_fread(&curve->m_max_x,sizeof(double),1,input); + curve->m_max_x = DoubleVal(curve->m_max_x); + + nc_fread(&curve->m_gamma,sizeof(double),1,input); + curve->m_gamma = DoubleVal(curve->m_gamma); + + nc_fread(&curve->m_min_y,sizeof(double),1,input); + curve->m_min_y = DoubleVal(curve->m_min_y); + + nc_fread(&curve->m_max_y,sizeof(double),1,input); + curve->m_max_y = DoubleVal(curve->m_max_y); + + //get number of anchors (always located after box data) + nc_fread(&curve->m_numAnchors,1,1,input); + + // It seems that if there is no curve then the 62 bytes in the buffer + // are either all 0x00 (D70) or 0xFF (D2H). + // We therefore switch these values with the default values. + if (curve->m_min_x==1.0) { + curve->m_min_x = 0.0; + DEBUG_PRINT("DEBUG: NEF X MIN -> %e (changed)\n",curve->m_min_x); + } + if (curve->m_max_x==0.0) { + curve->m_max_x = 1.0; + DEBUG_PRINT("DEBUG: NEF X MAX -> %e (changed)\n",curve->m_max_x); + } + if (curve->m_min_y==1.0) { + curve->m_min_y = 0.0; + DEBUG_PRINT("DEBUG: NEF Y MIN -> %e (changed)\n",curve->m_min_y); + } + if (curve->m_max_y==0.0) { + curve->m_max_y = 1.0; + DEBUG_PRINT("DEBUG: NEF Y MAX -> %e (changed)\n",curve->m_max_y); + } + if (curve->m_gamma==0.0 || curve->m_gamma==255.0+255.0/256.0) { + curve->m_gamma = 1.0; + DEBUG_PRINT("DEBUG: NEF GAMMA -> %e (changed)\n",curve->m_gamma); + } + if (curve->m_numAnchors==255) { + curve->m_numAnchors = 0; + DEBUG_PRINT("DEBUG: NEF NUMBER OF ANCHORS -> %u (changed)\n", + curve->m_numAnchors); + } + if (curve->m_numAnchors > NIKON_MAX_ANCHORS) { + curve->m_numAnchors = NIKON_MAX_ANCHORS; + DEBUG_PRINT("DEBUG: NEF NUMBER OF ANCHORS -> %u (changed)\n", + curve->m_numAnchors); + } + //Move to start of the anchor data + fseek(input, curveFilePos[i][2], curveFilePos[i][3]); + + //read in the anchor points + int rs = nc_fread(curve->m_anchors, sizeof(CurveAnchorPoint), + curve->m_numAnchors, input); + if (curve->m_numAnchors != rs) { + nc_message(NC_SET_ERROR, "Error reading all anchor points\n"); + return NC_ERROR; + } + + int j; + for (j = 0; j < curve->m_numAnchors; j++) + { + curve->m_anchors[j].x = DoubleVal(curve->m_anchors[j].x); + curve->m_anchors[j].y = DoubleVal(curve->m_anchors[j].y); + } + + DEBUG_PRINT("DEBUG: Loading Data:\n"); + DEBUG_PRINT("DEBUG: CURVE_TYPE: %u \n",curve->m_curveType); + DEBUG_PRINT("DEBUG: BOX->MIN_X: %f\n",curve->m_min_x); + DEBUG_PRINT("DEBUG: BOX->MAX_X: %f\n",curve->m_max_x); + DEBUG_PRINT("DEBUG: BOX->GAMMA: %f\n",curve->m_gamma); + DEBUG_PRINT("DEBUG: BOX->MIN_Y: %f\n",curve->m_min_y); + DEBUG_PRINT("DEBUG: BOX->MAX_Y: %f\n",curve->m_max_x); + +#ifdef _DEBUG + int i_dbg; + for(i_dbg = 0; i_dbg < curve->m_numAnchors; i_dbg++) + { + DEBUG_PRINT("DEBUG: ANCHOR X,Y: %f,%f\n", + curve->m_anchors[i_dbg].x,curve->m_anchors[i_dbg].y); + } + DEBUG_PRINT("\n"); +#endif + } + fclose(input); + return NC_SUCCESS; +} + +/********************************************* +CurveDataSample: + Samples from a spline curve constructed from + the Nikon data. + + curve - Pointer to curve struct to hold the data. + sample - Pointer to sample struct to hold the data. +**********************************************/ +int CurveDataSample(CurveData *curve, CurveSample *sample) +{ + int i = 0, n; + + double x[20]; + double y[20]; + + //The box points (except the gamma) are what the anchor points are relative + //to so... + + double box_width = curve->m_max_x - curve->m_min_x; + double box_height = curve->m_max_y - curve->m_min_y; + double gamma = 1.0/curve->m_gamma; + + //build arrays for processing + if (curve->m_numAnchors == 0) + { + //just a straight line using box coordinates + x[0] = curve->m_min_x; + y[0] = curve->m_min_y; + x[1] = curve->m_max_x; + y[1] = curve->m_max_y; + n = 2; + } + else + { + for(i = 0; i < curve->m_numAnchors; i++) + { + x[i] = curve->m_anchors[i].x*box_width + curve->m_min_x; + y[i] = curve->m_anchors[i].y*box_height + curve->m_min_y; + } + n = curve->m_numAnchors; + } + //returns an array of second derivatives used to calculate the spline curve. + //this is a malloc'd array that needs to be freed when done. + //The setings currently calculate the natural spline, which closely matches + //camera curve output in raw files. + double *ypp = spline_cubic_set(n, x, y, 2, 0.0, 2, 0.0); + if (ypp==NULL) return NC_ERROR; + + //first derivative at a point + double ypval = 0; + + //second derivate at a point + double yppval = 0; + + //Now build a table + int val; + double res = 1.0/(double)(sample->m_samplingRes-1); + + //allocate enough space for the samples + DEBUG_PRINT("DEBUG: SAMPLING ALLOCATION: %u bytes\n", + sample->m_samplingRes*sizeof(int)); + DEBUG_PRINT("DEBUG: SAMPLING OUTPUT RANGE: 0 -> %u\n", sample->m_outputRes); + + // sample->m_Samples = (unsigned short int *)realloc(sample->m_Samples, + // sample->m_samplingRes * sizeof(short int)); + // nc_merror(sample->m_Samples, "CurveDataSample"); + + int firstPointX = x[0] * (sample->m_samplingRes-1); + int firstPointY = pow(y[0], gamma) * (sample->m_outputRes-1); + int lastPointX = x[n-1] * (sample->m_samplingRes-1); + int lastPointY = pow(y[n-1], gamma) * (sample->m_outputRes-1); + int maxY = curve->m_max_y * (sample->m_outputRes-1); + int minY = curve->m_min_y * (sample->m_outputRes-1); + + for(i = 0; i < (int)sample->m_samplingRes; i++) + { + //get the value of the curve at a point + //take into account that curves may not necessarily begin at x = 0.0 + //nor end at x = 1.0 + + //Before the first point and after the last point, take a strait line + if (i < firstPointX) { + sample->m_Samples[i] = firstPointY; + } else if (i > lastPointX) { + sample->m_Samples[i] = lastPointY; + } else { + //within range, we can sample the curve + if (gamma==1.0) + val = spline_cubic_val( n, x, i*res, y, + ypp, &ypval, &yppval ) * (sample->m_outputRes-1) + 0.5; + else + val = pow(spline_cubic_val( n, x, i*res, y, + ypp, &ypval, &yppval ), gamma) * + (sample->m_outputRes-1) + 0.5; + + sample->m_Samples[i] = MIN(MAX(val,minY),maxY); + } + } + + free(ypp); + return NC_SUCCESS; +} + +/********************************************* +CurveDataReset: + Reset curve to straight line but don't touch the curve name. +**********************************************/ +void CurveDataReset(CurveData *curve) +{ + curve->m_min_x = 0; + curve->m_max_x = 1; + curve->m_min_y = 0; + curve->m_max_y = 1; + curve->m_gamma = 1; + curve->m_numAnchors = 2; + curve->m_anchors[0].x = 0; + curve->m_anchors[0].y = 0; + curve->m_anchors[1].x = 1; + curve->m_anchors[1].y = 1; +} + +/********************************************* +CurveDataIsTrivial: + Check if the curve is a trivial linear curve. +**********************************************/ +int CurveDataIsTrivial(CurveData *curve) +{ + if ( curve->m_min_x != 0 ) return FALSE; + if ( curve->m_max_x != 1 ) return FALSE; + if ( curve->m_min_y != 0 ) return FALSE; + if ( curve->m_max_y != 1 ) return FALSE; + if ( curve->m_numAnchors < 2 ) return TRUE; + if ( curve->m_numAnchors != 2 ) return FALSE; + if ( curve->m_anchors[0].x != 0 ) return FALSE; + if ( curve->m_anchors[0].y != 0 ) return FALSE; + if ( curve->m_anchors[1].x != 1 ) return FALSE; + if ( curve->m_anchors[1].y != 1 ) return FALSE; + return TRUE; +} + +/********************************************* +CurveDataSetPoint: + Change the position of point to the new (x,y) coordinate. + The end-points get a special treatment. When these are moved all the + other points are moved together, keeping their relative position constant. +**********************************************/ +void CurveDataSetPoint(CurveData *curve, int point, double x, double y) +{ + int i; + double left = curve->m_anchors[0].x; + double right = curve->m_anchors[curve->m_numAnchors-1].x; + if (point==0) { + for (i=0; im_numAnchors; i++) + curve->m_anchors[i].x = x + (curve->m_anchors[i].x-left) * + (right-x) / (right-left); + } else if (point==curve->m_numAnchors-1) { + for (i=0; im_numAnchors; i++) + curve->m_anchors[i].x = left + (curve->m_anchors[i].x-left) * + (x-left) / (right-left); + } else { + curve->m_anchors[point].x = x; + } + curve->m_anchors[point].y = y; +} + +/**************************************************** +SampleToCameraCurve: + + EXPERIMENTAL!!!!! + + Transforms the curve generated by sampling the + spline interpolator into the curve that is used by + the camera. + + This is a special function. While the function places + no special restrictions on sampling resolution or + output resolution, it should be noted that Nikon D70 + camera curve is 4096 entries of 0-255. + + If you intend on using this function as such, you should + set the sampling resolution and output resolution + accordingly. + + curve - The Nikon curve to sample and transform. +*****************************************************/ +#define CAMERA_LINEAR_CURVE_SLOPE 0.26086956521739130434782608695652 +#define CAMERA_LINEAR_LIMIT ((276.0/4096.0)*65536.0) + +int SampleToCameraCurve(CurveData *curve, CurveSample *sample) +{ + unsigned int i = 0; + + if (curve->m_numAnchors < 2) + { + nc_message(NC_SET_ERROR, "Not enough anchor points(need at least two)!\n"); + return NC_ERROR; + } + + double x[20]; + double y[20]; + + //The box points (except the gamma) are what the anchor points are relative + //to so... + + double box_width = curve->m_max_x - curve->m_min_x; + double box_height = curve->m_max_y - curve->m_min_y; + double gamma = 1.0/curve->m_gamma; + + //build arrays for processing + if (curve->m_numAnchors == 0) + { + //just a straight line using box coordinates + x[0] = curve->m_min_x; + y[0] = curve->m_min_y; + x[1] = curve->m_max_x; + y[1] = curve->m_max_y; + } + else + { + for(i = 0; i < (unsigned int)curve->m_numAnchors; i++) + { + x[i] = curve->m_anchors[i].x*box_width + curve->m_min_x; + y[i] = curve->m_anchors[i].y*box_height + curve->m_min_y; + } + } + + //returns an array of second derivatives used to calculate the spline curve. + //this is a malloc'd array that needs to be freed when done. + //The setings currently calculate the natural spline, which closely matches + //camera curve output in raw files. + double *ypp = spline_cubic_set(curve->m_numAnchors,x,y,2, 0.0, 2, 0.0); + if (ypp==NULL) return NC_ERROR; + + //first derivative at a point + double ypval = 0; + + //second derivate at a point + double yppval = 0; + + //Now build a table + double val = 0; + double res = 1.0/(double)sample->m_samplingRes; + + DEBUG_PRINT("DEBUG: SAMPLING RESOLUTION: %u bytes\n", + sample->m_samplingRes*sizeof(int)); + DEBUG_PRINT("DEBUG: SAMPLING OUTPUT RANGE: 0 -> %u\n", sample->m_outputRes); + + double outres = sample->m_outputRes; + + for(i = 0; i < sample->m_samplingRes; i++) + { + //get the value of the curve at a point + //take into account that curves may not necessarily begin at x = 0.0 + //nor end at x = 1.0 + if (i*res < curve->m_min_x || i*res > curve->m_max_x) + { + val = 0.0; + } + else + { + //within range, okay to sample the curve + val = spline_cubic_val ( curve->m_numAnchors, x, i*res, + y, ypp, &ypval, &yppval ); + + //Compensate for gamma. + val = pow(val,gamma); + + //cap at the high end of the range + if (val > curve->m_max_y) + { + val = curve->m_max_y; + } + //cap at the low end of the range + else if (val < curve->m_min_y) + { + val = curve->m_min_y; + } + + //transform "linear curve" to the camera curve + //outres = 4096; + //val *= outres; + + //this equation is used inside Nikon's program to transform + //the curves into the camera curves. + //FIX LINEAR SECTION + /*if (val < CAMERA_LINEAR_CURVE_SLOPE) + { + //do linear + val = val*4096*CAMERA_LINEAR_CURVE_SLOPE; + + } + else*/ + { + //do real curve?? + val = ( log(7*val+1.0) / log(4*val+2.0) )*142.0+104.0*(val); + } + + //cap at the high end of the range + if (val > outres*curve->m_max_y) + { + val = outres; + } + //cap at the low end of the range + else if (val < curve->m_min_y*outres) + { + val = curve->m_min_y*outres; + } + + } + + //save the sample + sample->m_Samples[i] = (unsigned int)floor(val); + } + + free(ypp); + return NC_SUCCESS; +} + +/************************************************************ +SaveNikonDataFile: + Savess a curve to a Nikon ntc or ncv file. + + data - A NikonData structure containing info of all the curves. + fileName - The filename. + filetype - Indicator for an NCV or NTC file. + version - The version of the Nikon file to write +**************************************************************/ +int SaveNikonDataFile(NikonData *data, char *outfile, int filetype, int version) +{ + FILE *output = NULL; + int i = 0,r = 0,g = 0,b = 0; + unsigned short_tmp = 0; + unsigned int long_tmp = 0; + double double_tmp = 0; + CurveData *curve = NULL; + version = version; + + //used for file padding + unsigned char pad[32]; + memset(pad,0,32); + + output = g_fopen(outfile,"wb+"); + if (!output) + { + nc_message(NC_SET_ERROR, "Error creating curve file '%s': %s\n", + outfile, strerror(errno)); + return NC_ERROR; + } + + //write out file header + nc_fwrite(FileTypeHeaders[filetype],HEADER_SIZE,1,output); + + if (filetype == NCV_FILE) + { + //write out unknown header bytes + short_tmp = ShortVal(NCV_UNKNOWN_HEADER_DATA); + nc_fwrite(&short_tmp, 2, 1, output); + + //write out file size - header + //Placeholder.The real filesize is written at the end. + //NCV files have two size location, one here and one in the + //NTC section of the file + long_tmp = 0; + nc_fwrite(&long_tmp, 4, 1, output); + + //write second header chunk + nc_fwrite(NCVSecondFileHeader,1,NCV_SECOND_HEADER_LENGTH,output); + + //From here until almost the end, the file is an NTC file + nc_fwrite(NTCFileHeader,NTC_FILE_HEADER_LENGTH,1,output); + } + + //patch version? (still unsure about this one) + if (data->m_patch_version < NIKON_PATCH_4) + { + data->m_patch_version = NIKON_PATCH_5; + } + short_tmp = ShortVal(data->m_patch_version); + nc_fwrite(&short_tmp, 2, 1, output); + + //write out file size - header + //Placeholder.The real filesize is written at the end. + long_tmp = 0; + nc_fwrite(&long_tmp, 4, 1, output); + + //write out version + unsigned int forced_ver = ShortVal(NIKON_VERSION_4_1); + nc_fwrite(&forced_ver, 4, 1, output); + + //write out pad (this is a 7 byte pad) + nc_fwrite(&pad,1,7,output); + + //now wash and repeat for the four sections of data + for(i = 0; i < 4; i++) + { + //write out section header (same as NTC file header) + nc_fwrite(FileSectionHeader,1,NTC_FILE_HEADER_LENGTH,output); + + //write out section type + long_tmp = LongVal(i); + nc_fwrite(&long_tmp,4,1,output); + + //write out unknown data + short_tmp = ShortVal(NTC_UNKNOWN_DATA); + nc_fwrite(&short_tmp,2,1,output); + + //write out pad byte + nc_fwrite(pad,1,1,output); + + //write out components + switch (i) + { + case 0: + r = g = b = 0; + break; + + case 1: + r = 255; + g = b = 0; + break; + + case 2: + g = 255; + r = b = 0; + break; + + case 3: + b = 255; + g = r = 0; + break; + } + + long_tmp = LongVal(r); + nc_fwrite(&long_tmp,4,1,output); + + long_tmp = LongVal(g); + nc_fwrite(&long_tmp,4,1,output); + + long_tmp = LongVal(b); + nc_fwrite(&long_tmp,4,1,output); + + //write out pad (12 byte pad) + nc_fwrite(pad,12,1,output); + + //write out rgb weights + switch (i) + { + case 0: + r = g = b = 255; + break; + + case 1: + r = 255; + g = b = 0; + break; + + case 2: + g = 255; + r = b = 0; + break; + + case 3: + b = 255; + g = r = 0; + break; + } + + long_tmp = LongVal(r); + nc_fwrite(&long_tmp,4,1,output); + + long_tmp = LongVal(g); + nc_fwrite(&long_tmp,4,1,output); + + long_tmp = LongVal(b); + nc_fwrite(&long_tmp,4,1,output); + + curve = &data->curves[i]; + //write out curve data + if (curve->m_numAnchors >= 2) + { + //we have a legit curve, use the data as is + double_tmp = DoubleVal(curve->m_min_x); + nc_fwrite(&double_tmp,sizeof(double),1,output); + + double_tmp = DoubleVal(curve->m_max_x); + nc_fwrite(&double_tmp,sizeof(double),1,output); + + double_tmp = DoubleVal(curve->m_gamma); + nc_fwrite(&double_tmp,sizeof(double),1,output); + + double_tmp = DoubleVal(curve->m_min_y); + nc_fwrite(&double_tmp,sizeof(double),1,output); + + double_tmp = DoubleVal(curve->m_max_y); + nc_fwrite(&double_tmp,sizeof(double),1,output); + + //write out number of anchor points (minimum is two) + nc_fwrite(&curve->m_numAnchors,1,1,output); + + //write out pad + nc_fwrite(pad,NUM_POINTS_TO_ANCHOR_OFFSET,1,output); + + //write out anchor point data + if (curve->m_anchors) + { + int i; + for (i = 0; i < curve->m_numAnchors; i++) + { + double_tmp = DoubleVal(curve->m_anchors[i].x); + nc_fwrite(&double_tmp,sizeof(double),1,output); + double_tmp = DoubleVal(curve->m_anchors[i].y); + nc_fwrite(&double_tmp,sizeof(double),1,output); + } + } + else + { + nc_message(NC_SET_ERROR,"Curve anchor data is NULL! Aborting file write!\n"); + return NC_ERROR; + } + } + else + { + DEBUG_PRINT("NOTE: There are < 2 anchor points for curve %u! Forcing curve defaults.\n",i); + DEBUG_PRINT("This should not be a concern unless it is happening for curve 0\n"); + //This curve either has not been correctly initialized or is empty. + //Force defaults. + double default_val = 0; + nc_fwrite(&default_val,sizeof(double),1,output); //min x + default_val = DoubleVal(1.0); + nc_fwrite(&default_val,sizeof(double),1,output); //max_x + //gamma has a default of 1 + default_val = DoubleVal(1.0); + nc_fwrite(&default_val,sizeof(double),1,output); //gamma + default_val = 0; + nc_fwrite(&default_val,sizeof(double),1,output); //min y + default_val = DoubleVal(1.0); + nc_fwrite(&default_val,sizeof(double),1,output); //max y + + //force the number of anchors to be 2 + unsigned char num = 2; + nc_fwrite(&num,1,1,output); + + //write out pad + nc_fwrite(pad,NUM_POINTS_TO_ANCHOR_OFFSET,1,output); + + //if the number of anchors was < 2, force default values. + default_val = 0; + nc_fwrite(&default_val,sizeof(double),1,output); //min x + nc_fwrite(&default_val,sizeof(double),1,output); //min y + default_val = DoubleVal(1.0); + nc_fwrite(&default_val,sizeof(double),1,output); //max x + nc_fwrite(&default_val,sizeof(double),1,output); //max y + + } + + //write out pad + nc_fwrite(pad,END_ANCHOR_DATA_PAD_LENGTH,1,output); + } + + if (filetype == NCV_FILE) + { + //write out the file terminator if this is an NCV file + nc_fwrite(NCVFileTerminator,NCV_FILE_TERMINATOR_LENGTH,1,output); + } + + //calculate the file size + //size = filesize - size of header - 2 bytes (unknown data after the end of the header) + long size = ftell(output)-HEADER_SIZE-2; + + //set the file write position to the size location + fseek(output, FILE_SIZE_OFFSET, SEEK_SET); + + //write out the file size + size = LongVal(size); + nc_fwrite(&size,4,1,output); + + if (filetype == NCV_FILE) + { + //another size needs to placed in the NTC header inside the file + fseek(output, NCV_SECOND_FILE_SIZE_OFFSET, SEEK_SET); + + //The - 6 is interesting. The last 6 bytes of the terminator must have some special meaning because + //the calculated size in files from the Nikon progs always calculate size bytes short. + //I'm assuming it is more than coincedence that those bytes match the last 6 bytes + //of the NCV second file header. I've yet to determine their significance. + size = LongVal(size - NCV_HEADER_SIZE - 6); + nc_fwrite(&size,4,1,output); + + } + fclose(output); + + return NC_SUCCESS; +} + +/************************************************************ +SaveNikonCurveFile: + Saves out curves to a Nikon ntc or ncv file. This function + takes a single curve and uses defaults for the other curves. + Typically, the curve used is the tone curve. + + curve - A CurveData structure. This is usually the tone curve + curve_type - The curve type (TONE_CURVE, RED_CURVE, etc.) + fileName - The filename. + filetype - Indicator for an NCV or NTC file. + version - The version of the Nikon file to write + +NOTE: The only version tested is Nikon 4.1 anything + other than this may result in unpredictable behavior. + For now, the version passed in is ignored and is forced + to 4.1. + + This function is just a helper function that allows the user + to just carry around a single curve. +**************************************************************/ +int SaveNikonCurveFile(CurveData *curve, int curve_type, char *outfile, + int filetype, int version) +{ + NikonData data; + //clear the structure + memset(&data,0,sizeof(data)); + //assume that it's the tone curve + data.curves[curve_type] = *curve; + //call the work horse + return SaveNikonDataFile(&data, outfile, filetype, version); +} + +/********************************************* +SaveSampledNikonCurve: + Saves a sampling from a curve to text file to + be processed by UFRaw. + + sample - Pointer to sampled curve struct to hold the data. + fileName - The filename. +**********************************************/ +int SaveSampledNikonCurve(CurveSample *sample, char *outfile) +{ + unsigned int i = 0; + FILE *output = NULL; + + if (outfile == NULL || strlen(outfile) == 0) + { + nc_message(NC_SET_ERROR, + "Output filename cannot be null or empty!\n"); + } + + output = g_fopen(outfile,"wb+"); + + if (!output) + { + nc_message(NC_SET_ERROR, "Error creating curve file '%s': %s\n", + outfile, strerror(errno)); + return NC_ERROR; + } + + if (!sample->m_Samples) + { + nc_message(NC_SET_ERROR, + "Sample array has not been allocated or is corrupt!\n"); + return NC_ERROR; + } + + DEBUG_PRINT("DEBUG: OUTPUT FILENAME: %s\n",outfile); + fprintf(output,"%u %u\n",0,sample->m_Samples[0]); + for(i = 1; i < sample->m_samplingRes; i++) + { + // Print sample point only if different than previous one + if (sample->m_Samples[i]!=sample->m_Samples[i-1]) + { + fprintf(output,"%u %u\n",i,sample->m_Samples[i]); + } + } + // Make sure the last point is also printed + if (sample->m_Samples[i-1]==sample->m_Samples[i-2]) + { + fprintf(output,"%u %u\n",i-1,sample->m_Samples[i-1]); + } + + fclose(output); + return NC_SUCCESS; +} + +/******************************************************* +CurveSampleInit: + Init and allocate curve sample. +********************************************************/ +CurveSample *CurveSampleInit(unsigned int samplingRes, unsigned int outputRes) +{ + CurveSample *sample = (CurveSample*)calloc(1, sizeof(CurveSample)); + nc_merror(sample, "CurveSampleInit"); + sample->m_samplingRes = samplingRes; + sample->m_outputRes = outputRes; + if (samplingRes>0) { + sample->m_Samples = (unsigned short int*)calloc(samplingRes, sizeof(short int)); + nc_merror(sample->m_Samples, "CurveSampleInit"); + } else { + sample->m_Samples = NULL; + } + return sample; +} + +/******************************************************* +CurveSampleFree: + Frees memory allocated for this curve sample. +********************************************************/ +int CurveSampleFree(CurveSample *sample) +{ + //if these are null, they've already been deallocated + if (sample==NULL) return NC_SUCCESS; + + if (sample->m_Samples!=NULL) + { + free(sample->m_Samples); + sample->m_Samples = NULL; + } + + free(sample); + + return NC_SUCCESS; +} + +/**************************************** +ConvertNikonCurveData: + The main driver. Takes a filename and + processes the curve, if possible. + + fileName - The file to process. +*****************************************/ +int ConvertNikonCurveData(char *inFileName, char *outFileName, + unsigned int samplingRes, unsigned int outputRes) +{ + //Load the curve data from the ncv/ntc file + NikonData data; + char tmpstr[1024]; + + if ( samplingRes <= 1 || outputRes <= 1 || samplingRes > MAX_RESOLUTION + || outputRes > MAX_RESOLUTION ) + { + nc_message(NC_SET_ERROR, "Error, sampling and output resolution" + "must be 1 <= res <= %u\n", MAX_RESOLUTION); + return NC_ERROR; + } + + //loads all the curve data. Does not allocate sample arrays. + if (LoadNikonData(inFileName, &data) != NC_SUCCESS) + { + return NC_ERROR; + } + + CurveSample *sample = CurveSampleInit(samplingRes, outputRes); + + //Cycle through all curves + int i; + for (i = 0; i < NUM_CURVE_TYPES; i++) + { + //Populates the samples array for the given curve + if (SampleToCameraCurve( &data.curves[i], sample) != NC_SUCCESS) + { + CurveSampleFree(sample); + return NC_ERROR; + } + + //rename output files + strncpy(tmpstr, outFileName, 1023); + tmpstr[1023] = '\0'; + //if the name has an extension, attempt to remove it + if (tmpstr[strlen(tmpstr)-4] == '.') + { + tmpstr[strlen(tmpstr)-4] = '\0'; + } + + switch(i) + { + case TONE_CURVE: + strncat(tmpstr, "_TONE.txt", 1023); + break; + + case RED_CURVE: + strncat(tmpstr, "_RED.txt", 1023); + break; + + case GREEN_CURVE: + strncat(tmpstr, "_GREEN.txt", 1023); + break; + + case BLUE_CURVE: + strncat(tmpstr, "_BLUE.txt", 1023); + break; + + default: + //should never get here + break; + } + + //print out curve data + if (SaveSampledNikonCurve(sample, tmpstr) != NC_SUCCESS) + { + CurveSampleFree(sample); + return NC_ERROR; + } + } + + //must be called when finished with a CurveSample structure + CurveSampleFree(sample); + + return NC_SUCCESS; +} + +/***************************************************** +FindTIFFOffset: + Moves the file pointer to the location + indicated by the TAG-TYPE pairing. This is meant just + as a helper function for this code. Uses elsewhere + may be limited. + + file - Nikon File ptr + num_entries - Number of entries to search through + tiff_tag - The tiff tag to match. + tiff_type - The tiff type to match. +*******************************************************/ +int FindTIFFOffset(FILE *file, unsigned short num_entries, unsigned short tiff_tag, unsigned short tiff_type) +{ + unsigned short tag = 0; + unsigned short type = 0; + unsigned int offset = 0; + + int i; + for(i = 0; i < num_entries; i++) + { + //get tag 2 bytes + tag = (fgetc(file)<<8)|fgetc(file); + if (tag == tiff_tag) + { + //get type 2 bytes + type = (fgetc(file)<<8)|fgetc(file); + if (type == tiff_type) //Type for length of field + { + //get length (4 bytes) + offset = (fgetc(file)<<24)|(fgetc(file)<<16)|(fgetc(file)<<8)|fgetc(file); + //get value\offset 4 bytes + offset = (fgetc(file)<<24)|(fgetc(file)<<16)|(fgetc(file)<<8)|fgetc(file); + fseek(file,offset,SEEK_SET); + return 1; //true; + } + } + else + { + //advance to next entry + fseek(file,10,SEEK_CUR); + } + } + return 0; //false; +} + +/******************************************************* +RipNikonNEFData: + Gets Nikon NEF data. For now, this is just the tone + curve data. + + infile - The input file + curve - data structure to hold data in. + sample_p - pointer to the curve sample reference. + can be NULL if curve sample is not needed. +********************************************************/ +int RipNikonNEFData(char *infile, CurveData *data, CurveSample **sample_p) +{ + unsigned short byte_order = 0; + unsigned short num_entries = 0; + unsigned short version = 0; + unsigned int offset = 0; + + //open the file + FILE *file = g_fopen(infile,"rb"); + + //make sure we have a valid file + if (file == NULL) + { + nc_message(NC_SET_ERROR, "Error opening '%s': %s\n", + infile, strerror(errno)); + return NC_ERROR; + } + + //gets the byte order + nc_fread(&byte_order,2,1,file); + byte_order = ShortVal(byte_order); + if (byte_order != 0x4d4d) + { + //Must be in motorola format if it came from a NIKON + nc_message(NC_SET_ERROR, + "NEF file data format is Intel. Data format should be Motorola.\n"); + return NC_ERROR; + } + + //get the version + //nc_fread(&version,2,1,file); + version = (fgetc(file)<<8)|fgetc(file); + if (version != 0x002a) + { + //must be 42 or not a valid TIFF + nc_message(NC_SET_ERROR, + "NEF file version is %u. Version should be 42.\n",version); + return NC_ERROR; + } + + //get offset to first IFD + offset = (fgetc(file)<<24)|(fgetc(file)<<16)|(fgetc(file)<<8)|fgetc(file); + //go to the IFD + fseek(file,offset,SEEK_SET); + //get number of entries + num_entries = (fgetc(file)<<8)|fgetc(file); + + //move file pointer to exif offset + if (!FindTIFFOffset(file,num_entries,TIFF_TAG_EXIF_OFFSET,TIFF_TYPE_LONG)) + { + nc_message(NC_SET_ERROR, + "NEF data entry could not be found with tag %u and type %u.\n", + TIFF_TAG_EXIF_OFFSET, TIFF_TYPE_LONG); + return NC_ERROR; + } + + //get number of entries + num_entries = (fgetc(file)<<8)|fgetc(file); + + //move file pointer to maker note offset + if (!FindTIFFOffset(file,num_entries,TIFF_TAG_MAKER_NOTE_OFFSET,TIFF_TYPE_UNDEFINED)) + { + nc_message(NC_SET_ERROR, + "NEF data entry could not be found with tag %u and type %u.\n", + TIFF_TAG_MAKER_NOTE_OFFSET, TIFF_TYPE_UNDEFINED); + return NC_ERROR; + } + + ////////////////////////////////////////////////////////////////////////// + //NOTE: At this point, this section of the file acts almost like another + // file header. Skip the first bytes, (which just say nikon with a + // few bytes at the end. Offsets from here on in are from the start + // of this section, not the start of the file. + ////////////////////////////////////////////////////////////////////////// + + //Check the name. If it isn't Nikon then we can't do anything with this file. + char name[6]; + nc_fread(name,6,1,file); + if (strcmp(name,"Nikon") != 0) + { + nc_message(NC_SET_ERROR, + "NEF string identifier is %s. Should be: Nikon.\n",name); + return NC_ERROR; + } + fseek(file,4,SEEK_CUR); + + //save the current file location, as all other offsets for this section run off this. + unsigned long pos = ftell(file); + + //get byte order (use a regular fread) + nc_fread(&byte_order,2,1,file); + byte_order = ShortVal(byte_order); + if (byte_order != 0x4d4d) + { + //Must be in motorola format or not from a Nikon + nc_message(NC_SET_ERROR, + "NEF secondary file data format is Intel. " + "Data format should be Motorola.\n"); + return NC_ERROR; + } + + //get version + version = (fgetc(file)<<8)|fgetc(file); + if (version != 0x002a) + { + nc_message(NC_SET_ERROR, + "NEF secondary file version is %u. Version should be 42.\n", + version); + return NC_ERROR; + } + + //get offset to first IFD + offset = (fgetc(file)<<24)| (fgetc(file)<<16)|(fgetc(file)<<8)|fgetc(file); + //go to the IFD (these offsets are NOT from the start of the file, + //just the start of the section). + fseek(file,pos+offset,SEEK_SET); + //get number of entries + num_entries = (fgetc(file)<<8)|fgetc(file); + + //move file position to tone curve data + if (!FindTIFFOffset(file,num_entries,TIFF_TAG_CURVE_OFFSET,TIFF_TYPE_UNDEFINED)) + { + nc_message(NC_SET_ERROR, + "NEF data entry could not be found with tag %u and type %u.\n", + TIFF_TAG_CURVE_OFFSET, TIFF_TYPE_UNDEFINED); + return NC_ERROR; + } + + offset = ftell(file); + return RipNikonNEFCurve(file, offset+pos, data, sample_p); +} + + +/******************************************************* +RipNikonNEFCurve: + The actual retriever for the curve data from the NEF + file. + + file - The input file. + infile - Offset to retrieve the data + curve - data structure to hold curve in. + sample_p - pointer to the curve sample reference. + can be NULL if curve sample is not needed. +********************************************************/ +int RipNikonNEFCurve(FILE *file, int offset, CurveData *data, + CurveSample **sample_p) +{ + int i; + + //seek to the offset of the data. Skip first two bytes (section isn't needed). + fseek(file, offset+2, SEEK_SET); + + memset(data,0,sizeof(CurveData)); + ///////////////////////////////////////////////// + //GET CURVE DATA + ///////////////////////////////////////////////// + //get box data and gamma + data->m_min_x = (double)fgetc(file)/255.0; + data->m_max_x = (double)fgetc(file)/255.0; + data->m_min_y = (double)fgetc(file)/255.0; + data->m_max_y = (double)fgetc(file)/255.0; + //16-bit fixed point. + data->m_gamma = (double)fgetc(file) + ((double)fgetc(file)/256.0); + + //DEBUG_PRINT("DEBUG: NEF SECTION SIZE -> %u\n",data->section_size); + DEBUG_PRINT("DEBUG: NEF X MIN -> %e\n",data->m_min_x); + DEBUG_PRINT("DEBUG: NEF X MAX -> %e\n",data->m_max_x); + DEBUG_PRINT("DEBUG: NEF Y MIN -> %e\n",data->m_min_y); + DEBUG_PRINT("DEBUG: NEF Y MAX -> %e\n",data->m_max_y); + //DEBUG_PRINT("DEBUG: NEF GAMMA (16-bit fixed point) -> %e\n",(data->m_gamma>>8)+(data->m_gamma&0x00ff)/256.0); + DEBUG_PRINT("DEBUG: NEF GAMMA -> %e\n",data->m_gamma); + // It seems that if there is no curve then the 62 bytes in the buffer + // are either all 0x00 (D70) or 0xFF (D2H). + // We therefore switch these values with the default values. + if (data->m_min_x==1.0) { + data->m_min_x = 0.0; + DEBUG_PRINT("DEBUG: NEF X MIN -> %e (changed)\n",data->m_min_x); + } + if (data->m_max_x==0.0) { + data->m_max_x = 1.0; + DEBUG_PRINT("DEBUG: NEF X MAX -> %e (changed)\n",data->m_max_x); + } + if (data->m_min_y==1.0) { + data->m_min_y = 0.0; + DEBUG_PRINT("DEBUG: NEF Y MIN -> %e (changed)\n",data->m_min_y); + } + if (data->m_max_y==0.0) { + data->m_max_y = 1.0; + DEBUG_PRINT("DEBUG: NEF Y MAX -> %e (changed)\n",data->m_max_y); + } + if (data->m_gamma==0.0 || data->m_gamma==255.0+255.0/256.0) { + data->m_gamma = 1.0; + DEBUG_PRINT("DEBUG: NEF GAMMA -> %e (changed)\n",data->m_gamma); + } + + //get number of anchor points (there should be at least 2 + nc_fread(&data->m_numAnchors,1,1,file); + DEBUG_PRINT("DEBUG: NEF NUMBER OF ANCHORS -> %u\n",data->m_numAnchors); + if (data->m_numAnchors==255) { + data->m_numAnchors = 0; + DEBUG_PRINT("DEBUG: NEF NUMBER OF ANCHORS -> %u (changed)\n", + data->m_numAnchors); + } + if (data->m_numAnchors > NIKON_MAX_ANCHORS) { + data->m_numAnchors = NIKON_MAX_ANCHORS; + DEBUG_PRINT("DEBUG: NEF NUMBER OF ANCHORS -> %u (changed)\n", + data->m_numAnchors); + } + + //convert data to doubles + for(i = 0; i < data->m_numAnchors; i++) + { + //get anchor points + data->m_anchors[i].x = (double)fgetc(file)/255.0; + data->m_anchors[i].y = (double)fgetc(file)/255.0; + } + + //The total number of points possible is 25 (50 bytes). + //At this point we subtract the number of bytes read for points from the max (50+1) + fseek(file,(51 - data->m_numAnchors*2),SEEK_CUR); + + //get data (always 4096 entries, 1 byte apiece) + DEBUG_PRINT("DEBUG: NEF data OFFSET -> %ld\n",ftell(file)); + + if (sample_p!=NULL) + { + // Sampling res is always 4096, and output res is alway 256 + *sample_p = CurveSampleInit(4096, 256); + + //get the samples + for(i = 0; i < 4096; i++) + { + (*sample_p)->m_Samples[i] = (unsigned int)fgetc(file); + } + } + + return NC_SUCCESS; +} + +/******************************* +main: + Uh....no comment. :) +********************************/ +#ifdef _STAND_ALONE_ + +int main(int argc, char* argv[]) +{ + //make sure we can continue processing + if (ProcessArgs(argc,argv) == NC_SUCCESS) + { + + //if we are in NEF mode, rip the curve out of the RAW file + if (program_mode == NEF_MODE) + { + NikonData data; + + //intiialze the structure to zero + memset(&data,0,sizeof(NikonData)); + + if (RipNikonNEFData(nikonFilename, &data.curves[TONE_CURVE], NULL) + != NC_SUCCESS) + { + return NC_ERROR; + } + + CurveSample *sample = CurveSampleInit(65536, 256); + + if (CurveDataSample(&data.curves[TONE_CURVE], sample) + != NC_SUCCESS) + { + CurveSampleFree(sample); + return NC_ERROR; + } + + if (SaveSampledNikonCurve(sample, exportFilename) != NC_SUCCESS) + { + CurveSampleFree(sample); + return NC_ERROR; + } + + if (SaveNikonCurveFile(&data.curves[TONE_CURVE], TONE_CURVE, + "outcurve.ncv",NCV_FILE, NIKON_VERSION_4_1)) + { + CurveSampleFree(sample); + return NC_ERROR; + } + + //This can also be used + if (SaveNikonDataFile(&data,"outcurve2.ncv",NCV_FILE, NIKON_VERSION_4_1)) + { + CurveSampleFree(sample); + return NC_ERROR; + } + + CurveSampleFree(sample); + } + //else, process a nikon curve file + else + { + //do the deed + ConvertNikonCurveData(nikonFilename,exportFilename, + standalone_samplingRes, standalone_outputRes); + } + } + return NC_SUCCESS; +} +#endif diff --git a/src/common/nikon_curve.h b/src/common/nikon_curve.h new file mode 100644 index 000000000000..99f27949c9d1 --- /dev/null +++ b/src/common/nikon_curve.h @@ -0,0 +1,717 @@ +/*************************************************** + nikon_curve.h - read Nikon NTC/NCV files + + Copyright 2004-2008 by Shawn Freeman, Udi Fuchs + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +****************************************************/ + +/*************************************************** + + This program reads in a Nikon NTC/NCV file, + interperates it's tone curve, and writes out a + simple ascii file containing a table of interpolation + values. + + You'll note that this has been written in way that can be used in a standalone program or + incorporated into another program. You can use the functions seperately or + you can just call ConvertNikonCurveData with an input and output file. + + I've tried to document the code as clearly as possible. Let me know if you + have any problems! + + Thanks goes out to Udi Fuchs for wanting to incorporate nikon curve loading into his program. + This will make GIMP just that much better. :) + + NOTES: + 08/06/2005 (Shawn Freeman) + The last endian fix would not have worked correctly. The new fix should correct the + endianess problem. + + The fix uses a new function DetermineEndianess to assign function pointers the correct + swapping function based on the endianess of the machine. + + For programs that use this code on big endian machines, it is important that this function + be called. So for now, each function that reads or writes data to a file will call this + function. + + + 07/27/2005 (Shawn Freeman) + Someone discovered the dreaded endianess bug. NTC and NCV files are written in little-endian + format, so when a Mac (or other big endian machine) tries to read in the file it blows up. + + There doesn't appear to be a way to determine the endianess of a machine a runtime (at + least not in an ANSI compliant way). The solution? Custom read and write functions that + can handle the endianess. + + The ENDIANESS define can be set to either LITTLE_ENDIAN (compiles code for little endian + machines), BIG_ENDIAN (compiles code for big endian machines), or UNKNOWN_ENDIAN (compiles + code that dynamically determines endianess and runs appropriate code). + + Performance isn't much of an issues since files are small, but it is more efficient + to compile with the endianess set than to leave it at unknown. + + Unfortunately, I don't have a way to test this code. But it should do the trick. + + 06/20/2005 (Udi Fuchs) + Added the function CurveDataReset() + + 06/14/2005 (Udi Fuchs) + Added the function CurveDataSetPoint() + + 06/13/2005 (Shawn Freeman) + Added a couple of explicit casts to keep some compilers from complaining. + At this point, I'd say this code is decent enough to classified as 1.0. Right now + there are no outstanding bugs left that I am aware of. The code has been fairly well + documented and has a significant amount of error checking built in. + + There are still some experimental code in here that could be removed, along with options + for compiling on differnt compilers. + + I don't have as much time as I used to have, due to a new baby and everything.But + I should still have time to do some tweaks, features, and fixes if necessary. + + Enjoy! + + 06/10/2005 (Udi Fuchs) + Removed the name "Nikon" from general structures/functions + NikonAnchorPoint -> CurveAnchorPoint + NikonCurve -> CurveData + SampleNikonCurve() -> CurveDataSample() + FreeNikonCurve() -> CurveSampleFree() + Moved the sampling data to the CurveSample structure, now nikon_curve + and UFRaw can use the sampel CurveData structure. + Removed unused functions RemoveRedundantData(), ResampleNEFCurve() + Curve sampling changed - the curve before first point and after + the last point is flattened. + + 04/16/2005 Removed malloc calls and replaced them with fixed arrays. The Nikon camera + does not support any more than 20 anchors for their curve. This speeds up the + code a little bit. + + This version also supports the latest version of the NTC/NCV file format. + + 03/18/2005 The last remaining incompatibility between MSVC and gcc: + + #define vsnprintf _vsnprintf + + Other Fixes: + 1. Fixed a bug that was incorrectly writing out minimum y-axis values for color curves. + + 03/12/2005 I found some additional bugs when writing out ntc/ncv files that would cause the Nikon + software to crash. The problem was two-fold. + + First, all four curves in the file must contain some information. The SaveNikonCurveFile now + checks to make sure that a curve has good data. If it doesn't, then defaults are written in their + place. + + Why would a curve contain incorrect information? If you're only working with a tone curve in a NikonData + structure, the color curves would not contain any information. The save function automatically corrects + this. + + Second, the version number. Right now, the only version this code supports is Nikon 4.1. So while the + save function takes the version as a paramter, internally it is ignored and forced to 4.1. + + A second version of the SaveNikonCurveFile function now takes a single curve as an argument and uses + defaults for the rest of the curves in the file. The function allows you to specify which curve your + saving (TONE_CURVE, RED_CURVE, GREEN_CURVE, or BLUE_CURVE). Typically, the curve would be tone. + + + 03/11/2005 Udi added some debugging code for use with UFRaw. Unfortunately, the debugging code uses + macros with variable arguments, which are not supported by all compilers (namely MSVC). + I added the following: + + __MSVC__ : Define this if you are compiling with MSVC. This enables code that allows + the program to still print out debug information, though not quite as robust + as with using gcc. + + __WITH_UFRAW__ : Define this if this code is compiling with UFRaw, This allows for connecting + out to the UFRaw error handler. + + In regards to these flags, the code has been changed around a little bit. I also moved some code + to more "appropriate" locations, in keeping with the layout of these files. + + I also renamed the SaveNikonCurve function to SaveNikonCurveFile for clarity. I also fixed a bug + in the function to handle NikonData structures with only one used curve. This allows extracting + the curve from an NEF file and saving it to an NTC/NCV file. This would also allow a curve editor + to write out files successfully, even if they only use tone curves. + + 03/05/2005 Added a fix to allow SampleNikonCurve to be called more than once so that a programmer + can resample the curve. This would be used when extracting a curve from an NEF file + and rebuilding the NTC/NCV file curve from the data. + + Also added a fix that allows for curves with no anchors, just box coordinates. + + The ResampleCurve function has been renamed to ResampleNEFCurve. This is to avoid confusion. + THIS FUNCTION SHOULD NOT BE USED FOR STANDARD NTC/NCV/NEF CURVE GENERATION. This function is + only useful for building a smooth curve from the 4096 byte tone curve table in an NEF file. + This function may end up going away if it's not found useful. + + 03/04/2005 Added a new function called ResampleCurve that allows the programmer to resample curves + that are contained in a NikonCurve structure. A cubic spline interpolator is used in the + resampling for the smoothest possible curve. This functions was added at the request of + Udi Fuchs to allow UFRaw to read in and resample curves stored in NEF files. + + A word of caution when using this function. While the function stores all of it's results + in the passed in curve structure, those results may not be compatible with the camera and/or + the NTC/NCV files. Specifically, this function will most likely contain anchor counts much + greater than 20, which is the maximum. It is NOT RECOMMENDED that you try and save out to + NTC/NCV format after running this function unless you're absolutely sure you know what you + are doing. + + With that out of the way, this function is most useful to create a smoother curve than what + comes from the camera and the NTC/NCV curve files. + + 02/09/2005 Found a fixed a subtle, yet glaring bug in the curve sampling functions. The sampling + functions were not obeying the limits set in the ncv/ntc files. Loading one of these + files that contained any start and end points other than 0 and 1 would result in + the code sampling from the extrapolated parts of the spline curve instead of the + interpolated curve. This actually resulted in a crash in UFRaw, since it was returning + huge numbers as a result of signed to unsigned conversion. + + 02/06/2005 Finally figured out the mathematical relationship (or a close approximation) between + the Nikon curves and curves imbedded in the NEF files. General form is: + + x = a*x + b*ln(cx+1)/ln(dx+2) + + I've found this gives a very good match for the curves. The new function to use to get + this capability is SampleToCameraCurve. This does a standard curve sample then runs the + value through the above transform. Quite useful if you want to force-embed a particular + curve into a batch of NEF files. + + + 02/01/2005 Fixed some issues to better operate with UFRaw. Removed macros and replaced with + with byte reading and shifting (to avoid including windows crud). + + The function RipNEFCurve now uses the standard Nikon Curve structure. The NEFData + structure is now obsolete, along with the save and free functions (Removed). + + 01/29/2005 Added support for getting the tone curve data out of an NEF file. This will generate + a file similar to the other curve data files generated by this progrom in standalone + mode. + + A new command line option has been added to specify NEF processing (-nef filename). The + NEF capabilities have only been tested with Nikon D70 NEF's. + + 01/14/2005 Fixed some bugs. The static data declarations in the header were not declared + static, resulting in errors when trying to compile into another program. + + Added a function to remove redundant calculated curve data and placing it in a + an allocated array. This should help facilitate a no diska access interface to UFRaw. + + Tweaked a few lines that were either not compiling or had warnings when compiled with + gcc. + + Changed the unsigned shorts to unsigned ints just for the sake of it. May be useful if + anyone wants to do bor than 16 bits of sampling. Also means that the program uses a + bit more memory when processing. + + 01/11/2005 Merged changes from Udi. Added the ability to set both sampling resolution and + output resolution in LoadNikonData. For example, you can set the sampling resolution + to 65536 and output resolution to 256. + + I also added a #define (_STAND_ALONE_) for compiling as a standalone program.Undef + if you just want to use the functions. + + Additional command line options are avalable: + -sr Specifies sampling resolution + -or Specifies output resolution + + Some "how to use" info comes up if there are no arguments on the command line. + If these aren't specified, defaults are used. This only applies to standalone. + + 01/08/2005 Removed all code relating to based on Numerical Recipes since it requires + a soul-sucking liscense. The spline calculation code has been replaced by + the freely available code from John Bukardt.I only included the functions from + the library that were necessary to avoid bloat. The code has also been converted + to using standard C. The original code is available from: + + http://www.csit.fsu.edu/~burkardt/cpp_src/spline/spline.html + + New Features: + 1. Heavy error checking, with verbose output. + 2. Supports writing out to NTC and NCV files. + 2. In debug mode, lots of information is displayed to the console. + 3. Supports exporting Tone and RGB curves from NCV and NTC files. + 4. Removed non-ANSI functions, data declarations etc. + 4. Alot of tweaks. + 5. Alot more testing ;) + + The standalone prorgam outputs 4 files, each ending in the name of the curve data + it contains. If you use this like a library, you can make it do whatever you want. + + 01/06/2005 This is version .01. The gamma calculation might not match + what Nikon's Capture does. It only supports NTC files right now. + NCV format will be supported shortly, but will not impact + the structure of this "library". Other additional functionality + will be to support the color curve sections of NTC/NCV files. + + @author: Shawn Freeman 1/06/2005 + @liscense: GNU GPL +****************************************************/ +#ifndef _NIKON_CURVE_H +#define _NIKON_CURVE_H + +#include + +#define NC_VERSION "1.2" +#define NC_DATE "2005-08-06" + +////////////////////////////////////////// +//COMPILER CONTROLS +////////////////////////////////////////// +//Undef this if you don't want to use the stand-alone program +//This is mainly for debugging purposes +//#define _STAND_ALONE_ + +//Define this if you are using Microsoft Visual C++. This enables code to +//deal with the fact the MSVC does not support variable argument macros. +//#define __MSVC__ + +//the only remaining incompatibility between MSVC and gcc +#ifdef __MSVC__ + #define vsnprintf _vsnprintf +#endif + +//Define this if using with UFRaw +//#define __WITH_UFRAW__ + +//Flags used to determine what file we're trying to process. +//Should only be used in standalone mode. +#ifdef _STAND_ALONE_ + +#define CURVE_MODE 0 +#define NEF_MODE 1 + +#endif + + +/******************************************************************************* +Information regarding the file format. + +Section Headers: + +Order of Box Data: Left x, Right x, Midpoint x (gamma), Bottom y, Top y + +Order of Anchor Data: Start x, Start y, Anchor 1 x, Anchor 1 y, ... , End x, End y + +Anchor Point Data: This is aligned on 8 byte boundries. However, the section must + end on a 16 byte boundary, which means an 8 byte pad is added. +********************************************************************************/ + +//DEFINES FOR WRITING OUT DATA (for ntc/ncv files) +#define NCV_HEADER_SIZE 0x3E //Combined header length for an NCV file +#define NCV_SECOND_FILE_SIZE_OFFSET 0x3F //4 bytes (int). File size - NCV_header +#define NCV_UNKNOWN_HEADER_DATA 0x002 //2 bytes. (?) +#define NCV_SECOND_HEADER_LENGTH 23 +#define NCV_FILE_TERMINATOR_LENGTH 23 + +#define NTC_FILE_HEADER_LENGTH 0x10 //16 bytes. Doesn't change +//This seemed to change when Nikon released an updated capture program +//from 4.1 to 4.2. This may be an int but not sure. +#define NCV_PATCH_OFFSET 0x3D //2 bytes(?) +#define NTC_PATCH_OFFSET 0x10 //2 bytes(?) +#define FILE_SIZE_OFFSET 0x12 //4 bytes (int). File size - header. +#define NTC_VERSION_OFFSET 0x16 //4 bytes (int).(?) + //9 byte pad(?) + //16 bytes. Another section header goes here. + +//From here down repeats for every section +#define NTC_SECTION_TYPE_OFFSET 0x00 //4 bytes (int) (0,1,2,3) + +#define NTC_UNKNOWN 0x05 //2 bytes. Doesn't change but not sure what they do (0x03ff) +#define NTC_UNKNOWN_DATA 0x3FF // + +#define NTC_RED_COMPONENT_OFFSET 0x08 //4 bytes (int) (0-255) +#define NTC_GREEN_COMPONENT_OFFSET 0x0C //4 bytes (int) (0-255) +#define NTC_BLUE_COMPONENT_OFFSET 0x0F //4 bytes (int) (0-255) + //12 byte pad all zeros(align to 16?) + +#define NTC_RED_WEIGHT_OFFSET 0x1F //4 bytes (int) (0-255) +#define NTC_GREEN_WEIGHT_OFFSET 0x23 //4 bytes (int) (0-255) +#define NTC_BLUE_WEIGHT_OFFSET 0x27 //4 bytes (int) (0-255) + +#define END_ANCHOR_DATA_PAD_LENGTH 0x08 //Always all zeros +#define NTC_SECTION_HEADER_LENGTH 0x10 //Doesn't change + + +//DEFINES FOR READING IN DATA +#define HEADER_SIZE 0x10 //First characters may be unicode Japanese? + +#define NTC_BOX_DATA 0x5C //Start of box data +#define NTC_NUM_ANCHOR_POINTS 0x84 //Number of anchor points plus 2 for start and end points +#define NTC_ANCHOR_DATA_START 0x88 //Beginning of anchor point data + +#define NCV_BOX_DATA 0x89 //Start of box data +#define NCV_NUM_ANCHOR_POINTS 0xB2 //Number of anchor points plus 2 for start and end points +#define NCV_ANCHOR_DATA_START 0xB5 //Beginning of anchor point data + +//array indices to retrive data +#define PATCH_DATA 0 +#define BOX_DATA 1 +#define NUM_ANCHOR_POINTS 2 +#define ANCHOR_DATA 3 + +//Some data sections sizes for calculating offsets +#define NEXT_SECTION_BOX_DATA_OFFSET 0x43 //after the anchor data, this is the offset to + //the beginning of the next section's box data + +#define NUM_POINTS_TO_ANCHOR_OFFSET 0x03 //number of bytes from the number of anchor points + //byte to the start of anchor data. +//Nikon version defines +#define NIKON_VERSION_4_1 0x00000401 +#define NIKON_PATCH_4 0x04ff +#define NIKON_PATCH_5 0x05ff +#define NIKON_MAX_ANCHORS 20 + +//file types +#define NTC_FILE 0 +#define NCV_FILE 1 +#define NUM_FILE_TYPES 2 + +//Curve Types +#define TONE_CURVE 0 +#define RED_CURVE 1 +#define GREEN_CURVE 2 +#define BLUE_CURVE 3 +#define NUM_CURVE_TYPES 4 + +//Maximum resoltuion allowed due to space considerations. +#define MAX_RESOLUTION 65536 + +////////////////////////////// +//NEF/TIFF MACROS AND DEFINES +////////////////////////////// +#define TIFF_TAG_EXIF_OFFSET 34665 +#define TIFF_TAG_MAKER_NOTE_OFFSET 37500 +#define TIFF_TAG_CURVE_OFFSET 140 + +#define TIFF_TYPE_UNDEFINED 7 +#define TIFF_TYPE_LONG 4 + + +//////////////////////// +//////////////////////// +//ERROR HANDLING +//////////////////////// +//////////////////////// +#define NC_SUCCESS 0 +#define NC_ERROR 100 +#define NC_WARNING 104 +#define NC_SET_ERROR 200 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//DATA STRUCTURES +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +/********************************************************** +CurveData: + Structure for the curve data inside a NTC/NCV file. +***********************************************************/ +typedef struct +{ + double x; + double y; +} CurveAnchorPoint; + +typedef struct +{ + char name[80]; + + //Type for this curve + unsigned int m_curveType; + + //Box data + double m_min_x; + double m_max_x; + double m_min_y; + double m_max_y; + double m_gamma; + + //Number of anchor points + unsigned char m_numAnchors; + + //contains a list of anchors, 2 doubles per each point, x-y format + //max is 20 points + CurveAnchorPoint m_anchors[NIKON_MAX_ANCHORS]; + +} CurveData; + +typedef struct +{ + //Number of samples to use for the curve. + unsigned int m_samplingRes; + unsigned int m_outputRes; + + //Sampling array + unsigned short int *m_Samples; // jo: changed to short int to save memory + +} CurveSample; + + +/******************************************** +NikonPoint: + Simple point structure. Used for storing +reduced data from a sampled curve. +*********************************************/ +typedef struct +{ + unsigned int x; + unsigned int y; +} NikonPoint; + +/********************************************* +NikonData: + Overall data structure for Nikon file data +**********************************************/ +typedef struct +{ + //Number of output points + int m_fileType; + unsigned short m_patch_version; + CurveData curves[4]; +} NikonData; + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//FUNCTIONS +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +/************************************************************ +nc_message: + The Nikon Curve message handler. Udi Fuchs created this +to make the error handling consistent across the code. + + code - Message code + format - The message to format + ... - variable arguments +**************************************************************/ + +void nc_message(int code, char *format, ...); +void DEBUG_PRINT(char *format, ...); + +/******************************************************************* + d3_np_fs: + Helper function for calculating and storing tridiagnol matrices. + Cubic spline calculations involve these types of matrices. +*********************************************************************/ +double *d3_np_fs ( int n, double a[], double b[] ); + +/******************************************************************* + spline_cubic_set: + spline_cubic_set gets the second derivatives for the curve to be used in + spline construction + + n = number of control points + t[] = x array + y[] = y array + ibcbeg = initial point condition (see function notes). + ybcbeg = beginning value depending on above flag + ibcend = end point condition (see function notes). + ybcend = end value depending on above flag + + returns the y value at the given tval point +*********************************************************************/ +double *spline_cubic_set ( int n, double t[], double y[], int ibcbeg, + double ybcbeg, int ibcend, double ybcend ); +/******************************************************************* + spline_cubic_val: + spline_cubic_val gets a value from spline curve. + + n = number of control points + t[] = x array + tval = x value you're requesting the data for, can be anywhere on the interval. + y[] = y array + ypp[] = second derivative array calculated by spline_cubic_set + ypval = first derivative value of requested point + yppval = second derivative value of requested point + + returns the y value at the given tval point +*********************************************************************/ +double spline_cubic_val ( int n, double t[], double tval, double y[], + double ypp[], double *ypval, double *yppval ); + + +/********************************************* +LoadNikonData: + Loads a curve from a Nikon ntc or ncv file. + + fileName - The filename. + curve - Pointer to curve struct to hold the data. + resolution - How many data points to sample from the curve +**********************************************/ +int LoadNikonData(char *fileName, NikonData *data); + +/********************************************* +CurveDataSample: + Samples from a spline curve constructed from + the curve data. + + curve - Pointer to curve struct to hold the data. + sample - Pointer to sample struct to hold the data. +**********************************************/ +int CurveDataSample(CurveData *curve, CurveSample *sample); + +/********************************************* + * CurveDataReset: + * Reset curve to straight line but don't touch the curve name. + **********************************************/ +void CurveDataReset(CurveData *curve); + +/********************************************* + * CurveDataIsTrivial: + * Check if the curve is a trivial linear curve. + ***********************************************/ +int CurveDataIsTrivial(CurveData *curve); + +/********************************************* + CurveDataSetPoint: + Change the position of point to the new (x,y) coordinate. + The end-points get a special treatment. When these are moved all the + other points are moved together, keeping their relative position constant. +**********************************************/ +void CurveDataSetPoint(CurveData *curve, int point, double x, double y); + +/**************************************************** +SampleToCameraCurve: + Transforms the curve generated by sampling the + spline interpolator into the curve that is used by + the camera. + + This is a special function. While the function places + no special restrictions on sampling resolution or + output resolution, it should be noted that Nikon D70 + camera curve is 4096 entries of 0-255. + + If you intend on using this function as such, you should + set the sampling resolution and output resolution + accordingly. + + curve - The Nikon curve to sample and transform. +*****************************************************/ +int SampleToCameraCurve(CurveData *curve, CurveSample *sample); + +/************************************************************ +SaveNikonDataFile: + Savess a curve to a Nikon ntc or ncv file. + + data - A NikonData structure containing info of all the curves. + fileName - The filename. + filetype - Indicator for an NCV or NTC file. + version - The version of the Nikon file to write + +NOTE: The only version tested is Nikon 4.1 anything + other than this may result in unpredictable behavior. + For now, the version passed in is ignored and is forced + to 4.1. +**************************************************************/ +int SaveNikonDataFile(NikonData *data, char *outfile, int filetype, int version); + +/************************************************************ +SaveNikonCurveFile: + Saves out curves to a Nikon ntc or ncv file. This function + takes a single curve and uses defaults for the other curves. + Typically, the curve used is the tone curve. + + curve - A NikonCurve structure. This is usually the tone curve + curve_type - The curve type (TONE_CURVE, RED_CURVE, etc.) + fileName - The filename. + filetype - Indicator for an NCV or NTC file. + version - The version of the Nikon file to write + +NOTE: The only version tested is Nikon 4.1 anything + other than this may result in unpredictable behavior. + For now, the version passed in is ignored and is forced + to 4.1. + + This function is just a helper function that allows the user + to just carry around a single curve. +**************************************************************/ +int SaveNikonCurveFile(CurveData *curve, int curve_type, char *outfile, + int filetype, int version); + +/********************************************* +SaveSampledNikonCurve: + Saves a sampling from a curve to text file to + be processed by UFRaw. + + sample - Pointer to the sampled curve. + fileName - The filename. +**********************************************/ +int SaveSampledNikonCurve(CurveSample *sample, char *outfile); + +/***************************************************** +FindTIFFOffset: + Moves the file pointer to the location + indicated by the TAG-TYPE pairing. This is meant just + as a helper function for this code. Uses elsewhere + may be limited. + + file - Nikon File ptr + num_entries - Number of entries to search through + tiff_tag - The tiff tag to match. + tiff_type - The tiff type to match. +*******************************************************/ +int FindTIFFOffset(FILE *file, unsigned short num_entries, unsigned short tiff_tag, + unsigned short tiff_type); + +/******************************************************* +RipNikonNEFData: + Gets Nikon NEF data. For now, this is just the tone + curve data. This is more of a helper function for running + in stand-alone. This function basically finds the correct + file offset, and then calls RipNikonNEFCurve. + + infile - The input file + curve - data structure to hold data in. + sample_p - pointer to the curve sample reference. + can be NULL if curve sample is not needed. +********************************************************/ +int RipNikonNEFData(char *infile, CurveData *data, CurveSample **sample_p); + +/******************************************************* +RipNikonNEFCurve: + The actual retriever for the curve data from the NEF + file. + + file - The input file. + infile - Offset to retrieve the data + curve - data structure to hold curve in. + sample_p - pointer to the curve sample reference. + can be NULL if curve sample is not needed. +********************************************************/ +int RipNikonNEFCurve(FILE *file, int offset, CurveData *data, + CurveSample **sample_p); + +/******************************************************* +CurveSampleInit: + Init and allocate curve sample. +********************************************************/ +CurveSample *CurveSampleInit(unsigned int samplingRes, unsigned int outputRes); + +/******************************************************* +CurveSampleFree: + Frees memory allocated for this curve sample. +********************************************************/ +int CurveSampleFree(CurveSample *sample); + +/**************************************** +ConvertNikonCurveData: + The main driver. Takes a filename and + processes the curve, if possible. + + fileName - The file to process. +*****************************************/ +int ConvertNikonCurveData(char *inFileName, char *outFileName, unsigned int samplingRes, unsigned int outputRes); + +#endif + + diff --git a/src/control/control.c b/src/control/control.c new file mode 100644 index 000000000000..4535a1b375c7 --- /dev/null +++ b/src/control/control.c @@ -0,0 +1,954 @@ +#include "control/control.h" +#include "library/library.h" +#include "develop/develop.h" +#include "common/darktable.h" +#include "gui/gtk.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_QWERTY +#define KEY_LEFT GDK_a +#define KEY_RIGHT GDK_e +#define KEY_UP GDK_comma +#define KEY_DOWN GDK_o +#define KEY_FIT GDK_apostrophe +#define KEY_SWITCH GDK_period +#define KEY_TOGGLE_FULLSCREEN GDK_f +#define KEY_LEAVE_FULLSCREEN GDK_Escape +#else +#define KEY_LEFT GDK_a +#define KEY_RIGHT GDK_d +#define KEY_UP GDK_w +#define KEY_DOWN GDK_s +#define KEY_FIT GDK_q +#define KEY_SWITCH GDK_e +#define KEY_TOGGLE_FULLSCREEN GDK_f +#define KEY_LEAVE_FULLSCREEN GDK_Escape +#endif + +void dt_ctl_settings_init(dt_control_t *s) +{ + // init global defaults. + pthread_mutex_init(&(s->global_mutex), NULL); + pthread_mutex_init(&(s->image_mutex), NULL); + + char *homedir = getenv("HOME"); + snprintf(s->global_settings.dbname, 512, "%s/.darktabledb", homedir); + + s->global_settings.version = DT_VERSION; + + s->global_settings.gui = DT_LIBRARY; + s->global_settings.gui_fullscreen = 0; + // expand everything + // except top/bottm, retract everything but the lib button in lib mode + // retract lib button in dev mode + s->global_settings.gui_top = s->global_settings.gui_bottom = 0; + s->global_settings.gui_left = s->global_settings.gui_right = -1; + s->global_settings.gui_export = + s->global_settings.gui_library = 1<global_settings.gui_navigation = + s->global_settings.gui_history = s->global_settings.gui_histogram = + s->global_settings.gui_tonecurve = s->global_settings.gui_gamma = + s->global_settings.gui_hsb = 1<global_settings.lib_zoom = DT_LIBRARY_MAX_ZOOM; + s->global_settings.lib_zoom_x = 0.0f; + s->global_settings.lib_zoom_y = 0.0f; + s->global_settings.lib_center = 0; + s->global_settings.lib_pan = 0; + s->global_settings.lib_track = 0; + s->global_settings.lib_image_mouse_over_id = -1; + + s->global_settings.dev_closeup = 0; + s->global_settings.dev_zoom_x = 0; + s->global_settings.dev_zoom_y = 0; + s->global_settings.dev_zoom = DT_ZOOM_FIT; + + s->global_settings.dev_export_format = DT_DEV_EXPORT_JPG; + + strncpy(s->global_settings.dev_op, "original", 20); + + s->global_settings.dev_gamma_linear = 0.1; + s->global_settings.dev_gamma_gamma = 0.45; + + s->image_settings.dev_gamma_linear = 0.1; + s->image_settings.dev_gamma_gamma = 0.45; + + s->image_settings.tonecurve_preset = 0; + s->image_settings.tonecurve_x[0] = s->image_settings.tonecurve_y[0] = 0.0; + s->image_settings.tonecurve_x[1] = s->image_settings.tonecurve_y[1] = 0.08; + s->image_settings.tonecurve_x[2] = s->image_settings.tonecurve_y[2] = 0.4;//0.3; + s->image_settings.tonecurve_x[3] = s->image_settings.tonecurve_y[3] = 0.6;//0.7; + s->image_settings.tonecurve_x[4] = s->image_settings.tonecurve_y[4] = 0.92; + s->image_settings.tonecurve_x[5] = s->image_settings.tonecurve_y[5] = 1.0; + + memcpy(&(s->global_defaults), &(s->global_settings), sizeof(dt_ctl_settings_t)); + memcpy(&(s->image_defaults), &(s->image_settings), sizeof(dt_ctl_image_settings_t)); +} + +int dt_control_load_config(dt_control_t *c) +{ + int rc; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select settings from settings", -1, &stmt, NULL); + if(rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) + { + pthread_mutex_lock(&(darktable.control->global_mutex)); + darktable.control->global_settings.version = -1; + const void *set = sqlite3_column_blob(stmt, 0); + int len = sqlite3_column_bytes(stmt, 0); + if(len == sizeof(dt_ctl_settings_t)) memcpy(&(darktable.control->global_settings), set, len); + rc = sqlite3_finalize(stmt); + + if(darktable.control->global_settings.version != DT_VERSION) + { + fprintf(stderr, "[load_config] wrong version %d (should be %d), substituting defaults.\n", darktable.control->global_settings.version, DT_VERSION); + memcpy(&(darktable.control->global_settings), &(darktable.control->global_defaults), sizeof(dt_ctl_settings_t)); + pthread_mutex_unlock(&(darktable.control->global_mutex)); + // revert to matching version: + rc = sqlite3_prepare_v2(darktable.db, "update settings set settings = ?1 where rowid = 1", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_blob(stmt, 1, &(darktable.control->global_defaults), sizeof(dt_ctl_settings_t), SQLITE_STATIC); + rc = sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + DT_CTL_SET_GLOBAL(gui, DT_LIBRARY); + dt_control_restore_gui_settings(DT_LIBRARY); + } + else + { + pthread_mutex_unlock(&(darktable.control->global_mutex)); + DT_CTL_SET_GLOBAL(gui, DT_LIBRARY); + dt_control_restore_gui_settings(DT_LIBRARY); + // TODO: get last from sql query! + // dt_film_roll_open(darktable.library->film, film_id); + // weg: dt_film_roll_import(darktable.library->film, darktable.control->global_settings.lib_last_film); + int width, height; + DT_CTL_GET_GLOBAL(width, gui_w); + DT_CTL_GET_GLOBAL(height, gui_h); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "main_window"); + gtk_window_resize(GTK_WINDOW(widget), width, height); + } + } + else + { // db not yet there, create it + rc = sqlite3_finalize(stmt); + rc = sqlite3_exec(darktable.db, "create table settings (settings blob)", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_exec(darktable.db, "create table film_rolls (id integer primary key, datetime_accessed char(20), folder varchar(1024))", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_exec(darktable.db, "create table images (id integer primary key, film_id integer, width int, height int, filename varchar(256), maker varchar(30), model varchar(30), exposure real, aperture real, iso real, focal_length real, datetime_taken char(20), integer flags, foreign key(film_id) references film_rolls(id))", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_exec(darktable.db, "create table mipmaps (imgid int, level int, data blob, primary key(imgid, level), foreign key(imgid) references images(id))", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_exec(darktable.db, "create table selected_images (imgid integer, foreign key(imgid) references images(id))", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_exec(darktable.db, "create table history (imgid integer, num integer, hash integer, operation char(20), op_params blob, settings blob, foreign key(imgid) references images(id))", NULL, NULL, NULL); + HANDLE_SQLITE_ERR(rc); + + // TODO: - table tags "tag str" "key#" + // TODO: - table frequency tagXtag? + // TODO: - table tag X film_roll + // TODO: - table tag X image + rc = sqlite3_prepare_v2(darktable.db, "insert into settings (settings) values (?1)", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_blob(stmt, 1, &(darktable.control->global_defaults), sizeof(dt_ctl_settings_t), SQLITE_STATIC); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + DT_CTL_SET_GLOBAL(gui, DT_LIBRARY); + dt_control_restore_gui_settings(DT_LIBRARY); + } + return 0; +} + +int dt_control_write_config(dt_control_t *c) +{ + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + dt_control_save_gui_settings(gui); + + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "main_window"); + DT_CTL_SET_GLOBAL(gui_x, widget->allocation.x); + DT_CTL_SET_GLOBAL(gui_y, widget->allocation.y); + DT_CTL_SET_GLOBAL(gui_w, widget->allocation.width); + DT_CTL_SET_GLOBAL(gui_h, widget->allocation.height); + + int rc; + sqlite3_stmt *stmt; + pthread_mutex_lock(&(darktable.control->global_mutex)); + rc = sqlite3_prepare_v2(darktable.db, "update settings set settings = ?1 where rowid = 1", -1, &stmt, NULL); + rc = sqlite3_bind_blob(stmt, 1, &(darktable.control->global_settings), sizeof(dt_ctl_settings_t), SQLITE_STATIC); + rc = sqlite3_step(stmt); + rc = sqlite3_finalize(stmt); + pthread_mutex_unlock(&(darktable.control->global_mutex)); + return 0; +} + +void dt_control_init(dt_control_t *s) +{ + dt_ctl_settings_init(s); + s->progress = 200.0f; + + pthread_cond_init(&s->cond, NULL); + pthread_mutex_init(&s->cond_mutex, NULL); + pthread_mutex_init(&s->queue_mutex, NULL); + + int k; for(k=0;kidle[k] = k; + s->idle_top = DT_CONTROL_MAX_JOBS; + s->queued_top = 0; + // start threads + s->num_threads = DT_CTL_WORKER_RESERVED + 6;//4; // TODO: omp_get procs equiv.! + s->thread = (pthread_t *)malloc(sizeof(pthread_t)*s->num_threads); + s->running = 1; + for(k=0;knum_threads;k++) + pthread_create(s->thread + k, NULL, dt_control_work, s); + for(k=0;kthread_res + k, NULL, dt_control_work_res, s); + s->new_res[k] = 0; + } + s->button_down = 0; +} + +void dt_control_cleanup(dt_control_t *s) +{ + pthread_mutex_lock(&s->cond_mutex); + s->running = 0; + pthread_mutex_unlock(&s->cond_mutex); + pthread_cond_broadcast(&s->cond); + gdk_threads_leave(); + int k; for(k=0;knum_threads;k++) + pthread_join(s->thread[k], NULL); + for(k=0;kthread_res[k], NULL); + gdk_threads_enter(); + pthread_mutex_destroy(&s->queue_mutex); + pthread_mutex_destroy(&s->cond_mutex); +} + +void dt_control_job_init(dt_job_t *j, const char *msg, ...) +{ +#ifdef DT_CONTROL_JOB_DEBUG + va_list ap; + va_start(ap, msg); + vsnprintf(j->description, DT_CONTROL_DESCRIPTION_LEN, msg, ap); + va_end(ap); +#endif +} + +void dt_control_job_print(dt_job_t *j) +{ +#ifdef DT_CONTROL_JOB_DEBUG + dt_print(DT_DEBUG_CONTROL, "%s", j->description); +#endif +} + +int32_t dt_control_run_job_res(dt_control_t *s, int32_t res) +{ + assert(res < DT_CTL_WORKER_RESERVED && res >= 0); + dt_job_t *j = NULL; + pthread_mutex_lock(&s->queue_mutex); + if(s->new_res[res]) j = s->job_res + res; + s->new_res[res] = 0; + pthread_mutex_unlock(&s->queue_mutex); + if(!j) return -1; + + dt_print(DT_DEBUG_CONTROL, "[run_job_res %d] ", (int)pthread_self()); + dt_control_job_print(j); + dt_print(DT_DEBUG_CONTROL, "\n"); + + j->execute(j); + return 0; +} + +int32_t dt_control_run_job(dt_control_t *s) +{ + dt_job_t *j; + int32_t i; + pthread_mutex_lock(&s->queue_mutex); + // dt_print(DT_DEBUG_CONTROL, "[run_job] %d\n", s->queued_top); + if(s->queued_top == 0) + { + pthread_mutex_unlock(&s->queue_mutex); + return -1; + } + i = s->queued[--s->queued_top]; + j = s->job + i; + pthread_mutex_unlock(&s->queue_mutex); + + dt_print(DT_DEBUG_CONTROL, "[run_job %d] ", dt_control_get_threadid()); + dt_control_job_print(j); + dt_print(DT_DEBUG_CONTROL, "\n"); + j->execute(j); + + pthread_mutex_lock(&s->queue_mutex); + assert(s->idle_top < DT_CONTROL_MAX_JOBS); + s->idle[s->idle_top++] = i; + pthread_mutex_unlock(&s->queue_mutex); + return 0; +} + +int32_t dt_control_add_job_res(dt_control_t *s, dt_job_t *job, int32_t res) +{ + // TODO: pthread cancel and restart in tough cases? + pthread_mutex_lock(&s->queue_mutex); + dt_print(DT_DEBUG_CONTROL, "[add_job_res] %d ", res); + dt_control_job_print(job); + dt_print(DT_DEBUG_CONTROL, "\n"); + s->job_res[res] = *job; + s->new_res[res] = 1; + pthread_mutex_unlock(&s->queue_mutex); + pthread_mutex_lock(&s->cond_mutex); + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->cond_mutex); + return 0; +} + +int32_t dt_control_add_job(dt_control_t *s, dt_job_t *job) +{ + int32_t i; + pthread_mutex_lock(&s->queue_mutex); + dt_print(DT_DEBUG_CONTROL, "[add_job] %d ", s->idle_top); + dt_control_job_print(job); + dt_print(DT_DEBUG_CONTROL, "\n"); + if(s->idle_top != 0) + { + i = --s->idle_top; + s->job[s->idle[i]] = *job; + s->queued[s->queued_top++] = s->idle[i]; + pthread_mutex_unlock(&s->queue_mutex); + } + else + { + pthread_mutex_unlock(&s->queue_mutex); + return -1; + } + + // notify workers + pthread_mutex_lock(&s->cond_mutex); + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->cond_mutex); + return 0; +} + +int32_t dt_control_get_threadid() +{ + int32_t threadid = 0; + while(darktable.control->thread[threadid] != pthread_self()) threadid++; + assert(threadid < darktable.control->num_threads); + return threadid; +} + +int32_t dt_control_get_threadid_res() +{ + int32_t threadid = 0; + while(darktable.control->thread_res[threadid] != pthread_self()) threadid++; + assert(threadid < DT_CTL_WORKER_RESERVED); + return threadid; +} + +void *dt_control_work_res(void *ptr) +{ + dt_control_t *s = (dt_control_t *)ptr; + int32_t threadid = dt_control_get_threadid_res(); + while(s->running) + { + // dt_print(DT_DEBUG_CONTROL, "[control_work] %d\n", threadid); + if(dt_control_run_job_res(s, threadid) < 0) + { + // wait for a new job. + int old; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); + pthread_mutex_lock(&s->cond_mutex); + pthread_cond_wait(&s->cond, &s->cond_mutex); + pthread_mutex_unlock(&s->cond_mutex); + pthread_setcancelstate(old, NULL); + } + } + return NULL; +} + +void *dt_control_work(void *ptr) +{ + dt_control_t *s = (dt_control_t *)ptr; + // int32_t threadid = dt_control_get_threadid(); + while(s->running) + { + // dt_print(DT_DEBUG_CONTROL, "[control_work] %d\n", threadid); + if(dt_control_run_job(s) < 0) + { + // wait for a new job. + pthread_mutex_lock(&s->cond_mutex); + pthread_cond_wait(&s->cond, &s->cond_mutex); + pthread_mutex_unlock(&s->cond_mutex); + } + } + return NULL; +} + + +// ================================================================================ +// gui functions: +// ================================================================================ + +void dt_control_configure(int32_t width, int32_t height) +{ + darktable.control->tabborder = fmaxf(10, width/100.0); + // float tb = darktable.control->tabborder; + // re-configure all components: + // dt_dev_configure(darktable.develop, width - 2*tb, height - 2*tb); +} + +void *dt_control_expose(void *voidptr) +{ + darktable.control->gui_thread = pthread_self(); + while(1) + { + int width, height, pointerx, pointery; + // gdk_threads_enter(); + gdk_drawable_get_size(darktable.gui->pixmap, &width, &height); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_get_pointer(widget, &pointerx, &pointery); + // gdk_threads_leave(); + + //create a gtk-independant surface to draw on + cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(cst); + + // TODO: control_expose: only redraw the part not overlapped by temporary control panel show! + // + float tb = fmaxf(10, width/100.0); + darktable.control->tabborder = tb; + darktable.control->width = width; + darktable.control->height = height; + + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_rectangle(cr, 0, 0, width, height); + cairo_rectangle(cr, tb, tb, width-2*tb, height-2*tb); + cairo_fill(cr); + + cairo_save(cr); + cairo_translate(cr, tb, tb); + cairo_rectangle(cr, 0, 0, width - 2*tb, height - 2*tb); + cairo_clip(cr); + cairo_new_path(cr); + switch(darktable.control->global_settings.gui) + { + case DT_LIBRARY: + dt_library_expose(darktable.library, cr, width - 2*tb, height - 2*tb, pointerx-tb, pointery-tb); + break; + case DT_DEVELOP: + dt_dev_expose(darktable.develop, cr, width - 2*tb, height - 2*tb); + break; + default: + break; + } + cairo_restore(cr); + + // draw gui arrows. + cairo_set_source_rgb (cr, .6, .6, .6); + + cairo_move_to (cr, 0.0, height/2-tb); + cairo_rel_line_to (cr, 0.0, 2*tb); + cairo_rel_line_to (cr, tb, -tb); + cairo_close_path (cr); + cairo_fill(cr); + + cairo_move_to (cr, width, height/2-tb); + cairo_rel_line_to (cr, 0.0, 2*tb); + cairo_rel_line_to (cr, -tb, -tb); + cairo_close_path (cr); + cairo_fill(cr); + + cairo_move_to (cr, width/2-tb, height); + cairo_rel_line_to (cr, 2*tb, 0.0); + cairo_rel_line_to (cr, -tb, -tb); + cairo_close_path (cr); + cairo_fill(cr); + + cairo_move_to (cr, width/2-tb, 0); + cairo_rel_line_to (cr, 2*tb, 0.0); + cairo_rel_line_to (cr, -tb, tb); + cairo_close_path (cr); + cairo_fill(cr); + + // draw status bar, if any + if(darktable.control->progress < 100.0) + { + tb = fmaxf(20, width/40.0); + char num[10]; + cairo_rectangle(cr, width*0.4, height*0.85, width*0.2*darktable.control->progress/100.0f, tb); + cairo_fill(cr); + cairo_set_source_rgb(cr, 0., 0., 0.); + cairo_rectangle(cr, width*0.4, height*0.85, width*0.2, tb); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0.9, 0.9, 0.9); + cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, tb/3); + cairo_move_to (cr, width/2.0-10, height*0.85+2.*tb/3.); + snprintf(num, 10, "%d%%", (int)darktable.control->progress); + cairo_show_text (cr, num); + } + + cairo_destroy(cr); + + // gdk_threads_enter(); + cairo_t *cr_pixmap = gdk_cairo_create(darktable.gui->pixmap); + cairo_set_source_surface (cr_pixmap, cst, 0, 0); + cairo_paint(cr_pixmap); + cairo_destroy(cr_pixmap); + + // GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + // gtk_widget_queue_draw(widget); + // gdk_threads_leave(); + + cairo_surface_destroy(cst); + return NULL; + } + return NULL; +} + +void dt_control_mouse_leave() +{ + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + if(gui == DT_LIBRARY) + { + dt_library_mouse_leave(darktable.library); + } +} + +void dt_control_mouse_moved(double x, double y, int which) +{ + float tb = darktable.control->tabborder; + float wd = darktable.control->width; + float ht = darktable.control->height; + + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + if(x > tb && x < wd-tb && y > tb && y < ht-tb) + { + if(gui == DT_LIBRARY) + { + // fwd to lib or dev + dt_library_mouse_moved(darktable.library, x-tb, y-tb, which); + } + else // DT_DEVELOP + { + if(darktable.control->button_down) + { // depending on dev_zoom, adjust dev_zoom_x/y. + dt_develop_t *dev = darktable.develop; + const int cwd = dev->cache_width, cht = dev->cache_height; + const int iwd = dev->image->width, iht = dev->image->height; + float scale = 1.0f; + dt_dev_zoom_t zoom; + int closeup; + DT_CTL_GET_GLOBAL(zoom, dev_zoom); + DT_CTL_GET_GLOBAL(closeup, dev_closeup); + if(zoom == DT_ZOOM_FIT) return; //scale = fminf(iwd/(float)cwd, iht/(float)cht); + if(closeup) scale = .5f; + if(zoom == DT_ZOOM_FILL) scale = fmaxf(iwd/(float)cwd, iht/(float)cht); + float old_zoom_x, old_zoom_y; + DT_CTL_GET_GLOBAL(old_zoom_x, dev_zoom_x); + DT_CTL_GET_GLOBAL(old_zoom_y, dev_zoom_y); + float zx = old_zoom_x - scale*(x - darktable.control->button_x)/iwd; + float zy = old_zoom_y - scale*(y - darktable.control->button_y)/iht; + dt_dev_check_zoom_bounds(darktable.develop, &zx, &zy, zoom, closeup, NULL, NULL); + DT_CTL_SET_GLOBAL(dev_zoom_x, zx); + DT_CTL_SET_GLOBAL(dev_zoom_y, zy); + darktable.control->button_x = x; + darktable.control->button_y = y; + dt_control_queue_draw(); + } + } + } +} + +void dt_control_button_released(double x, double y, int which, uint32_t state) +{ + darktable.control->button_down = 0; + float tb = darktable.control->tabborder; + // float wd = darktable.control->width; + // float ht = darktable.control->height; + + // always do this, to avoid missing some events. + // if(x > tb && x < wd-tb && y > tb && y < ht-tb) + { + // fwd to lib or dev + dt_library_button_released(darktable.library, x-tb, y-tb, which, state); + } +} + +void dt_ctl_switch_mode() +{ + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + int32_t id; + DT_CTL_GET_GLOBAL(id, lib_image_mouse_over_id); + dt_control_save_gui_settings(gui); + if(gui == DT_DEVELOP) + { + gui = DT_LIBRARY; + dt_control_restore_gui_settings(gui); + dt_dev_leave(); + } + else if(id >= 0) + { + gui = DT_DEVELOP; + dt_control_restore_gui_settings(gui); + DT_CTL_SET_GLOBAL(dev_zoom, DT_ZOOM_FIT); + DT_CTL_SET_GLOBAL(dev_closeup, 0); + dt_dev_enter(); + } + DT_CTL_SET_GLOBAL(gui, gui); +} + +void dt_control_button_pressed(double x, double y, int which, int type, uint32_t state) +{ + darktable.control->button_down = 1; + darktable.control->button_x = x; + darktable.control->button_y = y; + float tb = darktable.control->tabborder; + float wd = darktable.control->width; + float ht = darktable.control->height; + GtkWidget *widget; + + if(x > tb && x < wd-tb && y > tb && y < ht-tb) + { + if(type == GDK_2BUTTON_PRESS) dt_ctl_switch_mode(); + // fwd to lib or dev + // dt_library_button_pressed(darktable.library, (x-tb)/(wd-2.0*tb), (y-tb)/(wd-2.0*tb), which); + else dt_library_button_pressed(darktable.library, x-tb, y-tb, which, state); + } + else if(x < tb) + { + widget = glade_xml_get_widget (darktable.gui->main_window, "left"); + if(GTK_WIDGET_VISIBLE(widget)) gtk_widget_hide(widget); + else gtk_widget_show(widget); + } + else if(x > wd-tb) + { + widget = glade_xml_get_widget (darktable.gui->main_window, "right"); + if(GTK_WIDGET_VISIBLE(widget)) gtk_widget_hide(widget); + else gtk_widget_show(widget); + } + else if(y < tb) + { + widget = glade_xml_get_widget (darktable.gui->main_window, "top"); + if(GTK_WIDGET_VISIBLE(widget)) gtk_widget_hide(widget); + else gtk_widget_show(widget); + } + else if(y > ht-tb) + { + widget = glade_xml_get_widget (darktable.gui->main_window, "bottom"); + if(GTK_WIDGET_VISIBLE(widget)) gtk_widget_hide(widget); + else gtk_widget_show(widget); + } +} + +void dt_control_gui_queue_draw() +{ + if(darktable.control->running) + { + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(widget); + } +} + +void dt_control_queue_draw() +{ + if(darktable.control->running) + { + if(pthread_self() != darktable.control->gui_thread) gdk_threads_enter(); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(widget); + if(pthread_self() != darktable.control->gui_thread) gdk_threads_leave(); + } +} + +void dt_control_restore_gui_settings(dt_ctl_gui_mode_t mode) +{ + int8_t bit; + GtkWidget *widget; + + DT_CTL_GET_GLOBAL(bit, gui_left); + widget = glade_xml_get_widget (darktable.gui->main_window, "left"); + if(bit & (1<main_window, "right"); + if(bit & (1<main_window, "top"); + if(bit & (1<main_window, "bottom"); + if(bit & (1<main_window, "navigation_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "library_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "history_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "histogram_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "tonecurve_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "gamma_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "hsb_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "export_expander"); + gtk_expander_set_expanded(GTK_EXPANDER(widget), (bit & (1<main_window, "left"); + if(GTK_WIDGET_VISIBLE(widget)) bit |= 1<main_window, "right"); + if(GTK_WIDGET_VISIBLE(widget)) bit |= 1<main_window, "bottom"); + if(GTK_WIDGET_VISIBLE(widget)) bit |= 1<main_window, "top"); + if(GTK_WIDGET_VISIBLE(widget)) bit |= 1<main_window, "navigation_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "library_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "history_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "histogram_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "tonecurve_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "gamma_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "hsb_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "export_expander"); + if(gtk_expander_get_expanded(GTK_EXPANDER(widget))) bit |= 1<main_window, "main_window"); + DT_CTL_GET_GLOBAL(fullscreen, gui_fullscreen); + if(fullscreen) gtk_window_unfullscreen(GTK_WINDOW(widget)); + else gtk_window_fullscreen (GTK_WINDOW(widget)); + fullscreen ^= 1; + DT_CTL_SET_GLOBAL(gui_fullscreen, fullscreen); + break; + case KEY_LEAVE_FULLSCREEN: + widget = glade_xml_get_widget (darktable.gui->main_window, "main_window"); + gtk_window_unfullscreen(GTK_WINDOW(widget)); + fullscreen = 0; + DT_CTL_SET_GLOBAL(gui_fullscreen, fullscreen); + break; + case GDK_Tab: + widget = glade_xml_get_widget (darktable.gui->main_window, "left"); + visible = GTK_WIDGET_VISIBLE(widget); + if(visible) gtk_widget_hide(widget); + else gtk_widget_show(widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "right"); + if(visible) gtk_widget_hide(widget); + else gtk_widget_show(widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "bottom"); + if(visible) gtk_widget_hide(widget); + else gtk_widget_show(widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "top"); + if(visible) gtk_widget_hide(widget); + else gtk_widget_show(widget); + break; + default: + break; + } + if(gui == DT_LIBRARY) switch (which) + { +#if 0 + case GDK_Left: case KEY_LEFT: + DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over_i); + if(selected > 0) selected --; + DT_CTL_SET_GLOBAL(lib_image_mouse_over_i, selected); + DT_CTL_SET_GLOBAL(lib_track, 1); + break; + case GDK_Right: case KEY_RIGHT: + DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over_i); + if(selected < DT_LIBRARY_MAX_ZOOM-1) selected ++; + DT_CTL_SET_GLOBAL(lib_image_mouse_over_i, selected); + DT_CTL_SET_GLOBAL(lib_track, 1); + break; + case GDK_Up: case KEY_UP: + DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over_j); + if(selected > 0) selected --; + DT_CTL_SET_GLOBAL(lib_image_mouse_over_j, selected); + DT_CTL_SET_GLOBAL(lib_track, 1); + break; + case GDK_Down: case KEY_DOWN: + DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over_j); + selected ++; + DT_CTL_SET_GLOBAL(lib_image_mouse_over_j, selected); + DT_CTL_SET_GLOBAL(lib_track, 1); + break; +#endif + case GDK_1: + DT_CTL_SET_GLOBAL(lib_zoom, 1); + break; + case KEY_FIT: + DT_CTL_SET_GLOBAL(lib_zoom, DT_LIBRARY_MAX_ZOOM); + DT_CTL_SET_GLOBAL(lib_center, 1); + break; + default: + break; + } + else if(gui == DT_DEVELOP) switch (which) + { + case GDK_1: + DT_CTL_GET_GLOBAL(zoom, dev_zoom); + DT_CTL_GET_GLOBAL(closeup, dev_closeup); + if(zoom == DT_ZOOM_1) closeup ^= 1; + DT_CTL_SET_GLOBAL(dev_closeup, closeup); + DT_CTL_SET_GLOBAL(dev_zoom, DT_ZOOM_1); + break; + case GDK_2: + DT_CTL_SET_GLOBAL(dev_zoom, DT_ZOOM_FILL); + DT_CTL_SET_GLOBAL(dev_zoom_x, 0.0); + DT_CTL_SET_GLOBAL(dev_zoom_y, 0.0); + DT_CTL_SET_GLOBAL(dev_closeup, 0); + break; + case GDK_3: + DT_CTL_SET_GLOBAL(dev_zoom, DT_ZOOM_FIT); + DT_CTL_SET_GLOBAL(dev_closeup, 0); + break; + default: + break; + } + widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(widget); + widget = glade_xml_get_widget (darktable.gui->main_window, "navigation"); + gtk_widget_queue_draw(widget); + return 1; +} + +void dt_control_get_tonecurve(uint16_t *tonecurve, dt_ctl_image_settings_t *settings) +{ + dt_gui_curve_editor_get_curve(&(darktable.gui->tonecurve), tonecurve, settings); +} + +void dt_control_add_history_item(int32_t num, const char *label) +{ + // TODO: add new widget if this happens: + assert(num < 10); + char wdname[20]; + snprintf(wdname, 20, "history_%02d", num); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, wdname); + gtk_widget_show(widget); + gtk_button_set_label(GTK_BUTTON(widget), label); +} + +void dt_control_clear_history_items(int32_t num) +{ + assert(num >= 0 && num <= 10); + char wdname[20]; + for(int k=num+1;k<10;k++) + { + snprintf(wdname, 20, "history_%02d", k); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, wdname); + gtk_widget_hide(widget); + } +} + diff --git a/src/control/control.h b/src/control/control.h new file mode 100644 index 000000000000..4fafb06287e0 --- /dev/null +++ b/src/control/control.h @@ -0,0 +1,124 @@ +#ifndef DT_CONTROL_H +#define DT_CONTROL_H + +#include +#include +#ifdef _OPENMP +# include +#endif + +#include "control/settings.h" +// #include "control/job.def" + +#define DT_CONTROL_MAX_JOBS 100 +#define DT_CONTROL_JOB_DEBUG +#define DT_CONTROL_DESCRIPTION_LEN 256 +// reserved workers +#define DT_CTL_WORKER_RESERVED 6 +#define DT_CTL_WORKER_1 0 // dev load raw +#define DT_CTL_WORKER_2 1 // dev zoom 1 +#define DT_CTL_WORKER_3 2 // dev zoom fill +#define DT_CTL_WORKER_4 3 // dev zoom fit +#define DT_CTL_WORKER_5 4 // dev small prev +#define DT_CTL_WORKER_6 5 // dev prefetch + + +// called from gui +void *dt_control_expose(void *voidptr); +void dt_control_button_pressed(double x, double y, int which, int type, uint32_t state); +void dt_control_button_released(double x, double y, int which, uint32_t state); +void dt_control_mouse_moved(double x, double y, int which); +void dt_control_mouse_leave(); +int dt_control_key_pressed(uint32_t which); +void dt_control_configure(int32_t width, int32_t height); +void dt_control_gui_queue_draw(); + +// called from core +void dt_control_queue_draw(); +void dt_control_get_tonecurve(uint16_t *tonecurve, dt_ctl_image_settings_t *settings); +void dt_control_add_history_item(int32_t num, const char *label); +void dt_control_clear_history_items(int32_t num); + +void dt_control_save_gui_settings(dt_ctl_gui_mode_t mode); +void dt_control_restore_gui_settings(dt_ctl_gui_mode_t mode); + +/** + * smalles unit of work. + */ +struct dt_job_t; +typedef struct dt_job_t +{ + void (*execute)(struct dt_job_t *job); + int32_t param[32]; +#ifdef DT_CONTROL_JOB_DEBUG + char description[DT_CONTROL_DESCRIPTION_LEN]; +#endif +} +dt_job_t; + +void dt_control_job_init(dt_job_t *j, const char *msg, ...); +void dt_control_job_print(dt_job_t *j); + + +/** + * this manages everything time-consuming. + * distributes the jobs on all processors, + * performs scheduling. + */ +typedef struct dt_control_t +{ + // gui related stuff + double tabborder; + int32_t width, height; + float progress; + pthread_t gui_thread; + int button_down; + double button_x, button_y; + + // gui settings + dt_ctl_settings_t global_settings, global_defaults; + dt_ctl_image_settings_t image_settings, image_defaults; + pthread_mutex_t global_mutex, image_mutex; + + // job management + int32_t running; + pthread_mutex_t queue_mutex, cond_mutex; + pthread_cond_t cond; + int32_t num_threads; + pthread_t *thread; + dt_job_t job[DT_CONTROL_MAX_JOBS]; + int32_t idle[DT_CONTROL_MAX_JOBS]; + int32_t queued[DT_CONTROL_MAX_JOBS]; + int32_t idle_top, queued_top; + dt_job_t job_res[DT_CTL_WORKER_RESERVED]; + uint8_t new_res[DT_CTL_WORKER_RESERVED]; + pthread_t thread_res[DT_CTL_WORKER_RESERVED]; +} +dt_control_t; + +void dt_control_init(dt_control_t *s); +void dt_control_cleanup(dt_control_t *s); + +int dt_control_load_config(dt_control_t *c); +int dt_control_write_config(dt_control_t *c); + +int32_t dt_control_run_job(dt_control_t *s); +int32_t dt_control_add_job(dt_control_t *s, dt_job_t *job); +int32_t dt_control_run_job_res(dt_control_t *s, int32_t res); +int32_t dt_control_add_job_res(dt_control_t *s, dt_job_t *job, int32_t res); + +void *dt_control_work(void *ptr); +void *dt_control_work_res(void *ptr); +int32_t dt_control_get_threadid(); +int32_t dt_control_get_threadid_res(); + +static inline int32_t dt_ctl_get_num_procs() +{ +#ifdef _OPENMP + return omp_get_num_procs(); +#else + return 1; +#endif +} + +#endif diff --git a/src/control/jobs.c b/src/control/jobs.c new file mode 100644 index 000000000000..ec0d95897d9d --- /dev/null +++ b/src/control/jobs.c @@ -0,0 +1,87 @@ +#include "control/jobs.h" + +void dt_image_load_job_init(dt_job_t *job, dt_image_t *image, dt_image_buffer_t mip) +{ + job->execute = &dt_image_load_job_run; + dt_image_load_t *t = (dt_image_load_t *)job->param; + t->image = image; + t->mip = mip; + dt_control_job_init(job, "load image %d mip %d", image->id, mip); +} + +void dt_image_load_job_run(dt_job_t *job) +{ + dt_image_load_t *t = (dt_image_load_t *)job->param; + int ret = dt_image_load(t->image, t->mip); + // drop read lock, as this is only speculative async loading. + if(!ret) dt_image_release(t->image, t->mip, 'r'); +} + +void dt_film_import1_init(dt_job_t *job, dt_film_roll_t *film) +{ + job->execute = &dt_film_import1_run; + dt_film_import1_t *t = (dt_film_import1_t *)job->param; + t->film = film; + dt_control_job_init(job, "cache load raw images for preview"); +} + +void dt_film_import1_run(dt_job_t *job) +{ + dt_film_import1_t *t = (dt_film_import1_t *)job->param; + dt_film_import1(t->film); +} + +// ==================== +// develop: +// ==================== + +void dt_dev_raw_load_job_run(dt_job_t *job) +{ + dt_dev_raw_load_t *t = (dt_dev_raw_load_t *)job->param; + dt_dev_raw_load(t->dev, t->image); +} + +void dt_dev_raw_load_job_init(dt_job_t *job, dt_develop_t *dev, dt_image_t *image) +{ + job->execute =&dt_dev_raw_load_job_run; + dt_dev_raw_load_t *t = (dt_dev_raw_load_t *)job->param; + t->dev = dev; + t->image = image; + dt_control_job_init(job, "develop load raw image %s", image->filename); +} + +void dt_dev_cache_load_job_run(dt_job_t *job) +{ + dt_dev_cache_load_t *t = (dt_dev_cache_load_t *)job->param; + dt_dev_cache_load(t->dev, t->stackpos, t->zoom); +} + +void dt_dev_cache_load_job_init(dt_job_t *job, dt_develop_t *dev, int32_t stackpos, dt_dev_zoom_t zoom) +{ + job->execute = &dt_dev_cache_load_job_run; + dt_dev_cache_load_t *t = (dt_dev_cache_load_t *)job->param; + t->dev = dev; + t->stackpos = stackpos; + t->zoom = zoom; + dt_control_job_init(job, "develop load cache history %d for zoom level %d", stackpos, zoom); +} + +void dt_dev_small_cache_load_run(dt_job_t *job) +{ + dt_dev_small_cache_load_t *t = (dt_dev_small_cache_load_t *)job->param; + (void)dt_dev_small_cache_load(t->dev); +} + +void dt_dev_small_cache_load_init(dt_job_t *job, dt_develop_t *dev) +{ + job->execute = &dt_dev_small_cache_load_run; + dt_dev_small_cache_load_t *t = (dt_dev_small_cache_load_t *)job->param; + t->dev = dev; + dt_control_job_init(job, "develop load small cache"); +} + +void dt_dev_export_init(dt_job_t *job) +{ + job->execute = &dt_dev_export; + dt_control_job_init(job, "develop export selected"); +} diff --git a/src/control/jobs.h b/src/control/jobs.h new file mode 100644 index 000000000000..1eefc50e3f5b --- /dev/null +++ b/src/control/jobs.h @@ -0,0 +1,62 @@ +#ifndef DT_CONTROL_JOBS_H +#define DT_CONTROL_JOBS_H + +#include "common/image.h" +#include "control/control.h" +#include "library/library.h" +#include "develop/develop.h" + +typedef struct dt_image_load_t +{ + dt_image_t *image; + dt_image_buffer_t mip; +} +dt_image_load_t; + +void dt_image_load_job_run(dt_job_t *job); +void dt_image_load_job_init(dt_job_t *job, dt_image_t *image, dt_image_buffer_t mip); + +typedef struct dt_film_import1_t +{ + dt_film_roll_t *film; +} +dt_film_import1_t; + +void dt_film_import1_run(dt_job_t *job); +void dt_film_import1_init(dt_job_t *job, dt_film_roll_t *film); + + +typedef struct dt_dev_raw_load_t +{ + dt_develop_t *dev; + dt_image_t *image; +} +dt_dev_raw_load_t; + +void dt_dev_raw_load_job_run(dt_job_t *job); +void dt_dev_raw_load_job_init(dt_job_t *job, dt_develop_t *dev, dt_image_t *image); + +enum dt_dev_zoom_t; +typedef struct dt_dev_cache_load_t +{ + dt_develop_t *dev; + int32_t stackpos; + enum dt_dev_zoom_t zoom; +} +dt_dev_cache_load_t; + +void dt_dev_cache_load_job_run(dt_job_t *job); +void dt_dev_cache_load_job_init(dt_job_t *job, dt_develop_t *dev, int32_t stackpos, enum dt_dev_zoom_t zoom); + +typedef struct dt_dev_small_cache_load_t +{ + dt_develop_t *dev; +} +dt_dev_small_cache_load_t; + +void dt_dev_small_cache_load_run(dt_job_t *job); +void dt_dev_small_cache_load_init(dt_job_t *job, dt_develop_t *dev); + +void dt_dev_export_init(dt_job_t *job); + +#endif diff --git a/src/control/settings.h b/src/control/settings.h new file mode 100644 index 000000000000..12ee24717ba4 --- /dev/null +++ b/src/control/settings.h @@ -0,0 +1,120 @@ +#ifndef DT_CTL_SETTINGS_H +#define DT_CTL_SETTINGS_H + +#include + +// thread-safe interface between core and gui. +// also serves to store user settings. + +#define DT_CTL_GET_GLOBAL(x, attrib) \ + pthread_mutex_lock(&(darktable.control->global_mutex)); \ + x = darktable.control->global_settings.attrib; \ + pthread_mutex_unlock(&(darktable.control->global_mutex)) + +#define DT_CTL_SET_GLOBAL(attrib, x) \ + pthread_mutex_lock(&(darktable.control->global_mutex)); \ + darktable.control->global_settings.attrib = x; \ + pthread_mutex_unlock(&(darktable.control->global_mutex)) + +#define DT_CTL_GET_GLOBAL_STR(x, attrib, n) \ + pthread_mutex_lock(&(darktable.control->global_mutex)); \ + strncpy(x, darktable.control->global_settings.attrib, n); \ + pthread_mutex_unlock(&(darktable.control->global_mutex)) + +#define DT_CTL_SET_GLOBAL_STR(attrib, x, n) \ + pthread_mutex_lock(&(darktable.control->global_mutex)); \ + strncpy(darktable.control->global_settings.attrib, x, n); \ + pthread_mutex_unlock(&(darktable.control->global_mutex)) + +#define DT_CTL_GET_IMAGE(x, attrib) \ + pthread_mutex_lock(&(darktable.control->image_mutex)); \ + x = darktable.control->image_settings.attrib; \ + pthread_mutex_unlock(&(darktable.control->image_mutex)) + +#define DT_CTL_SET_IMAGE(attrib, x) \ + pthread_mutex_lock(&(darktable.control->image_mutex)); \ + darktable.control->image_settings.attrib = x; \ + pthread_mutex_unlock(&(darktable.control->image_mutex)) + +typedef enum dt_ctl_gui_mode_t +{ + DT_LIBRARY = 1, + DT_DEVELOP = 2 +} +dt_ctl_gui_mode_t; + +typedef enum dt_dev_zoom_t +{ + DT_ZOOM_FIT = 0, + DT_ZOOM_FILL = 1, + DT_ZOOM_1 = 2 +} +dt_dev_zoom_t; + +typedef char dt_dev_operation_t[20]; + +#define DEV_NUM_OP_PARAMS 10 + +typedef union dt_dev_operation_params_t +{ + int32_t i[DEV_NUM_OP_PARAMS]; + float f[DEV_NUM_OP_PARAMS]; +} +dt_dev_operation_params_t; + +typedef enum dt_dev_export_format_t +{ + DT_DEV_EXPORT_JPG, + DT_DEV_EXPORT_PNG, + DT_DEV_EXPORT_PPM16, + DT_DEV_EXPORT_PFM +} +dt_dev_export_format_t; + +typedef struct dt_ctl_settings_t +{ + // global + int32_t version; + char dbname[512]; + + // develop + float dev_gamma_linear, dev_gamma_gamma; + dt_dev_operation_t dev_op; // currently active op + dt_dev_operation_params_t dev_op_params; // params for currently active op + dt_dev_export_format_t dev_export_format; + + // library + float lib_zoom, lib_zoom_x, lib_zoom_y; + int32_t lib_center, lib_pan, lib_track; + int32_t lib_image_mouse_over_id; + + // navigation + float dev_zoom_x, dev_zoom_y; + dt_dev_zoom_t dev_zoom; + int dev_closeup; + + // gui + // widget settings for lib/dev view, fullscreen, window geometry + dt_ctl_gui_mode_t gui; + int32_t gui_x, gui_y, gui_w, gui_h; + uint8_t gui_fullscreen; + uint8_t gui_left, gui_right, gui_bottom, gui_top; + uint8_t gui_navigation, gui_library, gui_history; + uint8_t gui_histogram, gui_tonecurve, gui_gamma, gui_hsb, gui_export; +} +dt_ctl_settings_t; + +enum dt_dev_zoom_t; +// gui controlled per-image parameters +typedef struct dt_ctl_image_settings_t +{ + // gamma correction + float dev_gamma_linear, dev_gamma_gamma; + + // tone curve + float tonecurve_x[6], tonecurve_y[6]; + int tonecurve_preset; +} +dt_ctl_image_settings_t; + +#endif diff --git a/src/develop/develop.c b/src/develop/develop.c new file mode 100644 index 000000000000..b018e07e9942 --- /dev/null +++ b/src/develop/develop.c @@ -0,0 +1,881 @@ +#include "develop/develop.h" +#include "develop/imageop.h" +#include "control/jobs.h" +#include "control/control.h" +#include "common/image_cache.h" + +#include +#include +#include +#include + +uint8_t dt_dev_default_gamma[0x10000]; +float dt_dev_de_gamma[0x100]; + +int32_t dt_dev_update_fixed_pipeline(dt_develop_t *dev, dt_dev_operation_t op, int32_t hash) +{ + if(strncmp(op, "original", 20) == 0) + { + dt_control_get_tonecurve(dev->tonecurve, &(dev->history[dev->history_top-1].settings)); + dt_dev_set_gamma(dev); + dt_dev_set_histogram_pre(dev); + } + else if(strncmp(op, "tonecurve", 20) == 0) + { + dt_control_get_tonecurve(dev->tonecurve, &(dev->history[dev->history_top-1].settings)); + } + else if(strncmp(op, "gamma", 20) == 0) + { + dt_dev_set_gamma(dev); + } + else + { // plugin op on buffer + dt_dev_set_histogram_pre(dev); + hash += (dev->last_hash++) * (1<<11); + } + dt_dev_set_histogram(dev); + return hash; +} + +void dt_dev_update_cache(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom) +{ + float zoom_x, zoom_y; + DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); + DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); + int test = dt_dev_test_cached_buf(dev, img, zoom) && + dev->cache_zoom_x[img->cacheline[zoom]] == zoom_x && dev->cache_zoom_y[img->cacheline[zoom]] == zoom_y; + pthread_mutex_lock(&dev->cache_mutex); + if(!dev->image || !dev->gui_attached || dev->image_loading || test) + { + pthread_mutex_unlock(&dev->cache_mutex); + return; // refuse silly cache op. + } + pthread_mutex_unlock(&dev->cache_mutex); + dt_job_t job; + dt_dev_cache_load_job_init(&job, dev, dev->history_top - 1, zoom); + int err = dt_control_add_job_res(darktable.control, &job, DT_CTL_WORKER_2 + zoom); + if(err) fprintf(stderr, "[dev_update_cache] job queue exceeded!\n"); +} + +void dt_dev_update_small_cache(dt_develop_t *dev) +{ + if(!dev->gui_attached) return; + // only one process running anyways, jobs will not pile up + // pthread_mutex_lock(&dev->cache_mutex); + // if(dev->small_raw_loading || dev->small_raw_hash == dev->history[dev->history_top-1].num) + // { + // pthread_mutex_unlock(&dev->cache_mutex); + // return; + // } + // dev->small_raw_loading = 2; + // pthread_mutex_unlock(&dev->cache_mutex); + dt_job_t job; + dt_dev_small_cache_load_init(&job, dev); + int err = dt_control_add_job_res(darktable.control, &job, DT_CTL_WORKER_5); + if(err) fprintf(stderr, "[dev_update_small_cache] job queue exceeded!\n"); +} + +void dt_dev_set_gamma_array(dt_develop_t *dev, const float linear, const float gamma, uint8_t *arr) +{ + double a, b, c, g; + if(linear<1.0) + { + g = gamma*(1.0-linear)/(1.0-gamma*linear); + a = 1.0/(1.0+linear*(g-1)); + b = linear*(g-1)*a; + c = pow(a*linear+b, g)/linear; + } + else + { + a = b = g = 0.0; + c = 1.0; + } + + for(int k=0;k<0x10000;k++) + { + int32_t tmp = 0x10000 * powf(k/(float)0x10000, 1./2.2); + if (k<0x10000*linear) tmp = MIN(c*k, 0xFFFF); + else tmp = MIN(pow(a*k/0x10000+b, g)*0x10000, 0xFFFF); + arr[k] = tmp>>8; + } +} + +void dt_dev_set_gamma(dt_develop_t *dev) +{ + dt_dev_image_t *img = dev->history + dev->history_top - 1; + dt_dev_set_gamma_array(dev, img->settings.dev_gamma_linear, img->settings.dev_gamma_gamma, dev->gamma); +} + +void dt_dev_init(dt_develop_t *dev, int32_t gui_attached) +{ + dev->gui_attached = gui_attached; + dev->small_backbuf = dev->backbuf = NULL; + dev->backbuf_hash = dev->small_backbuf_hash = -1; + dev->image = NULL; + dev->image_loading = 0; + dev->small_raw_cached = NULL; + dev->small_raw_loading = 0; + dev->small_raw_hash = -1; + dev->small_raw_height = dev->small_raw_width = -1; + pthread_mutex_init(&dev->cache_mutex, NULL); + dev->num_cachelines = DT_DEV_CACHELINES; + dev->cache = (float **)malloc(dev->num_cachelines*sizeof(float*)); + dev->cache_zoom_x = (float *)malloc(dev->num_cachelines*sizeof(float)); + dev->cache_zoom_y = (float *)malloc(dev->num_cachelines*sizeof(float)); + dev->cache_sorted = (int32_t *)malloc(dev->num_cachelines*sizeof(int32_t)); + dev->cache_hash = (int32_t *)malloc(dev->num_cachelines*sizeof(uint32_t)); + dev->cache_used = (int32_t *)malloc(dev->num_cachelines*sizeof(uint32_t)); + dev->small_raw_buf = NULL; + for(int k=0;knum_cachelines;k++) + { + dev->cache_hash[k] = -1; + dev->cache[k] = NULL; + dev->cache_used[k] = 0; + dev->cache_sorted[k] = k; + } + dev->cache_width = dev->cache_height = -1; + + dev->history_top = 0; + dev->history_max = 10; + dev->history = (dt_dev_image_t *)malloc(sizeof(dt_dev_image_t)*dev->history_max); + for(int k=0;k<3;k++) dev->history[0].cacheline[k] = 0; + + float lin, gam; + DT_CTL_GET_GLOBAL(lin, dev_gamma_linear); + DT_CTL_GET_GLOBAL(gam, dev_gamma_gamma); + dt_dev_set_gamma_array(dev, lin, gam, dt_dev_default_gamma); + dt_dev_set_gamma_array(dev, lin, gam, dev->gamma); + int last = 0; // invert 0.1 0.45 fn + for(int i=0;i<0x100;i++) for(int k=last;k<0x10000;k++) + if(dt_dev_default_gamma[k] >= i) { last = k; dt_dev_de_gamma[i] = k/(float)0x10000; break; } + // for(int k=0;k<0x10000;k++) dt_dev_de_gamma[k] = CLAMP(0xffff*powf(k/(double)0xffff, 2.2), 0, 0xffff); + + dev->histogram = (uint32_t *)malloc(sizeof(int32_t)*256*4); + bzero(dev->histogram, sizeof(int32_t)*256*4); + dev->histogram_max = -1; + dev->histogram_pre = (uint32_t *)malloc(sizeof(int32_t)*256*4); + bzero(dev->histogram_pre, sizeof(int32_t)*256*4); + dev->histogram_pre_max = -1; +} + +void dt_dev_cleanup(dt_develop_t *dev) +{ + if(dev->image) + { + dt_image_release(dev->image, DT_IMAGE_FULL, 'w'); + dt_image_release(dev->image, DT_IMAGE_FULL, 'r'); + dt_image_release(dev->image, DT_IMAGE_MIPF, 'r'); + } + + pthread_mutex_destroy(&dev->cache_mutex); + for(int k=0;knum_cachelines;k++) free(dev->cache[k]); + free(dev->cache); + free(dev->cache_sorted); + free(dev->cache_used); + free(dev->cache_hash); + free(dev->cache_zoom_x); + free(dev->cache_zoom_y); + free(dev->history); + free(dev->histogram); + free(dev->histogram_pre); + free(dev->backbuf); + free(dev->small_backbuf); + free(dev->small_raw_buf); +} + +int dt_dev_small_cache_load(dt_develop_t *dev) +{ + dt_print(DT_DEBUG_DEV, "[small_cache_load] hash %d -> %d\n", dev->small_raw_hash, dev->history[dev->history_top-1].num); + if(!dev->image) return 1; + // pthread_mutex_lock(&dev->cache_mutex); + // if(dev->small_raw_loading == 1) + // { + // pthread_mutex_unlock(&dev->cache_mutex); + // return; + // } + // dev->small_raw_loading = 1; + // pthread_mutex_unlock(&dev->cache_mutex); + dt_image_buffer_t mip = dt_image_get(dev->image, DT_IMAGE_MIPF, 'r'); + if(mip != DT_IMAGE_MIPF) + { // fail. :( + pthread_mutex_lock(&dev->cache_mutex); + dev->small_raw_loading = 0; + pthread_mutex_unlock(&dev->cache_mutex); + return 1; + } + int wd, ht; + dt_image_get_mip_size(dev->image, DT_IMAGE_MIPF, &wd, &ht); + dev->small_raw_width = wd; + dev->small_raw_height = ht; +restart_small_cache: + dt_image_check_buffer(dev->image, DT_IMAGE_MIPF, 3*wd*ht*sizeof(float)); + // need 16-bit buf update? + pthread_mutex_lock(&dev->cache_mutex); + int hash_in = dev->history[dev->history_top-1].num; + int test = dev->small_raw_hash != dev->history[dev->history_top-1].num; + pthread_mutex_unlock(&dev->cache_mutex); + if(test) + { + float *tmp = dev->image->mipf; + // memcpy(dev->small_raw_cached, dev->small_raw, wd*ht*3*sizeof(uint16_t)); + // perform all image operations on stack + const int num_in = dev->history[0].num; + for(int k=1;khistory_top;k++) + { + dt_dev_image_t *img = dev->history + k; + dt_iop_execute(dev->small_raw_buf, tmp, wd, ht, wd, ht, img->operation, &(img->op_params)); + if(img->num != num_in) tmp = dev->small_raw_buf; + } + dev->small_raw_cached = tmp; // set to orig or buf. + if(dev->history[dev->history_top-1].num != hash_in) goto restart_small_cache; // obsoleted + } + if(dev->small_backbuf_hash != dev->history[dev->history_top-1].num) + { +// #pragma omp parallel for schedule(static) shared(dev) + for(int i=0;ismall_backbuf[4*i+2-k] = dev->gamma[dev->tonecurve[(int)CLAMP(0xffff*dev->small_raw_cached[3*i+k], 0, 0xffff)]]; + } + + dt_image_release(dev->image, mip, 'r'); + pthread_mutex_lock(&dev->cache_mutex); + dev->small_backbuf_hash = + dev->small_raw_hash = dev->history[dev->history_top-1].num; + dev->small_raw_loading = 0; + pthread_mutex_unlock(&dev->cache_mutex); + (void)dt_dev_update_fixed_pipeline(dev, dev->history[dev->history_top-1].operation, 0); + dt_control_queue_draw(); + return 0; +} + +void dt_dev_cache_load(dt_develop_t *dev, int32_t stackpos, dt_dev_zoom_t zoom) +{ + pthread_mutex_lock(&dev->cache_mutex); + if(!dev->image || dev->image_loading || !dev->image->pixels || dev->cache_width < 0 || dev->image->shrink) + { + pthread_mutex_unlock(&dev->cache_mutex); + return; // refuse silly cache op. + } + pthread_mutex_unlock(&dev->cache_mutex); + + dt_dev_image_t *img = dev->history + stackpos; + float zoom_x, zoom_y; + DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); + DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); + const int cwd = dev->cache_width, cht = dev->cache_height; + const int iwd = dev->image->width, iht = dev->image->height; + // already there, just recently written by this worker thread? + if(dt_dev_test_cached_buf(dev, img, zoom) && dev->cache_zoom_x[img->cacheline[zoom]] == zoom_x && dev->cache_zoom_y[img->cacheline[zoom]] == zoom_y) + return; + // goto finalize; + // printf("[dev_cache_load] img %d zoom %d to cacheline %d for stackpos %d\n", img->num, zoom, img->cacheline[zoom], stackpos); + + if(stackpos == 0) + { // clip and zoom from original raw image + dt_dev_reserve_new_cached_buf(dev, img, zoom); + dev->cache_zoom_x[img->cacheline[zoom]] = zoom_x; + dev->cache_zoom_y[img->cacheline[zoom]] = zoom_y; + dt_image_check_buffer(dev->image, DT_IMAGE_FULL, 3*iwd*iht*sizeof(float)); + float *pixels = dev->image->pixels; + // resample original image + if(zoom == DT_ZOOM_1) + { + int x = MAX(0, (int)((zoom_x + .5f)*iwd - cwd/2)), y = MAX(0, (int)((zoom_y + .5f)*iht - cht/2)); + const int cht2 = MIN(cht, iht - y); + const int cwd2 = MIN(cwd, iwd - x); + int idx = 0; + for(int j=0;jcache[img->cacheline[zoom]][3*idx + k] = pixels[3*(iwd*y + x) + k]; + x++; idx++; + } + y++; x = MAX(0, (int)((zoom_x + .5f)*iwd - cwd/2)); + idx = cwd*j; + } + } + else if(zoom == DT_ZOOM_FILL) + { + float boxw = cwd/(iwd*fmaxf(cwd/(float)iwd, cht/(float)iht)), boxh = cht/(iht*fmaxf(cwd/(float)iwd, cht/(float)iht)); + dt_iop_clip_and_zoom(pixels, (zoom_x + .5f)*iwd - boxw*iwd/2, (zoom_y + .5f)*iht - boxh*iht/2, boxw*iwd, boxh*iht, iwd, iht, + dev->cache[img->cacheline[zoom]], 0, 0, cwd, cht, cwd, cht); + } + else + { + float scale = fminf(cwd/(float)iwd, cht/(float)iht); + dt_iop_clip_and_zoom(pixels, 0, 0, iwd, iht, iwd, iht, + dev->cache[img->cacheline[zoom]], 0, 0, scale*iwd, scale*iht, cwd, cht); + } + dt_dev_release_cached_buf(dev, img, zoom); + } + else + { + // get image from last operation (stack - 1): + float *buf = NULL, *dst = NULL; + // recursively acquire buffers of previous operations, let cache_load decide whether loading is necessary. + dt_dev_cache_load(dev, stackpos-1, zoom); + buf = dt_dev_get_cached_buf(dev, img-1, zoom, 'r'); + if(buf) + { + dst = dt_dev_get_cached_buf(dev, img, zoom, 'w'); // maybe parent buffer is ours? + if(!dst) dt_dev_reserve_new_cached_buf(dev, img, zoom); + dev->cache_zoom_x[img->cacheline[zoom]] = zoom_x; + dev->cache_zoom_y[img->cacheline[zoom]] = zoom_y; + if(buf != dev->cache[img->cacheline[zoom]]) + { // only perform op if buffer is there and op affected it + // get operation and params, apply to buffer. + const float sx = iwd*fminf(cwd/(float)iwd, cht/(float)iht); + const float sy = iht*fminf(cwd/(float)iwd, cht/(float)iht); + const int cwd2 = zoom == DT_ZOOM_FIT ? sx : cwd; + const int cht2 = zoom == DT_ZOOM_FIT ? sy : cht; + dt_iop_execute(dev->cache[img->cacheline[zoom]], buf, cwd2, cht2, cwd, cht, img->operation, &(img->op_params)); + dt_dev_release_cached_buf(dev, img-1, zoom); + } + dt_dev_release_cached_buf(dev, img, zoom); + } + } + (void)dt_dev_update_fixed_pipeline(dev, img->operation, 0); +// finalize: + if(stackpos == dev->history_top - 1) + { + dt_control_queue_draw(); +// uint16_t *buf = dt_dev_get_cached_buf(dev, img, zoom, 'w'); +// #pragma omp parallel for schedule(static) shared(dev,buf) +// for(int i=0;ibackbuf[4*i+2-k] = dev->gamma[dev->tonecurve[buf[3*i+k]]]>>8; +// dt_dev_release_cached_buf(dev, img, zoom); + } +} + +void dt_dev_raw_load(dt_develop_t *dev, dt_image_t *img) +{ + int err; + pthread_mutex_lock(&dev->cache_mutex); + // only load if not already there. + if(!dev->image->pixels || dev->image->shrink) + { + // TODO: + if(dev->image_loading) + { + // kill still running job + fprintf(stderr, "[dev_raw_load] still old image loading! TODO: kill job!\n"); + pthread_mutex_unlock(&dev->cache_mutex); + return; + } + +restart: + dev->image_loading = 1; + dev->image->shrink = 0; + pthread_mutex_unlock(&dev->cache_mutex); + err = dt_image_load(img, DT_IMAGE_FULL); + if(err) + { + fprintf(stderr, "[dev_raw_load] failed to load image %s!\n", img->filename); + } + + // obsoleted by another job? + if(dev->image != img) + { + printf("[dev_raw_load] recovering from obsoleted read!\n"); + img = dev->image; + pthread_mutex_lock(&dev->cache_mutex); + goto restart; + } + + pthread_mutex_lock(&dev->cache_mutex); + dev->image_loading = 0; + pthread_mutex_unlock(&dev->cache_mutex); + + // flush cashe + dev->backbuf_hash = -1; + dev->histogram_max = -1; + dev->histogram_pre_max = -1; + for(int k=0;knum_cachelines;k++) dev->cache_hash[k] = -1; + + // printf("[dev_raw_load] starting cache jobs %s\n", img->filename); + // create cached arrays in threads. + for(int k=0;k<3;k++) + dt_dev_update_cache(dev, dev->history + dev->history_top - 1, k); + } + else pthread_mutex_unlock(&dev->cache_mutex); +} + +void dt_dev_load_image(dt_develop_t *dev, dt_image_t *image) +{ + // printf("[dt_dev_load_image]\n"); + // int selected; + dev->last_hash = 1; + // DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over); + if(dev->image != image) + { + dev->image = image; + // invalidate all caches. + dev->small_backbuf_hash = -1; + dev->backbuf_hash = -1; + dev->small_raw_hash = -1; + for(int k=0;knum_cachelines;k++) dev->cache_hash[k] = -1; + } + dt_dev_read_history(dev); + + // DT_CTL_SET_GLOBAL(dev_zoom_x, 0); + // DT_CTL_SET_GLOBAL(dev_zoom_y, 0); + pthread_mutex_lock(&dev->cache_mutex); + dev->small_raw_loading = 0; + pthread_mutex_unlock(&dev->cache_mutex); + + dt_control_get_tonecurve(dev->tonecurve, &(dev->history[dev->history_top-1].settings)); + dt_dev_set_gamma(dev); + + if(dev->gui_attached) + { + if(!dev->image->pixels || dev->image->shrink) + { + dt_job_t job; + dt_dev_raw_load_job_init(&job, dev, dev->image); + int32_t err = dt_control_add_job_res(darktable.control, &job, DT_CTL_WORKER_1); + if(err) fprintf(stderr, "[dev_load_image] job queue exceeded!\n"); + } + } + else dt_dev_raw_load(dev, dev->image); +} + +void dt_dev_configure(dt_develop_t *dev, int32_t width, int32_t height) +{ + // re-alloc cache on resize, flushes all entries :( + if(dev->cache_width != width || dev->cache_height != height) + { + pthread_mutex_lock(&dev->cache_mutex); + // jobs still working will schedule a redraw when finished. + for(int k=0;knum_cachelines;k++) + { + if(dev->cache_used[k]) + { + pthread_mutex_unlock(&dev->cache_mutex); + return; + } + } + dev->cache_width = width; + dev->cache_height = height; + for(int k=0;knum_cachelines;k++) + { + dev->cache_hash[k] = -1; + dev->cache_used[k] = 0; + free(dev->cache[k]); + dev->cache[k] = (float *)malloc(3*sizeof(float)*dev->cache_width*dev->cache_height); + bzero(dev->cache[k], 3*sizeof(float)*dev->cache_width*dev->cache_height); + dev->cache_sorted[k] = k; + } + free(dev->backbuf); + free(dev->small_backbuf); + free(dev->small_raw_buf); + int wd, ht; + dt_image_get_mip_size(dev->image, DT_IMAGE_MIPF, &wd, &ht); + // printf("cache configure! size %d\n", size); + dev->backbuf = (uint8_t *)malloc(4*sizeof(uint8_t)*dev->cache_width*dev->cache_height); + dev->small_raw_buf = (float *)malloc(3*sizeof(float)*wd*ht); + bzero(dev->small_raw_buf, 3*sizeof(float)*wd*ht); + dev->small_backbuf = (uint8_t *)malloc(4*sizeof(uint8_t)*wd*ht); + pthread_mutex_unlock(&dev->cache_mutex); + // update caches + dt_job_t job; + dt_dev_raw_load_job_init(&job, dev, dev->image); + int32_t err = dt_control_add_job_res(darktable.control, &job, DT_CTL_WORKER_1); + if(err) fprintf(stderr, "[dev_expose] job queue exceeded!\n"); + } +} + +void dt_dev_set_histogram_pre(dt_develop_t *dev) +{ + if(!dev->image) return; + dt_dev_image_t *img = dev->history + dev->history_top - 1; + float *buf = dt_dev_get_cached_buf(dev, img, DT_ZOOM_FIT, 'r'); + if(buf) + { + const float sx = dev->image->width *fminf(dev->cache_width/(float)dev->image->width, dev->cache_height/(float)dev->image->height); + const float sy = dev->image->height*fminf(dev->cache_width/(float)dev->image->width, dev->cache_height/(float)dev->image->height); + dev->histogram_pre_max = dt_iop_create_histogram_f(buf, sx, sy, dev->cache_width, dev->histogram_pre); + dt_dev_release_cached_buf(dev, img, DT_ZOOM_FIT); + } + else if(dev->small_raw_hash == img->num) + { + dev->histogram_pre_max = dt_iop_create_histogram_f(dev->small_raw_cached, dev->small_raw_width, dev->small_raw_height, dev->small_raw_width, dev->histogram_pre); + dt_dev_update_cache(dev, img, DT_ZOOM_FIT); + } +} + +void dt_dev_set_histogram(dt_develop_t *dev) +{ + if(!dev->image) return; + dt_dev_image_t *img = dev->history + dev->history_top - 1; + float *buf = dt_dev_get_cached_buf(dev, img, DT_ZOOM_FIT, 'r'); + if(buf) + { + const float sx = dev->image->width *fminf(dev->cache_width/(float)dev->image->width, dev->cache_height/(float)dev->image->height); + const float sy = dev->image->height*fminf(dev->cache_width/(float)dev->image->width, dev->cache_height/(float)dev->image->height); + dev->histogram_max = dt_iop_create_histogram_final_f(buf, sx, sy, dev->cache_width, dev->histogram, dev->gamma, dev->tonecurve); + dt_dev_release_cached_buf(dev, img, DT_ZOOM_FIT); + } + else if(dev->small_raw_hash == img->num) + { + dev->histogram_max = dt_iop_create_histogram_final_f(dev->small_raw_cached, dev->small_raw_width, dev->small_raw_height, dev->small_raw_width, dev->histogram, dev->gamma, dev->tonecurve); + dt_dev_update_cache(dev, img, DT_ZOOM_FIT); + } +} + +int32_t dt_dev_test_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom) +{ + pthread_mutex_lock(&dev->cache_mutex); + int32_t cacheline = img->cacheline[zoom]; + int32_t hash = (img->num << 4) | zoom; + int32_t res = 0; + if(dev->cache_hash[cacheline] == hash) res = 1; // found it! + pthread_mutex_unlock(&dev->cache_mutex); + return res; +} + +float *dt_dev_get_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom, char mode) +{ + // dt_print(DT_DEBUG_DEV, "[dev_get_cached_buf] img %d %d %d\n", img->cacheline[0], img->cacheline[1], img->cacheline[2]); + pthread_mutex_lock(&dev->cache_mutex); + int32_t cacheline = img->cacheline[zoom]; + int32_t hash = (img->num << 4) | zoom; + if(dev->cache_hash[cacheline] != hash || (dev->cache_used[cacheline] & 2)) + { + dt_print(DT_DEBUG_DEV, "[dev_get_cached_buf] miss line %d for img %d|%d, write lock: %d\n", cacheline, img->num, zoom, dev->cache_used[cacheline] & 2); + pthread_mutex_unlock(&dev->cache_mutex); + return NULL; // cache miss: not there or currently writing. + } + // sort lru list + for(int k=0;knum_cachelines;k++) + { + if(dev->cache_sorted[k] == cacheline) + { + for(int i=k;inum_cachelines-1;i++) dev->cache_sorted[i] = dev->cache_sorted[i+1]; + dev->cache_sorted[dev->num_cachelines-1] = cacheline; + break; + } + } + dev->cache_used[cacheline] = 1; + if(mode == 'w') dev->cache_used[cacheline] |= 2; + dt_print(DT_DEBUG_DEV, "[dev_get_cached_buf] img %d zoom %d in cacheline %d mode '%c'\n", img->num, zoom, cacheline, mode); + pthread_mutex_unlock(&dev->cache_mutex); + return dev->cache[cacheline]; +} + +int32_t dt_dev_reserve_new_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom) +{ + pthread_mutex_lock(&dev->cache_mutex); + int32_t hash = (img->num << 4) | zoom; + // kick lru object and reserve it as mru for this hash. + int32_t line = img->cacheline[zoom]; + if(dev->cache_hash[line] != hash) line = dev->cache_sorted[0]; + int32_t kicked = dev->cache_hash[line]; + dev->cache_hash[line] = hash; + img->cacheline[zoom] = line; + int found = 0; + for(int i=0;inum_cachelines-1;i++) + { + if(dev->cache_sorted[i] == line) found = 1; + if(found) dev->cache_sorted[i] = dev->cache_sorted[i+1]; + } + dt_print(DT_DEBUG_DEV, "[dev_reserve_new_cached_buf] line %d for img %d|%d (was hash %d), write lock: %d\n", line, img->num, zoom, kicked, dev->cache_used[line] == 2); + + dev->cache_sorted[dev->num_cachelines-1] = line; + dev->cache_used[line] = 2; + // propagate up the stack + for(dt_dev_image_t *i2 = img+1;i2 < dev->history+dev->history_top && i2->num == img->num;i2++) for(int k=0;k<3;k++) i2->cacheline[k] = img->cacheline[k]; + pthread_mutex_unlock(&dev->cache_mutex); + return line; +} + +void dt_dev_release_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom) +{ + pthread_mutex_lock(&dev->cache_mutex); + int32_t line = img->cacheline[zoom]; + dev->cache_used[line] = 0; + pthread_mutex_unlock(&dev->cache_mutex); + dt_print(DT_DEBUG_DEV, "[dev_release_buf] line %d for img %d|%d\n", line, img->num, zoom); +} + +// helper used to synch a single history item with db +int dt_dev_write_history_item(dt_develop_t *dev, dt_dev_image_t *h, int32_t num) +{ + if(!dev->image) return 1; + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(darktable.db, "select num from history where imgid = ?1 and num = ?2", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, dev->image->id); + rc = sqlite3_bind_int (stmt, 2, num); + if(sqlite3_step(stmt) != SQLITE_ROW) + { + rc = sqlite3_finalize(stmt); + rc = sqlite3_prepare_v2(darktable.db, "insert into history (imgid, num) values (?1, ?2)", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, dev->image->id); + rc = sqlite3_bind_int (stmt, 2, num); + rc = sqlite3_step (stmt); + } + rc = sqlite3_finalize (stmt); + rc = sqlite3_prepare_v2(darktable.db, "update history set operation = ?1, op_params = ?2, settings = ?3, hash = ?4 where imgid = ?5 and num = ?6", -1, &stmt, NULL); + rc = sqlite3_bind_text(stmt, 1, h->operation, strlen(h->operation), SQLITE_STATIC); + rc = sqlite3_bind_blob(stmt, 2, &(h->op_params), sizeof(dt_dev_operation_params_t), SQLITE_STATIC); + rc = sqlite3_bind_blob(stmt, 3, &(h->settings), sizeof(dt_ctl_image_settings_t), SQLITE_STATIC); + rc = sqlite3_bind_int (stmt, 4, h->num); + rc = sqlite3_bind_int (stmt, 5, dev->image->id); + rc = sqlite3_bind_int (stmt, 6, num); + rc = sqlite3_step (stmt); + rc = sqlite3_finalize (stmt); + return 0; +} + +void dt_dev_add_history_item(dt_develop_t *dev, dt_dev_operation_t op) +{ + dt_dev_image_t *img = dev->history + dev->history_top - 1; + if(dev->gui_attached) + { + dt_ctl_gui_mode_t gui; + DT_CTL_GET_GLOBAL(gui, gui); + if(gui != DT_DEVELOP) return; + dt_control_clear_history_items(dev->history_top-1); + } + // this is called exclusively from the gtk thread, so no lock is necessary. + int32_t num = dt_dev_update_fixed_pipeline(dev, op, img->num); + + if(strncmp(img->operation, op, 20) != 0) + { + if(dev->gui_attached) dt_control_add_history_item(dev->history_top, op); + for(int k=0;k<3;k++) img[1].cacheline[k] = img[0].cacheline[k]; + img ++; + dev->history_top++; + if(dev->history_top >= dev->history_max) + { + dev->history_max *= 2; + dt_dev_image_t *tmp = (dt_dev_image_t *)malloc(sizeof(dt_dev_image_t)*dev->history_max); + memcpy(tmp, dev->history, dev->history_max/2 * sizeof(dt_dev_image_t)); + free(dev->history); + dev->history = tmp; + } + } + if(dev->gui_attached) + { + pthread_mutex_lock(&(darktable.control->image_mutex)); + img->settings = darktable.control->image_settings; + pthread_mutex_unlock(&(darktable.control->image_mutex)); + } + DT_CTL_SET_GLOBAL_STR(dev_op, img->operation, 20); + DT_CTL_GET_GLOBAL(img->op_params, dev_op_params); + img->num = num; + strncpy(img->operation, op, 20); + dt_print(DT_DEBUG_DEV, "pushing history %d item with hash %d, operation %d and cachelines %d %d %d\n", dev->history_top-1, num, op, img->cacheline[0], img->cacheline[1], img->cacheline[2]); + + dev->small_backbuf_hash = -1; + dt_dev_update_small_cache(dev); +} + +void dt_dev_pop_history_items(dt_develop_t *dev, int32_t cnt) +{ + // this is called exclusively from the gtk thread, so no lock is necessary. + if(cnt == dev->history_top-1) return; + dev->history_top = cnt + 1; + if(dev->gui_attached) + { + pthread_mutex_lock(&(darktable.control->image_mutex)); + darktable.control->image_settings = dev->history[dev->history_top-1].settings; + pthread_mutex_unlock(&(darktable.control->image_mutex)); + DT_CTL_SET_GLOBAL_STR(dev_op, dev->history[dev->history_top-1].operation, 20); + DT_CTL_SET_GLOBAL(dev_op_params, dev->history[dev->history_top-1].op_params); + } + (void)dt_dev_update_fixed_pipeline(dev, "original", 0); + + dev->small_backbuf_hash = -1; + dt_dev_update_small_cache(dev); +} + +void dt_dev_write_history(dt_develop_t *dev) +{ + for(int k=0;khistory_top;k++) + (void)dt_dev_write_history_item(dev, dev->history+k, k); +#if 0 + char filename[512]; + snprintf(filename, 512, "%s/.darktable_cache/%s.hist", darktable.library->film->dirname, dev->image->filename); + FILE *f = fopen(filename, "wb"); + if(!f) return; // TODO: write to common stream, error handling + // write stack + (void)fwrite(&(dev->history_top), 1, sizeof(int32_t), f); + for(int k=0;khistory_top;k++) + (void)fwrite(dev->history + k, 1, sizeof(dt_dev_image_t), f); + fclose(f); +#endif +} + +void dt_dev_read_history(dt_develop_t *dev) +{ + if(dev->gui_attached) dt_control_clear_history_items(0); + if(!dev->image) return; + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(darktable.db, "select * from history where imgid = ?1 order by num", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, dev->image->id); + dev->history_top = 0; + if(sqlite3_step(stmt) == SQLITE_ROW) + { + do + { + dt_dev_image_t *h = dev->history + dev->history_top; + h->num = sqlite3_column_int(stmt, 2); + strncpy(h->operation, (char *)sqlite3_column_text(stmt, 3), 20); + memcpy(&(h->op_params), sqlite3_column_blob(stmt, 4), sizeof(dt_dev_operation_params_t)); + memcpy(&(h->settings), sqlite3_column_blob(stmt, 5), sizeof(dt_ctl_image_settings_t)); + dev->history_top++; + if(dev->history_top >= dev->history_max) + { + dev->history_max *= 2; + dt_dev_image_t *tmp = (dt_dev_image_t *)malloc(sizeof(dt_dev_image_t)*dev->history_max); + memcpy(tmp, dev->history, dev->history_max/2 * sizeof(dt_dev_image_t)); + free(dev->history); + dev->history = tmp; + } + if(dev->gui_attached) dt_control_add_history_item(dev->history_top-1, dev->history[dev->history_top-1].operation); + } + while(sqlite3_step(stmt) == SQLITE_ROW); + rc = sqlite3_finalize (stmt); + } + else + { + rc = sqlite3_finalize (stmt); + dev->history_top = 1; + dev->history[0].num = dev->image->id << 6; + for(int k=0;k<3;k++) dev->history[0].cacheline[k] = 0; + dev->history[0].settings = darktable.control->image_defaults; + strncpy(dev->history[0].operation, "original", 20); + if(dev->gui_attached) dt_control_add_history_item(0, "original"); + dt_dev_write_history_item(dev, dev->history, 0); + } + +#if 0 + char filename[512]; + snprintf(filename, 512, "%s/.darktable_cache/%s.hist", darktable.library->film->dirname, dev->image->filename); + FILE *f = fopen(filename, "rb"); + if(!f) + { + dev->history_top = 1; + dev->history[0].num = dev->image->id << 6; + for(int k=0;k<3;k++) dev->history[0].cacheline[k] = 0; + dev->history[0].settings = darktable.control->image_defaults; + strncpy(dev->history[0].operation, "original", 20); + if(dev->gui_attached) dt_control_add_history_item(0, "original"); + } + else + { + (void)fread(&(dev->history_top), 1, sizeof(int32_t), f); + for(int k=0;khistory_top;k++) + { + (void)fread(dev->history + k, 1, sizeof(dt_dev_image_t), f); + if(dev->gui_attached) dt_control_add_history_item(k, dev->history[k].operation); + } + fclose(f); + } +#endif + if(dev->gui_attached) + { + DT_CTL_SET_GLOBAL_STR(dev_op, dev->history[dev->history_top-1].operation, 20); + DT_CTL_SET_GLOBAL(dev_op_params, dev->history[dev->history_top-1].op_params); + pthread_mutex_lock(&(darktable.control->image_mutex)); + darktable.control->image_settings = dev->history[dev->history_top - 1].settings; + pthread_mutex_unlock(&(darktable.control->image_mutex)); + } + (void)dt_dev_update_fixed_pipeline(dev, "original", 0); +} + +void dt_dev_check_zoom_bounds(dt_develop_t *dev, float *zoom_x, float *zoom_y, dt_dev_zoom_t zoom, int closeup, float *boxww, float *boxhh) +{ + float boxw = 1, boxh = 1; + if(zoom == DT_ZOOM_1) + { + const float imgw = (closeup ? 2 : 1)*dev->image->width; + const float imgh = (closeup ? 2 : 1)*dev->image->height; + const float devw = MIN(imgw, dev->cache_width); + const float devh = MIN(imgh, dev->cache_height); + boxw = fminf(1.0, devw/imgw); boxh = fminf(1.0, devh/imgh); + } + else if(zoom == DT_ZOOM_FILL) + { + const float imgw = dev->image->width; + const float imgh = dev->image->height; + const float devw = dev->cache_width; + const float devh = dev->cache_height; + boxw = devw/(imgw*fmaxf(devw/imgw, devh/imgh)); boxh = devh/(imgh*fmaxf(devw/imgw, devh/imgh)); + } + + if(*zoom_x < boxw/2 - .5f) *zoom_x = boxw/2 - .5f; + if(*zoom_x > .5f - boxw/2) *zoom_x = .5f - boxw/2; + if(*zoom_y < boxh/2 - .5f) *zoom_y = boxh/2 - .5f; + if(*zoom_y > .5f - boxh/2) *zoom_y = .5f - boxh/2; + + if(boxww) *boxww = boxw; + if(boxhh) *boxhh = boxh; +} + +void dt_dev_export(dt_job_t *job) +{ + while(1) + { + // TODO: progress bar in ctl? + char filename[512]; + dt_image_t *img = NULL; + pthread_mutex_lock(&(darktable.library->film->images_mutex)); + int32_t rc, start = darktable.library->film->last_exported++; + pthread_mutex_unlock(&(darktable.library->film->images_mutex)); + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select * from (select * from selected_images) as dreggn limit ?1, 1", -1, &stmt, NULL); + rc = sqlite3_bind_int(stmt, 1, start); + if(sqlite3_step(stmt) == SQLITE_ROW) + { + int imgid = sqlite3_column_int(stmt, 0); + img = dt_image_cache_get(imgid, 'r'); + } + sqlite3_finalize(stmt); + if(!img) + { + // TODO: reset progress bar + return; + } + + snprintf(filename, 512, "%s/darktable_exported", darktable.library->film->dirname); + if(g_mkdir_with_parents(filename, 0755)) + { + dt_image_cache_release(img, 'r'); + fprintf(stderr, "[dev_export] could not create directory %s!\n", filename); + return; + } + snprintf(filename, 512-4, "%s/darktable_exported/%s", darktable.library->film->dirname, img->filename); + char *c = filename + strlen(filename); + for(;c>filename && *c != '.';c--); + if(c <= filename) c = filename + strlen(filename); + + // read type from global config. + dt_dev_export_format_t fmt; + DT_CTL_GET_GLOBAL(fmt, dev_export_format); + switch(fmt) + { + case DT_DEV_EXPORT_JPG: + strncpy(c, ".jpg", 4); + dt_imageio_export_8(img, filename); + break; + case DT_DEV_EXPORT_PNG: + strncpy(c, ".png", 4); + dt_imageio_export_8(img, filename); + break; + case DT_DEV_EXPORT_PPM16: + strncpy(c, ".ppm", 4); + dt_imageio_export_16(img, filename); + break; + case DT_DEV_EXPORT_PFM: + strncpy(c, ".pfm", 4); + dt_imageio_export_f(img, filename); + break; + default: + break; + } + dt_image_cache_release(img, 'r'); + } +} diff --git a/src/develop/develop.h b/src/develop/develop.h new file mode 100644 index 000000000000..1a3c2ab790fc --- /dev/null +++ b/src/develop/develop.h @@ -0,0 +1,123 @@ +#ifndef DARKTABLE_DEVELOP_H +#define DARKTABLE_DEVELOP_H + +#include "common/imageio.h" +#include "library/library.h" +#include "control/settings.h" + +#include +#include + +#define DT_DEV_CACHELINES 5 + +extern uint8_t dt_dev_default_gamma[0x10000]; +extern float dt_dev_de_gamma[0x100]; + +/** + * image for devel module. + * stores cache line pointers, + * along with an img-op last performed on this img. + * can be stacked for history. + */ +typedef struct dt_dev_image_t +{ + dt_dev_operation_t operation; + dt_dev_operation_params_t op_params; + int32_t cacheline[3]; + int32_t num; + dt_ctl_image_settings_t settings; +} +dt_dev_image_t; + + +typedef struct dt_develop_t +{ + int32_t gui_attached; // != 0 if the gui should be notified of changes in hist stack etc. + int32_t image_loading; + + // small cached version while loading + float *small_raw_buf; + float *small_raw_cached; + int32_t small_raw_hash; + int32_t small_raw_loading; + int32_t small_raw_width, small_raw_height; + + // converted img last viewed + uint8_t *backbuf; + uint8_t *small_backbuf; + int32_t backbuf_hash, small_backbuf_hash; + + // image under consideration. + dt_image_t *image; + + // cached versions of raw images, various zoom rates + pthread_mutex_t cache_mutex; + int32_t num_cachelines; + int32_t cache_width, cache_height; + float *cache_zoom_x, *cache_zoom_y; + float **cache; + int32_t *cache_sorted; + int32_t *cache_used; + int32_t *cache_hash; + + // history stack + uint32_t last_hash; + int32_t history_top, history_max; + dt_dev_image_t *history; + + // image specific stuff + uint32_t *histogram, *histogram_pre; + uint32_t histogram_max, histogram_pre_max; + + // gamma table + uint8_t gamma[0x10000]; + uint16_t tonecurve[0x10000]; +} +dt_develop_t; + +void dt_dev_init(dt_develop_t *dev, int32_t gui_attached); +void dt_dev_cleanup(dt_develop_t *dev); + +void dt_dev_cache_load(dt_develop_t *dev, int32_t stackpos, dt_dev_zoom_t zoom); +void dt_dev_raw_load(dt_develop_t *dev, dt_image_t *img); +int dt_dev_small_cache_load(dt_develop_t *dev); + +void dt_dev_load_image(dt_develop_t *dev, struct dt_image_t *img); + +void dt_dev_add_history_item(dt_develop_t *dev, dt_dev_operation_t op); +void dt_dev_pop_history_items(dt_develop_t *dev, int32_t cnt); +void dt_dev_write_history(dt_develop_t *dev); +void dt_dev_read_history(dt_develop_t *dev); + +void dt_dev_set_gamma(dt_develop_t *dev); +void dt_dev_set_histogram(dt_develop_t *dev); +void dt_dev_set_histogram_pre(dt_develop_t *dev); + +void dt_dev_configure(dt_develop_t *dev, int32_t width, int32_t height); +void dt_dev_check_zoom_bounds(dt_develop_t *dev, float *zoom_x, float *zoom_y, dt_dev_zoom_t zoom, int closeup, float *boxw, float *boxh); + +float *dt_dev_get_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom, char mode); +int32_t dt_dev_reserve_new_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom); +int32_t dt_dev_test_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom); +void dt_dev_release_cached_buf(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom); + +void dt_dev_update_cache(dt_develop_t *dev, dt_dev_image_t *img, dt_dev_zoom_t zoom); +void dt_dev_update_small_cache(dt_develop_t *dev); + +struct dt_job_t; +void dt_dev_export(struct dt_job_t *job); + + +// ==================== +// gui methods + +// void dt_dev_init(dt_develop_t *dev); +// void dt_dev_cleanup(dt_develop_t *dev); + +void dt_dev_leave(); +void dt_dev_enter(); + +void dt_dev_image_expose(struct dt_develop_t *dev, dt_dev_image_t *image, cairo_t *cr, int32_t width, int32_t height); +void dt_dev_expose(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height); + +#endif diff --git a/src/develop/imageop.c b/src/develop/imageop.c new file mode 100644 index 000000000000..eb94cd729d4a --- /dev/null +++ b/src/develop/imageop.c @@ -0,0 +1,143 @@ +#include "control/control.h" +#include "develop/imageop.h" +#include +#include +#include +#include +#include + +void dt_iop_init(dt_iop_t *iop) +{ + iop->module = NULL; + // TODO: add dummy module entries for fixed function pipeline with empty execute etc. + // TODO: query modules directory (-ies) + // TODO: read libmodule.so + // TODO: init dt_iop_module_t: + // name, functions, execute gui_init method: + // TODO: gui system: add expander to right panel, gui_init return: a container + // TODO: develop read history: check if operation is available in this distro! + // TODO: qsort module by name! +} + +void dt_iop_cleanup(dt_iop_t *iop) +{ + // TODO: + // for(int k=0;knum_modules) iop->module[k].gui_cleanup(); + free(iop->module); +} + +void dt_iop_gui_reset() +{ + // TODO: reset all modules + dt_iop_gui_reset_hsb(); +} + +void dt_iop_gui_init() +{ + // TODO: init all modules' guis? + dt_iop_gui_init_hsb(); +} + +void dt_iop_execute(float *dst, const float *src, const int32_t wd, const int32_t ht, const int32_t bufwd, const int32_t bufht, + dt_dev_operation_t operation, dt_dev_operation_params_t *params) +{ + // TODO: bsearch module op => execute (else print some error) + if(strncmp(operation, "hsb", 20) == 0) + { + dt_iop_execute_hsb(dst, src, wd, ht, bufwd, bufht, operation, params); + } + // fprintf(stderr, "[dt_iop_execute] unknown operation %d\n", operation); +} + +void dt_iop_clip_and_zoom(const float *i, int32_t ix, int32_t iy, int32_t iw, int32_t ih, int32_t ibw, int32_t ibh, + float *o, int32_t ox, int32_t oy, int32_t ow, int32_t oh, int32_t obw, int32_t obh) +{ + const float scalex = iw/(float)ow; + const float scaley = ih/(float)oh; + int32_t ix2 = MAX(ix, 0); + int32_t iy2 = MAX(iy, 0); + int32_t ox2 = MAX(ox, 0); + int32_t oy2 = MAX(oy, 0); + int32_t oh2 = MIN(MIN(oh, (ibh - iy2)/scaley), obh - oy2); + int32_t ow2 = MIN(MIN(ow, (ibw - ix2)/scalex), obw - ox2); + assert((int)(ix2 + ow2*scalex) <= ibw); + assert((int)(iy2 + oh2*scaley) <= ibh); + assert(ox2 + ow2 <= obw); + assert(oy2 + oh2 <= obh); + assert(ix2 >= 0 && iy2 >= 0 && ox2 >= 0 && oy2 >= 0); + float x = ix2, y = iy2; + for(int s=0;s rgb[1] ? (rgb[0] > rgb[2] ? rgb[0] : rgb[2]) : (rgb[1] > rgb[2] ? rgb[1] : rgb[2]); + hist[4*lum+3] ++; + } + // don't count <= 0 pixels + for(int k=19;k<4*256;k+=4) max = max > hist[k] ? max : hist[k]; + return max; +} + +uint32_t dt_iop_create_histogram_f(const float *pixels, int32_t width, int32_t height, int32_t stride, uint32_t *hist) +{ + uint32_t max = 0; + bzero(hist, sizeof(uint32_t)*4*0x100); + for(int j=0;j rgb[1] ? (rgb[0] > rgb[2] ? rgb[0] : rgb[2]) : (rgb[1] > rgb[2] ? rgb[1] : rgb[2]); + hist[4*lum+3] ++; + } + // don't count <= 0 pixels + for(int k=19;k<4*256;k+=4) max = max > hist[k] ? max : hist[k]; + return max; +} + +uint32_t dt_iop_create_histogram_8(const uint8_t *pixels, int32_t width, int32_t height, int32_t stride, uint32_t *hist) +{ + uint32_t max = 0; + bzero(hist, sizeof(uint32_t)*4*0x100); + for(int j=0;j rgb[1] ? (rgb[0] > rgb[2] ? rgb[0] : rgb[2]) : (rgb[1] > rgb[2] ? rgb[1] : rgb[2]); + hist[4*lum+3] ++; + } + for(int k=19;k<4*256;k+=4) max = max > hist[k] ? max : hist[k]; + return max; +} + diff --git a/src/develop/imageop.h b/src/develop/imageop.h new file mode 100644 index 000000000000..a2d5c992fee8 --- /dev/null +++ b/src/develop/imageop.h @@ -0,0 +1,44 @@ +#ifndef DT_DEVELOP_IMAGEOP_H +#define DT_DEVELOP_IMAGEOP_H + +#include "develop/develop.h" +#include "develop/iop_hsb.h" + +#define dt_red 2 +#define dt_green 1 +#define dt_blue 0 + +typedef struct dt_iop_module_t +{ + dt_dev_operation_t op; // TODO: typedef char[20] + void (*gui_reset) (); // TODO: darktable as param + void (*gui_init) (); // TODO: return container + void (*gui_cleanup) (); // TODO: pass this container + void (*execute) (float *dst, const float *src, const int32_t wd, const int32_t ht, const int32_t bufwd, const int32_t bufht, + dt_dev_operation_t operation, dt_dev_operation_params_t *params); +} +dt_iop_module_t; + +typedef struct dt_iop_t +{ + int32_t num_modules; + dt_iop_module_t *module; +} +dt_iop_t; + + +// job that transforms in pixel block to out pixel block, 4x supersampling +void dt_iop_clip_and_zoom(const float *i, int32_t ix, int32_t iy, int32_t iw, int32_t ih, int32_t ibw, int32_t ibh, + float *o, int32_t ox, int32_t oy, int32_t ow, int32_t oh, int32_t obw, int32_t obh); + +uint32_t dt_iop_create_histogram_final_f(const float *pixels, int32_t width, int32_t height, int32_t stride, uint32_t *hist, uint8_t *gamma, uint16_t *tonecurve); +uint32_t dt_iop_create_histogram_f(const float *pixels, int32_t width, int32_t height, int32_t stride, uint32_t *hist); +uint32_t dt_iop_create_histogram_8 (const uint8_t *pixels, int32_t width, int32_t height, int32_t stride, uint32_t *hist); + +void dt_iop_execute(float *dst, const float *src, const int32_t wd, const int32_t ht, const int32_t bufwd, const int32_t bufht, + dt_dev_operation_t operation, dt_dev_operation_params_t *params); + +void dt_iop_gui_reset(); +void dt_iop_gui_init(); + +#endif diff --git a/src/develop/iop_hsb.c b/src/develop/iop_hsb.c new file mode 100644 index 000000000000..e29f62cc4d84 --- /dev/null +++ b/src/develop/iop_hsb.c @@ -0,0 +1,193 @@ +#include "develop/iop_hsb.h" +#include "gui/gtk.h" +// #include "control/settings.h" +#include "control/control.h" +#include "develop/imageop.h" + +#include +#include + +void +dt_iop_gui_reset_hsb () +{ + GtkWidget *widget; + float hue, sat, bri; + dt_dev_operation_t op; + DT_CTL_GET_GLOBAL_STR(op, dev_op, 20); + DT_CTL_GET_GLOBAL(hue, dev_op_params.f[0]); + DT_CTL_GET_GLOBAL(sat, dev_op_params.f[1]); + DT_CTL_GET_GLOBAL(bri, dev_op_params.f[2]); + if(strncmp(op, "hsb", 20)) + { + hue = sat = bri = 1.0; + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_hue"); + gtk_range_set_value(GTK_RANGE(widget), hue); + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_saturation"); + gtk_range_set_value(GTK_RANGE(widget), sat); + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_brightness"); + gtk_range_set_value(GTK_RANGE(widget), bri); + } +} + +void +dt_iop_gui_init_hsb () +{ + // hsb expander + GtkWidget *widget; + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_hue"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (dt_iop_gui_callback_hsb), (gpointer)0); + + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_saturation"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (dt_iop_gui_callback_hsb), (gpointer)1); + + widget = glade_xml_get_widget (darktable.gui->main_window, "hsb_brightness"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (dt_iop_gui_callback_hsb), (gpointer)2); +} + +void +dt_iop_gui_callback_hsb (GtkRange *range, gpointer user_data) +{ + if(darktable.gui->reset) return; + // FIXME: is this block needed?? + dt_dev_operation_t op; + DT_CTL_GET_GLOBAL_STR(op, dev_op, 20); + if(strncmp(op, "hsb", 20)) + { + DT_CTL_SET_GLOBAL(dev_op_params.f[0], 1.0); + DT_CTL_SET_GLOBAL(dev_op_params.f[1], 1.0); + DT_CTL_SET_GLOBAL(dev_op_params.f[2], 1.0); + DT_CTL_SET_GLOBAL_STR(dev_op, "hsb", 20); + } + // FIXME: end. + if(user_data == (gpointer)0) + { + float tmp = gtk_range_get_value(range); + DT_CTL_SET_GLOBAL(dev_op_params.f[0], tmp); + } + else if(user_data == (gpointer)1) + { + float tmp = gtk_range_get_value(range); + DT_CTL_SET_GLOBAL(dev_op_params.f[1], tmp); + } + else + { + float tmp = gtk_range_get_value(range); + DT_CTL_SET_GLOBAL(dev_op_params.f[2], tmp); + } + dt_dev_add_history_item(darktable.develop, "hsb"); +} + +// helper functions: +void +dt_rgb_to_hsv_f(const float *rgb, float *hsv) +{ + float min, max, delta; + min = fminf(rgb[dt_red], fminf(rgb[dt_green], rgb[dt_blue])); + max = fmaxf(rgb[dt_red], fmaxf(rgb[dt_green], rgb[dt_blue])); + hsv[2] = max; + delta = max - min; + + if(max == 0) hsv[1] = hsv[0] = 0; + else + { + hsv[1] = delta / max; + float tmph; + if (delta == 0) tmph = 0; + else if(rgb[dt_red] == max) tmph = (rgb[dt_green] - rgb[dt_blue]) / delta; // between yellow & magenta + else if(rgb[dt_green] == max) tmph = 2 + (rgb[dt_blue] - rgb[dt_red]) / delta; // between cyan & yellow + else tmph = 4 + (rgb[dt_red] - rgb[dt_green]) / delta; // between magenta & cyan + hsv[0] = tmph*60./360.; + } +} + +void +dt_hsv_to_rgb_f(const float *hsv, float *rgb) +{ + const float h = hsv[0]*6.0; + const int i = (int)h; + const float f = (h - i), s = hsv[1], v = hsv[2]; + const float p = v * (1. - s); + const float q = v * (1. - (s * f)); + const float t = v * (1. - (s * (1. - f ))); + switch(i) + { + case 0: rgb[dt_red] = v; rgb[dt_green] = t; rgb[dt_blue] = p; break; + case 1: rgb[dt_red] = q; rgb[dt_green] = v; rgb[dt_blue] = p; break; + case 2: rgb[dt_red] = p; rgb[dt_green] = v; rgb[dt_blue] = t; break; + case 3: rgb[dt_red] = p; rgb[dt_green] = q; rgb[dt_blue] = v; break; + case 4: rgb[dt_red] = t; rgb[dt_green] = p; rgb[dt_blue] = v; break; + default: rgb[dt_red] = v; rgb[dt_green] = p; rgb[dt_blue] = q; break; + } +} + +#if 0 +void dt_rgb_to_hsv_16(const uint16_t *rgb, uint16_t *hsv) +{ + uint64_t min, max, delta; + min = MIN(rgb[red], MIN(rgb[green], rgb[blue])); + max = MAX(rgb[red], MAX(rgb[green], rgb[blue])); + hsv[2] = max; + delta = max - min; + + if(max == 0) hsv[1] = hsv[0] = 0; + else + { + hsv[1] = (0xffff * delta) / max;//I16CLAMP(delta / (float)max); + double tmph; + if (delta == 0) tmph = 0; + else if(rgb[red] == max) tmph = (rgb[green] - rgb[blue]) / (double)delta; // between yellow & magenta + else if(rgb[green] == max) tmph = 2 + (rgb[blue] - rgb[red]) / (double)delta; // between cyan & yellow + else tmph = 4 + (rgb[red] - rgb[green]) / (double)delta; // between magenta & cyan + hsv[0] = (uint16_t)(0x10000*tmph*60./360.); // rotation allowed + } +} + +void dt_hsv_to_rgb_16(const uint16_t *hsv, uint16_t *rgb) +{ + const double h = hsv[0]*6.0/0x10000; + const int i = (int)h; + const int64_t f = 0x10000*(h - i), s = hsv[1], v = hsv[2]; + const uint16_t p = v * (0x10000 - s)/0x10000; + const uint16_t q = v * (0x10000 - (s * f)/0x10000)/0x10000; + const uint16_t t = v * (0x10000 - (s * ( 0x10000 - f ))/0x10000)/0x10000; + switch(i) + { + case 0: rgb[red] = v; rgb[green] = t; rgb[blue] = p; break; + case 1: rgb[red] = q; rgb[green] = v; rgb[blue] = p; break; + case 2: rgb[red] = p; rgb[green] = v; rgb[blue] = t; break; + case 3: rgb[red] = p; rgb[green] = q; rgb[blue] = v; break; + case 4: rgb[red] = t; rgb[green] = p; rgb[blue] = v; break; + default: rgb[red] = v; rgb[green] = p; rgb[blue] = q; break; + } +} +#endif + +// main op function: convert buffer applying hue saturation brightness changes. +void +dt_iop_execute_hsb(float *dst, const float *src, const int32_t wd, const int32_t ht, const int32_t bufwd, const int32_t bufht, + dt_dev_operation_t operation, dt_dev_operation_params_t *params) +{ + float hsbmul[3]; + hsbmul[0] = params->f[0]; + hsbmul[1] = params->f[1]; + hsbmul[2] = params->f[2]; + float hsv[3]; + const int ht2 = MIN(ht, bufht); + const int wd2 = MIN(wd, bufwd); + int idx = 0; + for(int j=0;j +#include +#include +#include "gui/curveeditor.h" +#include "gui/histogram.h" +#include "develop/develop.h" +#include "control/control.h" +#include "gui/gtk.h" + +#define DT_GUI_CURVE_EDITOR_DRAW_RES 64 +#define DT_GUI_CURVE_EDITOR_INSET 5 +#define DT_GUI_CURVE_INFL .3f + +#ifndef M_PI +# define M_PI 3.14159265358979323846 /* pi */ +#endif + +void dt_gui_curve_editor_init(dt_gui_curve_editor_t *c, GtkWidget *widget) +{ + c->mouse_x = c->mouse_y = -1.0; + c->selected = -1; c->selected_offset = 0.0; + c->dragging = 0; + + c->draw.m_samplingRes = DT_GUI_CURVE_EDITOR_DRAW_RES; + c->draw.m_outputRes = 0x10000; + c->draw.m_Samples = (uint16_t *)malloc(c->draw.m_samplingRes*sizeof(uint16_t)); + c->draw_min.m_samplingRes = DT_GUI_CURVE_EDITOR_DRAW_RES; + c->draw_min.m_outputRes = 0x10000; + c->draw_min.m_Samples = (uint16_t *)malloc(c->draw.m_samplingRes*sizeof(uint16_t)); + c->draw_max.m_samplingRes = DT_GUI_CURVE_EDITOR_DRAW_RES; + c->draw_max.m_outputRes = 0x10000; + c->draw_max.m_Samples = (uint16_t *)malloc(c->draw.m_samplingRes*sizeof(uint16_t)); + + c->convert.m_samplingRes = 0x10000; + c->convert.m_outputRes = 0x10000; + c->convert.m_Samples = NULL; + + c->curve.m_curveType = TONE_CURVE; + c->curve.m_numAnchors = 6; + c->curve.m_gamma = 1.0; + c->curve.m_min_x = 0.0; + c->curve.m_max_x = 1.0; + c->curve.m_min_y = 0.0; + c->curve.m_max_y = 1.0; + + // CurveDataSample(&c->curve, &c->draw); + + g_signal_connect (G_OBJECT (widget), "expose-event", + G_CALLBACK (dt_gui_curve_editor_expose), c); + g_signal_connect (G_OBJECT (widget), "button-press-event", + G_CALLBACK (dt_gui_curve_editor_button_press), c); + g_signal_connect (G_OBJECT (widget), "button-release-event", + G_CALLBACK (dt_gui_curve_editor_button_release), c); + g_signal_connect (G_OBJECT (widget), "motion-notify-event", + G_CALLBACK (dt_gui_curve_editor_motion_notify), c); + g_signal_connect (G_OBJECT (widget), "leave-notify-event", + G_CALLBACK (dt_gui_curve_editor_leave_notify), c); +} + +void dt_gui_curve_editor_cleanup(dt_gui_curve_editor_t *c) +{ + free(c->draw.m_Samples); +} + +gboolean dt_gui_curve_editor_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + dt_gui_curve_editor_t *c = (dt_gui_curve_editor_t *)user_data; + c->mouse_x = c->mouse_y = -1.0; + gtk_widget_queue_draw(widget); + return TRUE; +} + +gboolean dt_gui_curve_editor_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + dt_gui_curve_editor_t *c = (dt_gui_curve_editor_t *)user_data; + const int inset = DT_GUI_CURVE_EDITOR_INSET; + int width = widget->allocation.width, height = widget->allocation.height; + cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(cst); + // clear bg + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + + cairo_translate(cr, inset, inset); + width -= 2*inset; height -= 2*inset; + + for(int k=0;k<6;k++) + { + DT_CTL_GET_IMAGE(c->curve.m_anchors[k].x, tonecurve_x[k]); + DT_CTL_GET_IMAGE(c->curve.m_anchors[k].y, tonecurve_y[k]); + } +#if 0 + // draw shadow around + float alpha = 1.0f; + for(int k=0;kmouse_y > 0 || c->dragging) + { + const float tmp = c->curve.m_anchors[c->selected].y; + float tmp2 = 0.0f; + if(c->selected == 2) tmp2 = c->curve.m_anchors[1].y; + if(c->selected == 3) tmp2 = c->curve.m_anchors[4].y; + if(c->selected == 2) c->curve.m_anchors[1].y = fminf(c->selected_min, fmaxf(0.0, c->curve.m_anchors[1].y + DT_GUI_CURVE_INFL*(c->selected_min - c->curve.m_anchors[2].y))); + if(c->selected == 3) c->curve.m_anchors[4].y = fmaxf(c->selected_min, fminf(1.0, c->curve.m_anchors[4].y + DT_GUI_CURVE_INFL*(c->selected_min - c->curve.m_anchors[3].y))); + c->curve.m_anchors[c->selected].y = c->selected_min; + CurveDataSample(&c->curve, &c->draw_min); + if(c->selected == 2) c->curve.m_anchors[1].y = fminf(c->selected_max, fmaxf(0.0, c->curve.m_anchors[1].y + DT_GUI_CURVE_INFL*(c->selected_max - c->curve.m_anchors[2].y))); + if(c->selected == 3) c->curve.m_anchors[4].y = fmaxf(c->selected_max, fminf(1.0, c->curve.m_anchors[4].y + DT_GUI_CURVE_INFL*(c->selected_max - c->curve.m_anchors[3].y))); + c->curve.m_anchors[c->selected].y = c->selected_max; + CurveDataSample(&c->curve, &c->draw_max); + c->curve.m_anchors[c->selected].y = tmp; + if(c->selected == 2) c->curve.m_anchors[1].y = tmp2; + if(c->selected == 3) c->curve.m_anchors[4].y = tmp2; + } + CurveDataSample(&c->curve, &c->draw); + + // draw grid + cairo_set_line_width(cr, .4); + cairo_set_source_rgb (cr, .1, .1, .1); + for(int k=1;k<4;k++) + { + cairo_move_to(cr, k/4.0*width, 0); cairo_line_to(cr, k/4.0*width, height); + // cairo_move_to(cr, c->curve.m_anchors[k].x*width, 0); cairo_line_to(cr, c->curve.m_anchors[k].x*width, height); + cairo_stroke(cr); + cairo_move_to(cr, 0, k/4.0*height); cairo_line_to(cr, width, k/4.0*height); + cairo_stroke(cr); + } + + // draw selected cursor + cairo_set_line_width(cr, 1.); + cairo_translate(cr, 0, height); + + // draw lum h istogram in background + dt_develop_t *dev = darktable.develop; + uint32_t *hist, hist_max; + hist = dev->histogram_pre; + hist_max = dev->histogram_pre_max; + if(hist_max > 0) + { + cairo_save(cr); + cairo_scale(cr, 256.0/width, -height/(float)hist_max); + cairo_set_source_rgba(cr, .2, .2, .2, 0.5); + dt_gui_histogram_draw_8(cr, hist, 3); + cairo_restore(cr); + } + + if(c->mouse_y > 0 || c->dragging) + { + // draw min/max, if selected + cairo_set_source_rgba(cr, .6, .6, .6, .5); + cairo_move_to(cr, 0, 0); + for(int k=0;kdraw.m_samplingRes;k++) cairo_line_to(cr, k*width/(float)c->draw_min.m_samplingRes, - height/(float)c->draw_min.m_outputRes*c->draw_min.m_Samples[k]); + for(int k=c->draw.m_samplingRes-2;k>0;k--) cairo_line_to(cr, k*width/(float)c->draw_max.m_samplingRes, - height/(float)c->draw_max.m_outputRes*c->draw_max.m_Samples[k]); + cairo_close_path(cr); + cairo_fill(cr); + // draw mouse focus circle + cairo_set_source_rgb(cr, .9, .9, .9); + const float pos = c->draw.m_samplingRes * c->mouse_x/(float)width; + int k = (int)pos; const float f = k - pos; + if(k >= c->draw.m_samplingRes-1) k = c->draw.m_samplingRes - 2; + float ht = -height/(float)c->draw.m_outputRes*(f*c->draw.m_Samples[k] + (1-f)*c->draw.m_Samples[k+1]); + cairo_arc(cr, c->mouse_x, ht+2.5, 4, 0, 2.*M_PI); + cairo_stroke(cr); + } + + // draw curve + cairo_set_line_width(cr, 2.); + cairo_set_source_rgb(cr, .9, .9, .9); + // cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_move_to(cr, 0, 0); + for(int k=0;kdraw.m_samplingRes;k++) cairo_line_to(cr, k*width/(float)c->draw.m_samplingRes, - height/(float)c->draw.m_outputRes*c->draw.m_Samples[k]); + cairo_stroke(cr); + + cairo_destroy(cr); + cairo_t *cr_pixmap = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface (cr_pixmap, cst, 0, 0); + cairo_paint(cr_pixmap); + cairo_destroy(cr_pixmap); + cairo_surface_destroy(cst); + return TRUE; +} + +gboolean dt_gui_curve_editor_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + const int inset = DT_GUI_CURVE_EDITOR_INSET; + dt_gui_curve_editor_t *c = (dt_gui_curve_editor_t *)user_data; + int height = widget->allocation.height - 2*inset, width = widget->allocation.width - 2*inset; + if(!c->dragging) c->mouse_x = CLAMP(event->x - inset, 0, width); + c->mouse_y = CLAMP(event->y - inset, 0, height); + + if(c->dragging) + { + float f = c->selected_y - (c->mouse_y-c->selected_offset)/height; + f = fmaxf(c->selected_min, fminf(c->selected_max, f)); + if(c->selected == 2) c->curve.m_anchors[1].y = fminf(f, fmaxf(0.0, c->curve.m_anchors[1].y + DT_GUI_CURVE_INFL*(f - c->curve.m_anchors[2].y))); + if(c->selected == 3) c->curve.m_anchors[4].y = fmaxf(f, fminf(1.0, c->curve.m_anchors[4].y + DT_GUI_CURVE_INFL*(f - c->curve.m_anchors[3].y))); + c->curve.m_anchors[c->selected].y = f; + DT_CTL_SET_IMAGE(tonecurve_y[c->selected], f); + if(c->selected == 2) DT_CTL_SET_IMAGE(tonecurve_y[1], c->curve.m_anchors[1].y); + if(c->selected == 3) DT_CTL_SET_IMAGE(tonecurve_y[4], c->curve.m_anchors[4].y); + dt_dev_add_history_item(darktable.develop, "tonecurve"); + } + else + { + float pos = (event->x - inset)/width; + float min = 100.0; + int nearest = 0; + for(int k=1;kcurve.m_numAnchors-1;k++) + { + float dist = (pos - c->curve.m_anchors[k].x); dist *= dist; + if(dist < min) { min = dist; nearest = k; } + } + c->selected = nearest; + c->selected_y = c->curve.m_anchors[c->selected].y; + c->selected_offset = c->mouse_y; + // c->selected_min = .5f*(c->selected_y + c->curve.m_anchors[c->selected-1].y); + // c->selected_max = .5f*(c->selected_y + c->curve.m_anchors[c->selected+1].y); + const float f = 0.8f; + c->selected_min = fmaxf(c->selected_y - 0.2f, (1.-f)*c->selected_y + f*c->curve.m_anchors[c->selected-1].y); + c->selected_max = fminf(c->selected_y + 0.2f, (1.-f)*c->selected_y + f*c->curve.m_anchors[c->selected+1].y); + if(c->selected == 1) c->selected_max *= 0.7; + if(c->selected == 4) c->selected_min = 1.0 - 0.7*(1.0 - c->selected_min); + } + gtk_widget_queue_draw(widget); + gint x, y; + gdk_window_get_pointer(event->window, &x, &y, NULL); + return TRUE; +} + +gboolean dt_gui_curve_editor_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ // set active point + dt_gui_curve_editor_t *c = (dt_gui_curve_editor_t *)user_data; + c->dragging = 1; + return TRUE; +} + +gboolean dt_gui_curve_editor_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_gui_curve_editor_t *c = (dt_gui_curve_editor_t *)user_data; + c->dragging = 0; + return TRUE; +} + +void dt_gui_curve_editor_get_curve(dt_gui_curve_editor_t *c, uint16_t *curve_data, dt_ctl_image_settings_t *settings) +{ + CurveData curve; + curve.m_curveType = TONE_CURVE; + curve.m_numAnchors = 6; + curve.m_gamma = 1.0; + curve.m_min_x = 0.0; + curve.m_max_x = 1.0; + curve.m_min_y = 0.0; + curve.m_max_y = 1.0; + + for(int k=0;k<6;k++) + { + curve.m_anchors[k].x = settings->tonecurve_x[k]; + curve.m_anchors[k].y = settings->tonecurve_y[k]; + } + assert(curve.m_anchors[0].x != curve.m_anchors[1].x); + c->convert.m_Samples = curve_data; + CurveDataSample(&curve, &c->convert); + + /*for(int k=0;k<6;k++) + { + DT_CTL_GET_IMAGE(c->curve.m_anchors[k].x, tonecurve_x[k]); + DT_CTL_GET_IMAGE(c->curve.m_anchors[k].y, tonecurve_y[k]); + } + c->convert.m_Samples = curve; + CurveDataSample(&c->curve, &c->convert);*/ +} + + diff --git a/src/gui/curveeditor.h b/src/gui/curveeditor.h new file mode 100644 index 000000000000..349a8bfa748d --- /dev/null +++ b/src/gui/curveeditor.h @@ -0,0 +1,30 @@ +#ifndef DARKTABLE_CURVE_EDITOR_H +#define DARKTABLE_CURVE_EDITOR_H + +#include +#include + +#include "common/nikon_curve.h" +#include "control/settings.h" + +typedef struct dt_gui_curve_editor_t +{ + double mouse_x, mouse_y; + int selected, dragging; + double selected_offset, selected_y, selected_min, selected_max; + CurveData curve; + CurveSample draw, draw_max, draw_min; + CurveSample convert; +} +dt_gui_curve_editor_t; + +void dt_gui_curve_editor_init(dt_gui_curve_editor_t *c, GtkWidget *widget); +void dt_gui_curve_editor_cleanup(dt_gui_curve_editor_t *c); +gboolean dt_gui_curve_editor_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data); +gboolean dt_gui_curve_editor_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data); +gboolean dt_gui_curve_editor_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_curve_editor_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_curve_editor_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data); +void dt_gui_curve_editor_get_curve(dt_gui_curve_editor_t *c, uint16_t *curve, dt_ctl_image_settings_t *settings); + +#endif diff --git a/src/gui/develop_view.c b/src/gui/develop_view.c new file mode 100644 index 000000000000..bb942baa11b7 --- /dev/null +++ b/src/gui/develop_view.c @@ -0,0 +1,168 @@ +#include "develop/develop.h" +#include "control/jobs.h" +#include "develop/imageop.h" +#include "common/image_cache.h" +#include "control/control.h" + +#include +#include +#include + +void dt_dev_image_expose(dt_develop_t *dev, dt_dev_image_t *image, cairo_t *cr, int32_t width, int32_t height) +{ + // float bordercol = 1.0f; + float *buf = NULL; + // get cached image for this zoom rate (or 1:1 for closeup view) + + int32_t zoom, closeup; + float zoom_x, zoom_y; + DT_CTL_GET_GLOBAL(zoom, dev_zoom); + DT_CTL_GET_GLOBAL(closeup, dev_closeup); + DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); + DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); + buf = dt_dev_get_cached_buf(dev, image, zoom, 'r'); + + dt_print(DT_DEBUG_DEV, "[dev_expose] hashes: %d %d\n", dev->small_raw_hash, dev->history[dev->history_top-1].num); + if(buf && zoom_x == dev->cache_zoom_x[image->cacheline[zoom]] && zoom_y == dev->cache_zoom_y[image->cacheline[zoom]]) + { + int32_t wd = dev->cache_width, ht = dev->cache_height; + // TODO: if(dev->backbuf_hash != dev->history[dev->history_top-1].num) +// #pragma omp parallel for schedule(static) shared(dev,buf) + for(int i=0;ibackbuf[4*i+2-k] = dev->gamma[dev->tonecurve[(int)CLAMP(buf[3*i+k]*0xffff, 0, 0xffff)]]; + int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, wd); + cairo_surface_t *surface = cairo_image_surface_create_for_data (dev->backbuf, CAIRO_FORMAT_RGB24, wd, ht, stride); + + if(zoom == DT_ZOOM_FIT) + { + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + const float scale = fminf(wd/(float)dev->image->width, ht/(float)dev->image->height); + cairo_translate(cr, wd/2, ht/2); + cairo_translate(cr, -scale*dev->image->width/2, -scale*dev->image->height/2); + cairo_rectangle(cr, 0, 0, scale*dev->image->width, scale*dev->image->height); + } + else if(zoom == DT_ZOOM_1) + { + // center lo-res images + const int iwd = (closeup ? 2 : 1)*dev->image->width; + const int iht = (closeup ? 2 : 1)*dev->image->height; + if(iwd < wd) cairo_translate(cr, (wd-iwd)/2, 0); + if(iht < ht) cairo_translate(cr, 0, (ht-iht)/2); + if(iwd < wd || iht < ht) + { + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + } + if(closeup) + { + cairo_scale(cr, 2., 2.); + cairo_translate(cr, -wd/4, -ht/4); + // fix for border near zero, to avoid buffer segfault in load_cache + if((zoom_x+.5f)*dev->image->width <= wd/2) cairo_translate(cr, wd/2 - (zoom_x+.5f)*dev->image->width, 0); + if((zoom_y+.5f)*dev->image->height <= ht/2) cairo_translate(cr, 0, ht/2 - (zoom_y+.5f)*dev->image->height); + } + cairo_rectangle(cr, 0, 0, MIN(iwd,wd), MIN(iht,ht)); + } + else cairo_rectangle(cr, 0, 0, wd, ht); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST); + cairo_fill(cr); + cairo_surface_destroy (surface); + dt_dev_release_cached_buf(dev, image, zoom); + } + else if(dev->small_backbuf_hash == dev->history[dev->history_top-1].num) + { + dt_dev_update_cache(dev, image, zoom); + int32_t wd = dev->small_raw_width, ht = dev->small_raw_height; + float fwd, fht; + dt_image_get_exact_mip_size(dev->image, DT_IMAGE_MIPF, &fwd, &fht); + float zoom_scale; + uint8_t *buf8 = dev->small_backbuf; + switch(zoom) + { + case DT_ZOOM_FIT: + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + zoom_x = zoom_y = 0.0f; + zoom_scale = fminf(width/fwd, height/fht); + break; + case DT_ZOOM_FILL: + zoom_scale = fmaxf(width/fwd, height/fht); + break; + default: // 1:1 or higher + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + zoom_scale = dev->image->width/fwd; + break; + } + int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, dev->small_raw_width); + cairo_surface_t *surface = cairo_image_surface_create_for_data (buf8, CAIRO_FORMAT_RGB24, dev->small_raw_width, dev->small_raw_height, stride); + cairo_translate(cr, width/2.0, height/2.0f); + cairo_translate(cr, (dev->small_raw_width-fwd), (dev->small_raw_height-fht)); + // cairo_scale(cr, zoom_scale*(dev->small_raw_width/fwd), zoom_scale*(dev->small_raw_height/fht)); + cairo_scale(cr, zoom_scale, zoom_scale); + if(zoom == DT_ZOOM_1 && closeup) cairo_scale(cr, 2., 2.); + cairo_translate(cr, -.5f*wd-zoom_x*fwd, -.5f*ht-zoom_y*fht); + // cairo_translate(cr, -1, -1); // compensate jumping draw below pointer + cairo_rectangle(cr, 0, 0, fwd, fht); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST); + cairo_fill(cr); + cairo_surface_destroy (surface); + } + else + { + dt_dev_update_cache(dev, image, zoom); + dt_dev_update_small_cache(dev); + } + DT_CTL_SET_GLOBAL(dev_zoom_y, zoom_y); + DT_CTL_SET_GLOBAL(dev_zoom_x, zoom_x); +} + +void dt_dev_enter() +{ + int selected; + DT_CTL_GET_GLOBAL(selected, lib_image_mouse_over_id); + + DT_CTL_SET_GLOBAL(dev_zoom_x, 0); + DT_CTL_SET_GLOBAL(dev_zoom_y, 0); + + dt_dev_load_image(darktable.develop, dt_image_cache_use(selected, 'r')); + dt_dev_configure(darktable.develop, darktable.control->width - 2*darktable.control->tabborder, darktable.control->height - 2*darktable.control->tabborder); +} + +void dt_dev_leave() +{ + if(darktable.develop->image->pixels) + dt_image_release(darktable.develop->image, DT_IMAGE_FULL, 'r'); + + dt_dev_write_history(darktable.develop); + DT_CTL_SET_GLOBAL_STR(dev_op, "original", 20); + + int wd = darktable.develop->small_raw_width, ht = darktable.develop->small_raw_height; + if(!dt_dev_small_cache_load(darktable.develop)) + { + if(dt_image_alloc(darktable.develop->image, DT_IMAGE_MIP4)) return; + dt_image_check_buffer(darktable.develop->image, DT_IMAGE_MIP4, sizeof(uint8_t)*4*wd*ht); + memcpy(darktable.develop->image->mip[DT_IMAGE_MIP4], darktable.develop->small_backbuf, sizeof(uint8_t)*4*wd*ht); + if(dt_imageio_preview_write(darktable.develop->image, DT_IMAGE_MIP4)) + fprintf(stderr, "[dev_leave] could write mip level %d of image %s to database!\n", DT_IMAGE_MIP4, darktable.develop->image->filename); + dt_image_update_mipmaps(darktable.develop->image); + dt_image_release(darktable.develop->image, DT_IMAGE_MIP4, 'w'); + dt_image_release(darktable.develop->image, DT_IMAGE_MIP4, 'r'); + dt_image_release(darktable.develop->image, DT_IMAGE_MIPF, 'r'); + } + dt_image_cache_release(darktable.develop->image, 'r'); +} + +void dt_dev_expose(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height) +{ + // if(dev->cache_width != width || dev->cache_height != height) return; + + // dt_dev_configure(dev, width, height); + // draw top history item. + if(dev->history_top > 0) + dt_dev_image_expose(dev, dev->history + dev->history_top - 1, cr, width, height); +} + diff --git a/src/gui/gtk.c b/src/gui/gtk.c new file mode 100644 index 000000000000..de4c8037dbf8 --- /dev/null +++ b/src/gui/gtk.c @@ -0,0 +1,375 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/darktable.h" +#include "library/library.h" +#include "develop/develop.h" +#include "develop/imageop.h" +#include "gui/gtk.h" +#include "control/control.h" +#include "control/jobs.h" + +static gboolean +expose (GtkWidget *da, GdkEventExpose *event, gpointer user_data) +{ + dt_control_expose(NULL); + gdk_draw_drawable(da->window, + da->style->fg_gc[GTK_WIDGET_STATE(da)], darktable.gui->pixmap, + // Only copy the area that was exposed. + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + + // update other widgets + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "navigation"); + gtk_widget_queue_draw(widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "histogram"); + gtk_widget_queue_draw(widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "tonecurve"); + gtk_widget_queue_draw(widget); + + // test quit cond (thread safe, 2nd pass) + if(!darktable.control->running) + { + dt_cleanup(); + gtk_main_quit(); + } + + // reset operations, update expanders + dt_dev_operation_t op; + DT_CTL_GET_GLOBAL_STR(op, dev_op, 20); + darktable.gui->reset = 1; + float gamma, linear, zoom; + DT_CTL_GET_IMAGE(gamma, dev_gamma_gamma); + DT_CTL_GET_IMAGE(linear, dev_gamma_linear); + widget = glade_xml_get_widget (darktable.gui->main_window, "gamma_linear"); + gtk_range_set_value(GTK_RANGE(widget), linear); + widget = glade_xml_get_widget (darktable.gui->main_window, "gamma_gamma"); + gtk_range_set_value(GTK_RANGE(widget), gamma); + + // reset non-fixed pipeline: + dt_iop_gui_reset(); + + DT_CTL_GET_GLOBAL(zoom, lib_zoom); + widget = glade_xml_get_widget (darktable.gui->main_window, "library_zoom"); + gtk_range_set_value(GTK_RANGE(widget), zoom); + darktable.gui->reset = 0; + + return TRUE; +} + +void +export_button_clicked (GtkWidget *widget, gpointer user_data) +{ + // read "export_format" to global settings + GtkWidget *wid = glade_xml_get_widget (darktable.gui->main_window, "export_format"); + gchar *text = gtk_combo_box_get_active_text(GTK_COMBO_BOX(wid)); + if(text != NULL) + { + if (!strcmp(text, "8-bit jpg")) { DT_CTL_SET_GLOBAL(dev_export_format, DT_DEV_EXPORT_JPG); } + else if(!strcmp(text, "8-bit png")) { DT_CTL_SET_GLOBAL(dev_export_format, DT_DEV_EXPORT_PNG); } + else if(!strcmp(text, "16-bit ppm")) { DT_CTL_SET_GLOBAL(dev_export_format, DT_DEV_EXPORT_PPM16); } + else if(!strcmp(text, "float pfm")) { DT_CTL_SET_GLOBAL(dev_export_format, DT_DEV_EXPORT_PFM); } + g_free(text); + } + pthread_mutex_lock(&(darktable.library->film->images_mutex)); + darktable.library->film->last_exported = 0; + pthread_mutex_unlock(&(darktable.library->film->images_mutex)); + for(int k=0;kmain_window, "main_window"); + GtkWidget *filechooser = gtk_file_chooser_dialog_new ("Import file or directory", + GTK_WINDOW (win), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, //GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run (GTK_DIALOG (filechooser)) == GTK_RESPONSE_ACCEPT) + { + char *filename; + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filechooser)); + dt_film_roll_import(darktable.library->film, filename); + g_free (filename); + } + gtk_widget_destroy (filechooser); + win = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(win); +} + +static gboolean +scrolled (GtkWidget *widget, GdkEventScroll *event, gpointer user_data) +{ + int zoom; + DT_CTL_GET_GLOBAL(zoom, lib_zoom); + if(event->direction == GDK_SCROLL_UP) + { + zoom-=2; + if(zoom < 1) zoom = 1; + DT_CTL_SET_GLOBAL(lib_zoom, zoom); + } + else if(event->direction == GDK_SCROLL_DOWN) + { + zoom+=2; + if(zoom > 2*DT_LIBRARY_MAX_ZOOM) zoom = 2*DT_LIBRARY_MAX_ZOOM; + DT_CTL_SET_GLOBAL(lib_zoom, zoom); + } + GtkWidget *range = glade_xml_get_widget (darktable.gui->main_window, "library_zoom"); + gtk_range_set_value(GTK_RANGE(range), zoom); + gtk_widget_queue_draw(widget); + return TRUE; +} + +void quit() +{ + // thread safe quit, 1st pass: + pthread_mutex_lock(&darktable.control->cond_mutex); + darktable.control->running = 0; + pthread_mutex_unlock(&darktable.control->cond_mutex); + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(widget); +} + +static gboolean +configure (GtkWidget *da, GdkEventConfigure *event, gpointer user_data) +{ + static int oldw = 0; + static int oldh = 0; + //make our selves a properly sized pixmap if our window has been resized + if (oldw != event->width || oldh != event->height) + { + //create our new pixmap with the correct size. + GdkPixmap *tmppixmap = gdk_pixmap_new(da->window, event->width, event->height, -1); + //copy the contents of the old pixmap to the new pixmap. This keeps ugly uninitialized + //pixmaps from being painted upon resize + int minw = oldw, minh = oldh; + if(event->width < minw) minw = event->width; + if(event->height < minh) minh = event->height; + gdk_draw_drawable(tmppixmap, da->style->fg_gc[GTK_WIDGET_STATE(da)], darktable.gui->pixmap, 0, 0, 0, 0, minw, minh); + //we're done with our old pixmap, so we can get rid of it and replace it with our properly-sized one. + g_object_unref(darktable.gui->pixmap); + darktable.gui->pixmap = tmppixmap; + } + oldw = event->width; + oldh = event->height; + + dt_control_configure(event->width, event->height); + + return TRUE; +} + +static void +zoom (GtkRange *range, gpointer user_data) +{ + GtkWidget *widget; + int zoom; + zoom = gtk_range_get_value(range); + DT_CTL_SET_GLOBAL(lib_zoom, zoom); + widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + gtk_widget_queue_draw(widget); +} + +static void +gamma (GtkRange *range, gpointer user_data) +{ + if(darktable.gui->reset) return; + if(user_data == (gpointer)0) + { + float linear = gtk_range_get_value(range); + DT_CTL_SET_IMAGE(dev_gamma_linear, linear); + } + else + { + float gamma = gtk_range_get_value(range); + DT_CTL_SET_IMAGE(dev_gamma_gamma, gamma); + } + dt_dev_add_history_item(darktable.develop, "gamma"); +} + + +static void +realize (GtkWidget *widget, + gpointer data) +{ +} + +static gboolean +key_pressed (GtkWidget *w, GdkEventKey *event, gpointer user_data) +{ + return dt_control_key_pressed(event->keyval); +} + +static gboolean +button_pressed (GtkWidget *w, GdkEventButton *event, gpointer user_data) +{ + dt_control_button_pressed(event->x, event->y, event->button, event->type, event->state); + gtk_widget_queue_draw(w); + return TRUE; +} + +static gboolean +button_released (GtkWidget *w, GdkEventButton *event, gpointer user_data) +{ + dt_control_button_released(event->x, event->y, event->button, event->state); + gtk_widget_queue_draw(w); + return TRUE; +} + +static gboolean +mouse_moved (GtkWidget *w, GdkEventMotion *event, gpointer user_data) +{ + dt_control_mouse_moved(event->x, event->y, event->state); + gint x, y; + gdk_window_get_pointer(event->window, &x, &y, NULL); + return TRUE; +} + +gboolean center_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + dt_control_mouse_leave(); + return TRUE; +} + + +int +dt_gui_gtk_init(dt_gui_gtk_t *gui, int argc, char *argv[]) +{ + GtkWidget *widget; + + if (!g_thread_supported ()) g_thread_init(NULL); + gdk_threads_init(); + gdk_threads_enter(); + gtk_init (&argc, &argv); + + gtk_rc_parse ("darktable.gtkrc"); + + /* load the interface */ + darktable.gui->main_window = glade_xml_new ("darktable.glade", NULL, NULL); + + /* connect the signals in the interface */ + + widget = glade_xml_get_widget (darktable.gui->main_window, "button_import"); + g_signal_connect (G_OBJECT (widget), "clicked", + G_CALLBACK (import_button_clicked), + NULL); + + /* Have the delete event (window close) end the program */ + widget = glade_xml_get_widget (darktable.gui->main_window, "main_window"); + g_signal_connect (G_OBJECT (widget), "delete_event", + G_CALLBACK (quit), NULL); + g_signal_connect (G_OBJECT (widget), "key-press-event", + G_CALLBACK (key_pressed), NULL); + + gtk_widget_show_all(widget); + widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + + g_signal_connect_after (G_OBJECT (widget), "realize", + G_CALLBACK (realize), NULL); + g_signal_connect (G_OBJECT (widget), "configure-event", + G_CALLBACK (configure), NULL); + g_signal_connect (G_OBJECT (widget), "expose-event", + G_CALLBACK (expose), NULL); + g_signal_connect (G_OBJECT (widget), "motion-notify-event", + G_CALLBACK (mouse_moved), NULL); + g_signal_connect (G_OBJECT (widget), "leave-notify-event", + G_CALLBACK (center_leave), NULL); + g_signal_connect (G_OBJECT (widget), "button-press-event", + G_CALLBACK (button_pressed), NULL); + g_signal_connect (G_OBJECT (widget), "button-release-event", + G_CALLBACK (button_released), NULL); + g_signal_connect (G_OBJECT (widget), "scroll-event", + G_CALLBACK (scrolled), NULL); + // TODO: left, right, top, bottom: + //leave-notify-event + + // lib zoom + widget = glade_xml_get_widget (darktable.gui->main_window, "library_zoom"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (zoom), NULL); + + // gamma correction expander + widget = glade_xml_get_widget (darktable.gui->main_window, "gamma_linear"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (gamma), (gpointer)0); + + widget = glade_xml_get_widget (darktable.gui->main_window, "gamma_gamma"); + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (gamma), (gpointer)1); + + // tone curve + widget = glade_xml_get_widget (darktable.gui->main_window, "tonecurve"); + dt_gui_curve_editor_init(&gui->tonecurve, widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "navigation"); + dt_gui_navigation_init(&gui->navigation, widget); + + widget = glade_xml_get_widget (darktable.gui->main_window, "histogram"); + dt_gui_histogram_init(&gui->histogram, widget); + + for(long int k=0;k<10;k++) + { + char wdname[20]; + snprintf(wdname, 20, "history_%02ld", k); + widget = glade_xml_get_widget (darktable.gui->main_window, wdname); + g_signal_connect (G_OBJECT (widget), "clicked", + G_CALLBACK (history_button_clicked), + (gpointer)k); + } + + widget = glade_xml_get_widget (darktable.gui->main_window, "export_button"); + g_signal_connect (G_OBJECT (widget), "clicked", + G_CALLBACK (export_button_clicked), + (gpointer)0); + + widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + GTK_WIDGET_UNSET_FLAGS (widget, GTK_DOUBLE_BUFFERED); + // GTK_WIDGET_SET_FLAGS (widget, GTK_DOUBLE_BUFFERED); + GTK_WIDGET_SET_FLAGS (widget, GTK_APP_PAINTABLE); + + darktable.gui->reset = 0; + dt_iop_gui_init(); + return 0; +} + +void dt_gui_gtk_cleanup(dt_gui_gtk_t *gui) +{ + dt_gui_curve_editor_cleanup(&gui->tonecurve); + dt_gui_navigation_cleanup(&gui->navigation); + dt_gui_histogram_cleanup(&gui->histogram); +} + +void dt_gui_gtk_run(dt_gui_gtk_t *gui) +{ + GtkWidget *widget = glade_xml_get_widget (darktable.gui->main_window, "center"); + darktable.gui->pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); + /* start the event loop */ + gtk_main (); + gdk_threads_leave(); +} + diff --git a/src/gui/gtk.h b/src/gui/gtk.h new file mode 100644 index 000000000000..e6213963e050 --- /dev/null +++ b/src/gui/gtk.h @@ -0,0 +1,26 @@ +#ifndef DT_GUI_GTK_H +#define DT_GUI_GTK_H + +#include +#include +#include "gui/curveeditor.h" +#include "gui/navigation.h" +#include "gui/histogram.h" + + +typedef struct dt_gui_gtk_t +{ + GladeXML *main_window; + GdkPixmap *pixmap; + dt_gui_curve_editor_t tonecurve; + dt_gui_navigation_t navigation; + dt_gui_histogram_t histogram; + int32_t reset; +} +dt_gui_gtk_t; + +int dt_gui_gtk_init(dt_gui_gtk_t *gui, int argc, char *argv[]); +void dt_gui_gtk_run(dt_gui_gtk_t *gui); +void dt_gui_gtk_cleanup(dt_gui_gtk_t *gui); + +#endif diff --git a/src/gui/histogram.c b/src/gui/histogram.c new file mode 100644 index 000000000000..8a7edb42389e --- /dev/null +++ b/src/gui/histogram.c @@ -0,0 +1,125 @@ +#include "gui/histogram.h" +#include "develop/develop.h" +#include "develop/imageop.h" + +#define DT_HIST_INSET 5 + +void dt_gui_histogram_init(dt_gui_histogram_t *n, GtkWidget *widget) +{ + n->dragging = 0; + g_signal_connect (G_OBJECT (widget), "expose-event", + G_CALLBACK (dt_gui_histogram_expose), n); + g_signal_connect (G_OBJECT (widget), "button-press-event", + G_CALLBACK (dt_gui_histogram_button_press), n); + g_signal_connect (G_OBJECT (widget), "button-release-event", + G_CALLBACK (dt_gui_histogram_button_release), n); + g_signal_connect (G_OBJECT (widget), "motion-notify-event", + G_CALLBACK (dt_gui_histogram_motion_notify), n); + g_signal_connect (G_OBJECT (widget), "leave-notify-event", + G_CALLBACK (dt_gui_histogram_leave_notify), n); +} + +void dt_gui_histogram_cleanup(dt_gui_histogram_t *n) {} + +gboolean dt_gui_histogram_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + const int inset = DT_HIST_INSET; + int width = widget->allocation.width, height = widget->allocation.height; + cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(cst); + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_paint(cr); + + cairo_translate(cr, inset, inset); + width -= 2*inset; height -= 2*inset; + +#if 0 + // draw shadow around + float alpha = 1.0f; + for(int k=0;khistogram; + hist_max = dev->histogram_max; + if(hist_max > 0) + { + // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); + cairo_translate(cr, 0, height); + cairo_scale(cr, 256.0/width, -height/(float)hist_max); + cairo_set_line_width(cr, 1.); + cairo_set_source_rgba(cr, 1., 0., 0., 0.5); + dt_gui_histogram_draw_8(cr, hist, 0); + cairo_set_source_rgba(cr, 0., 1., 0., 0.5); + dt_gui_histogram_draw_8(cr, hist, 1); + cairo_set_source_rgba(cr, 0., 0., 1., 0.5); + dt_gui_histogram_draw_8(cr, hist, 2); + // cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT); + } + + cairo_destroy(cr); + cairo_t *cr_pixmap = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface (cr_pixmap, cst, 0, 0); + cairo_paint(cr_pixmap); + cairo_destroy(cr_pixmap); + cairo_surface_destroy(cst); + return TRUE; +} + +gboolean dt_gui_histogram_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + return TRUE; +} +gboolean dt_gui_histogram_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + return TRUE; +} +gboolean dt_gui_histogram_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + return TRUE; +} +gboolean dt_gui_histogram_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + return TRUE; +} + +void dt_gui_histogram_draw_8(cairo_t *cr, uint32_t *hist, int32_t channel) +{ + cairo_move_to(cr, 0, 0); + for(int k=0;k<256;k++) + { + // cairo_move_to(cr, k, 0); + cairo_line_to(cr, k, hist[4*k+channel]); + //cairo_stroke(cr); + } + cairo_line_to(cr, 255, 0); + cairo_close_path(cr); + cairo_fill(cr); +} diff --git a/src/gui/histogram.h b/src/gui/histogram.h new file mode 100644 index 000000000000..06b6cd1c8259 --- /dev/null +++ b/src/gui/histogram.h @@ -0,0 +1,22 @@ +#ifndef DARKTABLE_GUI_HISTOGRAM_H +#define DARKTABLE_GUI_HISTOGRAM_H + +#include +#include + +typedef struct dt_gui_histogram_t +{ + int32_t dragging; +} +dt_gui_histogram_t; + +void dt_gui_histogram_init(dt_gui_histogram_t *n, GtkWidget *widget); +void dt_gui_histogram_cleanup(dt_gui_histogram_t *n); +gboolean dt_gui_histogram_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data); +gboolean dt_gui_histogram_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data); +gboolean dt_gui_histogram_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_histogram_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_histogram_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data); +void dt_gui_histogram_draw_8(cairo_t *cr, uint32_t *hist, int32_t channel); + +#endif diff --git a/src/gui/navigation.c b/src/gui/navigation.c new file mode 100644 index 000000000000..fd4bf186bdaa --- /dev/null +++ b/src/gui/navigation.c @@ -0,0 +1,170 @@ + +#include "gui/gtk.h" +#include "gui/navigation.h" +#include "library/library.h" +#include "develop/develop.h" +#include "control/control.h" + +#include +#define DT_NAVIGATION_INSET 5 + + +void dt_gui_navigation_init(dt_gui_navigation_t *n, GtkWidget *widget) +{ + n->dragging = 0; + g_signal_connect (G_OBJECT (widget), "expose-event", + G_CALLBACK (dt_gui_navigation_expose), n); + g_signal_connect (G_OBJECT (widget), "button-press-event", + G_CALLBACK (dt_gui_navigation_button_press), n); + g_signal_connect (G_OBJECT (widget), "button-release-event", + G_CALLBACK (dt_gui_navigation_button_release), n); + g_signal_connect (G_OBJECT (widget), "motion-notify-event", + G_CALLBACK (dt_gui_navigation_motion_notify), n); + g_signal_connect (G_OBJECT (widget), "leave-notify-event", + G_CALLBACK (dt_gui_navigation_leave_notify), n); +} + +void dt_gui_navigation_cleanup(dt_gui_navigation_t *n) {} + +gboolean dt_gui_navigation_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +{ + const int inset = DT_NAVIGATION_INSET; + int width = widget->allocation.width, height = widget->allocation.height; + cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cr = cairo_create(cst); + + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_paint(cr); + + width -= 2*inset; height -= 2*inset; + cairo_translate(cr, inset, inset); + + dt_image_t *image = darktable.develop->image; + dt_image_buffer_t mip = DT_IMAGE_NONE; + int32_t iiwd, iiht; + float iwd, iht; + if(image) + { + mip = dt_image_get_matching_mip_size(image, width, height, &iiwd, &iiht); + mip = dt_image_get(image, mip, 'r'); + dt_image_get_exact_mip_size(image, mip, &iwd, &iht); + dt_image_get_mip_size(image, mip, &iiwd, &iiht); + } + if(mip != DT_IMAGE_NONE) + { + const float scale = fminf(width/iwd, height/iht); + int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, iiwd); + // cairo_save(cr); + cairo_surface_t *surface = cairo_image_surface_create_for_data (image->mip[mip], CAIRO_FORMAT_RGB24, iiwd, iiht, stride); + cairo_translate(cr, width/2.0, height/2.0f); + cairo_scale(cr, scale, scale); + cairo_translate(cr, -.5f*iwd, -.5f*iht); + + // draw shadow around + float alpha = 1.0f; + for(int k=0;k<4;k++) + { + cairo_rectangle(cr, -k/scale, -k/scale, iwd + 2*k/scale, iht + 2*k/scale); + cairo_set_source_rgba(cr, 0, 0, 0, alpha); + alpha *= 0.6f; + cairo_fill(cr); + } + + cairo_rectangle(cr, 0, 0, iwd, iht); + // cairo_stroke_preserve(cr); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_fill(cr); + cairo_surface_destroy (surface); + // cairo_restore(cr); + // draw box where we are + dt_dev_zoom_t zoom; + int closeup; + float zoom_x, zoom_y; + DT_CTL_GET_GLOBAL(zoom, dev_zoom); + DT_CTL_GET_GLOBAL(closeup, dev_closeup); + DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); + DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); + if(zoom != DT_ZOOM_FIT) + { + float boxw = 1, boxh = 1; + dt_dev_check_zoom_bounds(darktable.develop, &zoom_x, &zoom_y, zoom, closeup, &boxw, &boxh); + + DT_CTL_SET_GLOBAL(dev_zoom_x, zoom_x); + DT_CTL_SET_GLOBAL(dev_zoom_y, zoom_y); + + cairo_translate(cr, iwd*(.5f+zoom_x), iht*(.5f+zoom_y)); + cairo_set_source_rgb(cr, 0., 0., 0.); + cairo_set_line_width(cr, 1.f); + boxw *= iwd; + boxh *= iht; + cairo_rectangle(cr, -boxw/2-1, -boxh/2-1, boxw+2, boxh+2); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 1., 1., 1.); + cairo_rectangle(cr, -boxw/2, -boxh/2, boxw, boxh); + cairo_stroke(cr); + } + dt_image_release(image, mip, 'r'); + } + + cairo_destroy(cr); + cairo_t *cr_pixmap = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface (cr_pixmap, cst, 0, 0); + cairo_paint(cr_pixmap); + cairo_destroy(cr_pixmap); + cairo_surface_destroy(cst); + return TRUE; +} + +void dt_gui_navigation_set_position(dt_gui_navigation_t *n, double x, double y, int wd, int ht) +{ + dt_dev_zoom_t zoom; + int closeup; + float zoom_x, zoom_y; + DT_CTL_GET_GLOBAL(zoom, dev_zoom); + DT_CTL_GET_GLOBAL(closeup, dev_closeup); + DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); + DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); + + if(n->dragging && zoom != DT_ZOOM_FIT) + { + const int inset = DT_NAVIGATION_INSET; + const float width = wd - 2*inset, height = ht - 2*inset; + const float iwd = darktable.develop->image->width, iht = darktable.develop->image->height; + zoom_x = fmaxf(-.5, fminf(((x-inset)/width - .5f), .5))/(iwd*fminf(wd/iwd, ht/iht)/wd); + zoom_y = fmaxf(-.5, fminf(((y-inset)/height - .5f), .5))/(iht*fminf(wd/iwd, ht/iht)/ht); + dt_dev_check_zoom_bounds(darktable.develop, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL); + DT_CTL_SET_GLOBAL(dev_zoom_x, zoom_x); + DT_CTL_SET_GLOBAL(dev_zoom_y, zoom_y); + + dt_control_gui_queue_draw(); + } +} + +gboolean dt_gui_navigation_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + dt_gui_navigation_t *n = (dt_gui_navigation_t *)user_data; + dt_gui_navigation_set_position(n, event->x, event->y, widget->allocation.width, widget->allocation.height); + gint x, y; // notify gtk for motion_hint. + gdk_window_get_pointer(event->window, &x, &y, NULL); + return TRUE; +} +gboolean dt_gui_navigation_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_gui_navigation_t *n = (dt_gui_navigation_t *)user_data; + n->dragging = 1; + dt_gui_navigation_set_position(n, event->x, event->y, widget->allocation.width, widget->allocation.height); + return TRUE; +} +gboolean dt_gui_navigation_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_gui_navigation_t *n = (dt_gui_navigation_t *)user_data; + n->dragging = 0; + return TRUE; +} +gboolean dt_gui_navigation_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + return TRUE; +} +void dt_gui_navigation_get_pos(dt_gui_navigation_t *n, float *x, float *y) +{ +} diff --git a/src/gui/navigation.h b/src/gui/navigation.h new file mode 100644 index 000000000000..0f136b47a8d3 --- /dev/null +++ b/src/gui/navigation.h @@ -0,0 +1,22 @@ +#ifndef DARKTABLE_NAVIGATION_H +#define DARKTABLE_NAVIGATION_H + +#include +#include + +typedef struct dt_gui_navigation_t +{ + int32_t dragging; +} +dt_gui_navigation_t; + +void dt_gui_navigation_init(dt_gui_navigation_t *n, GtkWidget *widget); +void dt_gui_navigation_cleanup(dt_gui_navigation_t *n); +gboolean dt_gui_navigation_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data); +gboolean dt_gui_navigation_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data); +gboolean dt_gui_navigation_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_navigation_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data); +gboolean dt_gui_navigation_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data); +void dt_gui_navigation_get_pos(dt_gui_navigation_t *n, float *x, float *y); + +#endif diff --git a/src/library/film.c b/src/library/film.c new file mode 100644 index 000000000000..86eea6a01bf1 --- /dev/null +++ b/src/library/film.c @@ -0,0 +1,122 @@ +#include "library/library.h" +#include "common/imageio.h" +#include "control/control.h" +#include "control/jobs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void dt_film_roll_init(dt_film_roll_t *film) +{ + pthread_mutex_init(&film->images_mutex, NULL); + film->last_loaded = film->num_images = film->last_exported = 0; + film->dirname[0] = '\0'; + film->dir = NULL; +} + +void dt_film_import1(dt_film_roll_t *film) +{ + const gchar *d_name; + char filename[1024]; + dt_image_t image; + + while(1) + { + pthread_mutex_lock(&film->images_mutex); + if (film->dir && (d_name = g_dir_read_name(film->dir)) && darktable.control->running) + { + snprintf(filename, 1024, "%s/%s", film->dirname, d_name); + image.film_id = film->id; + film->last_loaded++; + } + else + { + if(film->dir) + { + g_dir_close(film->dir); + film->dir = NULL; + } + darktable.control->progress = 200.0f; + pthread_mutex_unlock(&film->images_mutex); + return; + } + pthread_mutex_unlock(&film->images_mutex); + + if(!dt_image_import(film->id, filename)) + { + pthread_mutex_lock(&film->images_mutex); + darktable.control->progress = 100.0f*film->last_loaded/(float)film->num_images; + pthread_mutex_unlock(&film->images_mutex); + dt_control_queue_draw(); + } // else not an image. + } +} + +void dt_film_roll_cleanup(dt_film_roll_t *film) +{ + pthread_mutex_destroy(&film->images_mutex); +} + +int dt_film_roll_open(dt_film_roll_t *film, const int32_t id) +{ + // TODO: + // select id from images where film_id = id + // and prefetch to cache using image_open + // TODO: update last used film date stamp + DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, -1); + return 1; +} +int dt_film_roll_import(dt_film_roll_t *film, const char *dirname) +{ + film->id = -1; + int rc; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select id from film_rolls where folder = ?1", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_text(stmt, 1, dirname, strlen(dirname), SQLITE_STATIC); + HANDLE_SQLITE_ERR(rc); + if(sqlite3_step(stmt) == SQLITE_ROW) film->id = sqlite3_column_int(stmt, 0); + rc = sqlite3_finalize(stmt); + if(film->id <= 0) + { + // TODO: insert timestamp + // gsize gret = g_date_strftime(gchar *s, gsize slen, "%Y:%m:%d %H:%M:%S", const GDate *date); + + rc = sqlite3_prepare_v2(darktable.db, "insert into film_rolls (id, folder) values (null, ?1)", -1, &stmt, NULL); + HANDLE_SQLITE_ERR(rc); + rc = sqlite3_bind_text(stmt, 1, dirname, strlen(dirname), SQLITE_STATIC); + HANDLE_SQLITE_ERR(rc); + pthread_mutex_lock(&(darktable.db_insert)); + rc = sqlite3_step(stmt); + if(rc != SQLITE_DONE) fprintf(stderr, "failed to insert film roll! %s\n", sqlite3_errmsg(darktable.db)); + rc = sqlite3_finalize(stmt); + film->id = sqlite3_last_insert_rowid(darktable.db); + pthread_mutex_unlock(&(darktable.db_insert)); + } + if(film->id <= 0) return 1; + + DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, -1); + + film->last_loaded = 0; + strncpy(film->dirname, dirname, 512); + film->dir = g_dir_open(film->dirname, 0, NULL); + darktable.control->progress = .001f; + for(int k=0;k +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void dt_library_init(dt_library_t *lib) +{ + lib->film = (dt_film_roll_t *)malloc(sizeof(dt_film_roll_t)); + lib->select_offset_x = lib->select_offset_y = 0.5f; + lib->last_selected_id = -1; + lib->button = 0; + lib->modifiers = 0; + dt_film_roll_init(lib->film); +} + +void dt_library_cleanup(dt_library_t *lib) +{ + dt_film_roll_cleanup(lib->film); + free(lib->film); +} + +void dt_library_toggle_selection(int iid) +{ + int rc; + sqlite3_stmt *stmt; + rc = sqlite3_prepare_v2(darktable.db, "select * from selected_images where imgid = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, iid); + if(sqlite3_step(stmt) == SQLITE_ROW) + { + sqlite3_finalize(stmt); + rc = sqlite3_prepare_v2(darktable.db, "delete from selected_images where imgid = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, iid); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + else + { + sqlite3_finalize(stmt); + rc = sqlite3_prepare_v2(darktable.db, "insert into selected_images values (?1)", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, iid); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + } +} + +void dt_library_expose(dt_library_t *lib, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) +{ + float zoom, zoom_x, zoom_y; + int32_t mouse_over_id, pan, track, center; + DT_CTL_GET_GLOBAL(mouse_over_id, lib_image_mouse_over_id); + DT_CTL_GET_GLOBAL(zoom, lib_zoom); + DT_CTL_GET_GLOBAL(zoom_x, lib_zoom_x); + DT_CTL_GET_GLOBAL(zoom_y, lib_zoom_y); + DT_CTL_GET_GLOBAL(pan, lib_pan); + DT_CTL_GET_GLOBAL(track, lib_track); + DT_CTL_GET_GLOBAL(center, lib_center); + + cairo_set_source_rgb (cr, .2, .2, .2); + cairo_paint(cr); + + const float wd = width/zoom; + const float ht = width/zoom; + + static int oldpan = 0; + static float oldzoom = -1; + if(oldzoom < 0) oldzoom = zoom; + + // TODO: exaggerate mouse gestures to pan when zoom == 1 + if(pan)// && mouse_over_id >= 0) + { + zoom_x = lib->select_offset_x - /* (zoom == 1 ? 2. : 1.)*/pointerx; + zoom_y = lib->select_offset_y - /* (zoom == 1 ? 2. : 1.)*/pointery; + } + + if(oldzoom != zoom) + { + float oldx = (pointerx + zoom_x)*oldzoom/width; + float oldy = (pointery + zoom_y)*oldzoom/width; + if(zoom == 1) + { + zoom_x = (int)oldx*wd; + zoom_y = (int)oldy*ht; + } + else + { + zoom_x = oldx*wd - pointerx; + zoom_y = oldy*ht - pointery; + } + } + oldzoom = zoom; + + // TODO: track on aoe, keys! + if(center) + { + if(mouse_over_id >= 0) + { + zoom_x -= width*.5f - pointerx; + zoom_y -= height*.5f - pointery; + } + else zoom_x = zoom_y = 0.0; + center = 0; + } + + // mouse left the area + if(!pan) DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, -1); + + int offset_i = (int)(zoom_x/wd); + int offset_j = (int)(zoom_y/ht); + float offset_x = zoom == 1 ? 0.0 : zoom_x/wd - (int)(zoom_x/wd); + float offset_y = zoom == 1 ? 0.0 : zoom_y/ht - (int)(zoom_y/ht); + const int max_rows = zoom == 1 ? 1 : 2 + (int)((height)/ht + .5); + const int max_cols = zoom == 1 ? 1 : MIN(DT_LIBRARY_MAX_ZOOM - MAX(0, offset_i), 1 + (int)(zoom+.5)); + + int offset = MAX(0, offset_i) + DT_LIBRARY_MAX_ZOOM*offset_j; + int seli = (int)((pointerx + zoom_x)/wd) - MAX(offset_i, 0); + int selj = (int)((pointery + zoom_y)/ht) - offset_j; + + sqlite3_stmt *stmt, *stmt2; + int rc, rc2, id, clicked1, last_seli = 1<<30, last_selj = 1<<30; + clicked1 = (oldpan == 0 && pan == 1 && lib->button == 1); + if(clicked1 && + (lib->modifiers & GDK_SHIFT_MASK) == 0 && (lib->modifiers & GDK_CONTROL_MASK) == 0) + { // clear selected if no modifier + rc2 = sqlite3_prepare_v2(darktable.db, "delete from selected_images", -1, &stmt2, NULL); + rc2 = sqlite3_step(stmt2); + sqlite3_finalize(stmt2); + } + // TODO: order by and where clauses from sort widget! + rc = sqlite3_prepare_v2(darktable.db, "select * from (select id from images where film_id = ?1 order by filename) as dreggn limit ?2, ?3", -1, &stmt, NULL); + cairo_translate(cr, -offset_x*wd, -offset_y*ht); + cairo_translate(cr, -MIN(offset_i*wd, 0.0), 0.0); + for(int row = 0; row < max_rows; row++) + { + if(offset < 0) + { + cairo_translate(cr, 0, ht); + offset += DT_LIBRARY_MAX_ZOOM; + continue; + } + rc = sqlite3_bind_int (stmt, 1, lib->film->id); + rc = sqlite3_bind_int (stmt, 2, offset); + rc = sqlite3_bind_int (stmt, 3, max_cols); + for(int col = 0; col < max_cols; col++) + { + if(sqlite3_step(stmt) == SQLITE_ROW) + { + id = sqlite3_column_int(stmt, 0); + dt_image_t *image = dt_image_cache_get(id, 'r'); + if(image) + { + // set mouse over id + if((zoom == 1 && mouse_over_id < 0) || (!pan && seli == col && selj == row)) + { + mouse_over_id = image->id; + DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, mouse_over_id); + } + // add clicked image to selected table + if(clicked1) + { + // FIXME: whatever comes first assumtion is broken! + // if((lib->modifiers & GDK_SHIFT_MASK) && (last_seli == (1<<30)) && + // (image->id == lib->last_selected_id || image->id == mouse_over_id)) { last_seli = col; last_selj = row; } + // if(last_seli < (1<<30) && ((lib->modifiers & GDK_SHIFT_MASK) && (col >= MIN(last_seli,seli) && row >= MIN(last_selj,selj) && + // col <= MAX(last_seli,seli) && row <= MAX(last_selj,selj)) && (col != last_seli || row != last_selj)) || + if((lib->modifiers & GDK_SHIFT_MASK) && image->id == lib->last_selected_id) { last_seli = col; last_selj = row; } + if(last_seli < (1<<30) && ((lib->modifiers & GDK_SHIFT_MASK) && (col >= last_seli && row >= last_selj && + col <= seli && row <= selj) && (col != last_seli || row != last_selj)) || + (seli == col && selj == row)) + { // insert all in range if shift, or only the one the mouse is over for ctrl or plain click. + dt_library_toggle_selection(image->id); + lib->last_selected_id = image->id; + } + } + cairo_save(cr); + dt_image_expose(image, image->id, cr, wd, zoom == 1 ? height : ht, zoom); + cairo_restore(cr); + dt_image_cache_release(image, 'r'); + } + } + cairo_translate(cr, wd, 0.0f); + } + cairo_translate(cr, -max_cols*wd, ht); + offset += DT_LIBRARY_MAX_ZOOM; + rc = sqlite3_reset(stmt); + rc = sqlite3_clear_bindings(stmt); + } + sqlite3_finalize(stmt); + + oldpan = pan; + DT_CTL_SET_GLOBAL(lib_zoom_x, zoom_x); + DT_CTL_SET_GLOBAL(lib_zoom_y, zoom_y); + DT_CTL_SET_GLOBAL(lib_track, track); + DT_CTL_SET_GLOBAL(lib_center, center); +} + +void dt_library_button_released(dt_library_t *lib, double x, double y, int which, uint32_t state) +{ + DT_CTL_SET_GLOBAL(lib_pan, 0); +} + +void dt_library_button_pressed(dt_library_t *lib, double xx, double yy, int which, uint32_t state) +{ + lib->modifiers = state; + lib->button = which; + DT_CTL_GET_GLOBAL(lib->select_offset_x, lib_zoom_x); + DT_CTL_GET_GLOBAL(lib->select_offset_y, lib_zoom_y); + lib->select_offset_x += xx; + lib->select_offset_y += yy; + DT_CTL_SET_GLOBAL(lib_pan, 1); +} + +void dt_library_mouse_leave(dt_library_t *lib) +{ + if(!darktable.control->global_settings.lib_pan && darktable.control->global_settings.lib_zoom != 1) + { + DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, -1); + dt_control_queue_draw(); // remove focus + } +} + +void dt_library_mouse_moved(dt_library_t *lib, double xx, double yy, int which) +{ + dt_control_queue_draw(); +} + +void dt_image_expose(dt_image_t *img, int32_t index, cairo_t *cr, int32_t width, int32_t height, int32_t zoom) +{ + float bgcol = 0.4, fontcol = 0.5, bordercol = 0.1; + int selected = 0, imgsel; + DT_CTL_GET_GLOBAL(imgsel, lib_image_mouse_over_id); + // if(img->flags & DT_IMAGE_SELECTED) selected = 1; + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_prepare_v2(darktable.db, "select * from selected_images where imgid = ?1", -1, &stmt, NULL); + rc = sqlite3_bind_int (stmt, 1, img->id); + if(sqlite3_step(stmt) == SQLITE_ROW) selected = 1; + sqlite3_finalize(stmt); + if(selected == 1) + { + bgcol = 0.6; fontcol = 0.5; + } + if(imgsel == img->id) { bgcol = 0.8; fontcol = 0.7; } // selected = 1; + float imgwd = 0.8f; + if(zoom == 1) + { + // TODO: draw exif info etc + imgwd = .97f; + // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); + } + else + { + char num[10]; + cairo_rectangle(cr, 0.01f*width, 0.01f*height, 0.99f*width, 0.99f*height); + cairo_set_source_rgb(cr, bgcol, bgcol, bgcol); + cairo_fill(cr); + // cairo_rectangle(cr, 0, 0, width, height); + // cairo_set_source_rgb(cr, bgcol, bgcol, bgcol); + // cairo_fill_preserve(cr); + // cairo_set_source_rgb(cr, fontcol, fontcol, fontcol); + // cairo_set_line_width(cr, 1); + // cairo_stroke(cr); + + cairo_set_source_rgb(cr, fontcol, fontcol, fontcol); + cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, .3*width); + + cairo_move_to (cr, .0*width, .24*height); + snprintf(num, 10, "%d", index); + cairo_show_text (cr, num); + } + +#if 1 + int32_t iwd = width*imgwd, iht = height*imgwd, stride; + float scale = 1.0; + dt_image_buffer_t mip; + mip = dt_image_get_matching_mip_size(img, imgwd*width, imgwd*height, &iwd, &iht); + mip = dt_image_get(img, mip, 'r'); + dt_image_get_mip_size(img, mip, &iwd, &iht); + float fwd, fht; + dt_image_get_exact_mip_size(img, mip, &fwd, &fht); + cairo_surface_t *surface = NULL; + if(mip != DT_IMAGE_NONE) + { + stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, iwd); + surface = cairo_image_surface_create_for_data (img->mip[mip], CAIRO_FORMAT_RGB24, iwd, iht, stride); + scale = fminf(width*imgwd/fwd, height*imgwd/fht); + } + + // draw centered and fitted: + cairo_save(cr); + cairo_translate(cr, width/2.0, height/2.0f); + cairo_scale(cr, scale, scale); + cairo_translate(cr, -.5f*fwd, -.5f*fht); + + const float border = zoom == 1 ? 16/scale : 2/scale; + + if(mip != DT_IMAGE_NONE) + { + cairo_set_source_surface (cr, surface, 0, 0); + cairo_rectangle(cr, 0, 0, fwd, fht); + cairo_fill(cr); + cairo_surface_destroy (surface); + dt_image_release(img, mip, 'r'); + } + + if(zoom == 1) cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST); + cairo_rectangle(cr, 0, 0, fwd, fht); + cairo_set_source_rgb(cr, bordercol, bordercol, bordercol); + if(selected) + { + cairo_set_line_width(cr, 1./scale); + if(zoom == 1) + { // draw shadow around border + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_stroke(cr); + // cairo_new_path(cr); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + float alpha = 1.0f; + for(int k=0;k<16;k++) + { + cairo_rectangle(cr, 0, 0, fwd, fht); + cairo_new_sub_path(cr); + cairo_rectangle(cr, -k/scale, -k/scale, fwd+2.*k/scale, fht+2.*k/scale); + cairo_set_source_rgba(cr, 0, 0, 0, alpha); + alpha *= 0.6f; + cairo_fill(cr); + } + } + else + { + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_new_sub_path(cr); + cairo_rectangle(cr, -border, -border, fwd+2.*border, fht+2.*border); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol); + cairo_fill(cr); + } + } + else + { + cairo_set_line_width(cr, 1); + cairo_stroke(cr); + } + cairo_restore(cr); + + if(selected && (zoom == 1)) + { + cairo_set_source_rgb(cr, fontcol, fontcol, fontcol); + cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, .02*width); + + cairo_move_to (cr, .0*width, .024*height); + cairo_show_text(cr, img->filename); + // cairo_text_path(cr, image->filename); + // cairo_fill_preserve(cr); + // cairo_set_source_rgb(cr, 0, 0, 0); + // cairo_stroke(cr); + } + + // if(zoom == 1) cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT); +#endif +} + diff --git a/src/library/library.h b/src/library/library.h new file mode 100644 index 000000000000..b8155e5a9daa --- /dev/null +++ b/src/library/library.h @@ -0,0 +1,63 @@ + +#ifndef DARKTABLE_LIBRARY_H +#define DARKTABLE_LIBRARY_H + +#include "common/darktable.h" +#include "common/image.h" +#include +#include +#include + +#define DT_LIBRARY_MAX_ZOOM 13 + +void dt_image_expose(dt_image_t *image, int32_t i, cairo_t *cr, int32_t width, int32_t height, int32_t zoom); + +/** + * film roll. + * this is one directory of images on disk. + * also manages the preview image cache. + */ +typedef struct dt_film_roll_t +{ + int32_t id; + char dirname[512]; + pthread_mutex_t images_mutex; + GDir *dir; + int32_t num_images, last_loaded, last_exported; +} +dt_film_roll_t; + +struct dt_library_t; +void dt_film_roll_init(dt_film_roll_t *film); +void dt_film_roll_cleanup(dt_film_roll_t *film); +int dt_film_roll_open(dt_film_roll_t *film, const int32_t id); +int dt_film_roll_import(dt_film_roll_t *film, const char *dirname); + +void dt_film_import1(dt_film_roll_t *film); + +/** + * this organises the whole library: + * previously imported film rolls.. + */ +typedef struct dt_library_t +{ + dt_film_roll_t *film; + // tmp mouse vars: + float select_offset_x, select_offset_y; + int32_t last_selected_id; + int button; + uint32_t modifiers; +} +dt_library_t; + +void dt_library_init(dt_library_t *lib); +void dt_library_cleanup(dt_library_t *lib); +// event handling: +void dt_library_expose(dt_library_t *lib, cairo_t *cr, int width, int height, int32_t pointerx, int32_t pointery); +void dt_library_button_pressed(dt_library_t *lib, double x, double y, int which, uint32_t state); +void dt_library_button_released(dt_library_t *lib, double x, double y, int which, uint32_t state); +void dt_library_mouse_moved(dt_library_t *lib, double x, double y, int which); +void dt_library_mouse_leave(dt_library_t *lib); + + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000000..2801583b5183 --- /dev/null +++ b/src/main.c @@ -0,0 +1,11 @@ +#include "common/darktable.h" +#include "gui/gtk.h" +#include + +int main (int argc, char *argv[]) +{ + if(dt_init(argc, argv)) exit(1); + dt_gui_gtk_run(darktable.gui); + exit(0); +} + diff --git a/src/shared.am b/src/shared.am new file mode 100644 index 000000000000..b3cee9c5cfdf --- /dev/null +++ b/src/shared.am @@ -0,0 +1 @@ +AM_CFLAGS=$(MAGICK_CPPFLAGS) $(CAIRO_CFLAGS) $(GLADE_CFLAGS) $(GTK_CFLAGS) $(GTHREAD_CFLAGS) $(SQLITE_CFLAGS) -Wall -std=c99 -g -march=native -mfpmath=sse -ffast-math -msse2 -I$(srcdir)/LibRaw -fopenmp -fPIC -fno-strict-aliasing