├── LICENSE ├── Nanocolor-Introduction.pdf ├── Nanocolor.md ├── README.md ├── nanocolor.c ├── nanocolor.h ├── nanocolorProcessing.h ├── nanocolorUtils.c └── nanocolorUtils.h /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Nanocolor-Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshula/Nanocolor/db3a6c9d778dca5d6feff9fd4ef13da84fe43e70/Nanocolor-Introduction.pdf -------------------------------------------------------------------------------- /Nanocolor.md: -------------------------------------------------------------------------------- 1 | # Nanocolor 2 | 3 | ## A very small color transform library 4 | 5 | ### Pixar Animation Studios 6 | 7 | ## Introduction 8 | 9 | Color management and transformation is a complex domain. 10 | Values are captured by cameras or generated by renderers, 11 | transferred to a storage medium, combined into frame buffers 12 | to make new images, transformed for display, and projected 13 | by devices. During each step, the color values may be 14 | transformed by photochemical, electro-optical, and digital 15 | processes. 16 | 17 | A renderer's input working space to output working space is 18 | an interesting subset of that domain, and it's the subset 19 | that Nanocolor concerns itself with. 20 | 21 | OpenEXR goes so far as to restrict itself to linear working 22 | spaces, and describes them completed by specifying chromaticities 23 | and an adapted whitepoint in the CIE XYZ 1931 space. Nanocolor 24 | takes inspiration from this, and uses the equations in SMPTE 25 | document RP177-1993 (reaffirmed in 2002) ~ SMPTE Recommended 26 | Practice: Derivation of Basic Television Equations. 27 | 28 | Interesting colors do not just come from OpenEXR however, but 29 | may originate as properties in a MaterialX shade graph, vertex 30 | attributes in an OpenUSD file, or PNG or TIFF textures, to 31 | name a few common sources. 32 | 33 | Amongst all these sources, only OpenEXR specifies colors in terms 34 | of chromaticities and whitepoint. Some sources specify the color 35 | space in documentation, whereas others name color spaces in the 36 | data itself, or refer to site specific configuration files in 37 | the OpenColorIO format, and other alternatives. This lack of 38 | broad agreement on such things as what the name of a colorspace 39 | actually refers to makes accurate and reproducible color 40 | calculations between software packages and studios challenging. 41 | 42 | MaterialX takes an interesting perspective; it names a bakers' 43 | dozen of color spaces and defines them as OpenEXR does, and 44 | also introduces a notion of being able to remove an input transform 45 | from a color in order to accomodate image formats commonly used 46 | as texture sources for shader graphs. 47 | 48 | Nanocolor follows this idea, and also introduces that all the 49 | color spaces must be invertible, in the sense that a value can 50 | have its input transform removed to make it a linear color value, 51 | linear values may be transformed as desired through matrix 52 | multiplication, and an output transform may be re-applied. This 53 | allows Nanocolor to transform any color space with this invertible 54 | property to any other with the invertible property. 55 | 56 | ## Nanocolor API 57 | 58 | For a description of data types, please refer to Nanocolor.h 59 | 60 | ```c 61 | NcColorSpace NcGetNamedColorSpace(const char* name); 62 | NcM33f NcGetRGBToXYZMatrix(NcColorSpace* cs); 63 | NcM33f NcGetXYZToRGBMatrix(NcColorSpace* cs); 64 | NcColorTransform NcGetRGBToRGBTransform(NcColorSpace* src, NcColorSpace* dst); 65 | NcRGB NcTransformColor(NcColorSpace* dst, NcColorSpace* src, NcRGB rgb); 66 | NcXYZ NcRGBToXYZ(NcColorSpace* cs, NcRGB rgb); 67 | NcRGB NcXYZToRGB(NcColorSpace* cs, NcXYZ xyz); 68 | void NcInitColorSpace(NcColorSpace* cs); 69 | NcColorSpace NcMakeEmptyColorSpace(void); 70 | ``` 71 | 72 | `NcGetNamedColorSpace` ~ returns a named color space, if it is 73 | known by Nanocolor 74 | 75 | `NcGetRGBToXYZMatrix` ~ given a color space compute the 76 | corresponding RP177-1993 3x3 matrix 77 | 78 | `NcGetXYZToRGBMatrix` ~ given a color space compute the 79 | corresponding RP177-1993 3x3 matrix 80 | 81 | `NcGetRGBToRGBTransform` ~ given two color spaces, compute a 82 | color transform that moves a color from the source color 83 | space to a destination 84 | 85 | `NcTransformColor` ~ a convience function, that given a color and 86 | two color spaces, transforms the color and returns it. Note that 87 | if many values must be transformed it's far more efficient to reuse 88 | a color transform object. 89 | 90 | `NcRGBToXYZ` ~ return the XYZ coordinates for an RGB color 91 | 92 | `NcXYZToRGB` ~ return an RGB color given XYZ coordinates 93 | 94 | `NcInitColorSpace` ~ create an identity color space which can be 95 | further modified to create a color space object compatible with 96 | the other functions 97 | 98 | `NcMakeEmptyColorSpace` ~ allocates a color space object using 99 | malloc. deprecated. 100 | 101 | ## NanocolorUtils API 102 | 103 | `NanocolorUtils.h` declares a number of useful functions such as 104 | might be used by test programs and the like. It is an optional 105 | component, and you may choose to omit it from your project. 106 | 107 | ```c 108 | NcXYZ NcKelvinToYxy(float temperature, float luminosity); 109 | NcRGB* NcISO17321_AP0_ColorChips(); 110 | NcXYZ NcProjectToChromaticities(NcXYZ c); 111 | NcXYZ NcNormalizeXYZ(NcXYZ c); 112 | NcRGB NcRGBFromYxy(NcColorSpace* cs, NcYxy c); 113 | NcXYZ NcCIE1931ColorFromWavelength(float lambda, bool approx); 114 | ``` 115 | 116 | `NcKelvinToYxy` ~ returns an XYZ coordinate for the blackbody 117 | emission spectrum for values between 1667 and 25000K 118 | 119 | `NcISO17321_AP0_ColorChips` ~ return a pointer to 24 color values 120 | in the ap0 gamut corresponding to the 24 color chips in ISO 121 | standard chart 17321 122 | 123 | `NcProjectToChromaticities` ~ given a CIE XYZ 1931 color 124 | coordinate, project it to the regularized chromaticity coordinate 125 | 126 | `NcNormalizeXYZ(NcXYZ c)` ~ given a CIE XYZ 1931 color 127 | coordinate, normalize it to a unit luminance 128 | 129 | `NcRGBFromYxy` ~ given a CIEXY coordinate, and a luminace, 130 | compute an RGB value for the given color space 131 | 132 | `NcCIE1931ColorFromWavelength` ~ compute a CIE XYZ coordinate 133 | for a given wavelength. If plotted, the values will land on 134 | the boundary of the familiar color gamut diagram. 135 | 136 | ## Building 137 | 138 | The interface types are expected to never change, and to be 139 | compatible between all versions of Nanocolor. The functions however 140 | may change, and may be locally modified, and it's expected that 141 | more than one copy of Nanocolor may be linked. Therefore, they are 142 | namespaced via a macro, even though the names appear to be simply 143 | prefixed with Nc. When building Nanocolor, you may provide your 144 | own preprocessor macro to distinguish your copy of it. 145 | 146 | ```c 147 | #ifndef NCNAMESPACE 148 | #define NCNAMESPACE pxr_nc_1_0_ 149 | #endif 150 | ``` 151 | 152 | There are no build scripts included with Nanocolor. You may build 153 | it as a library if you wish, or you may include Nanocolor.cpp, 154 | and optionally NanocolorUtils.cpp in your project. 155 | 156 | ## License and Copyright 157 | 158 | ```c 159 | // 160 | // Copyright 2024 Pixar 161 | // 162 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 163 | // 164 | // You may obtain a copy of the Apache License at 165 | // 166 | // http://www.apache.org/licenses/LICENSE-2.0 167 | // 168 | // Unless required by applicable law or agreed to in writing, software 169 | // distributed under the Apache License with the above modification is 170 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 171 | // KIND, either express or implied. See the Apache License for the specific 172 | // language governing permissions and limitations under the Apache License. 173 | // 174 | ``` 175 | 176 | ## Acknowledgements 177 | 178 | Thanks to the scientists at Pixar, OpenColorIO, OpenEXR, OpenUSD, and MaterialX 179 | for the fruitful advice and feedback that helped guide the creation of Nanocolor. 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nanocolor 2 | 3 | ## A very small color transform library 4 | 5 | ### Pixar Animation Studios 6 | 7 | ## Introduction 8 | 9 | Color management and transformation is a complex domain. 10 | Values are captured by cameras or generated by renderers, 11 | transferred to a storage medium, combined into frame buffers 12 | to make new images, transformed for display, and projected 13 | by devices. During each step, the color values may be 14 | transformed by photochemical, electro-optical, and digital 15 | processes. 16 | 17 | A renderer's input working space to output working space is 18 | an interesting subset of that domain, and it's the subset 19 | that Nanocolor concerns itself with. 20 | 21 | OpenEXR goes so far as to restrict itself to linear working 22 | spaces, and describes them completed by specifying chromaticities 23 | and an adapted whitepoint in the CIEXYZ 1931 space. Nanocolor 24 | takes inspiration from this, and uses the equations in SMPTE 25 | document RP177-1993 (reaffirmed in 2002) ~ SMPTE Recommended 26 | Practice: Derivation of Basic Television Equations. 27 | 28 | Interesting colors do not just come from OpenEXR however, but 29 | may originate as properties in a MaterialX shade graph, vertex 30 | attributes in an OpenUSD file, or PNG or TIFF textures, to 31 | name a few common sources. 32 | 33 | Amongst all these sources, only OpenEXR specifies colors in terms 34 | of chromaticities and whitepoint. Some sources specify the color 35 | space in documentation, whereas others name color spaces in the 36 | data itself, or refer to site specific configuration files in 37 | the OpenColorIO format, and other alternatives. This lack of 38 | broad agreement on such things as what the name of a colorspace 39 | actually refers to makes accurate and reproducible color 40 | calculations between software packages and studios challenging. 41 | 42 | MaterialX takes an interesting perspective; it names a bakers' 43 | dozen of color spaces and defines them as OpenEXR does, and 44 | also introduces a notion of being able to remove an input transform 45 | from a color in order to accomodate image formats commonly used 46 | as texture sources for shader graphs. 47 | 48 | Nanocolor follows this idea, and also introduces that all the 49 | color spaces must be invertible, in the sense that a value can 50 | have its input transform removed to make it a linear color value, 51 | linear values may be transformed as desired through matrix 52 | multiplication, and an output transform may be re-applied. This 53 | allows Nanocolor to transform any color space with this invertible 54 | property to any other with the invertible property. 55 | 56 | _____ 57 | 58 | ## Definitions 59 | 60 | Nomenclature shall be as per ISO 22028-1 terminology, and will occasionally spell colour with a u per common usage. 61 | 62 | ### Colour space 63 | 64 | A geometric representation of colors in a metrical space. 65 | 66 | ### Colorimetric colour space 67 | 68 | A colour space having an exact and simple relationship to CIEXY colorimetric values. 69 | 70 | ### Adopted white 71 | 72 | A “spectral radiance distribution” that converts to achromatic colour signals where each component is 1.0 73 | 74 | ### Additive RGB colour space 75 | 76 | colorimetric colour space with three primary chromaticities, a white point, and a transfer function 77 | 78 | ### Colour space encoding 79 | 80 | “digital encoding of a colour space, including ... digital encoding method, and ... value range” 81 | 82 | ### Colour image encoding 83 | 84 | Colour space encoding plus context, including image state, intended viewing environment, and for print, reference medium 85 | 86 | _____ 87 | 88 | ## Scope 89 | 90 | Colour space encoding is in scope. 91 | 92 | Colour image encoding is out of scope. 93 | 94 | Camera vendor specific colour spaces are out of scope. 95 | 96 | Colour spaces relative to a particular encoded white (cf. LAB, LUV, etc but also IPT and other spaces optimized for gamut mapping) are out of scope. 97 | 98 | Hybrid Log Gamma encoding is out of scope. 99 | 100 | _____ 101 | 102 | ## Nanocolor API 103 | 104 | Refer to nanocolor.h 105 | 106 | ## Building 107 | 108 | There are no build scripts included with Nanocolor. You may build 109 | it as a library if you wish, or you may include nanocolor.c, 110 | and optionally nanocolorUtils.c in your project. 111 | 112 | ## License and Copyright 113 | 114 | ```c 115 | // 116 | // Copyright 2024 Pixar 117 | // 118 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 119 | // 120 | // You may obtain a copy of the Apache License at 121 | // 122 | // http://www.apache.org/licenses/LICENSE-2.0 123 | // 124 | // Unless required by applicable law or agreed to in writing, software 125 | // distributed under the Apache License with the above modification is 126 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 127 | // KIND, either express or implied. See the Apache License for the specific 128 | // language governing permissions and limitations under the Apache License. 129 | // 130 | ``` 131 | 132 | ## Acknowledgements 133 | 134 | Thanks to the scientists at Pixar, OpenColorIO, OpenEXR, OpenUSD, and MaterialX 135 | for the fruitful advice and feedback that helped guide the creation of Nanocolor. 136 | -------------------------------------------------------------------------------- /nanocolor.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Pixar 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 | // with the following modification; you may not use this file except in 6 | // compliance with the Apache License and the following modification to it: 7 | // Section 6. Trademarks. is deleted and replaced with: 8 | // 9 | // 6. Trademarks. This License does not grant permission to use the trade 10 | // names, trademarks, service marks, or product names of the Licensor 11 | // and its affiliates, except as required to comply with Section 4(c) of 12 | // the License and to reproduce the content of the NOTICE file. 13 | // 14 | // You may obtain a copy of the Apache License at 15 | // 16 | // http://www.apache.org/licenses/LICENSE-2.0 17 | // 18 | // Unless required by applicable law or agreed to in writing, software 19 | // distributed under the Apache License with the above modification is 20 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | // KIND, either express or implied. See the Apache License for the specific 22 | // language governing permissions and limitations under the Apache License. 23 | // 24 | 25 | #include "nanocolor.h" 26 | #include "nanocolorProcessing.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef __SSE2__ 33 | #include 34 | #include 35 | #endif 36 | 37 | #ifdef __ARM_NEON 38 | #include 39 | #endif 40 | 41 | // Internal data structure to hold computed color space data, and the initial 42 | // decsriptor. 43 | struct NcColorSpace { 44 | NcColorSpaceDescriptor desc; 45 | float K0, phi; 46 | NcM33f rgbToXYZ; 47 | }; 48 | 49 | static void _NcInitColorSpace(NcColorSpace* cs); 50 | 51 | static float nc_FromLinear(const NcColorSpace* cs, float t) { 52 | const float gamma = cs->desc.gamma; 53 | if (t < cs->K0 / cs->phi) 54 | return t * cs->phi; 55 | const float a = cs->desc.linearBias; 56 | return (1.f + a) * powf(t, 1.f / gamma) - a; 57 | } 58 | 59 | static float nc_ToLinear(const NcColorSpace* cs, float t) { 60 | const float gamma = cs->desc.gamma; 61 | if (t < cs->K0) 62 | return t / cs->phi; 63 | const float a = cs->desc.linearBias; 64 | return powf((t + a) / (1.f + a), gamma); 65 | } 66 | 67 | static const char _acescg[] = "acescg"; 68 | const char* Nc_acescg = _acescg; 69 | static const char _adobergb[] = "adobergb"; 70 | const char* Nc_adobergb = _adobergb; 71 | static const char _g18_ap1[] = "g18_ap1"; 72 | const char* Nc_g18_ap1 = _g18_ap1; 73 | static const char _g18_rec709[] = "g18_rec709"; 74 | const char* Nc_g18_rec709 = _g18_rec709; 75 | static const char _g22_ap1[] = "g22_ap1"; 76 | const char* Nc_g22_ap1 = _g22_ap1; 77 | static const char _g22_rec709[] = "g22_rec709"; 78 | const char* Nc_g22_rec709 = _g22_rec709; 79 | static const char _identity[] = "identity"; 80 | const char* Nc_identity = _identity; 81 | static const char _lin_adobergb[] = "lin_adobergb"; 82 | const char* Nc_lin_adobergb = _lin_adobergb; 83 | static const char _lin_ap0[] = "lin_ap0"; 84 | const char* Nc_lin_ap0 = _lin_ap0; 85 | static const char _lin_ap1[] = "lin_ap1"; 86 | const char* Nc_lin_ap1 = _lin_ap1; 87 | static const char _lin_displayp3[] = "lin_displayp3"; 88 | const char* Nc_lin_displayp3 = _lin_displayp3; 89 | static const char _lin_rec709[] = "lin_rec709"; 90 | const char* Nc_lin_rec709 = _lin_rec709; 91 | static const char _lin_rec2020[] = "lin_rec2020"; 92 | const char* Nc_lin_rec2020 = _lin_rec2020; 93 | static const char _lin_srgb[] = "lin_srgb"; 94 | const char* Nc_lin_srgb = _lin_srgb; 95 | static const char _raw[] = "raw"; 96 | const char* Nc_raw = _raw; 97 | static const char _srgb_displayp3[] = "srgb_displayp3"; 98 | const char* Nc_srgb_displayp3 = _srgb_displayp3; 99 | static const char _sRGB[] = "sRGB"; 100 | const char* Nc_sRGB = _sRGB; 101 | static const char _srgb_texture[] = "srgb_texture"; 102 | const char* Nc_srgb_texture = _srgb_texture; 103 | 104 | NCAPI const char* NcGetDescription(const NcColorSpace* cs) { 105 | if (!cs) 106 | return NULL; 107 | 108 | if (!strcmp(cs->desc.name, Nc_acescg)) 109 | return "Academy Color Encoding System (ACEScg), a color space designed for computer graphics."; 110 | if (!strcmp(cs->desc.name, Nc_adobergb)) 111 | return "Adobe RGB (1998), a color space developed by Adobe Systems."; 112 | if (!strcmp(cs->desc.name, Nc_g18_ap1)) 113 | return "Gamma 1.8, primaries from ACES, white point from ACES."; 114 | if (!strcmp(cs->desc.name, Nc_g18_rec709)) 115 | return "Gamma 1.8, primaries from Rec. 709, white point from D65."; 116 | if (!strcmp(cs->desc.name, Nc_g22_ap1)) 117 | return "Gamma 2.2, primaries from ACES, white point from ACES."; 118 | if (!strcmp(cs->desc.name, Nc_g22_rec709)) 119 | return "Gamma 2.2, primaries from Rec. 709, white point from D65."; 120 | if (!strcmp(cs->desc.name, Nc_identity)) 121 | return "Identity color space, no conversion."; 122 | if (!strcmp(cs->desc.name, Nc_lin_adobergb)) 123 | return "Linear Adobe RGB (1998), a color space developed by Adobe Systems."; 124 | if (!strcmp(cs->desc.name, Nc_lin_ap0)) 125 | return "Linear transfer, AP1 primaries, white point from ACES."; 126 | if (!strcmp(cs->desc.name, Nc_lin_ap1)) 127 | return "Linear transfer, AP0 primaries, white point from ACES."; 128 | if (!strcmp(cs->desc.name, Nc_lin_displayp3)) 129 | return "Linear Display P3, a color space using the Display P3 primaries."; 130 | if (!strcmp(cs->desc.name, Nc_lin_rec709)) 131 | return "Linear Rec. 709, a color space using the Rec. 709 primaries."; 132 | if (!strcmp(cs->desc.name, Nc_lin_rec2020)) 133 | return "Linear Rec. 2020, a color space using the Rec. 2020 primaries."; 134 | if (!strcmp(cs->desc.name, Nc_lin_srgb)) 135 | return "Linear sRGB, a color space using the sRGB primaries."; 136 | if (!strcmp(cs->desc.name, Nc_raw)) 137 | return "Raw color space, no conversion."; 138 | if (!strcmp(cs->desc.name, Nc_srgb_displayp3)) 139 | return "sRGB Display P3, a color space using the Display P3 primaries."; 140 | if (!strcmp(cs->desc.name, Nc_sRGB)) 141 | return "sRGB, a display color space developed by HP and Microsoft."; 142 | if (!strcmp(cs->desc.name, Nc_srgb_texture)) 143 | return "sRGB Texture, a color space using the sRGB primaries."; 144 | return cs->desc.name; 145 | } 146 | 147 | // White point chromaticities. 148 | #define _WpD65 { 0.3127, 0.3290 } 149 | #define _WpACES { 0.32168, 0.33767 } 150 | 151 | static NcColorSpace _colorSpaces[] = { 152 | { 153 | _acescg, 154 | { 0.713, 0.293 }, 155 | { 0.165, 0.830 }, 156 | { 0.128, 0.044 }, 157 | _WpACES, 158 | 1.0, 159 | 0.0, 160 | 0, 0, 161 | { 0,0,0, 0,0,0, 0,0,0 } 162 | }, 163 | { 164 | _adobergb, 165 | { 0.64, 0.33 }, 166 | { 0.21, 0.71 }, 167 | { 0.15, 0.06 }, 168 | _WpD65, 169 | 563.0/256.0, 170 | 0.0, 171 | 0, 0, 172 | { 0,0,0, 0,0,0, 0,0,0 } 173 | }, 174 | { 175 | _g18_ap1, 176 | { 0.713, 0.293 }, 177 | { 0.165, 0.830 }, 178 | { 0.128, 0.044 }, 179 | _WpACES, 180 | 1.8, 181 | 0.0, 182 | 0, 0, 183 | { 0,0,0, 0,0,0, 0,0,0 } 184 | }, 185 | { 186 | _g22_ap1, 187 | { 0.713, 0.293 }, 188 | { 0.165, 0.830 }, 189 | { 0.128, 0.044 }, 190 | _WpACES, 191 | 2.2, 192 | 0.0, 193 | 0, 0, 194 | { 0,0,0, 0,0,0, 0,0,0 } 195 | }, 196 | { 197 | _g18_rec709, 198 | { 0.640, 0.330 }, 199 | { 0.300, 0.600 }, 200 | { 0.150, 0.060 }, 201 | _WpD65, 202 | 1.8, 203 | 0.0, 204 | 0, 0, 205 | { 0,0,0, 0,0,0, 0,0,0 } 206 | }, 207 | { 208 | _g22_rec709, 209 | { 0.640, 0.330 }, 210 | { 0.300, 0.600 }, 211 | { 0.150, 0.060 }, 212 | _WpD65, 213 | 2.2, 214 | 0.0, 215 | 0, 0, 216 | { 0,0,0, 0,0,0, 0,0,0 } 217 | }, 218 | { 219 | _lin_adobergb, 220 | { 0.64, 0.33 }, 221 | { 0.21, 0.71 }, 222 | { 0.15, 0.06 }, 223 | _WpD65, 224 | 1.0, 225 | 0.0, 226 | 0, 0, 227 | { 0,0,0, 0,0,0, 0,0,0 } 228 | }, 229 | { 230 | _lin_ap0, 231 | { 0.7347, 0.2653 }, 232 | { 0.0000, 1.0000 }, 233 | { 0.0001, -0.0770 }, 234 | _WpACES, 235 | 1.0, 236 | 0.0, 237 | 0, 0, 238 | { 0,0,0, 0,0,0, 0,0,0 } 239 | }, 240 | { 241 | _lin_ap1, // same primaries and wp as acescg 242 | { 0.713, 0.293 }, 243 | { 0.165, 0.830 }, 244 | { 0.128, 0.044 }, 245 | _WpACES, 246 | 1.0, 247 | 0.0, 248 | 0, 0, 249 | { 0,0,0, 0,0,0, 0,0,0 } 250 | }, 251 | { 252 | _lin_displayp3, 253 | { 0.6800, 0.3200 }, 254 | { 0.2650, 0.6900 }, 255 | { 0.1500, 0.0600 }, 256 | _WpD65, 257 | 1.0, 258 | 0.0, 259 | 0, 0, 260 | { 0,0,0, 0,0,0, 0,0,0 } 261 | }, 262 | { 263 | _lin_rec709, 264 | { 0.640, 0.330 }, 265 | { 0.300, 0.600 }, 266 | { 0.150, 0.060 }, 267 | _WpD65, 268 | 1.0, 269 | 0.0, 270 | 0, 0, 271 | { 0,0,0, 0,0,0, 0,0,0 } 272 | }, 273 | { 274 | _lin_rec2020, 275 | { 0.708, 0.292 }, 276 | { 0.170, 0.797 }, 277 | { 0.131, 0.046 }, 278 | _WpD65, 279 | 1.0, 280 | 0.0, 281 | 0, 0, 282 | { 0,0,0, 0,0,0, 0,0,0 } 283 | }, 284 | { 285 | _lin_srgb, 286 | { 0.640, 0.330 }, 287 | { 0.300, 0.600 }, 288 | { 0.150, 0.060 }, 289 | _WpD65, 290 | 1.0, 291 | 0.0, 292 | 0, 0, 293 | { 0,0,0, 0,0,0, 0,0,0 } 294 | }, 295 | { 296 | _srgb_displayp3, 297 | { 0.6800, 0.3200 }, 298 | { 0.2650, 0.6900 }, 299 | { 0.1500, 0.0600 }, 300 | _WpD65, 301 | 2.4, 302 | 0.055, 303 | 0, 0, 304 | { 0,0,0, 0,0,0, 0,0,0 } 305 | }, 306 | { 307 | _srgb_texture, 308 | { 0.640, 0.330 }, 309 | { 0.300, 0.600 }, 310 | { 0.150, 0.060 }, 311 | _WpD65, 312 | 2.4, 313 | 0.055, 314 | 0, 0, 315 | { 0,0,0, 0,0,0, 0,0,0 } 316 | }, 317 | { 318 | _sRGB, 319 | { 0.640, 0.330 }, 320 | { 0.300, 0.600 }, 321 | { 0.150, 0.060 }, 322 | _WpD65, 323 | 2.4, 324 | 0.055, 325 | 0, 0, 326 | { 0,0,0, 0,0,0, 0,0,0 } 327 | }, 328 | { 329 | _identity, 330 | { 1.0, 0.0 }, // these chromaticities generate identity 331 | { 0.0, 1.0 }, 332 | { 0.0, 0.0 }, 333 | { 1.0/3.0, 1.0/3.0 }, 334 | 1.0, 335 | 0.0, 336 | 0, 0, 337 | { 0,0,0, 0,0,0, 0,0,0 } 338 | }, 339 | { 340 | _raw, 341 | { 1.0, 0.0 }, // these chromaticities generate identity 342 | { 0.0, 1.0 }, 343 | { 0.0, 0.0 }, 344 | { 1.0/3.0, 1.0/3.0 }, 345 | 1.0, 346 | 0.0, 347 | 0, 0, 348 | { 0,0,0, 0,0,0, 0,0,0 } 349 | } 350 | }; 351 | 352 | static const char* _colorSpaceNames[] = { 353 | _acescg, 354 | _adobergb, 355 | _g18_ap1, 356 | _g18_rec709, 357 | _g22_ap1, 358 | _g22_rec709, 359 | _identity, 360 | _lin_adobergb, 361 | _lin_ap0, 362 | _lin_ap1, 363 | _lin_displayp3, 364 | _lin_rec709, 365 | _lin_rec2020, 366 | _lin_srgb, 367 | _raw, 368 | _srgb_displayp3, 369 | _sRGB, 370 | _srgb_texture, 371 | NULL 372 | }; 373 | 374 | const char** NcRegisteredColorSpaceNames() 375 | { 376 | return _colorSpaceNames; 377 | } 378 | 379 | bool NcColorSpaceEqual(const NcColorSpace* cs1, const NcColorSpace* cs2) { 380 | if (!cs1 || !cs2) { 381 | return false; 382 | } 383 | 384 | if (cs1->desc.name == NULL || cs2->desc.name == NULL) { 385 | return false; 386 | } 387 | 388 | if (strcmp(cs1->desc.name, cs1->desc.name) != 0) { 389 | return false; 390 | } 391 | 392 | // compare color transform op matrix and transfer op 393 | for (int i = 0; i < 9; i++) { 394 | if (fabsf(cs1->rgbToXYZ.m[i] - cs2->rgbToXYZ.m[i]) > 1e-5f) { 395 | return false; 396 | } 397 | } 398 | if (fabsf(cs1->desc.gamma - cs2->desc.gamma) > 1e-3f) { 399 | return false; 400 | } 401 | if (fabsf(cs1->desc.linearBias - cs2->desc.linearBias) > 1e-3f) { 402 | return false; 403 | } 404 | return true; 405 | } 406 | 407 | static NcM33f NcM3ffInvert(NcM33f m) { 408 | NcM33f inv; 409 | const int M0 = 0, M1 = 3, M2 = 6, M3 = 1, M4 = 4, M5 = 7, M6 = 2, M7 = 5, M8 = 8; 410 | float det = m.m[M0] * (m.m[M4] * m.m[M8] - m.m[M5] * m.m[M7]) - 411 | m.m[M1] * (m.m[M3] * m.m[M8] - m.m[M5] * m.m[M6]) + 412 | m.m[M2] * (m.m[M3] * m.m[M7] - m.m[M4] * m.m[M6]); 413 | float invdet = 1.0 / det; 414 | inv.m[M0] = (m.m[M4] * m.m[M8] - m.m[M5] * m.m[M7]) * invdet; 415 | inv.m[M1] = (m.m[M2] * m.m[M7] - m.m[M1] * m.m[M8]) * invdet; 416 | inv.m[M2] = (m.m[M1] * m.m[M5] - m.m[M2] * m.m[M4]) * invdet; 417 | inv.m[M3] = (m.m[M5] * m.m[M6] - m.m[M3] * m.m[M8]) * invdet; 418 | inv.m[M4] = (m.m[M0] * m.m[M8] - m.m[M2] * m.m[M6]) * invdet; 419 | inv.m[M5] = (m.m[M2] * m.m[M3] - m.m[M0] * m.m[M5]) * invdet; 420 | inv.m[M6] = (m.m[M3] * m.m[M7] - m.m[M4] * m.m[M6]) * invdet; 421 | inv.m[M7] = (m.m[M1] * m.m[M6] - m.m[M0] * m.m[M7]) * invdet; 422 | inv.m[M8] = (m.m[M0] * m.m[M4] - m.m[M1] * m.m[M3]) * invdet; 423 | return inv; 424 | } 425 | 426 | static NcM33f NcM33fMultiply(NcM33f lh, NcM33f rh) { 427 | NcM33f m; 428 | m.m[0] = lh.m[0] * rh.m[0] + lh.m[1] * rh.m[3] + lh.m[2] * rh.m[6]; 429 | m.m[1] = lh.m[0] * rh.m[1] + lh.m[1] * rh.m[4] + lh.m[2] * rh.m[7]; 430 | m.m[2] = lh.m[0] * rh.m[2] + lh.m[1] * rh.m[5] + lh.m[2] * rh.m[8]; 431 | m.m[3] = lh.m[3] * rh.m[0] + lh.m[4] * rh.m[3] + lh.m[5] * rh.m[6]; 432 | m.m[4] = lh.m[3] * rh.m[1] + lh.m[4] * rh.m[4] + lh.m[5] * rh.m[7]; 433 | m.m[5] = lh.m[3] * rh.m[2] + lh.m[4] * rh.m[5] + lh.m[5] * rh.m[8]; 434 | m.m[6] = lh.m[6] * rh.m[0] + lh.m[7] * rh.m[3] + lh.m[8] * rh.m[6]; 435 | m.m[7] = lh.m[6] * rh.m[1] + lh.m[7] * rh.m[4] + lh.m[8] * rh.m[7]; 436 | m.m[8] = lh.m[6] * rh.m[2] + lh.m[7] * rh.m[5] + lh.m[8] * rh.m[8]; 437 | return m; 438 | } 439 | 440 | static void _NcInitColorSpace(NcColorSpace* cs) { 441 | if (!cs || cs->rgbToXYZ.m[8] != 0.0) 442 | return; 443 | 444 | const float a = cs->desc.linearBias; 445 | const float gamma = cs->desc.gamma; 446 | 447 | if (gamma == 1.f) { 448 | cs->K0 = 1.e9f; 449 | cs->phi = 1.f; 450 | } 451 | else { 452 | if (a <= 0.f) { 453 | cs->K0 = 0.f; 454 | cs->phi = 1.f; 455 | } 456 | else { 457 | cs->K0 = a / (gamma - 1.f); 458 | cs->phi = (a / 459 | expf(logf(gamma * a / 460 | (gamma + gamma * a - 1.f - a)) * gamma)) / 461 | (gamma - 1.f); 462 | } 463 | } 464 | 465 | // if the primaries are zero, the cs was defined by the 3x3 matrix, don't overwrite it. 466 | if (cs->desc.whitePoint.x == 0.f) 467 | return; 468 | 469 | NcM33f m; 470 | // To be consistent, we use SMPTE RP 177-1993 471 | // compute xyz [little xyz] 472 | float red[3] = { cs->desc.redPrimary.x, 473 | cs->desc.redPrimary.y, 474 | 1.f - cs->desc.redPrimary.x - cs->desc.redPrimary.y }; 475 | 476 | float green[3] = { cs->desc.greenPrimary.x, 477 | cs->desc.greenPrimary.y, 478 | 1.f - cs->desc.greenPrimary.x - cs->desc.greenPrimary.y }; 479 | 480 | float blue[3] = { cs->desc.bluePrimary.x, 481 | cs->desc.bluePrimary.y, 482 | 1.f - cs->desc.bluePrimary.x - cs->desc.bluePrimary.y }; 483 | 484 | float white[3] = { cs->desc.whitePoint.x, 485 | cs->desc.whitePoint.y, 486 | 1.f - cs->desc.whitePoint.x - cs->desc.whitePoint.y }; 487 | 488 | // Build the P matrix by column binding red, green, and blue: 489 | m.m[0] = red[0]; 490 | m.m[1] = green[0]; 491 | m.m[2] = blue[0]; 492 | m.m[3] = red[1]; 493 | m.m[4] = green[1]; 494 | m.m[5] = blue[1]; 495 | m.m[6] = red[2]; 496 | m.m[7] = green[2]; 497 | m.m[8] = blue[2]; 498 | 499 | // and W 500 | // white has luminance factor of 1.0, ie Y = 1 501 | float W[3] = { white[0] / white[1], white[1] / white[1], white[2] / white[1] }; 502 | 503 | // compute the coefficients to scale primaries 504 | NcM33f mInv = NcM3ffInvert(m); 505 | float C[3] = { 506 | mInv.m[0] * W[0] + mInv.m[1] * W[1] + mInv.m[2] * W[2], 507 | mInv.m[3] * W[0] + mInv.m[4] * W[1] + mInv.m[5] * W[2], 508 | mInv.m[6] * W[0] + mInv.m[7] * W[1] + mInv.m[8] * W[2] 509 | }; 510 | 511 | // multiply the P matrix by the diagonal matrix of coefficients 512 | m.m[0] *= C[0]; 513 | m.m[1] *= C[1]; 514 | m.m[2] *= C[2]; 515 | m.m[3] *= C[0]; 516 | m.m[4] *= C[1]; 517 | m.m[5] *= C[2]; 518 | m.m[6] *= C[0]; 519 | m.m[7] *= C[1]; 520 | m.m[8] *= C[2]; 521 | 522 | cs->rgbToXYZ = m; 523 | } 524 | 525 | void NcInitColorSpaceLibrary(void) { 526 | for (size_t i = 0; i < sizeof(_colorSpaces) / sizeof(_colorSpaces[0]); i++) { 527 | _NcInitColorSpace(&_colorSpaces[i]); 528 | } 529 | } 530 | 531 | const NcColorSpace* NcCreateColorSpace(const NcColorSpaceDescriptor* csd) { 532 | if (!csd) 533 | return NULL; 534 | 535 | NcColorSpace* cs = (NcColorSpace*) calloc(1, sizeof(*cs)); 536 | cs->desc = *csd; 537 | cs->desc.name = strdup(csd->name); 538 | _NcInitColorSpace(cs); 539 | return cs; 540 | } 541 | 542 | const NcColorSpace* NcCreateColorSpaceM33(const NcColorSpaceM33Descriptor* csd, 543 | bool* matrixIsNormalized) { 544 | if (!csd) 545 | return NULL; 546 | 547 | NcColorSpace* cs = (NcColorSpace*) calloc(1, sizeof(*cs)); 548 | cs->desc.name = strdup(csd->name); 549 | cs->desc.gamma = csd->gamma; 550 | cs->desc.linearBias = csd->linearBias; 551 | cs->rgbToXYZ = csd->rgbToXYZ; 552 | _NcInitColorSpace(cs); 553 | 554 | // fill in the assumed chromaticities 555 | NcXYZ whiteXYZ = NcRGBToXYZ(cs, (NcRGB){ 1, 1, 1 }); 556 | NcYxy whiteYxy = NcXYZToYxy(whiteXYZ); 557 | NcXYZ redXYZ = NcRGBToXYZ(cs, (NcRGB){ 1, 0, 0 }); 558 | NcXYZ greenXYZ = NcRGBToXYZ(cs, (NcRGB){ 0, 1, 0 }); 559 | NcXYZ blueXYZ = NcRGBToXYZ(cs, (NcRGB){ 0, 0, 1 }); 560 | NcYxy redYxy = NcXYZToYxy(redXYZ); 561 | NcYxy greenYxy = NcXYZToYxy(greenXYZ); 562 | NcYxy blueYxy = NcXYZToYxy(blueXYZ); 563 | 564 | // if the Y components are not close to one, the matrix was not a normalized 565 | // primary matrix; report that if requested. 566 | if (matrixIsNormalized) 567 | *matrixIsNormalized = (fabsf(redYxy.Y - 1) < 1e-3f && 568 | fabsf(greenYxy.Y - 1) < 1e-3f && 569 | fabsf(blueYxy.Y - 1) < 1e-3f && 570 | fabsf(whiteYxy.Y - 1) < 1e-3f); 571 | 572 | cs->desc.redPrimary = (NcChromaticity) { redYxy.x, redYxy.y }; 573 | cs->desc.greenPrimary = (NcChromaticity) { greenYxy.x, greenYxy.y }; 574 | cs->desc.bluePrimary = (NcChromaticity) { blueYxy.x, blueYxy.y }; 575 | cs->desc.whitePoint = (NcChromaticity) { whiteYxy.x, whiteYxy.y }; 576 | return cs; 577 | } 578 | 579 | void NcFreeColorSpace(const NcColorSpace* cs) { 580 | if (!cs) 581 | return; 582 | 583 | // don't free the built in color spaces 584 | for (size_t i = 0; i < sizeof(_colorSpaces) / sizeof(_colorSpaces[0]); i++) { 585 | if (cs == &_colorSpaces[i]) { 586 | return; 587 | } 588 | } 589 | 590 | free((void*)cs->desc.name); 591 | free((void*)cs); 592 | } 593 | 594 | NcM33f NcGetRGBToXYZMatrix(const NcColorSpace* cs) { 595 | if (!cs) 596 | return (NcM33f) {1,0,0, 0,1,0, 0,0,1}; 597 | 598 | return cs->rgbToXYZ; 599 | } 600 | 601 | NcM33f NcGetXYZToRGBMatrix(const NcColorSpace* cs) { 602 | if (!cs) 603 | return (NcM33f) {1,0,0, 0,1,0, 0,0,1}; 604 | 605 | return NcM3ffInvert(NcGetRGBToXYZMatrix(cs)); 606 | } 607 | 608 | NcM33f GetRGBtoRGBMatrix(const NcColorSpace* src, const NcColorSpace* dst) { 609 | NcM33f t = NcM33fMultiply(NcM3ffInvert(NcGetRGBToXYZMatrix(src)), 610 | NcGetXYZToRGBMatrix(dst)); 611 | return t; 612 | } 613 | 614 | NcM33f NcGetRGBToRGBMatrix(const NcColorSpace* src, const NcColorSpace* dst) { 615 | if (!dst || !src) { 616 | return (NcM33f){1,0,0,0,1,0,0,0,1}; 617 | } 618 | 619 | NcM33f toXYZ = NcGetRGBToXYZMatrix(src); 620 | NcM33f fromXYZ = NcGetXYZToRGBMatrix(dst); 621 | 622 | NcM33f tx = NcM33fMultiply(fromXYZ, toXYZ); 623 | return tx; 624 | } 625 | 626 | NcRGB NcTransformColor(const NcColorSpace* dst, const NcColorSpace* src, NcRGB rgb) { 627 | if (!dst || !src) { 628 | return rgb; 629 | } 630 | 631 | NcM33f tx = NcM33fMultiply(NcGetRGBToXYZMatrix(src), 632 | NcGetXYZToRGBMatrix(dst)); 633 | 634 | // if the source color space indicates a curve remove it. 635 | rgb.r = nc_ToLinear(src, rgb.r); 636 | rgb.g = nc_ToLinear(src, rgb.g); 637 | rgb.b = nc_ToLinear(src, rgb.b); 638 | 639 | NcRGB out; 640 | out.r = tx.m[0] * rgb.r + tx.m[1] * rgb.g + tx.m[2] * rgb.b; 641 | out.g = tx.m[3] * rgb.r + tx.m[4] * rgb.g + tx.m[5] * rgb.b; 642 | out.b = tx.m[6] * rgb.r + tx.m[7] * rgb.g + tx.m[8] * rgb.b; 643 | 644 | // if the destination color space indicates a curve apply it. 645 | out.r = nc_FromLinear(dst, out.r); 646 | out.g = nc_FromLinear(dst, out.g); 647 | out.b = nc_FromLinear(dst, out.b); 648 | return out; 649 | } 650 | 651 | void NcTransformColors(const NcColorSpace* dst, const NcColorSpace* src, NcRGB* rgb, size_t count) 652 | { 653 | if (!dst || !src || !rgb) 654 | return; 655 | 656 | NcM33f tx = NcM33fMultiply(NcGetRGBToXYZMatrix(src), 657 | NcGetXYZToRGBMatrix(dst)); 658 | 659 | // if the source color space indicates a curve remove it. 660 | for (size_t i = 0; i < count; i++) { 661 | NcRGB out = rgb[i]; 662 | out.r = nc_ToLinear(src, out.r); 663 | out.g = nc_ToLinear(src, out.g); 664 | out.b = nc_ToLinear(src, out.b); 665 | rgb[i] = out; 666 | } 667 | 668 | int start = 0; 669 | #if HAVE_SSE2 670 | __m128 m0 = _mm_set_ps(tx.m[0], tx.m[1], tx.m[2], 0); 671 | __m128 m1 = _mm_set_ps(tx.m[3], tx.m[4], tx.m[5], 0); 672 | __m128 m2 = _mm_set_ps(tx.m[6], tx.m[7], tx.m[8], 0); 673 | __m128 m3 = _mm_set_ps(0, 0, 0, 1); 674 | 675 | for (size_t i = 0; i < count - 1; i++) { 676 | __m128 rgba = _mm_loadu_ps(&rgb[i].r); // load rgbr 677 | 678 | // Set alpha component to 1.0 before multiplication 679 | rgba = _mm_add_ps(rgba, m3); 680 | 681 | // Perform the matrix multiplication 682 | __m128 rout = _mm_mul_ps(m0, rgba); 683 | rout = _mm_add_ps(rout, _mm_mul_ps(m1, rgba)); 684 | rout = _mm_add_ps(rout, _mm_mul_ps(m2, rgba)); 685 | rout = _mm_add_ps(rout, _mm_mul_ps(m3, rgba)); 686 | 687 | // Store the result 688 | _mm_storeu_ps(&rgb[i].r, rout); 689 | } 690 | 691 | // transform the last value separately, because _mm_storeu_ps 692 | // writes 4 floats, and we may not have 4 floats left 693 | start = count - 2; 694 | count = 1; 695 | #elif HAVE_NEON 696 | float32x4_t m0 = { tx.m[0], tx.m[1], tx.m[2], 0 }; 697 | float32x4_t m1 = { tx.m[3], tx.m[4], tx.m[5], 0 }; 698 | float32x4_t m2 = { tx.m[6], tx.m[7], tx.m[8], 0 }; 699 | float32x4_t m3 = { 0, 0, 0, 1 }; 700 | 701 | for (size_t i = 0; i < count - 1; i++) { 702 | float32x4_t rgba = vld1q_f32(&rgb[i].r); // load rgbr 703 | 704 | // Set alpha component to 1.0 before multiplication 705 | rgba = vsetq_lane_f32(1.0f, rgba, 3); 706 | 707 | // Perform the matrix multiplication 708 | float32x4_t rout = vmulq_f32(m0, rgba); 709 | rout = vmlaq_f32(rout, m1, rgba); 710 | rout = vmlaq_f32(rout, m2, rgba); 711 | rout = vmlaq_f32(rout, m3, rgba); 712 | 713 | // Store the result 714 | vst1q_f32(&rgb[i].r, rout); 715 | } 716 | // transform the last value separately, because _mm_storeu_ps 717 | // writes 4 floats, and we may not have 4 floats left 718 | start = count - 2; 719 | count = 1; 720 | #else 721 | for (size_t i = start; i < count; i++) { 722 | NcRGB in = rgb[i]; 723 | NcRGB out = { 724 | tx.m[0] * in.r + tx.m[1] * in.g + tx.m[2] * in.b, 725 | tx.m[3] * in.r + tx.m[4] * in.g + tx.m[5] * in.b, 726 | tx.m[6] * in.r + tx.m[7] * in.g + tx.m[8] * in.b 727 | }; 728 | rgb[i] = out; 729 | } 730 | #endif 731 | 732 | // if the destination color space indicates a curve apply it. 733 | for (size_t i = 0; i < count; i++) { 734 | NcRGB out = rgb[i]; 735 | out.r = nc_FromLinear(dst, out.r); 736 | out.g = nc_FromLinear(dst, out.g); 737 | out.b = nc_FromLinear(dst, out.b); 738 | rgb[i] = out; 739 | } 740 | } 741 | 742 | // same as NcTransformColor, but preserve alpha in the transformation 743 | void NcTransformColorsWithAlpha(const NcColorSpace* dst, const NcColorSpace* src, 744 | float* rgba, size_t count) 745 | { 746 | if (!dst || !src || !rgba) 747 | return; 748 | 749 | NcM33f tx = NcM33fMultiply(NcGetRGBToXYZMatrix(src), 750 | NcGetXYZToRGBMatrix(dst)); 751 | 752 | // if the source color space indicates a curve remove it. 753 | for (size_t i = 0; i < count; i++) { 754 | NcRGB out = { rgba[i * 4 + 0], rgba[i * 4 + 1], rgba[i * 4 + 2] }; 755 | out.r = nc_ToLinear(src, out.r); 756 | out.g = nc_ToLinear(src, out.g); 757 | out.b = nc_ToLinear(src, out.b); 758 | rgba[i * 4 + 0] = out.r; 759 | rgba[i * 4 + 1] = out.g; 760 | rgba[i * 4 + 2] = out.b; 761 | } 762 | 763 | #if HAVE_SSE2 764 | __m128 m0 = _mm_set_ps(tx.m[0], tx.m[1], tx.m[2], 0); 765 | __m128 m1 = _mm_set_ps(tx.m[3], tx.m[4], tx.m[5], 0); 766 | __m128 m2 = _mm_set_ps(tx.m[6], tx.m[7], tx.m[8], 0); 767 | __m128 m3 = _mm_set_ps(0,0,0,1); 768 | 769 | for (size_t i = 0; i < count; i += 4) { 770 | __m128 rgbaVec = _mm_loadu_ps(&rgba[i * 4]); // Load all components (r, g, b, a) 771 | 772 | __m128 rout = _mm_mul_ps(m0, rgbaVec); 773 | rout = _mm_add_ps(rout, _mm_mul_ps(m1, rgbaVec)); 774 | rout = _mm_add_ps(rout, _mm_mul_ps(m2, rgbaVec)); 775 | rout = _mm_add_ps(rout, _mm_mul_ps(m3, rgbaVec)); 776 | 777 | _mm_storeu_ps(&rgba[i * 4], rout); // Store the result 778 | } 779 | #elif HAVE_NEON 780 | float32x4x4_t matrix = { 781 | {tx.m[0], tx.m[1], tx.m[2], 0}, 782 | {tx.m[3], tx.m[4], tx.m[5], 0}, 783 | {tx.m[6], tx.m[7], tx.m[8], 0}, 784 | {0, 0, 0, 1} 785 | }; 786 | 787 | for (size_t i = 0; i < count; i += 4) { 788 | float32x4x4_t rgba_values = vld4q_f32(&rgba[i * 4]); 789 | 790 | float32x4_t rout = vmulq_f32(matrix.val[0], rgba_values.val[0]); 791 | rout = vmlaq_f32(rout, matrix.val[1], rgba_values.val[1]); 792 | rout = vmlaq_f32(rout, matrix.val[2], rgba_values.val[2]); 793 | rout = vmlaq_f32(rout, matrix.val[3], rgba_values.val[3]); 794 | 795 | vst1q_f32(&rgba[i * 4], rout); 796 | } 797 | #else 798 | for (size_t i = 0; i < count; i++) { 799 | NcRGB in = { rgba[i * 4 + 0], rgba[i * 4 + 1], rgba[i * 4 + 2] }; 800 | NcRGB out = { 801 | tx.m[0] * in.r + tx.m[1] * in.g + tx.m[2] * in.b, 802 | tx.m[3] * in.r + tx.m[4] * in.g + tx.m[5] * in.b, 803 | tx.m[6] * in.r + tx.m[7] * in.g + tx.m[8] * in.b 804 | }; 805 | rgba[i * 4 + 0] = out.r; 806 | rgba[i * 4 + 1] = out.g; 807 | rgba[i * 4 + 2] = out.b; 808 | // leave alpha alone 809 | } 810 | #endif 811 | 812 | // if the destination color space indicates a curve apply it. 813 | for (size_t i = 0; i < count; i++) { 814 | NcRGB out = { rgba[i * 4 + 0], rgba[i * 4 + 1], rgba[i * 4 + 2] }; 815 | out.r = nc_FromLinear(dst, out.r); 816 | out.g = nc_FromLinear(dst, out.g); 817 | out.b = nc_FromLinear(dst, out.b); 818 | rgba[i * 4 + 0] = out.r; 819 | rgba[i * 4 + 1] = out.g; 820 | rgba[i * 4 + 2] = out.b; 821 | } 822 | } 823 | 824 | NcXYZ NcRGBToXYZ(const NcColorSpace* ct, NcRGB rgb) { 825 | if (!ct) 826 | return (NcXYZ) {0,0,0}; 827 | 828 | rgb.r = nc_ToLinear(ct, rgb.r); 829 | rgb.g = nc_ToLinear(ct, rgb.g); 830 | rgb.b = nc_ToLinear(ct, rgb.b); 831 | 832 | NcM33f m = NcGetRGBToXYZMatrix(ct); 833 | return (NcXYZ) { 834 | m.m[0] * rgb.r + m.m[1] * rgb.g + m.m[2] * rgb.b, 835 | m.m[3] * rgb.r + m.m[4] * rgb.g + m.m[5] * rgb.b, 836 | m.m[6] * rgb.r + m.m[7] * rgb.g + m.m[8] * rgb.b 837 | }; 838 | } 839 | 840 | NcRGB NcXYZToRGB(const NcColorSpace* ct, NcXYZ xyz) { 841 | if (!ct) 842 | return (NcRGB) {0,0,0}; 843 | 844 | NcM33f m = NcGetXYZToRGBMatrix(ct); 845 | 846 | NcRGB rgb = { 847 | m.m[0] * xyz.x + m.m[1] * xyz.y + m.m[2] * xyz.z, 848 | m.m[3] * xyz.x + m.m[4] * xyz.y + m.m[5] * xyz.z, 849 | m.m[6] * xyz.x + m.m[7] * xyz.y + m.m[8] * xyz.z 850 | }; 851 | 852 | rgb.r = nc_FromLinear(ct, rgb.r); 853 | rgb.g = nc_FromLinear(ct, rgb.g); 854 | rgb.b = nc_FromLinear(ct, rgb.b); 855 | return rgb; 856 | } 857 | 858 | NcYxy NcXYZToYxy(NcXYZ xyz) { 859 | float sum = xyz.x + xyz.y + xyz.z; 860 | if (sum == 0.f) 861 | return (NcYxy) {0, 0, xyz.y}; 862 | 863 | return (NcYxy) {xyz.y, xyz.x / sum, xyz.y / sum}; 864 | } 865 | 866 | NCAPI NcXYZ NcYxyToXYZ(NcYxy Yxy) { 867 | return (NcXYZ) { 868 | Yxy.Y * Yxy.x / Yxy.y, 869 | Yxy.Y, 870 | Yxy.Y * (1.f - Yxy.x - Yxy.y) / Yxy.y }; 871 | } 872 | 873 | const NcColorSpace* NcGetNamedColorSpace(const char* name) 874 | { 875 | if (name) { 876 | for (size_t i = 0; i < sizeof(_colorSpaces) / sizeof(_colorSpaces[0]); i++) { 877 | if (strcmp(name, _colorSpaces[i].desc.name) == 0) { 878 | _NcInitColorSpace((NcColorSpace*) &_colorSpaces[i]); // ensure initialization 879 | return &_colorSpaces[i]; 880 | } 881 | } 882 | } 883 | 884 | // currently Nanocolor doesn't have a concept of registering new color spaces 885 | return NULL; 886 | } 887 | 888 | static bool CompareChromaticity(const NcChromaticity* a, const NcChromaticity* b, float threshold) { 889 | return fabsf(a->x - b->x) < threshold && 890 | fabsf(a->y - b->y) < threshold; 891 | } 892 | 893 | static bool CompareXYZ(const NcXYZ* a, const NcXYZ* b, float threshold) { 894 | return fabsf(a->x - b->x) < threshold && 895 | fabsf(a->y - b->y) < threshold && 896 | fabsf(a->z - b->z) < threshold; 897 | } 898 | 899 | static bool CompareCIEXYChromaticity(const NcChromaticity* a, 900 | const NcChromaticity* b, float threshold) { 901 | return fabsf(a->x - b->x) < threshold && 902 | fabsf(a->y - b->y) < threshold; 903 | } 904 | 905 | // The main reason this exists is that OpenEXR encodes colorspaces via primaries 906 | // and white point, and it would be good to be able to match an EXR file to a 907 | // known colorspace, rather than setting up unique transforms for each image. 908 | // Therefore constructing the namespaced name here via macro, to avoid including 909 | // utils itself. 910 | 911 | const char* 912 | NcMatchLinearColorSpace(NcChromaticity redPrimary, NcChromaticity greenPrimary, NcChromaticity bluePrimary, 913 | NcChromaticity whitePoint, float threshold) { 914 | for (size_t i = 0; i < sizeof(_colorSpaces) / sizeof(NcColorSpace); ++i) { 915 | if (_colorSpaces[i].desc.gamma != 1.0f) 916 | continue; 917 | if (CompareChromaticity(&_colorSpaces[i].desc.redPrimary, &redPrimary, threshold) && 918 | CompareChromaticity(&_colorSpaces[i].desc.greenPrimary, &greenPrimary, threshold) && 919 | CompareChromaticity(&_colorSpaces[i].desc.bluePrimary, &bluePrimary, threshold) && 920 | CompareChromaticity(&_colorSpaces[i].desc.whitePoint, &whitePoint, threshold)) 921 | return _colorSpaces[i].desc.name; 922 | } 923 | return NULL; 924 | } 925 | 926 | bool NcGetColorSpaceDescriptor(const NcColorSpace* cs, NcColorSpaceDescriptor* desc) { 927 | if (!cs || !desc) 928 | return false; 929 | if (cs->desc.whitePoint.x == 0.f) 930 | return false; 931 | memcpy(desc, &cs->desc, sizeof(NcColorSpaceDescriptor)); 932 | return true; 933 | } 934 | 935 | bool NcGetColorSpaceM33Descriptor(const NcColorSpace* cs, NcColorSpaceM33Descriptor* desc) { 936 | if (!cs || !desc) 937 | return false; 938 | desc->name = cs->desc.name; 939 | desc->gamma = cs->desc.gamma; 940 | desc->linearBias = cs->desc.linearBias; 941 | desc->rgbToXYZ = cs->rgbToXYZ; 942 | return true; 943 | } 944 | 945 | void NcGetK0Phi(const NcColorSpace* cs, float* K0, float* phi) { 946 | if (cs && K0 && phi) { 947 | *K0 = cs->K0; 948 | *phi = cs->phi; 949 | } 950 | } 951 | 952 | /* This is actually u'v', u'v' is uv scaled by 1.5 along the v axis 953 | */ 954 | 955 | typedef struct { 956 | float Y; 957 | float u; 958 | float v; 959 | } NcYuvPrime; 960 | 961 | NcYxy _NcYuv2Yxy(NcYuvPrime c) { 962 | float d = 6.f * c.u - 16.f * c.v + 12.f; 963 | return (NcYxy) { 964 | c.Y, 965 | 9.f * c.u / d, 966 | 4.f * c.v / d 967 | }; 968 | } 969 | 970 | /* Equations from the paper "An Algorithm to Calculate Correlated Colour 971 | Temperature" by M. Krystek in 1985, using a rational Chebyshev approximation. 972 | */ 973 | NcYxy NcKelvinToYxy(float T, float luminance) { 974 | if (T < 1000 || T > 15000) 975 | return (NcYxy) { 0, 0, 0 }; 976 | 977 | float u = (0.860117757 + 1.54118254e-4 * T + 1.2864121e-7 * T * T) / 978 | (1.0 + 8.42420235e-4 * T + 7.08145163e-7 * T * T); 979 | float v = (0.317398726 + 4.22806245e-5 * T + 4.20481691e-8 * T * T) / 980 | (1.0 - 2.89741816e-5 * T + 1.61456053e-7 * T * T); 981 | 982 | return _NcYuv2Yxy((NcYuvPrime) {luminance, u, 3.f * v / 2.f }); 983 | } 984 | 985 | NcYxy NcNormalizeYxy(NcYxy c) { 986 | return (NcYxy) { 987 | c.Y, 988 | c.Y * c.x / c.y, 989 | c.Y * (1.f - c.x - c.y) / c.y 990 | }; 991 | } 992 | 993 | static inline float sign_of(float x) { 994 | return x > 0 ? 1.f : (x < 0) ? -1.f : 0.f; 995 | } 996 | 997 | NcRGB NcYxyToRGB(const NcColorSpace* cs, NcYxy c) { 998 | NcYxy cYxy = NcNormalizeYxy(c); 999 | NcRGB rgb = NcXYZToRGB(cs, (NcXYZ) { cYxy.x, cYxy.Y, cYxy.y }); 1000 | NcRGB magRgb = { 1001 | fabsf(rgb.r), 1002 | fabsf(rgb.g), 1003 | fabsf(rgb.b) }; 1004 | 1005 | float maxc = (magRgb.r > magRgb.g) ? magRgb.r : magRgb.g; 1006 | maxc = maxc > magRgb.b ? maxc : magRgb.b; 1007 | NcRGB ret = (NcRGB) { 1008 | sign_of(rgb.r) * rgb.r / maxc, 1009 | sign_of(rgb.g) * rgb.g / maxc, 1010 | sign_of(rgb.b) * rgb.b / maxc }; 1011 | return ret; 1012 | } 1013 | 1014 | -------------------------------------------------------------------------------- /nanocolor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Pixar 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 | // with the following modification; you may not use this file except in 6 | // compliance with the Apache License and the following modification to it: 7 | // Section 6. Trademarks. is deleted and replaced with: 8 | // 9 | // 6. Trademarks. This License does not grant permission to use the trade 10 | // names, trademarks, service marks, or product names of the Licensor 11 | // and its affiliates, except as required to comply with Section 4(c) of 12 | // the License and to reproduce the content of the NOTICE file. 13 | // 14 | // You may obtain a copy of the Apache License at 15 | // 16 | // http://www.apache.org/licenses/LICENSE-2.0 17 | // 18 | // Unless required by applicable law or agreed to in writing, software 19 | // distributed under the Apache License with the above modification is 20 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | // KIND, either express or implied. See the Apache License for the specific 22 | // language governing permissions and limitations under the Apache License. 23 | // 24 | #ifndef PXR_BASE_GF_NC_NANOCOLOR_H 25 | #define PXR_BASE_GF_NC_NANOCOLOR_H 26 | 27 | #include 28 | #include 29 | 30 | // NCNAMESPACE is allows the introduction of a namespace to the symbols so that 31 | // multiple libraries can include the nanocolor library without symbol 32 | // conflicts. The default is nc_1_0_ to indicate the 1.0 version of Nanocolor. 33 | // 34 | // pxr: note that the PXR namespace macros are in pxr/pxr.h which 35 | // is a C++ only header; so the generated namespace prefixes can't be 36 | // used here. 37 | #ifndef NCNAMESPACE 38 | #define NCNAMESPACE pxr_nc_1_0_ 39 | #endif 40 | 41 | // The NCCONCAT macro is used to apply a namespace to the symbols in the public 42 | // interface. 43 | #define NCCONCAT1(a, b) a ## b 44 | #define NCCONCAT(a, b) NCCONCAT1(a, b) 45 | 46 | // NCAPI may be overridden externally to control symbol visibility. 47 | #ifndef NCAPI 48 | #define NCAPI 49 | #endif 50 | 51 | #ifdef __cplusplus 52 | #define NCEXTERNC extern "C" NCAPI 53 | #else 54 | #define NCEXTERNC extern NCAPI 55 | #endif 56 | 57 | #define NcChromaticity NCCONCAT(NCNAMESPACE, Chromaticity) 58 | #define NcXYZ NCCONCAT(NCNAMESPACE, XYZ) 59 | #define NcYxy NCCONCAT(NCNAMESPACE, Yxy) 60 | #define NcRGB NCCONCAT(NCNAMESPACE, RGB) 61 | #define NcM33f NCCONCAT(NCNAMESPACE, M33f) 62 | #define NcColorSpaceDescriptor NCCONCAT(NCNAMESPACE, ColorSpaceDescriptor) 63 | #define NcColorSpaceM33Descriptor NCCONCAT(NCNAMESPACE, ColorSpaceM33Descriptor) 64 | #define NcColorSpace NCCONCAT(NCNAMESPACE, ColorSpace) 65 | 66 | // NcChromaticity is a single coordinate in the CIE 1931 xy chromaticity diagram. 67 | typedef struct { 68 | float x, y; 69 | } NcChromaticity; 70 | 71 | // NcXYZ is a coordinate in the CIE 1931 2-degree XYZ color space. 72 | typedef struct { 73 | float x, y, z; 74 | } NcXYZ; 75 | 76 | // NcYxy is a chromaticity coordinate with luminance. 77 | typedef struct { 78 | float Y, x, y; 79 | } NcYxy; 80 | 81 | // NcRGB is an rgb coordinate with no intrinsic color space. 82 | typedef struct { 83 | float r, g, b; 84 | } NcRGB; 85 | 86 | // NcM33f is a 3x3 matrix of floats used for color space conversions. 87 | // It's stored in row major order, such that posting multiplying an NcRGB 88 | // as a column vector by an NcM33f will yield another NcRGB column 89 | // transformed by that matrix. 90 | typedef struct { 91 | float m[9]; 92 | } NcM33f; 93 | 94 | // NcColorSpaceDescriptor describes a color space. 95 | // The color space is defined by the red, green, and blue primaries, 96 | // the white point, the gamma of the log section, and the linear bias. 97 | typedef struct { 98 | const char* name; 99 | NcChromaticity redPrimary, greenPrimary, bluePrimary; 100 | NcChromaticity whitePoint; 101 | float gamma; // gamma of log section 102 | float linearBias; // where the linear section ends 103 | } NcColorSpaceDescriptor; 104 | 105 | // NcColorSpaceM33Descriptor describes a color space defined in terms of a 106 | // 3x3 matrix, the gamma of the log section, and the linear bias. 107 | typedef struct { 108 | const char* name; 109 | NcM33f rgbToXYZ; 110 | float gamma; // gamma of log section 111 | float linearBias; // where the linear section ends 112 | } NcColorSpaceM33Descriptor; 113 | 114 | // Opaque struct for the public interface 115 | typedef struct NcColorSpace NcColorSpace; 116 | 117 | #ifdef __cplusplus 118 | extern "C" { 119 | #endif 120 | 121 | /* 122 | The named color spaces provided by Nanocolor are as follows. 123 | Note that the names are shared with libraries such as MaterialX. 124 | 125 | - acescg: The Academy Color Encoding System, a color space designed 126 | for cinematic content creation and exchange, using AP1 primaries 127 | - adobergb: A color space developed by Adobe Systems. It has a wider gamut 128 | than sRGB and is suitable for photography and printing 129 | - g18_ap1: A color space with a 1.8 gamma and an AP1 primaries color gamut 130 | - g18_rec709: A color space with a 1.8 gamma, and primaries per the Rec. 709 131 | standard, commonly used in HDTV 132 | - g22_ap1: A color space with a 2.2 gamma and an AP1 primaries color gamut 133 | - g22_rec709: A color space with a 2.2 gamma, and primaries per the Rec. 709 134 | standard, commonly used in HDTV 135 | - identity: Indicates that no transform is applied. 136 | - lin_adobergb: The AdobeRGB gamut, and linear gamma 137 | - lin_ap0: AP0 primaries, and linear gamma 138 | - lin_ap1: AP1 primaries, and linear gamma; these are the ACESCg primaries 139 | - lin_displayp3: DisplayP3 gamut, and linear gamma 140 | - lin_rec709: A linearized version of the Rec. 709 color space. 141 | - lin_rec2020: Rec2020 gamut, and linear gamma 142 | - lin_srgb: sRGB gamut, linear gamma 143 | - raw: Indicates that no transform is applied. 144 | - srgb_displayp3: sRGB color space adapted to the Display P3 primaries 145 | - sRGB: The sRGB color space. 146 | - srgb_texture: The sRGB color space. 147 | */ 148 | 149 | NCEXTERNC const char* Nc_acescg; 150 | NCEXTERNC const char* Nc_adobergb; 151 | NCEXTERNC const char* Nc_g18_ap1; 152 | NCEXTERNC const char* Nc_g18_rec709; 153 | NCEXTERNC const char* Nc_g22_ap1; 154 | NCEXTERNC const char* Nc_g22_rec709; 155 | NCEXTERNC const char* Nc_identity; 156 | NCEXTERNC const char* Nc_lin_adobergb; 157 | NCEXTERNC const char* Nc_lin_ap0; 158 | NCEXTERNC const char* Nc_lin_ap1; 159 | NCEXTERNC const char* Nc_lin_displayp3; 160 | NCEXTERNC const char* Nc_lin_rec709; 161 | NCEXTERNC const char* Nc_lin_rec2020; 162 | NCEXTERNC const char* Nc_lin_srgb; 163 | NCEXTERNC const char* Nc_raw; 164 | NCEXTERNC const char* Nc_srgb_displayp3; 165 | NCEXTERNC const char* Nc_sRGB; 166 | NCEXTERNC const char* Nc_srgb_texture; 167 | 168 | // Declare the public interface using the namespacing macro. 169 | #define NcColorSpaceEqual NCCONCAT(NCNAMESPACE, ColorSpaceEqual) 170 | #define NcCreateColorSpace NCCONCAT(NCNAMESPACE, CreateColorSpace) 171 | #define NcCreateColorSpaceM33 NCCONCAT(NCNAMESPACE, CreateColorSpaceM33) 172 | #define NcFreeColorSpace NCCONCAT(NCNAMESPACE, FreeColorSpace) 173 | #define NcGetColorSpaceDescriptor NCCONCAT(NCNAMESPACE, GetColorSpaceDescriptor) 174 | #define NcGetColorSpaceM33Descriptor NCCONCAT(NCNAMESPACE, GetColorSpaceM33Descriptor) 175 | #define NcGetDescription NCCONCAT(NCNAMESPACE, GetDescription) 176 | #define NcGetK0Phi NCCONCAT(NCNAMESPACE, GetK0Phi) 177 | #define NcGetNamedColorSpace NCCONCAT(NCNAMESPACE, GetNamedColorSpace) 178 | #define NcGetRGBToRGBMatrix NCCONCAT(NCNAMESPACE, GetRGBToRGBMatrix) 179 | #define NcGetRGBToXYZMatrix NCCONCAT(NCNAMESPACE, GetRGBtoXYZMatrix) 180 | #define NcGetXYZToRGBMatrix NCCONCAT(NCNAMESPACE, GetXYZtoRGBMatrix) 181 | #define NcInitColorSpaceLibrary NCCONCAT(NCNAMESPACE, InitColorSpaceLibrary) 182 | #define NcMatchLinearColorSpace NCCONCAT(NCNAMESPACE, MatchLinearColorSpace) 183 | #define NcRegisteredColorSpaceNames NCCONCAT(NCNAMESPACE, RegisteredColorSpaceNames) 184 | 185 | /** 186 | * @brief Initializes the color space library. 187 | * 188 | * Initializes the color spaces provided in the built-in color space library. 189 | * This function is not thread-safe and must be called before NcGetNamedColorSpace 190 | * is called. 191 | * 192 | * @return void 193 | */ 194 | NCAPI void NcInitColorSpaceLibrary(void); 195 | 196 | /** 197 | * @brief Retrieves the names of the registered color spaces. 198 | * 199 | * Retrieves the names of the color spaces that have been registered. 200 | * This function must not be called before NcInitColorSpaceLibrary is called. 201 | * 202 | * @return Pointer to an array of strings containing the names of the registered color spaces. 203 | */ 204 | NCAPI const char** NcRegisteredColorSpaceNames(void); 205 | 206 | /** 207 | * @brief Retrieves a named color space. 208 | * 209 | * Retrieves a color space object based on the provided name. 210 | * This function must not be called before NcInitColorSpaceLibrary is called. 211 | * 212 | * @param name The name of the color space to retrieve. 213 | * @return Pointer to the color space object, or NULL if not found. 214 | */ 215 | NCAPI const NcColorSpace* NcGetNamedColorSpace(const char* name); 216 | 217 | /** 218 | * Creates a color space object based on the provided color space descriptor. 219 | * 220 | * @param cs Pointer to the color space descriptor. 221 | * @return Pointer to the created color space object, or NULL if creation fails. 222 | */ 223 | NCAPI const NcColorSpace* NcCreateColorSpace(const NcColorSpaceDescriptor* cs); 224 | 225 | /** 226 | * Creates a color space object based on the provided 3x3 matrix color space descriptor. 227 | * 228 | * @param cs Pointer to the 3x3 matrix color space descriptor. 229 | * @return Pointer to the created color space object, or NULL if creation fails. 230 | */ 231 | NCAPI const NcColorSpace* NcCreateColorSpaceM33(const NcColorSpaceM33Descriptor* cs, 232 | bool* matrixIsNormalized); 233 | 234 | /** 235 | * @brief Frees the memory associated with a color space object. 236 | * 237 | * Frees the memory associated with a color space object. 238 | * If this function is called on one of the built in library color spaces, it will 239 | * return without freeing the memory. 240 | * 241 | * @param cs Pointer to the color space object to be freed. 242 | * @return void 243 | */ 244 | NCAPI void NcFreeColorSpace(const NcColorSpace* cs); 245 | 246 | /** 247 | * Retrieves the RGB to XYZ transformation matrix for a given color space. 248 | * 249 | * @param cs Pointer to the color space object. 250 | * @return The 3x3 transformation matrix. 251 | */ 252 | NCAPI NcM33f NcGetRGBToXYZMatrix(const NcColorSpace* cs); 253 | 254 | /** 255 | * Retrieves the XYZ to RGB transformation matrix for a given color space. 256 | * 257 | * @param cs Pointer to the color space object. 258 | * @return The 3x3 transformation matrix. 259 | */ 260 | NCAPI NcM33f NcGetXYZToRGBMatrix(const NcColorSpace* cs); 261 | 262 | /** 263 | * Retrieves the RGB to RGB transformation matrix from source to destination color space. 264 | * 265 | * @param src Pointer to the source color space object. 266 | * @param dst Pointer to the destination color space object. 267 | * @return The 3x3 transformation matrix. 268 | */ 269 | NCAPI NcM33f NcGetRGBToRGBMatrix(const NcColorSpace* src, const NcColorSpace* dst); 270 | 271 | /** 272 | * Checks if two color space objects are equal by comparing their properties. 273 | * 274 | * @param cs1 Pointer to the first color space object. 275 | * @param cs2 Pointer to the second color space object. 276 | * @return True if the color space objects are equal, false otherwise. 277 | */ 278 | NCAPI bool NcColorSpaceEqual(const NcColorSpace* cs1, const NcColorSpace* cs2); 279 | 280 | /** 281 | * @brief Retrieves the color space descriptor. 282 | * 283 | * Returns true if the color space descriptor was filled in. Color spaces initialized 284 | * using a 3x3 matrix will not fill in the values. Note that 'name' within the populated 285 | * descriptor is a pointer to a string owned by the color space, and is valid only as 286 | * long as 'cs' is valid. 287 | * 288 | * @param cs Pointer to the color space object. 289 | * @param desc Pointer to the color space descriptor to be filled in. 290 | * @return True if the descriptor was filled in, false otherwise. 291 | */ 292 | NCAPI bool NcGetColorSpaceDescriptor(const NcColorSpace* cs, NcColorSpaceDescriptor*); 293 | 294 | /** 295 | * @brief Retrieves the 3x3 matrix color space descriptor. 296 | * 297 | * Returns true if the color space descriptor was filled in. All properly initialized 298 | * color spaces will be able to fill in the values. Note that 'name' within the populated 299 | * descriptor is a pointer to a string owned by the color space, and is valid only as 300 | * long as 'cs' is valid. 301 | * 302 | * @param cs Pointer to the color space object. 303 | * @param desc Pointer to the 3x3 matrix color space descriptor to be filled in. 304 | * @return True if the descriptor was filled in, false otherwise. 305 | */ 306 | NCAPI bool NcGetColorSpaceM33Descriptor(const NcColorSpace* cs, NcColorSpaceM33Descriptor*); 307 | 308 | /** 309 | * Returns a string describing the color space. 310 | * 311 | * @param cs Pointer to the color space object. 312 | * @return A string describing the color space. 313 | */ 314 | NCAPI const char* NcGetDescription(const NcColorSpace* cs); 315 | 316 | /** 317 | * @brief Retrieves the K0 and phi values of the color space. 318 | * 319 | * Retrieves the K0 and Phi values of the color space, which are used in curve 320 | * transformations. K0 represents the transition point in the curve function, 321 | * and Phi represents the slope of the linear segment before the transition. 322 | * 323 | * @param cs Pointer to the color space object. 324 | * @param K0 Pointer to store the K0 value. 325 | * @param phi Pointer to store the phi value. 326 | * @return void 327 | */ 328 | NCAPI void NcGetK0Phi(const NcColorSpace* cs, float* K0, float* phi); 329 | 330 | /** 331 | * @brief Matches a linear color space based on specified primaries and white point. 332 | * 333 | * Returns a string describing the color space that best matches the specified primaries 334 | * and white point. A reasonable epsilon for the comparison is 1e-4 because most color 335 | * spaces are defined to that precision. 336 | * 337 | * @param redPrimary Red primary chromaticity. 338 | * @param greenPrimary Green primary chromaticity. 339 | * @param bluePrimary Blue primary chromaticity. 340 | * @param whitePoint White point chromaticity. 341 | * @param epsilon Epsilon value for comparison. 342 | * @return A string describing the matched color space. 343 | */ 344 | NCAPI const char* NcMatchLinearColorSpace(NcChromaticity redPrimary, 345 | NcChromaticity greenPrimary, 346 | NcChromaticity bluePrimary, 347 | NcChromaticity whitePoint, 348 | float epsilon); 349 | 350 | #ifdef __cplusplus 351 | } 352 | #endif 353 | #endif /* PXR_BASE_GF_NC_NANOCOLOR_H */ 354 | -------------------------------------------------------------------------------- /nanocolorProcessing.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Pixar 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 | // with the following modification; you may not use this file except in 6 | // compliance with the Apache License and the following modification to it: 7 | // Section 6. Trademarks. is deleted and replaced with: 8 | // 9 | // 6. Trademarks. This License does not grant permission to use the trade 10 | // names, trademarks, service marks, or product names of the Licensor 11 | // and its affiliates, except as required to comply with Section 4(c) of 12 | // the License and to reproduce the content of the NOTICE file. 13 | // 14 | // You may obtain a copy of the Apache License at 15 | // 16 | // http://www.apache.org/licenses/LICENSE-2.0 17 | // 18 | // Unless required by applicable law or agreed to in writing, software 19 | // distributed under the Apache License with the above modification is 20 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | // KIND, either express or implied. See the Apache License for the specific 22 | // language governing permissions and limitations under the Apache License. 23 | // 24 | #ifndef PXR_BASE_GF_NC_NANOCOLOR_PROCESSING_H 25 | #define PXR_BASE_GF_NC_NANOCOLOR_PROCESSING_H 26 | 27 | #include "nanocolor.h" 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | // Declare the public interface using the namespacing macro. 33 | #define NcTransformColor NCCONCAT(NCNAMESPACE, TransformColor) 34 | #define NcTransformColors NCCONCAT(NCNAMESPACE, TransformColors) 35 | #define NcTransformColorsWithAlpha NCCONCAT(NCNAMESPACE, TransformColorsWithAlpha) 36 | #define NcXYZToRGB NCCONCAT(NCNAMESPACE, XYZToRGB) 37 | #define NcXYZToYxy NCCONCAT(NCNAMESPACE, XYZToYxy) 38 | #define NcYxyToRGB NCCONCAT(NCNAMESPACE, YxyToRGB) 39 | #define NcYxyToXYZ NCCONCAT(NCNAMESPACE, YxyToXYZ) 40 | #define NcRGBToXYZ NCCONCAT(NCNAMESPACE, RGBToXYZ) 41 | #define NcKelvinToYxy NCCONCAT(NCNAMESPACE, KelvinToYxy) 42 | 43 | /** 44 | * Transforms a color from one color space to another. 45 | * 46 | * @param dst Pointer to the destination color space object. 47 | * @param src Pointer to the source color space object. 48 | * @param rgb The RGB color to transform. 49 | * @return The transformed RGB color in the destination color space. 50 | */ 51 | NCAPI NcRGB NcTransformColor(const NcColorSpace* dst, const NcColorSpace* src, NcRGB rgb); 52 | 53 | /** 54 | * Transforms an array of colors from one color space to another. 55 | * 56 | * @param dst Pointer to the destination color space object. 57 | * @param src Pointer to the source color space object. 58 | * @param rgb Pointer to the array of RGB colors to transform. 59 | * @param count Number of colors in the array. 60 | * @return void 61 | */ 62 | NCAPI void NcTransformColors(const NcColorSpace* dst, const NcColorSpace* src, 63 | NcRGB* rgb, size_t count); 64 | 65 | /** 66 | * Transforms an array of colors with alpha channel from one color space to another. 67 | * 68 | * @param dst Pointer to the destination color space object. 69 | * @param src Pointer to the source color space object. 70 | * @param rgba Pointer to the array of RGBA colors to transform. 71 | * @param count Number of colors in the array. 72 | * @return void 73 | */ 74 | NCAPI void NcTransformColorsWithAlpha(const NcColorSpace* dst, const NcColorSpace* src, 75 | float* rgba, size_t count); 76 | 77 | /** 78 | * Converts an RGB color to XYZ color space using the provided color space. 79 | * 80 | * @param cs Pointer to the color space object. 81 | * @param rgb The RGB color to convert. 82 | * @return The XYZ color. 83 | */ 84 | NCAPI NcXYZ NcRGBToXYZ(const NcColorSpace* cs, NcRGB rgb); 85 | 86 | /** 87 | * Converts a XYZ color to RGB color space using the provided color space. 88 | * 89 | * @param cs Pointer to the color space object. 90 | * @param xyz The XYZ color to convert. 91 | * @return The RGB color. 92 | */ 93 | NCAPI NcRGB NcXYZToRGB(const NcColorSpace* cs, NcXYZ xyz); 94 | 95 | /** 96 | * Converts a XYZ color to Yxy color space. 97 | * 98 | * @param xyz The XYZ color to convert. 99 | * @return The Yxy color. 100 | */ 101 | NCAPI NcYxy NcXYZToYxy(NcXYZ xyz); 102 | 103 | /** 104 | * Converts an Yxy color coordinate to XYZ. 105 | * 106 | * @param Yxy The Yxy color coordinate. 107 | * @return The XYZ color coordinate. 108 | */ 109 | NCAPI NcXYZ NcYxyToXYZ(NcYxy Yxy); 110 | 111 | /** 112 | * Converts an Yxy color coordinate to RGB using the specified color space. 113 | * 114 | * @param cs The color space. 115 | * @param c The Yxy color coordinate. 116 | * @return The RGB color coordinate. 117 | */ 118 | NCAPI NcRGB NcYxyToRGB(const NcColorSpace* cs, NcYxy c); 119 | 120 | /** 121 | * @brief Returns an Yxy coordinate on the blackbody emission spectrum 122 | * 123 | * Returns an Yxy coordinate on the blackbody emission spectrum for values 124 | * between 1000 and 15000K. Note that temperatures below 1900 are out of gamut 125 | * for some common colorspaces, such as Rec709. 126 | * 127 | * @param temperature The blackbody temperature in Kelvin. 128 | * @param luminosity The luminosity. 129 | * @return An Yxy coordinate. 130 | */ 131 | NCAPI NcYxy NcKelvinToYxy(float temperature, float luminosity); 132 | 133 | #ifdef __cplusplus 134 | } 135 | #endif 136 | #endif /* PXR_BASE_GF_NC_NANOCOLOR_H */ 137 | -------------------------------------------------------------------------------- /nanocolorUtils.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Pixar 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 | // with the following modification; you may not use this file except in 6 | // compliance with the Apache License and the following modification to it: 7 | // Section 6. Trademarks. is deleted and replaced with: 8 | // 9 | // 6. Trademarks. This License does not grant permission to use the trade 10 | // names, trademarks, service marks, or product names of the Licensor 11 | // and its affiliates, except as required to comply with Section 4(c) of 12 | // the License and to reproduce the content of the NOTICE file. 13 | // 14 | // You may obtain a copy of the Apache License at 15 | // 16 | // http://www.apache.org/licenses/LICENSE-2.0 17 | // 18 | // Unless required by applicable law or agreed to in writing, software 19 | // distributed under the Apache License with the above modification is 20 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | // KIND, either express or implied. See the Apache License for the specific 22 | // language governing permissions and limitations under the Apache License. 23 | // 24 | 25 | #include "nanocolorUtils.h" 26 | #include "nanocolorProcessing.h" 27 | #include 28 | 29 | /* 30 | This is actually u'v', u'v' is uv scaled by 1.5 along the v axis 31 | */ 32 | 33 | typedef struct { 34 | float Y; 35 | float u; 36 | float v; 37 | } NcYuvPrime; 38 | 39 | NcYxy _NcYuv2Yxy(NcYuvPrime c) { 40 | float d = 6.f * c.u - 16.f * c.v + 12.f; 41 | return (NcYxy) { 42 | c.Y, 43 | 9.f * c.u / d, 44 | 4.f * c.v / d 45 | }; 46 | } 47 | 48 | /* Equations from the paper "An Algorithm to Calculate Correlated Colour 49 | Temperature" by M. Krystek in 1985, using a rational Chebyshev approximation 50 | designed. 51 | */ 52 | NcYxy NcKelvinToYxy(float T, float luminance) { 53 | if (T < 1000 || T > 15000) 54 | return (NcYxy) { 0, 0, 0 }; 55 | 56 | float u = (0.860117757 + 1.54118254e-4 * T + 1.2864121e-7 * T * T) / 57 | (1.0 + 8.42420235e-4 * T + 7.08145163e-7 * T * T); 58 | float v = (0.317398726 + 4.22806245e-5 * T + 4.20481691e-8 * T * T) / 59 | (1.0 - 2.89741816e-5 * T + 1.61456053e-7 * T * T); 60 | 61 | return _NcYuv2Yxy((NcYuvPrime) {luminance, u, 3.f * v / 2.f }); 62 | } 63 | 64 | // ISO 17321-1:2012 Table D.1 65 | // ap0 is the aces name for 2065-1 66 | /* 67 | CIE 1931 68 | AP0: ACES 2065-1 White Point AP1: cg, cc, cct, proxy 69 | red green blue red green blue 70 | x 0.7347 0.0000 0.0001 0.32168 0.713 0.165 0.128 71 | y 0.2653 1.0000 -0.0770 0.33767 0.293 0.830 0.044 72 | */ 73 | 74 | static NcRGB _ISO17321_ap0[24] = { 75 | { 0.11877f, 0.08709f, 0.05895f }, // patch 1 76 | { 0.40003f, 0.31916f, 0.23737f }, // patch 2 77 | { 0.18476f, 0.20398f, 0.31310f }, // patch 3 78 | { 0.10901f, 0.13511f, 0.06493f }, // patch 4 79 | { 0.26684f, 0.24604f, 0.40932f }, // patch 5 80 | { 0.32283f, 0.46208f, 0.40606f }, // patch 6 81 | { 0.38607f, 0.22744f, 0.05777f }, // patch 7 82 | { 0.13822f, 0.13037f, 0.33703f }, // patch 8 83 | { 0.30203f, 0.13752f, 0.12758f }, // patch 9 84 | { 0.09310f, 0.06347f, 0.13525f }, 85 | { 0.34877f, 0.43655f, 0.10613f }, 86 | { 0.48657f, 0.36686f, 0.08061f }, 87 | { 0.08731f, 0.07443f, 0.27274f }, 88 | { 0.15366f, 0.25692f, 0.09071f }, 89 | { 0.21743f, 0.07070f, 0.05130f }, 90 | { 0.58921f, 0.53944f, 0.09157f }, 91 | { 0.30904f, 0.14818f, 0.27426f }, 92 | { 0.14900f, 0.23377f, 0.35939f }, // out of gamut r709, R could be in error 93 | { 0.86653f, 0.86792f, 0.85818f }, 94 | { 0.57356f, 0.57256f, 0.57169f }, 95 | { 0.35346f, 0.35337f, 0.35391f }, 96 | { 0.20253f, 0.20243f, 0.20287f }, 97 | { 0.09467f, 0.09520f, 0.09637f }, 98 | { 0.03745f, 0.03766f, 0.03895f }, // patch 24 99 | }; 100 | 101 | // these measurements are under Illuminant C, which is not normative 102 | // https://home.cis.rit.edu/~cnspci/references/mccamy1976.pdf 103 | 104 | static NcYxy _McCamy1976_Yxy[24] = { 105 | { 10.10, 0.400, 0.350 }, 106 | { 35.80, 0.377, 0.345 }, 107 | { 19.30, 0.247, 0.251 }, 108 | { 13.30, 0.337, 0.422 }, 109 | { 24.30, 0.265, 0.240 }, 110 | { 43.10, 0.261, 0.343 }, 111 | { 30.10, 0.506, 0.407 }, 112 | { 12.00, 0.211, 0.175 }, 113 | { 19.80, 0.453, 0.306 }, 114 | { 6.60, 0.285, 0.202 }, 115 | { 44.30, 0.380, 0.489 }, 116 | { 43.10, 0.473, 0.438 }, 117 | { 6.10, 0.187, 0.129 }, 118 | { 23.40, 0.305, 0.478 }, 119 | { 12.00, 0.539, 0.313 }, 120 | { 59.10, 0.448, 0.470 }, 121 | { 19.80, 0.364, 0.233 }, 122 | { 19.80, 0.196, 0.252 }, 123 | { 90.00, 0.310, 0.316 }, 124 | { 59.10, 0.310, 0.316 }, 125 | { 36.20, 0.310, 0.316 }, 126 | { 19.80, 0.310, 0.316 }, 127 | { 9.00, 0.310, 0.316 }, 128 | { 3.10, 0.310, 0.316 }, 129 | }; 130 | 131 | // these measurements are under D65 illuminant, and may not match the ISO chart 132 | // https://xritephoto.com/documents/literature/en/ColorData-1p_EN.pdf 133 | #define RGB(r, g, b) {(float)(r)/255.f, (float)(g)/255.f, (float)(b)/255.f} 134 | static NcRGB _Checker_SRGB[24] = { 135 | RGB(115, 82, 68), 136 | RGB(194, 150, 130), 137 | RGB(98, 122, 157), 138 | RGB(87, 108, 67), 139 | RGB(133, 128, 177), 140 | RGB(103, 189, 170), 141 | RGB(214, 126, 44), 142 | RGB(80, 91, 166), 143 | RGB(193, 90, 99), 144 | RGB(94, 60, 108), 145 | RGB(157, 188, 64), 146 | RGB(224, 163, 46), 147 | RGB(56, 61, 150), 148 | RGB(70, 148, 73), 149 | RGB(175, 54, 60), 150 | RGB(231, 199, 31), 151 | RGB(187, 86, 149), 152 | RGB(8, 133, 161), 153 | RGB(243, 243, 242), 154 | RGB(200, 200, 200), 155 | RGB(160, 160, 160), 156 | RGB(122, 122, 121), 157 | RGB(85, 85, 85), 158 | RGB(52, 52, 52) 159 | }; 160 | #undef RGB 161 | 162 | static const char* _ISO17321_names[24] = { 163 | "Dark skin", 164 | "Light skin", 165 | "Blue sky", 166 | "Foliage", 167 | "Blue flower", 168 | "Bluish green", 169 | "Orange", 170 | "Purplish blue", 171 | "Moderate red", 172 | "Purple", 173 | "Yellow green", 174 | "Orange yellow", 175 | "Blue", 176 | "Green", 177 | "Red", 178 | "Yellow", 179 | "Magenta", 180 | "Cyan", 181 | "White", 182 | "Neutral 8", 183 | "Neutral 6.5", 184 | "Neutral 5", 185 | "Neutral 3.5", 186 | "Black" 187 | }; 188 | 189 | NcRGB* NcISO17321ColorChipsAP0() { return _ISO17321_ap0; } 190 | const char** NcISO17321ColorChipsNames() { return _ISO17321_names; } 191 | NcRGB* NcCheckerColorChipsSRGB() { return _Checker_SRGB; } 192 | NcYxy* NcMcCamy1976ColorChipsYxy() { return _McCamy1976_Yxy; } 193 | 194 | NcXYZ NcProjectToChromaticities(NcXYZ c) { 195 | float n = c.x + c.y + c.z; 196 | return (NcXYZ) { c.x / n, c.y / n, c.z / n }; 197 | } 198 | 199 | NcYxy NcNormalizeYxy(NcYxy c) { 200 | return (NcYxy) { 201 | c.Y, 202 | c.Y * c.x / c.y, 203 | c.Y * (1.f - c.x - c.y) / c.y 204 | }; 205 | } 206 | 207 | static inline float sign_of(float x) { 208 | return x > 0 ? 1.f : (x < 0) ? -1.f : 0.f; 209 | } 210 | 211 | NcRGB NcRGBFromYxy(const NcColorSpace* cs, NcYxy c) { 212 | NcYxy cYxy = NcNormalizeYxy(c); 213 | NcRGB rgb = NcXYZToRGB(cs, (NcXYZ) { cYxy.x, cYxy.Y, cYxy.y }); 214 | NcRGB magRgb = { 215 | fabsf(rgb.r), 216 | fabsf(rgb.g), 217 | fabsf(rgb.b) }; 218 | 219 | float maxc = (magRgb.r > magRgb.g) ? magRgb.r : magRgb.g; 220 | maxc = maxc > magRgb.b ? maxc : magRgb.b; 221 | NcRGB ret = (NcRGB) { 222 | sign_of(rgb.r) * rgb.r / maxc, 223 | sign_of(rgb.g) * rgb.g / maxc, 224 | sign_of(rgb.b) * rgb.b / maxc }; 225 | return ret; 226 | } 227 | -------------------------------------------------------------------------------- /nanocolorUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 Pixar 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "Apache License") 5 | // with the following modification; you may not use this file except in 6 | // compliance with the Apache License and the following modification to it: 7 | // Section 6. Trademarks. is deleted and replaced with: 8 | // 9 | // 6. Trademarks. This License does not grant permission to use the trade 10 | // names, trademarks, service marks, or product names of the Licensor 11 | // and its affiliates, except as required to comply with Section 4(c) of 12 | // the License and to reproduce the content of the NOTICE file. 13 | // 14 | // You may obtain a copy of the Apache License at 15 | // 16 | // http://www.apache.org/licenses/LICENSE-2.0 17 | // 18 | // Unless required by applicable law or agreed to in writing, software 19 | // distributed under the Apache License with the above modification is 20 | // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | // KIND, either express or implied. See the Apache License for the specific 22 | // language governing permissions and limitations under the Apache License. 23 | // 24 | 25 | #ifndef PXR_BASE_GF_NC_NANOCOLOR_UTILS_H 26 | #define PXR_BASE_GF_NC_NANOCOLOR_UTILS_H 27 | 28 | #include "nanocolor.h" 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #define NcKelvinToYxy NCCONCAT(NCNAMESPACE, KelvinToYxy) 35 | #define NcISO17321ColorChipsAP0 NCCONCAT(NCNAMESPACE, ISO17321ColorChipsAP0) 36 | #define NcISO17321ColorChipsNames NCCONCAT(NCNAMESPACE, ISO17321ColorChipsNames) 37 | #define NcCheckerColorChipsSRGB NCCONCAT(NCNAMESPACE, CheckerColorChipsSRGB) 38 | #define NcMcCamy1976ColorChipsYxy NCCONCAT(NCNAMESPACE, McCamy1976ColorChipsYxy) 39 | #define NcProjectToChromaticities NCCONCAT(NCNAMESPACE, ProjectToChromaticities) 40 | #define NcRGBFromYxy NCCONCAT(NCNAMESPACE, RGBFromYxy) 41 | #define NcCIE1931ColorFromWavelength NCCONCAT(NCNAMESPACE, CIE1931ColorFromWavelength) 42 | #define NcMatchLinearColorSpace NCCONCAT(NCNAMESPACE, MatchLinearColorSpace) 43 | 44 | /// \brief Returns an Yxy coordinate for the blackbody emission spectrum 45 | /// for values between 1000 and 15000K. Note that temperatures below 1900 46 | /// are out of gamut for Rec709. 47 | /// \param temperature The blackbody temperature in Kelvin. 48 | /// \param luminosity The luminosity. 49 | /// \return An Yxy coordinate. 50 | NCAPI NcYxy NcKelvinToYxy(float temperature, float luminosity); 51 | 52 | /// \brief Returns the names of the 24 color chips in the ISO 17321 color charts. 53 | /// \return An array of const char pointers containing the names. A nullptr 54 | /// follows the last name. 55 | NCAPI const char** NcISO17321ColorChipsNames(void); 56 | 57 | /// \brief Returns a pointer to 24 color values in AP0 corresponding to 58 | /// the 24 color chips in ISO 17321-1:2012 Table D.1. 59 | /// \return An array of NcRGB containing the color values. 60 | NCAPI NcRGB* NcISO17321ColorChipsAP0(void); 61 | 62 | /// \brief Returns color values under D65 illuminant for the checker color chips, 63 | /// similar but not matching the ISO table. 64 | /// \return An array of NcRGB containing the color values. 65 | NCAPI NcRGB* NcCheckerColorChipsSRGB(void); 66 | 67 | /// \brief Returns color values under Illuminant C for the McCamy 1976 color chips, 68 | /// similar to but not matching the ISO table or the x-rite table. 69 | /// \return An array of NcXYZ containing the color values. 70 | NCAPI NcYxy* NcMcCamy1976ColorChipsYxy(void); 71 | 72 | /// \brief Projects a XYZ 1931 color coordinate to the regularized chromaticity coordinate. 73 | /// \param c The XYZ color coordinate. 74 | /// \return The regularized chromaticity coordinate. 75 | NCAPI NcXYZ NcProjectToChromaticities(NcXYZ c); 76 | 77 | /// \brief Converts an Yxy color coordinate to RGB using the specified color space. 78 | /// \param cs The color space. 79 | /// \param c The Yxy color coordinate. 80 | /// \return The RGB color coordinate. 81 | NCAPI NcRGB NcRGBFromYxy(const NcColorSpace* cs, NcYxy c); 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif /* PXR_BASE_GF_NC_NANOCOLOR_UTILS_H */ 88 | 89 | --------------------------------------------------------------------------------