├── .gitattributes ├── GamutCompress.blink ├── GamutCompress.dctl ├── GamutCompress.fuse ├── GamutCompress.glsl ├── GamutCompress.nk ├── GamutCompress.xml ├── GamutCompress_blink.nk ├── README.md ├── docs ├── README.md ├── aces_gamut_compression_vwg_presentation_20200521.nk ├── aces_gamut_compression_vwg_presentation_20200528.nk ├── calculate_max_distance.nk ├── doc-flame.md ├── doc-fusion.md ├── doc-nuke.md ├── doc-resolve.md ├── gamut-compress-algorithm.md └── gamut-compress-documentation.md ├── images ├── aces_gamut_mapping_vwg_images_ap0 │ └── download_images_here ├── cie_1931_xy_chromaticity_diagram_plots │ ├── cie_1931_xy_colorwheel_acescg.exr │ ├── cie_1931_xy_plot_aces-ap1-gamut.exr │ ├── cie_1931_xy_plot_arri-alexa-widegamut.exr │ ├── cie_1931_xy_plot_filmlight-e-gamut.exr │ ├── cie_1931_xy_plot_grid.exr │ ├── cie_1931_xy_plot_spectrum-locus.exr │ └── cie_1931_xy_points_acescg.exr ├── collage.acescg.exr ├── collage.rrt.jpg ├── collage_compressed.rrt.jpg └── screenshots │ ├── CIE1931_xy_chromaticity_diagram.png │ ├── CIE1931_xy_chromaticity_diagram_aces_awg.png │ ├── CIE1931_xy_chromaticity_diagram_cameras.png │ ├── CIE1931_xy_chromaticity_diagram_displays.png │ ├── CIE1931_xy_chromaticity_diagram_pointers.png │ ├── CIE1931_xy_chromaticity_diagram_rec709.png │ ├── GamutCompress_nuke-ui-calc_distance.png │ ├── GamutCompress_nuke-ui.png │ ├── GamutCompress_resolve-ui.png │ ├── Light-source-SPD-curves-for-Ablue-LED-Bwhite-LED-Cwhite-CFL-and-Dyellow-CFL.png │ ├── bluebar_image.png │ ├── bluebar_image_aces_vs_arri.png │ ├── bluebar_image_and_plot_acescg.png │ ├── bluebar_image_and_plot_awg.png │ ├── bluebar_image_plot.png │ ├── camera_spectral_sensitivities.png │ ├── colorwheel_acescg_1931_xy.png │ ├── colorwheel_acescg_distance_1931_xy.png │ ├── colorwheel_acescg_distance_gamma_down_1931_xy.png │ ├── compression_function.png │ └── compression_function_aggressive.png └── utilities ├── CalculateDistance.nk └── VisualizeDistance.nk /.gitattributes: -------------------------------------------------------------------------------- 1 | *.exr filter=lfs diff=lfs merge=lfs -text 2 | 3 | -------------------------------------------------------------------------------- /GamutCompress.blink: -------------------------------------------------------------------------------- 1 | /* GamutCompress v0.7 2 | Written by Jed Smith with lots of help from the ACES Gamut Mapping VWG 3 | https://github.com/jedypod 4 | https://community.acescentral.com/t/rgb-saturation-gamut-mapping-approach-and-a-comp-vfx-perspective 5 | https://community.acescentral.com/c/aces-development-acesnext/vwg-aces-gamut-mapping-working-group 6 | */ 7 | kernel GamutCompression : public ImageComputationKernel { 8 | Image src; 9 | Image dst; 10 | 11 | param: 12 | float3 th; 13 | float3 dl; 14 | bool invert; 15 | 16 | local: 17 | float3 s; 18 | 19 | void init() { 20 | // Pre-calculate scale so compression function passes through distance limit: (x=dl, y=1) 21 | s = ((float3)(1.0f)-th)/sqrt(max((float3)(1.001f),dl)-(float3)(1.0f)); 22 | } 23 | 24 | void process() { 25 | float3 rgb = float3(src().x, src().y, src().z); 26 | 27 | float ac = max(rgb.x, max(rgb.y, rgb.z)); // Achromatic axis 28 | // Inverse RGB Ratios: distance from achromatic axis 29 | float3 d = ac == 0.0f ? (float3)(0.0f) : (ac-rgb)/fabs(ac); 30 | 31 | float3 cd; // Compressed distance 32 | // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj 33 | if (!invert) { 34 | cd.x = d.x 0.155251141552511f) { 23 | return _powf( 2.0f, in*17.52f - 9.72f); 24 | } else { 25 | return (in - 0.0729055341958355f) / 10.5402377416545f; 26 | } 27 | } 28 | 29 | // convert acescg to acescc 30 | __DEVICE__ float lin_to_acescc(float in) { 31 | if (in <= 0.0f) { 32 | return -0.3584474886f; 33 | } else if (in < _powf(2.0f, -15.0f)) { 34 | return (_log2f(_powf(2.0f, -16.0f) + in * 0.5f) + 9.72f) / 17.52f; 35 | } else { 36 | return (_log2f(in) + 9.72f) / 17.52f; 37 | } 38 | } 39 | 40 | // convert acescc to acescg 41 | __DEVICE__ float acescc_to_lin(float in) { 42 | if (in < -0.3013698630f) { 43 | return (_powf( 2.0f, in * 17.52f - 9.72f) - _powf( 2.0f, -16.0f)) * 2.0f; 44 | } else if (in < (_log2f(65504.0f)+9.72f)/17.52f) { 45 | return _powf(2.0f, in * 17.52f - 9.72f); 46 | } else { 47 | return 65504.0f; 48 | } 49 | } 50 | 51 | __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B) 52 | { 53 | 54 | float3 rgb = make_float3(p_R, p_G, p_B); 55 | 56 | // Linearize working colorspace 57 | if (working_colorspace == acescct) { 58 | rgb.x = acescct_to_lin(rgb.x); 59 | rgb.y = acescct_to_lin(rgb.y); 60 | rgb.z = acescct_to_lin(rgb.z); 61 | } else if (working_colorspace == acescc) { 62 | rgb.x = acescc_to_lin(rgb.x); 63 | rgb.y = acescc_to_lin(rgb.y); 64 | rgb.z = acescc_to_lin(rgb.z); 65 | } 66 | 67 | // Amount of outer gamut to affect 68 | float3 th = 1.0f-make_float3(th_c, th_m, th_y); 69 | 70 | // Distance limit: How far beyond the gamut boundary to compress 71 | float3 dl = 1.0f+make_float3(d_c, d_m, d_y); 72 | 73 | // Calculate scale so compression function passes through distance limit: (x=dl, y=1) 74 | float3 s; 75 | s.x = (1.0f-th.x)/_sqrtf(_fmaxf(1.001f, dl.x)-1.0f); 76 | s.y = (1.0f-th.y)/_sqrtf(_fmaxf(1.001f, dl.y)-1.0f); 77 | s.z = (1.0f-th.z)/_sqrtf(_fmaxf(1.001f, dl.z)-1.0f); 78 | 79 | // Achromatic axis 80 | float ac = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z)); 81 | 82 | // Inverse RGB Ratios: distance from achromatic axis 83 | float3 d = ac == 0.0f ? make_float3(0.0f, 0.0f, 0.0f) : (ac-rgb)/_fabs(ac); 84 | 85 | float3 cd; // Compressed distance 86 | // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj 87 | if (invert == 0) { 88 | cd.x = d.xsrcCompOrder); 198 | float3 rgb = make_float3(rgba.x, rgba.y, rgba.z); 199 | 200 | // Amount of outer gamut to affect 201 | float3 th = 1.0f-make_float3(params->th_c, params->th_m, params->th_y); 202 | 203 | // Distance limit: How far beyond the gamut boundary to compress 204 | float3 dl = 1.0f+make_float3(params->d_c, params->d_m, params->d_y); 205 | 206 | // Calculate scale so compression function passes through distance limit: (x=dl, y=1) 207 | float3 s; 208 | s.x = (1.0f-th.x)/_sqrtf(_fmaxf(1.001f, dl.x)-1.0f); 209 | s.y = (1.0f-th.y)/_sqrtf(_fmaxf(1.001f, dl.y)-1.0f); 210 | s.z = (1.0f-th.z)/_sqrtf(_fmaxf(1.001f, dl.z)-1.0f); 211 | 212 | // Achromatic axis 213 | float ac = _fmaxf(rgb.x, _fmaxf(rgb.y, rgb.z)); 214 | 215 | // Inverse RGB Ratios: distance from achromatic axis 216 | float3 d = ac == 0.0f ? make_float3(0.0f) : (ac-rgb)/_fabs(ac); 217 | 218 | float3 cd; // Compressed distance 219 | // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj 220 | if (params->invert == 0) { 221 | cd.x = d.x 0.155251141552511) { 25 | return pow( 2.0, val*17.52 - 9.72); 26 | } else { 27 | return (val - 0.0729055341958355) / 10.5402377416545; 28 | } 29 | } 30 | 31 | // convert acescg to acescc 32 | float lin_to_acescc(float val) { 33 | if (val <= 0.0) { 34 | return -0.3584474886; // =(log2( pow(2.,-16.))+9.72)/17.52 35 | } else if (val < pow(2.0, -15.0)) { 36 | return (log2(pow(2.0, -16.0)+val*0.5)+9.72)/17.52; 37 | } else { // (val >= pow(2.,-15)) 38 | return (log2(val)+9.72)/17.52; 39 | } 40 | } 41 | 42 | // convert acescc to acescg 43 | float acescc_to_lin(float val) { 44 | if (val < -0.3013698630) { // (9.72-15)/17.52 45 | return (pow(2.0, val*17.52-9.72) - pow(2.0, -16.0))*2.0; 46 | } else if (val < (log2(65504.0)+9.72)/17.52) { 47 | return pow(2.0, val*17.52-9.72); 48 | } else { // (val >= (log2(HALF_MAX)+9.72)/17.52) 49 | return 65504.0; 50 | } 51 | } 52 | 53 | void main() { 54 | vec2 coords = gl_FragCoord.xy / vec2( adsk_result_w, adsk_result_h ); 55 | // source pixels 56 | vec3 rgb = texture2D(frontTex, coords).rgb; 57 | float alpha = texture2D(matteTex, coords).g; 58 | float select = texture2D(selectiveTex, coords).g; 59 | 60 | // Linearize working colorspace 61 | if (working_colorspace == 1) { 62 | rgb.x = acescct_to_lin(rgb.x); 63 | rgb.y = acescct_to_lin(rgb.y); 64 | rgb.z = acescct_to_lin(rgb.z); 65 | } else if (working_colorspace == 2) { 66 | rgb.x = acescc_to_lin(rgb.x); 67 | rgb.y = acescc_to_lin(rgb.y); 68 | rgb.z = acescc_to_lin(rgb.z); 69 | } 70 | 71 | // Amount of outer gamut to affect 72 | vec3 th = 1.0-threshold; 73 | 74 | // Distance limit: How far beyond the gamut boundary to compress 75 | vec3 dl = 1.0+vec3(cyan, magenta, yellow); 76 | 77 | // Calculate scale so compression function passes through distance limit: (x=dl, y=1) 78 | vec3 s; 79 | s.x = (1.0-th.x)/sqrt(max(1.001, dl.x)-1.0); 80 | s.y = (1.0-th.y)/sqrt(max(1.001, dl.y)-1.0); 81 | s.z = (1.0-th.z)/sqrt(max(1.001, dl.z)-1.0); 82 | 83 | // Achromatic axis 84 | float ac = max(rgb.x, max(rgb.y, rgb.z)); 85 | 86 | // Inverse RGB Ratios: distance from achromatic axis 87 | vec3 d = ac==0.0?vec3(0.0):(ac-rgb)/abs(ac); 88 | 89 | vec3 cd; // Compressed distance 90 | // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj 91 | if (!invert) { 92 | cd.x = d.x a:link \{ color: #ccc \}\n\nGamutCompress v0.7
\nmaps out of gamut colors back into gamut.\n

Documentation"} 19 | addUserKnob {26 about_label l " " T "\n
\nAbout
\nWritten by Jed Smith
with help from the ACES Gamut Mapping VWG"} 20 | } 21 | Input { 22 | inputs 0 23 | name Input 24 | xpos -40 25 | ypos -298 26 | } 27 | Dot { 28 | name Dot5 29 | xpos -6 30 | ypos -186 31 | } 32 | set N50366260 [stack 0] 33 | Expression { 34 | channel0 rgba 35 | expr0 max(r,g,b) 36 | channel1 none 37 | channel2 none 38 | channel3 none 39 | name Norm_MaxRGB 40 | xpos -260 41 | ypos -189 42 | } 43 | Dot { 44 | name Dot6 45 | xpos -226 46 | ypos -126 47 | } 48 | set N4b7ba180 [stack 0] 49 | Dot { 50 | name Dot8 51 | xpos -226 52 | ypos 54 53 | } 54 | push $N4b7ba180 55 | push $N50366260 56 | MergeExpression { 57 | inputs 2 58 | expr0 Ar==0?0:(Ar-Br)/fabs(Ar) 59 | expr1 Ag==0?0:(Ag-Bg)/fabs(Ag) 60 | expr2 Ab==0?0:(Ab-Bb)/fabs(Ab) 61 | name RGB_to_InverseRGBRatios 62 | xpos -40 63 | ypos -129 64 | } 65 | Dot { 66 | name Dot9 67 | xpos -6 68 | ypos -96 69 | } 70 | set N4b7ae1c0 [stack 0] 71 | Expression { 72 | expr0 r 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /GamutCompress_blink.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | push $cut_paste_input 3 | Group { 4 | name GamutCompress_blink 5 | addUserKnob {20 GamutCompress} 6 | addUserKnob {6 use_gpu l "use gpu" t "use gpu for blinkscript node" -STARTLINE} 7 | use_gpu true 8 | addUserKnob {26 ""} 9 | addUserKnob {18 threshold t "Percentage of the outer gamut boundary to affect. A value of 0.2 means 20% of the outer gamut will be utilized for gamut compression." R 0 0.2} 10 | threshold 0.15 11 | addUserKnob {6 threshold_panelDropped l "panel dropped state" -STARTLINE +HIDDEN} 12 | addUserKnob {35 distance_presets l distance t "choose distance presets for common digital cinema camera source gamuts" M {"presets/ACEScg from Filmlight E-Gamut" "knobs this \{cyan 0.122 magenta 0.201 yellow 0.139\}" "presets/ACEScg from Alexa Wide Gamut" "knobs this \{cyan 0.078 magenta 0.22 yellow 0.056\}" "presets/ACEScg from RedWideGamutRGB" "knobs this \{cyan 0.143 magenta 0.239 yellow 0.316\}" "presets/ACEScg from Sony S-Gamut3.Cine" "knobs this \{cyan 0.074 magenta 0.264 yellow 0.049\}" "presets/ACEScg from Sony S-Gamut3" "knobs this \{cyan 0.089 magenta 0.182 yellow 0.004\}" "presets/ACEScg from Panasonic V-Gamut" "knobs this \{cyan 0.06 magenta 0.148 yellow 0.012\}"}} 13 | addUserKnob {7 cyan t "Maximum distance beyond the green-blue gamut boundary to compress to the gamut boundary." R 0 0.4} 14 | cyan 0.1 15 | addUserKnob {7 magenta t "Maximum distance beyond the blue-red gamut boundary to compress to the gamut boundary." R 0 0.4} 16 | magenta 0.2 17 | addUserKnob {7 yellow t "Maximum distance beyond the red-green gamut boundary to compress to the gamut boundary." R 0 0.4} 18 | yellow 0.1 19 | addUserKnob {4 direction M {forward inverse}} 20 | addUserKnob {20 info_tab l Info} 21 | addUserKnob {26 info_label l " " T "\n\nGamutCompress v0.7
\nmaps out of gamut colors back into gamut.\n

Documentation"} 22 | addUserKnob {26 about_label l " " T "\n
\nAbout
\nWritten by Jed Smith
with help from the ACES Gamut Mapping VWG"} 23 | } 24 | Input { 25 | inputs 0 26 | name Input 27 | xpos -40 28 | ypos -10 29 | } 30 | AddChannels { 31 | name AddChannels 32 | xpos -40 33 | ypos 44 34 | } 35 | BlinkScript { 36 | recompileCount 1 37 | ProgramGroup 1 38 | KernelDescription "2 \"GamutCompression\" iterate pixelWise c0d43fd0eba9efe2ea0c3fb1b173b0116570ea6ec879ba3765d9333e4c27883a 2 \"src\" Read Point \"dst\" Write Point 3 \"th\" Float 3 AAAAAAAAAAAAAAAAAAAAAA== \"dl\" Float 3 AAAAAAAAAAAAAAAAAAAAAA== \"invert\" Bool 1 AA== 3 \"th\" 3 1 \"dl\" 3 1 \"invert\" 1 1 1 \"s\" Float 3 1 AAAAAAAAAAAAAAAAAAAAAA==" 39 | kernelSource "kernel GamutCompression : public ImageComputationKernel \{\n Image src;\n Image dst;\n\n param:\n float3 th;\n float3 dl;\n bool invert;\n\n local:\n float3 s;\n\n void init() \{\n // Pre-calculate scale so compression function passes through distance limit: (x=dl, y=1)\n s = ((float3)(1.0f)-th)/sqrt(max((float3)(1.001f),dl)-(float3)(1.0f));\n \}\n\n void process() \{\n float3 rgb = float3(src().x, src().y, src().z);\n\n float ac = max(rgb.x, max(rgb.y, rgb.z)); // Achromatic axis\n // Inverse RGB Ratios: distance from achromatic axis\n float3 d = ac == 0.0f ? (float3)(0.0f) : (ac-rgb)/fabs(ac);\n\n float3 cd; // Compressed distance\n // Parabolic compression function: https://www.desmos.com/calculator/nvhp63hmtj\n if (!invert) \{\n cd.x = d.x](https://github.com/jedypod/gamut-compress/blob/master/images/collage.rrt.jpg?raw=true) [](https://github.com/jedypod/gamut-compress/blob/master/images/collage_compressed.rrt.jpg?raw=true) 124 | 125 | On the left are the original images with the ACES Rec.709 RRT display rendering transform applied. On the right are the same images, but with gamut compression applied. 126 | 127 | ## Implementations 128 | DCC Implementations for the following software packages are included. For specific installation instructions, check out the documentation links for each package below. 129 | - [Blinkscript for Nuke](docs/doc-nuke.md) (A [non-blinkscript node](GamutCompress.nk) is also provided for Nuke Non-Commercial). 130 | - [Fuse for Fusion or Resolve Lite](docs/doc-fusion.md) 131 | - [DCTL for Resolve Studio](docs/doc-resolve.md) 132 | - [Matchbox for Flame, Scratch and Baselight](docs/doc-flame.md) 133 | 134 | ## Download 135 | You can grab the latest "stable" release from the [Releases page](https://github.com/jedypod/gamut-compress/releases), or you can clone the repo to get the latest work. 136 | 137 | `git clone git@github.com:jedypod/gamut-compress.git` 138 | 139 | ## Documentation 140 | For more information about [how the it works](/docs/gamut-compress-algorithm.md) and [how to use it](/docs/gamut-compress-documentation.md), please check out the [documentation](/docs). 141 | 142 | ## About 143 | Written by Jed Smith in [collaboration](https://community.acescentral.com/t/rgb-saturation-gamut-mapping-approach-and-a-comp-vfx-perspective) with the [ACES Gamut Mapping Virtual Working Group](https://community.acescentral.com/c/aces-development-acesnext/vwg-aces-gamut-mapping-working-group). 144 | 145 | Also thanks to the contributions of [@nick-shaw](https://github.com/nick-shaw) for the initial versions of the DCTL and Matchbox implementations, and to Jacob Danell for the first version of the Fuse implementation. 146 | 147 | If you are looking for the so-called "Reference" Gamut Compression adopted by ACES in version 1.2, you can find it [here](https://docs.acescentral.com/guides/rgc-user/). However I would not recommend using it for the following reasons: 148 | 1. It has a fixed parameterization for the per-channel compression settings, which will yield different results depending on the camera source colorimetry. 149 | 2. It uses a more aggressive compression function which does not distribute values as evenly, resulting in the need for lower threshold settings and more problematic inversion (if that is required). 150 | 3. It will result in distortion of the chromaticities as part of the compression. This may be fine for creative grading, but for more technical workflows like in VFX, a linear compression towards the whitepoint may be desired instead. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | Here you can find documentation for the Gamut Compress tool. It's split up into a few documents so as to not be too overwhelming. 3 | 4 | ## [The Gamut Compression Algorithm](/docs/gamut-compress-algorithm.md) 5 | ## [Usage and Workflow](/docs/gamut-compress-documentation.md) 6 | 7 | ## DCC Implementation Documentation 8 | Here is specific documentation for each of the DCC implemenetations. 9 | 10 | - [Nuke](/docs/doc-nuke.md) 11 | - [Resolve](/docs/doc-resolve.md) 12 | - [Fusion](/docs/doc-fusion.md) 13 | - [Flame](/docs/doc-flame.md) 14 | 15 | 16 | 17 | ## Other 18 | There are also a few nuke scripts preserved here which were used to present work to the Gamut Mapping VWG. 19 | 20 | [This is the Nuke script](/docs/aces_gamut_compression_vwg_presentation_20200521.nk) used in the presentation given for the ACES Gamut Mapping virtual working group on May 21st 2020. 21 | 22 | Here are links to the [meeting notes](https://community.acescentral.com/t/notice-of-meeting-aces-gamut-mapping-vwg-meeting-13-5-21-2020/2891/2), and a [recording of the session](https://paper.dropbox.com/doc/Gamut-Mapping-Virtual-Working-Group--A0YcPBoTN8OjbKc3RCXgiKTtAg-1BByr3dSQvpjlYcOJlVgR). 23 | 24 | This nuke script does contain some nodes like the [PlotChromaticity](https://github.com/jedypod/nuke-colortools/blob/master/toolsets/PlotChromaticity.nk) tool that rely on BlinkScript, which will not work in [Nuke Non-Commercial](https://www.foundry.com/products/nuke/non-commercial). However, the same functionality can be achieved using a [PlotPoints](https://github.com/jedypod/nuke-colortools/blob/master/toolsets/PlotPoints.nk) node, though with some performance penalties. 25 | 26 | This nuke script contains exr images which are not in this git repository due to the size. You can [download them here](https://www.dropbox.com/sh/jbng5hfi6ofqlp8/AABLGGxd9PWYMxFAadHK5mspa?dl=0). These images have been sourced from [acescentral](https://acescentral.com) and the [ACES Gamut Mapping VWG](https://www.dropbox.com/sh/u6z2a0jboo4vno8/AAB-10qcflhpr0C5LWhs7Kq4a?dl=0) dropbox. They have been resized to 2k and converted to ACES 2065-1 colorspace. These images are not a complete set but rather the ones I've found to be the most interesting. 27 | 28 | If you download the image set to [/images/aces_gamut_mapping_vwg_images_ap0/](/images/aces_gamut_mapping_vwg_images_ap0/) the nuke script's relative paths should automatically find the images. 29 | 30 | There is also the [script from the 2020-05-28 meeting here](/docs/aces_gamut_compression_vwg_presentation_20200528.nk). -------------------------------------------------------------------------------- /docs/calculate_max_distance.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | ColorWheel { 3 | inputs 0 4 | name ColorWheel6 5 | note_font Helvetica 6 | selected true 7 | xpos 7550 8 | ypos 6639 9 | } 10 | Group { 11 | inputs 0 12 | name GamutPrimaries 13 | selected true 14 | xpos 7440 15 | ypos 6639 16 | postage_stamp true 17 | addUserKnob {20 GamutPrimaries_tab l GamutPrimaries} 18 | addUserKnob {41 gamut T ColorMatrix5.gamut} 19 | addUserKnob {41 red T Primaries.red} 20 | addUserKnob {41 green T Primaries.green} 21 | addUserKnob {41 blue T Primaries.blue} 22 | addUserKnob {41 white T Primaries.white} 23 | } 24 | Constant { 25 | inputs 0 26 | color {0 0 0 0} 27 | format "4 1 0 0 4 1 1 rgbw" 28 | name Constant3 29 | note_font "Bitstream Vera Sans" 30 | xpos 509 31 | ypos -42 32 | postage_stamp false 33 | } 34 | Reformat { 35 | type "to box" 36 | box_width 4 37 | box_height 2 38 | box_fixed true 39 | name Reformat1 40 | xpos 509 41 | ypos -16 42 | } 43 | Rectangle { 44 | area {0 0 1 2} 45 | color {1 0 0 0} 46 | name Rectangle1 47 | note_font "Bitstream Vera Sans" 48 | xpos 509 49 | ypos 10 50 | } 51 | Rectangle { 52 | area {1 0 2 2} 53 | color {0 1 0 0} 54 | name Rectangle2 55 | note_font "Bitstream Vera Sans" 56 | xpos 509 57 | ypos 36 58 | } 59 | Rectangle { 60 | area {2 0 3 2} 61 | color {0 0 1 0} 62 | name Rectangle3 63 | note_font "Bitstream Vera Sans" 64 | xpos 509 65 | ypos 59 66 | } 67 | Rectangle { 68 | area {3 0 4 2} 69 | color {1 1 1 0} 70 | name Rectangle4 71 | note_font "Bitstream Vera Sans" 72 | xpos 509 73 | ypos 85 74 | } 75 | Group { 76 | name ColorMatrix5 77 | label "\[value gamut] to XYZ\n" 78 | xpos 509 79 | ypos 128 80 | addUserKnob {20 GamutToXYZ_tab l GamutToXYZ} 81 | addUserKnob {4 gamut t "Choose gamut" M {XYZ ACES ACEScg "Filmlight E-Gamut" Rec709 Rec2020 P3D60 P3D65 P3DCI ArriAlexaWideGamut "AdobeRGB\t" AdobeWideGamutRGB ROMM RIMM ERIMM ProPhotoRGB REDDRAGONcolor REDDRAGONcolor2 REDcolor REDcolor2 REDcolor3 REDcolor4 REDWideGamutRGB GoProProtune CanonCinemaGamut SonySGamut SonySGamut3Cine PanasonicVGamut ""}} 82 | addUserKnob {6 invert +STARTLINE} 83 | addUserKnob {41 matrix T ColorMatrix.matrix} 84 | } 85 | Input { 86 | inputs 0 87 | name Input 88 | xpos -40 89 | ypos -10 90 | } 91 | ColorMatrix { 92 | matrix { 93 | {{curve(which) 1 0.9525524378 0.6624541879 0.7053968906 0.4123907983 0.6369580626 0.5049495697 0.4865709841 0.4451698363 0.6380076408 0.5766690373 0.7165006995 0.797760427 0.797760427 0.797760427 0.7976718545 0.5070186853 0.4462202489 0.4300414324 0.4581649601 0.4878340662 0.4517004192 0.7352752686 0.5022571683 0.7160496712 0.7064827085 0.5990839601 0.6796444654} {curve(which) 0 0 0.1340042055 0.1640413404 0.3575843275 0.1446169019 0.2646814585 0.2656676769 0.2771343887 0.2147038579 0.1855582297 0.1010205746 0.1351858526 0.1351858526 0.1351858526 0.1351878047 0.3587769568 0.3157556653 0.3700728714 0.3832037449 0.3432727158 0.3178463876 0.06860940903 0.2929667532 0.1296834797 0.1288010478 0.2489254922 0.1522114277} {curve(which) 0 9.367863095e-05 0.1561876982 0.08101774752 0.180480808 0.1688809693 0.1830150485 0.1982172877 0.1722826511 0.09774444997 0.1882286519 0.1467743814 0.03134934977 0.03134934977 0.03134934977 0.03133957833 0.0868505761 0.190669477 0.152531758 0.1112773567 0.1215386018 0.1830992699 0.1465712637 0.1552320272 0.1047228053 0.1151721701 0.1024464965 0.1186000481}} 94 | {{curve(which) 0 0.3439664543 0.2722287476 0.2801307142 0.2126390189 0.2627002299 0.237623319 0.2289745659 0.209491685 0.2919537723 0.2973450124 0.258728236 0.2880711257 0.2880711257 0.2880711257 0.2880405784 0.2207257152 0.1942579001 0.2022213936 0.1694435924 0.2289056629 0.2119505703 0.2866941094 0.1387997568 0.2612613738 0.2709796727 0.2150758505 0.2606855333} {curve(which) 1 0.7281661034 0.6740817428 0.8202066422 0.7151686549 0.6779980659 0.6891706586 0.6917385459 0.7215952873 0.8238410354 0.6273635626 0.7246823311 0.7118432522 0.7118432522 0.7118432522 0.7118694782 0.839184761 0.7385566831 0.7585275769 0.8648257852 0.7808576822 0.7230190039 0.8429791331 0.910841465 0.8696421385 0.786606431 0.8850684762 0.7748944759} {curve(which) 0 -0.07213255018 0.05368951708 -0.1003373638 0.07219231874 0.05930171534 0.07320601493 0.07928691059 0.06891305745 -0.1157948226 0.07529145479 0.01658944227 8.565396274e-05 8.565396274e-05 8.565396274e-05 8.991353388e-05 -0.05991046131 0.06718540192 0.03925102949 -0.03426937759 -0.009763340466 0.06503042579 -0.1296732277 -0.04964122549 -0.1309035122 -0.05758608505 -0.1001443192 -0.03558001295}} 95 | {{curve(which) 0 -3.863927134e-08 -0.005574660841 -0.1037815213 0.01933082007 0 0 0 0 0.0027982709 0.02703136392 -2.906408625e-08 -3.236030111e-08 -3.236030111e-08 -3.236030111e-08 0 -0.0544523783 -0.04792318866 -0.0176958181 -0.1061859056 -0.02100777067 -0.01945115253 -0.07968087494 0.07801423222 -0.009676366113 -0.009677864611 -0.03206583485 -0.009310216643} {curve(which) 0 0 0.004060741514 -0.07290724665 0.1191947311 0.0280726999 0.0449459292 0.04511339962 0.04706057906 -0.06703422964 0.07068887353 0.05121183768 1.2621717e-08 1.2621717e-08 1.2621717e-08 -1.262213711e-08 -0.0003228379355 -0.0002844714036 0.08768811822 0.02554347552 0.01782695204 0.01650637016 -0.3473432064 -0.3148325086 -0.2364816219 0.004600019194 -0.02765839547 -0.004612449091} {curve(which) 1 1.008825183 1.010339141 1.265746474 0.950532198 1.060985088 0.9638792276 1.043944359 0.9073553085 1.153293729 0.9913375378 0.7738927603 0.8251045942 0.8251045942 0.8251045942 0.8248898983 1.063571215 1.057001948 0.9388025999 1.089437366 1.01197505 1.011739731 1.51608181 1.325875998 1.335215807 1.094135642 1.148782015 1.102980375}} 96 | } 97 | invert {{parent.invert}} 98 | name ColorMatrix 99 | label "RGB to XYZ" 100 | xpos -40 101 | ypos 33 102 | addUserKnob {20 Gamut} 103 | addUserKnob {3 which} 104 | which {{parent.gamut}} 105 | addUserKnob {12 rxy +DISABLED} 106 | rxy {{curve(which) 0 0.7347 0.713 0.8 0.64 0.708 0.68 0.68 0.68 0.684 0.64 0.7347 0.7347 0.7347 0.7347 0.734699 0.7530442228 0.7530444911 0.6997470013 0.8786825105 0.7011810359 0.7011805919 0.780308 0.69848046 0.74 0.73 0.766 0.73} {curve(which) 0 0.2653 0.293 0.3177 0.33 0.292 0.32 0.32 0.32 0.313 0.33 0.2653 0.2653 0.2653 0.2653 0.265301 0.3278305767 0.3278310295 0.3290469303 0.3249640074 0.3290141556 0.3290136991 0.304253 0.19302645 0.27 0.28 0.275 0.28}} 107 | addUserKnob {12 gxy +DISABLED} 108 | gxy {{curve(which) 0 0 0.165 0.18 0.3 0.17 0.265 0.265 0.265 0.221 0.21 0.1152 0.1596 0.1596 0.1596 0.159597 0.2995702285 0.2995704905 0.304264039 0.3008887144 0.3006003047 0.3006003955 0.121595 0.32955538 0.17 0.14 0.225 0.165} {curve(which) 0 1 0.83 0.9 0.6 0.797 0.69 0.69 0.69 0.848 0.71 0.8264 0.8404 0.8404 0.8404 0.840403 0.700699322 0.7006994156 0.6236411451 0.6790547558 0.6837888343 0.6837888243 1.493994 1.02459662 1.14 0.855 0.8 0.84}} 109 | addUserKnob {12 bxy +DISABLED} 110 | bxy {{curve(which) 0 0.0001 0.128 0.065 0.15 0.131 0.15 0.15 0.15 0.0861 0.15 0.1566 0.0366 0.0366 0.0366 0.036598 0.07964206674 0.1450115843 0.1349139613 0.09539869461 0.1081544556 0.1453319462 0.095612 0.10844263 0.08 0.1 0.089 0.1} {curve(which) 0 -0.077 0.044 -0.0805 0.06 0.046 0.06 0.06 0.06 -0.102 0.06 0.0177 0.0001 0.0001 0.0001 0.000105 -0.05493795109 0.05109712509 0.03471744128 -0.02937932683 -0.008688175787 0.05161680362 -0.084589 -0.03467857 -0.1 -0.05 -0.087 -0.03}} 111 | addUserKnob {12 wxy +DISABLED} 112 | wxy {{curve(which) 0.33333 0.32168 0.32168 0.3127 0.3127 0.3127 0.32168 0.3127 0.314 0.3127 0.3127 0.3457 0.3457 0.3457 0.3457 0.345704 0.3216831877 0.3216832104 0.3216832894 0.3216832894 0.3216832104 0.3216832894 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127} {curve(which) 0.33333 0.33767 0.33767 0.329 0.329 0.329 0.33767 0.329 0.351 0.329 0.329 0.3585 0.3585 0.3585 0.3585 0.35854 0.337673316 0.3376736101 0.3376734472 0.3376734472 0.3376736101 0.3376734472 0.329 0.329 0.329 0.329 0.329 0.329}} 113 | } 114 | Output { 115 | name Output 116 | xpos -40 117 | ypos 86 118 | } 119 | end_group 120 | set N11e135b0 [stack 0] 121 | Colorspace { 122 | colorspace_in CIE-XYZ 123 | colorspace_out CIE-Yxy 124 | name Colorspace1 125 | label "\[value colorspace_in] -> \[value colorspace_out]" 126 | xpos 509 127 | ypos 185 128 | } 129 | NoOp { 130 | name Primaries 131 | note_font "Bitstream Vera Sans" 132 | xpos 509 133 | ypos 238 134 | addUserKnob {20 User} 135 | addUserKnob {12 red} 136 | red {{"\[sample this green 0 0]"} {"\[sample this blue 0 0]"}} 137 | addUserKnob {12 green} 138 | green {{"\[sample this green 1 0]"} {"\[sample this blue 1 0]"}} 139 | addUserKnob {12 blue} 140 | blue {{"\[sample this green 2 0]"} {"\[sample this blue 2 0]"}} 141 | addUserKnob {12 white} 142 | white {{"\[sample this green 3 0]"} {"\[sample this blue 3 0]"}} 143 | } 144 | push $N11e135b0 145 | Output { 146 | name Output 147 | xpos 400 148 | ypos 134 149 | } 150 | end_group 151 | Group { 152 | name ChromaticityConverter_live 153 | tile_color 0xbd91c9ff 154 | label "\[if \{\[value invert]\} \{return \"\[value dst_colorspace_name] to \[value src_colorspace_name]\"\} else \{return \"\[value src_colorspace_name] to \[value dst_colorspace_name]\"\}]" 155 | selected true 156 | xpos 7440 157 | ypos 6752 158 | addUserKnob {20 ChromaticityConverter_tab l ChromaticityConverter} 159 | addUserKnob {26 colorspace_chromaticities_label l " " T "Src Colorspace Chromaticities"} 160 | addUserKnob {35 src_presets l preset M {colorspace/ACES "knobs this \{rxy \{0.73470 0.26530\} gxy \{0.00000 1.00000\} bxy \{0.00010 -0.07700\} wxy \{0.32168 0.33767\} src_colorspace_name \{ACES\}\}" colorspace/ACEScg "knobs this \{rxy \{0.713 0.293\} gxy \{0.165 0.830\} bxy \{0.128 0.044\} wxy \{0.32168 0.33767\} src_colorspace_name \{ACEScg\}\}" "colorspace/Filmlight E-Gamut" "knobs this \{ rxy \{0.8 0.3177\} gxy \{0.18 0.9\} bxy \{0.065 -0.0805\} wxy \{0.3127 0.329\} src_colorspace_name \{Filmlight E-Gamut\}\}" colorspace/Rec709 "knobs this \{rxy \{0.64000 0.33000\} gxy \{0.30000 0.60000\} bxy \{0.15000 0.06000\} wxy \{0.31270 0.32900\} src_colorspace_name \{Rec709\}\}" colorspace/Rec2020 "knobs this \{rxy \{0.70800 0.29200\} gxy \{0.17000 0.79700\} bxy \{0.13100 0.04600\} wxy \{0.31270 0.32900\} src_colorspace_name \{Rec2020\}\}" colorspace/P3D60 "knobs this \{rxy \{0.68000 0.32000\} gxy \{0.26500 0.69000\} bxy \{0.15000 0.06000\} wxy \{0.32168 0.33767\} src_colorspace_name \{P3D60\}\}" colorspace/P3D65 "knobs this \{rxy \{0.68000 0.32000\} gxy \{0.26500 0.69000\} bxy \{0.15000 0.06000\} wxy \{0.31270 0.32900\} src_colorspace_name \{P3D65\}\}" colorspace/P3DCI "knobs this \{rxy \{0.68000 0.32000\} gxy \{0.26500 0.69000\} bxy \{0.15000 0.06000\} wxy \{0.31400 0.35100\} src_colorspace_name \{P3DCI\}\}" colorspace/ArriAlexaWideGamut "knobs this \{rxy \{0.68400 0.31300\} gxy \{0.22100 0.84800\} bxy \{0.08610 -0.10200\} wxy \{0.31270 0.32900\} src_colorspace_name \{ArriAlexaWideGamut\}\}" "colorspace/AdobeRGB\t" "knobs this \{rxy \{0.6400 0.3300\} gxy \{0.2100 0.7100\} bxy \{0.1500 0.0600\} wxy \{0.3127 0.3290\} src_colorspace_name \{AdobeRGB\}\}" colorspace/AdobeWideGamutRGB "knobs this \{rxy \{0.7347 0.2653\} gxy \{0.1152 0.8264\} bxy \{0.1566 0.0177\} wxy \{0.3457 0.3585\} src_colorspace_name \{AdobeWideGamutRGB\}\}" colorspace/ROMM "knobs this \{rxy \{7.34700000e-01 2.65300000e-01\} gxy \{1.59600000e-01 8.40400000e-01\} bxy \{3.66000000e-02 1.00000000e-04\} wxy \{0.3457 0.3585\} src_colorspace_name \{ROMM\}\}" colorspace/RIMM "knobs this \{rxy \{7.34700000e-01 2.65300000e-01\} gxy \{1.59600000e-01 8.40400000e-01\} bxy \{3.66000000e-02 1.00000000e-04\} wxy \{0.3457 0.3585\} src_colorspace_name \{RIMM\}\}" colorspace/ERIMM "knobs this \{rxy \{7.34700000e-01 2.65300000e-01\} gxy \{1.59600000e-01 8.40400000e-01\} bxy \{3.66000000e-02 1.00000000e-04\} wxy \{0.3457 0.3585\} src_colorspace_name \{ERIMM\}\}" colorspace/ProPhotoRGB "knobs this \{rxy \{0.734699 0.265301\} gxy \{0.159597 0.840403\} bxy \{0.036598 0.000105\} wxy \{0.345704 0.358540\} src_colorspace_name \{ProPhotoRGB\}\}" colorspace/REDDRAGONcolor "knobs this \{rxy \{0.753044222785 0.327830576682\} gxy \{0.299570228481 0.700699321956\} bxy \{0.079642066735 -0.0549379510888\} wxy \{0.321683187724 0.337673316035\} src_colorspace_name \{REDDRAGONcolor\}\}" colorspace/REDDRAGONcolor2 "knobs this \{rxy \{0.753044491143 0.327831029513\} gxy \{0.299570490451 0.700699415614\} bxy \{0.145011584278 0.0510971250879\} wxy \{0.321683210353 0.337673610062\} src_colorspace_name \{REDDRAGONcolor2\}\}" colorspace/REDcolor "knobs this \{rxy \{0.699747001291 0.329046930313\} gxy \{0.304264039024 0.623641145129\} bxy \{0.134913961296 0.0347174412813\} wxy \{0.321683289449 0.337673447208\} src_colorspace_name \{REDcolor\}\}" colorspace/REDcolor2 "knobs this \{rxy \{0.878682510476 0.32496400741\} gxy \{0.300888714367 0.679054755791\} bxy \{0.0953986946056 -0.0293793268343\} wxy \{0.321683289449 0.337673447208\} src_colorspace_name \{REDcolor2\}\}" colorspace/REDcolor3 "knobs this \{rxy \{0.701181035906 0.329014155583\} gxy \{0.300600304652 0.683788834269\} bxy \{0.108154455624 -0.00868817578666\} wxy \{0.321683210353 0.337673610062\} src_colorspace_name \{REDcolor3\}\}" colorspace/REDcolor4 "knobs this \{rxy \{0.701180591892 0.329013699116\} gxy \{0.300600395529 0.683788824257\} bxy \{0.145331946229 0.0516168036226\} wxy \{0.321683289449 0.337673447208\} src_colorspace_name \{REDcolor4\}\}" colorspace/REDWideGamutRGB "knobs this \{rxy \{0.780308 0.304253\} gxy \{0.121595 1.493994\} bxy \{0.095612 -0.084589\} wxy \{0.3127 0.3290\} src_colorspace_name \{REDWideGamutRGB\}\}" colorspace/GoProProtune "knobs this \{rxy \{0.69848046 0.19302645\} gxy \{0.32955538 1.02459662\} bxy \{0.10844263 -0.03467857\} wxy \{0.3127 0.329\} src_colorspace_name \{GoProProtune\}\}" colorspace/CanonCinemaGamut "knobs this \{rxy \{0.74 0.27\} gxy \{0.17 1.14\} bxy \{0.08 -0.1\} wxy \{0.3127 0.329\} src_colorspace_name \{CanonCinemaGamut\}\}" colorspace/SonySGamut "knobs this \{rxy \{0.73 0.28\} gxy \{0.14 0.855\} bxy \{0.1 -0.05\} wxy \{0.3127 0.329\} src_colorspace_name \{SonySGamut\}\}" colorspace/SonySGamut3Cine "knobs this \{rxy \{0.766 0.275\} gxy \{0.225 0.8\} bxy \{0.089 -0.087\} wxy \{0.3127 0.329\} src_colorspace_name \{SonySGamut3Cine\}\}" colorspace/PanasonicVGamut "knobs this \{rxy \{0.730 0.280\} gxy \{0.165 0.840\} bxy \{0.100 -0.030\} wxy \{0.3127 0.3290\} src_colorspace_name \{PanasonicVGamut\}\}" colorspace/XYZ "knobs this \{rxy \{0 0\} gxy \{0 0\} bxy \{0 0\} wxy \{0 0\} src_colorspace_name \{XYZ\}\}"}} 161 | addUserKnob {41 rxy l r T _knobchanged_.rxy} 162 | addUserKnob {41 gxy l g T _knobchanged_.gxy} 163 | addUserKnob {41 bxy l b T _knobchanged_.bxy} 164 | addUserKnob {41 wxy l w T _knobchanged_.wxy} 165 | addUserKnob {1 src_colorspace_name l src +DISABLED} 166 | src_colorspace_name ACES 167 | addUserKnob {26 ""} 168 | addUserKnob {26 dst_colorspace_chromaticities_label l " " T "Dst Colorspace Chromaticities"} 169 | addUserKnob {35 dst_presets l preset M {colorspace/ACES "knobs this \{drxy \{0.73470 0.26530\} dgxy \{0.00000 1.00000\} dbxy \{0.00010 -0.07700\} dwxy \{0.32168 0.33767\} dst_colorspace_name \{ACES\}\}" colorspace/ACEScg "knobs this \{drxy \{0.713 0.293\} dgxy \{0.165 0.830\} dbxy \{0.128 0.044\} dwxy \{0.32168 0.33767\} dst_colorspace_name \{ACEScg\}\}" "colorspace/Filmlight E-Gamut" "knobs this \{ drxy \{0.8 0.3177\} dgxy \{0.18 0.9\} dbxy \{0.065 -0.0805\} dwxy \{0.3127 0.329\} dst_colorspace_name \{Filmlight E-Gamut\}\}" colorspace/Rec709 "knobs this \{drxy \{0.64000 0.33000\} dgxy \{0.30000 0.60000\} dbxy \{0.15000 0.06000\} dwxy \{0.31270 0.32900\} dst_colorspace_name \{Rec709\}\}" colorspace/Rec2020 "knobs this \{drxy \{0.70800 0.29200\} dgxy \{0.17000 0.79700\} dbxy \{0.13100 0.04600\} dwxy \{0.31270 0.32900\} dst_colorspace_name \{Rec2020\}\}" colorspace/P3D60 "knobs this \{drxy \{0.68000 0.32000\} dgxy \{0.26500 0.69000\} dbxy \{0.15000 0.06000\} dwxy \{0.32168 0.33767\} dst_colorspace_name \{P3D60\}\}" colorspace/P3D65 "knobs this \{drxy \{0.68000 0.32000\} dgxy \{0.26500 0.69000\} dbxy \{0.15000 0.06000\} dwxy \{0.31270 0.32900\} dst_colorspace_name \{P3D65\}\}" colorspace/P3DCI "knobs this \{drxy \{0.68000 0.32000\} dgxy \{0.26500 0.69000\} dbxy \{0.15000 0.06000\} dwxy \{0.31400 0.35100\} dst_colorspace_name \{P3DCI\}\}" colorspace/ArriAlexaWideGamut "knobs this \{drxy \{0.68400 0.31300\} dgxy \{0.22100 0.84800\} dbxy \{0.08610 -0.10200\} dwxy \{0.31270 0.32900\} dst_colorspace_name \{ArriAlexaWideGamut\}\}" "colorspace/AdobeRGB\t" "knobs this \{drxy \{0.6400 0.3300\} dgxy \{0.2100 0.7100\} dbxy \{0.1500 0.0600\} dwxy \{0.3127 0.3290\} dst_colorspace_name \{AdobeRGB\}\}" colorspace/AdobeWideGamutRGB "knobs this \{drxy \{0.7347 0.2653\} dgxy \{0.1152 0.8264\} dbxy \{0.1566 0.0177\} dwxy \{0.3457 0.3585\} dst_colorspace_name \{AdobeWideGamutRGB\}\}" colorspace/ROMM "knobs this \{drxy \{7.34700000e-01 2.65300000e-01\} dgxy \{1.59600000e-01 8.40400000e-01\} dbxy \{3.66000000e-02 1.00000000e-04\} dwxy \{0.3457 0.3585\} dst_colorspace_name \{ROMM\}\}" colorspace/RIMM "knobs this \{drxy \{7.34700000e-01 2.65300000e-01\} dgxy \{1.59600000e-01 8.40400000e-01\} dbxy \{3.66000000e-02 1.00000000e-04\} dwxy \{0.3457 0.3585\} dst_colorspace_name \{RIMM\}\}" colorspace/ERIMM "knobs this \{drxy \{7.34700000e-01 2.65300000e-01\} dgxy \{1.59600000e-01 8.40400000e-01\} dbxy \{3.66000000e-02 1.00000000e-04\} dwxy \{0.3457 0.3585\} dst_colorspace_name \{ERIMM\}\}" colorspace/ProPhotoRGB "knobs this \{drxy \{0.734699 0.265301\} dgxy \{0.159597 0.840403\} dbxy \{0.036598 0.000105\} dwxy \{0.345704 0.358540\} dst_colorspace_name \{ProPhotoRGB\}\}" colorspace/REDDRAGONcolor "knobs this \{drxy \{0.753044222785 0.327830576682\} dgxy \{0.299570228481 0.700699321956\} dbxy \{0.079642066735 -0.0549379510888\} dwxy \{0.321683187724 0.337673316035\} dst_colorspace_name \{REDDRAGONcolor\}\}" colorspace/REDDRAGONcolor2 "knobs this \{drxy \{0.753044491143 0.327831029513\} dgxy \{0.299570490451 0.700699415614\} dbxy \{0.145011584278 0.0510971250879\} dwxy \{0.321683210353 0.337673610062\} dst_colorspace_name \{REDDRAGONcolor2\}\}" colorspace/REDcolor "knobs this \{drxy \{0.699747001291 0.329046930313\} dgxy \{0.304264039024 0.623641145129\} dbxy \{0.134913961296 0.0347174412813\} dwxy \{0.321683289449 0.337673447208\} dst_colorspace_name \{REDcolor\}\}" colorspace/REDcolor2 "knobs this \{drxy \{0.878682510476 0.32496400741\} dgxy \{0.300888714367 0.679054755791\} dbxy \{0.0953986946056 -0.0293793268343\} dwxy \{0.321683289449 0.337673447208\} dst_colorspace_name \{REDcolor2\}\}" colorspace/REDcolor3 "knobs this \{drxy \{0.701181035906 0.329014155583\} dgxy \{0.300600304652 0.683788834269\} dbxy \{0.108154455624 -0.00868817578666\} dwxy \{0.321683210353 0.337673610062\} dst_colorspace_name \{REDcolor3\}\}" colorspace/REDcolor4 "knobs this \{drxy \{0.701180591892 0.329013699116\} dgxy \{0.300600395529 0.683788824257\} dbxy \{0.145331946229 0.0516168036226\} dwxy \{0.321683289449 0.337673447208\} dst_colorspace_name \{REDcolor4\}\}" colorspace/REDWideGamutRGB "knobs this \{drxy \{0.780308 0.304253\} dgxy \{0.121595 1.493994\} dbxy \{0.095612 -0.084589\} dwxy \{0.3127 0.3290\} dst_colorspace_name \{REDWideGamutRGB\}\}" colorspace/GoProProtune "knobs this \{drxy \{0.69848046 0.19302645\} dgxy \{0.32955538 1.02459662\} dbxy \{0.10844263 -0.03467857\} dwxy \{0.3127 0.329\} dst_colorspace_name \{GoProProtune\}\}" colorspace/CanonCinemaGamut "knobs this \{drxy \{0.74 0.27\} dgxy \{0.17 1.14\} dbxy \{0.08 -0.1\} dwxy \{0.3127 0.329\} dst_colorspace_name \{CanonCinemaGamut\}\}" colorspace/SonySGamut "knobs this \{drxy \{0.73 0.28\} dgxy\{0.14 0.855\} dbxy \{0.1 -0.05\} dwxy \{0.3127 0.329\} dst_colorspace_name \{SonySGamut\}\}" colorspace/SonySGamut3Cine "knobs this \{drxy \{0.766 0.275\} dgxy \{0.225 0.8\} dbxy \{0.089 -0.087\} dwxy \{0.3127 0.329\} dst_colorspace_name \{SonySGamut3Cine\}\}" colorspace/PanasonicVGamut "knobs this \{drxy \{0.730 0.280\} dgxy \{0.165 0.840\} dbxy \{0.100 -0.030\} dwxy \{0.3127 0.3290\} dst_colorspace_name \{PanasonicVGamut\}\}" colorspace/XYZ "knobs this \{drxy \{0 0\} dgxy \{0 0\} dbxy \{0 0\} dwxy \{0 0\} dst_colorspace_name \{XYZ\}\}"}} 170 | addUserKnob {41 drxy l r T _knobchanged_.drxy} 171 | addUserKnob {41 dgxy l g T _knobchanged_.dgxy} 172 | addUserKnob {41 dbxy l b T _knobchanged_.dbxy} 173 | addUserKnob {41 dwxy l w T _knobchanged_.dwxy} 174 | addUserKnob {1 dst_colorspace_name l dst +DISABLED} 175 | dst_colorspace_name ACEScg 176 | addUserKnob {26 ""} 177 | addUserKnob {26 chromatic_adaptation_label l " " T "Chromatic Adaptation"} 178 | addUserKnob {41 cat_method l method T _knobchanged_.cat_method} 179 | addUserKnob {26 ""} 180 | addUserKnob {41 invert T _knobchanged_.invert} 181 | addUserKnob {22 create_matrix_node l "Create Matrix" t "Create ColorMatrix node with the current values." T "node = nuke.thisNode()\nnode\['calculate_matrix'].execute()\nnuke.root().begin()\n_ = \[n.setSelected(False) for n in nuke.allNodes(recurseGroups=True)]\nm = nuke.createNode('ColorMatrix')\nm\['matrix'].setValue(node\['matrix'].getValue())\nm.setXYpos(node.xpos()-120, node.ypos())\nm\['label'].setValue(node\['label'].getValue())" +STARTLINE} 182 | addUserKnob {41 matrix T ColorMatrix.matrix} 183 | addUserKnob {22 clear l Clear T "n = nuke.thisNode()\nn\['matrix'].setValue(\[1,0,0,0,1,0,0,0,1])\nn\['label'].setValue('')" +STARTLINE} 184 | addUserKnob {22 show_matrix l Show t "Show matrix values in a popup window for copying." T "node = nuke.thisNode()\nshow_4x4 = node\['show_matrix_4x4'].getValue()\nshow_commas = node\['show_matrix_commas'].getValue()\n\nif show_commas:\n sep = ', '\nelse:\n sep = ' '\n\nmtx = \[format(i, '.8f') for i in node\['matrix'].getValue()]\n\nif show_4x4:\n z = \['0']\n mtx = mtx\[0:3] + z + mtx\[3:6] + z + mtx\[6:9] + z*4 + \['1']\n\nnuke.message(sep.join(mtx))" +STARTLINE} 185 | addUserKnob {6 show_matrix_4x4 l "show 4x4" t "present values in a 4x4 matrix (useful for ocio)" -STARTLINE} 186 | addUserKnob {6 show_matrix_commas l "show comma sep" t "show matrix comma separated." -STARTLINE} 187 | addUserKnob {22 save_spimtx l "Save spimtx" T "# inspired by ColorMatrixPlus by hpd\ndef write_spimtx(mtx_node, spimtx_file):\n mtx_vals = mtx_node\['matrix'].getValue()\n with open(spimtx_file, \"w\") as f:\n f.write( \"%3.6f %3.6f %3.6f 0.0\\n\" % (\n mtx_vals\[0], mtx_vals\[1], mtx_vals\[2]) )\n f.write( \"%3.6f %3.6f %3.6f 0.0\\n\" % (\n mtx_vals\[3], mtx_vals\[4], mtx_vals\[5]) )\n f.write( \"%3.6f %3.6f %3.6f 0.0\\n\" % (\n mtx_vals\[6], mtx_vals\[7], mtx_vals\[8]) )\n\nnode = nuke.thisNode()\nfilepath = nuke.getFilename('Save Matrix', '*.spimtx')\n\nif filepath:\n if not filepath.endswith('.spimtx'):\n filepath = filepath + '.spimtx'\n write_spimtx(node, filepath)" +STARTLINE} 188 | addUserKnob {22 load_spimtx l "Load spimtx" -STARTLINE T "# Inspired by ColorMatrixPlus by hpd\ndef load_spimtx(spimtx, mtx_node):\n with open( spimtx, 'r') as file:\n lines = file.readlines()\n matrix_values = list()\n for line in lines:\n matrix_values.extend( map(float, line.strip().split(' ')\[0:3]) )\n mtx_node\['matrix'].setValue(matrix_values)\n\nspimtx = nuke.getFilename('Load SPIMTX', '*.spimtx')\nif spimtx:\n load_spimtx( spimtx, nuke.thisNode() )\n"} 189 | } 190 | Input { 191 | inputs 0 192 | name Input 193 | xpos -40 194 | ypos 206 195 | } 196 | Dot { 197 | name _knobchanged_ 198 | knobChanged "import nuke\nfloat2 = nuke.math.Vector2\nfloat3 = nuke.math.Vector3\nfloat3x3 = nuke.math.Matrix3\n\nCONE_RESP_MAT_BRADFORD = float3x3()\nCONE_RESP_MAT_CAT02 = float3x3()\nCONE_RESP_MAT_VONKRIES = float3x3()\nCONE_RESP_MAT_SHARP = float3x3()\nCONE_RESP_MAT_CMCCAT2000 = float3x3()\n\n# From ACESlib.Utilities_Color : 166\nCONE_RESP_MAT_BRADFORD.set(0.89510, -0.75020, 0.03890, 0.26640, 1.71350, -0.06850, -0.16140, 0.03670, 1.02960)\nCONE_RESP_MAT_CAT02.set(0.73280, -0.70360, 0.00300, 0.42960, 1.69750, 0.01360, -0.16240, 0.00610, 0.98340)\n\n# https://web.stanford.edu/~sujason/ColorBalancing/adaptation.html\n# from S. Bianco. \"Two New von Kries Based Chromatic Adapatation Transforms Found by Numerical Optimization.\"\nCONE_RESP_MAT_VONKRIES.set(0.40024, -0.2263, 0, 0.7076, 1.16532, 0, -0.08081, 0.0457, 0.91822)\nCONE_RESP_MAT_SHARP.set(1.2694, -0.8364, 0.0297, -0.0988, 1.8006, -0.0315, -0.1706, 0.0357, 1.0018)\nCONE_RESP_MAT_CMCCAT2000.set(0.7982, -0.5918, 0.0008, 0.3389, 1.5512, 0.239, -0.1371, 0.0406, 0.9753)\n\n\ndef transpose(mtx):\n # transpose (swap rows to columns) of a 3x3 matrix\n order = \[0, 3, 6, 1, 4, 7, 2, 5, 8]\n omtx = float3x3()\n for i in xrange(9):\n omtx\[i] = mtx\[order\[i]]\n return omtx\n\ndef mult_f33_f33(A, B):\n # Calculate multiplication of 3x3 matrix A and 3x3 matrix B\n C = float3x3()\n C.makeIdentity()\n\n # Row 1\n C\[0] = (A\[0]*B\[0] + A\[1]*B\[3] + A\[2]*B\[6])\n C\[1] = (A\[0]*B\[1] + A\[1]*B\[4] + A\[2]*B\[7])\n C\[2] = (A\[0]*B\[2] + A\[1]*B\[5] + A\[2]*B\[8])\n # Row 2\n C\[3] = (A\[3]*B\[0] + A\[4]*B\[3] + A\[5]*B\[6])\n C\[4] = (A\[3]*B\[1] + A\[4]*B\[4] + A\[5]*B\[7])\n C\[5] = (A\[3]*B\[2] + A\[4]*B\[5] + A\[5]*B\[8])\n # Row 3\n C\[6] = (A\[6]*B\[0] + A\[7]*B\[3] + A\[8]*B\[6])\n C\[7] = (A\[6]*B\[1] + A\[7]*B\[4] + A\[8]*B\[7])\n C\[8] = (A\[6]*B\[2] + A\[7]*B\[5] + A\[8]*B\[8])\n\n return C;\n\n\ndef mult_f3_f33(src, mtx):\n return float3(mtx\[0] * src\[0] + mtx\[1] * src\[1] + \n mtx\[2] * src\[2], mtx\[3] * src\[0] + mtx\[4] * src\[1] + \n mtx\[5] * src\[2], mtx\[6] * src\[0] + mtx\[7] * src\[1] + \n mtx\[8] * src\[2])\n\n\ndef XYZ_2_xyY(XYZ):\n xyY = float3()\n divisor = (XYZ\[0] + XYZ\[1] + XYZ\[2])\n if (divisor == 0.):\n divisor = 1e-10\n xyY.set(XYZ\[0] / divisor, XYZ\[1] / divisor, XYZ\[1])\n return xyY\n\n\ndef xyY_2_XYZ(xyY):\n XYZ = float3()\n XYZ.set(\n xyY\[0] * xyY\[2] / max( xyY\[1], 1e-10), \\\n xyY\[2], \\\n (1.0 - xyY\[0] - xyY\[1]) * xyY\[2] / max( xyY\[1], 1e-10)\n )\n return XYZ\n\n\ndef calculate_cat_matrix(src_xy, des_xy, coneRespMat=CONE_RESP_MAT_BRADFORD):\n # Calculates and returns a 3x3 Von Kries chromatic adaptation transform \n # from src_xy to des_xy using the cone response primaries defined \n # by coneRespMat. By default, coneRespMat is set to CONE_RESP_MAT_BRADFORD. \n # The default coneRespMat can be overridden at runtime. \n\n src_xyY = float3(src_xy\[0], src_xy\[1], 1.0)\n des_xyY = float3(des_xy\[0], des_xy\[1], 1.0)\n\n src_XYZ = xyY_2_XYZ( src_xyY )\n des_XYZ = xyY_2_XYZ( des_xyY )\n\n src_coneResp = mult_f3_f33(src_XYZ, coneRespMat)\n des_coneResp = mult_f3_f33(des_XYZ, coneRespMat)\n\n vkMat = float3x3()\n vkMat.set(\n des_coneResp\[0] / src_coneResp\[0], 0.0, 0.0,\n 0.0, des_coneResp\[1] / src_coneResp\[1], 0.0,\n 0.0, 0.0, des_coneResp\[2] / src_coneResp\[2]\n )\n\n cat_matrix = float3x3()\n coneRespMatInv = coneRespMat.inverse()\n cat_matrix = coneRespMat * ( vkMat * coneRespMatInv)\n \n return cat_matrix\n\n\ndef RGBtoXYZ(xy, Y=1.0, inverse=False):\n # given r g b chromaticities and whitepoint, convert RGB colors to XYZ\n # based on CtlColorSpace.cpp from the CTL source code : 77\n # param: xy - dict of chromaticity xy coordinates: rxy: float2(x, y) etc\n # param: Y - luminance of \"white\" - defaults to 1.0\n # param: inverse - calculate XYZ to RGB instead\n r = xy\['rxy']\n g = xy\['gxy']\n b = xy\['bxy']\n w = xy\['wxy']\n\n X = w.x * Y / w.y\n Z = (1 - w.x - w.y) * Y / w.y\n\n # Scale factors for matrix rows\n d = r.x * (b.y - g.y) + b.x * (g.y - r.y) + g.x * (r.y - b.y)\n\n Sr = (X * (b.y - g.y) - \\\n g.x * (Y * (b.y - 1) + \\\n b.y * (X + Z)) + \\\n b.x * (Y * (g.y - 1) + \\\n g.y * (X + Z))) / d\n \n Sg = (X * (r.y - b.y) + \\\n r.x * (Y * (b.y - 1) + \\\n b.y * (X + Z)) - \\\n b.x * (Y * (r.y - 1) + \\\n r.y * (X + Z))) / d\n\n Sb = (X * (g.y - r.y) - \\\n r.x * (Y * (g.y - 1) + \\\n g.y * (X + Z)) + \\\n g.x * (Y * (r.y - 1) + \\\n r.y * (X + Z))) / d\n\n # Assemble the matrix\n M = float3x3()\n M.set( Sr * r.x, Sr * r.y, Sr * (1 - r.x - r.y),\n Sg * g.x, Sg * g.y, Sg * (1 - g.x - g.y),\n Sb * b.x, Sb * b.y, Sb * (1 - b.x - b.y))\n if inverse:\n M = M.inverse()\n return M\n else:\n return M\n\n\ndef get_primaries(node, dst=False):\n # get colorspace chromaticities xy coordinates from node\n dst_pri = \['drxy', 'dgxy', 'dbxy', 'dwxy']\n pri = \['rxy', 'gxy', 'bxy', 'wxy']\n d = \{\}\n for i, p in enumerate(pri):\n if dst:\n d\[p] = float2(node\[dst_pri\[i]].getValue()\[0], node\[dst_pri\[i]].getValue()\[1])\n else:\n d\[p] = float2(node\[p].getValue()\[0], node\[p].getValue()\[1])\n return d\n\n\n\ndef start():\n node = nuke.thisGroup()\n \n identity_mtx = float3x3()\n identity_mtx.makeIdentity()\n\n invert = node\['invert'].getValue()\n label = node\['label']\n cat_method = node\['cat_method'].value()\n src_primaries = get_primaries(node)\n dst_primaries = get_primaries(node, dst=True)\n src_colorspace_name = node\['src_colorspace_name'].getValue()\n dst_colorspace_name = node\['dst_colorspace_name'].getValue()\n \n # Check for XYZ on src / destination\n enable_src = (src_colorspace_name != 'XYZ')\n enable_dst = (dst_colorspace_name != 'XYZ')\n whitepoint_changed = (src_primaries\['wxy']\[0] != dst_primaries\['wxy']\[0] or src_primaries\['wxy']\[1] != dst_primaries\['wxy']\[1])\n\n # Get chromatic adaptation method\n if cat_method == 'Bradford':\n coneRespMat = CONE_RESP_MAT_BRADFORD\n elif cat_method == 'cat02':\n coneRespMat = CONE_RESP_MAT_CAT02\n elif cat_method == 'vonKries Hunt-Pointer-Estevez D65-Normalized':\n coneRespMat = CONE_RESP_MAT_VONKRIES\n elif cat_method == 'cmccat2000':\n coneRespMat = CONE_RESP_MAT_CMCCAT2000\n elif cat_method == 'sharp':\n coneRespMat = CONE_RESP_MAT_SHARP\n elif cat_method == 'None':\n coneRespMat = identity_mtx\n whitepoint_changed = False\n\n if enable_src:\n # Calculate Source RGB to XYZ matrix\n src_rgb_to_xyz_mtx = RGBtoXYZ(src_primaries)\n\n if enable_dst:\n # Calculate Destination XYZ to RGB matrix\n dst_xyz_to_rgb_mtx = RGBtoXYZ(dst_primaries, inverse=True)\n\n if enable_src and not enable_dst:\n # Convert from Source RGB to XYZ only\n mtx = src_rgb_to_xyz_mtx\n\n if enable_dst and not enable_src:\n # Convert from XYZ to Destination RGB only\n mtx = dst_xyz_to_rgb_mtx\n\n if enable_src and enable_dst:\n if whitepoint_changed:\n # Calculate chromatic adaptation matrix\n cat_mtx = calculate_cat_matrix(src_primaries\['wxy'], dst_primaries\['wxy'], coneRespMat=coneRespMat)\n # We will use our own matrix multiplication algorithm since there seems to be precision issues with the nuke.math module :/\n # mtx = (cat_mtx * src_rgb_to_xyz_mtx) * dst_xyz_to_rgb_mtx\n mtx = mult_f33_f33(dst_xyz_to_rgb_mtx, mult_f33_f33(cat_mtx, src_rgb_to_xyz_mtx))\n else:\n mtx = mult_f33_f33(dst_xyz_to_rgb_mtx, src_rgb_to_xyz_mtx)\n\n if invert:\n mtx = mtx.inverse()\n\n node\['matrix'].setValue(mtx)\n\n return True\n\n\n\nn = nuke.thisNode()\ng = nuke.thisGroup()\nk = nuke.thisKnob()\n\nstart()\n" 199 | tile_color 0x5b0000 200 | note_font "Helvetica Bold" 201 | note_font_size 24 202 | note_font_color 0xff 203 | xpos -6 204 | ypos 258 205 | addUserKnob {20 Knobs} 206 | addUserKnob {12 rxy l r t "Red xy chromaticity coordinates in CIE 1931 colorspace."} 207 | rxy {0.7347 0.2653} 208 | addUserKnob {12 gxy l g t "Green xy chromaticity coordinates in CIE 1931 colorspace."} 209 | gxy {0 1} 210 | addUserKnob {12 bxy l b t "Blue xy chromaticity coordinates in CIE 1931 colorspace."} 211 | bxy {0.0001 -0.077} 212 | addUserKnob {12 wxy l w t "Whitepoint xy chromaticity coordinates in CIE 1931 colorspace."} 213 | wxy {0.32168 0.33767} 214 | addUserKnob {12 drxy l r t "Red xy chromaticity coordinates in CIE 1931 colorspace."} 215 | drxy {0.713 0.293} 216 | addUserKnob {12 dgxy l g t "Green xy chromaticity coordinates in CIE 1931 colorspace."} 217 | dgxy {0.165 0.83} 218 | addUserKnob {12 dbxy l b t "Blue xy chromaticity coordinates in CIE 1931 colorspace."} 219 | dbxy {0.128 0.044} 220 | addUserKnob {12 dwxy l w t "Whitepoint xy chromaticity coordinates in CIE 1931 colorspace."} 221 | dwxy {0.32168 0.33767} 222 | addUserKnob {4 cat_method l method t "Choose the chromatic adaptation method for adapting whitepoint." M {Bradford cat02 "vonKries Hunt-Pointer-Estevez D65-Normalized" cmccat2000 sharp None}} 223 | addUserKnob {6 invert t "Invert direction of matrix." +STARTLINE} 224 | } 225 | ColorMatrix { 226 | matrix { 227 | {1.451439381 -0.2365107685 -0.2149285674} 228 | {-0.07655383646 1.176229835 -0.09967593104} 229 | {0.008316127583 -0.0060324613 0.997716248} 230 | } 231 | name ColorMatrix 232 | xpos -40 233 | ypos 302 234 | } 235 | Output { 236 | name Output 237 | xpos -40 238 | ypos 350 239 | } 240 | end_group 241 | Group { 242 | name VisualizeColorDistance11 243 | selected true 244 | xpos 7440 245 | ypos 6830 246 | addUserKnob {20 VisualizeColorDistance} 247 | addUserKnob {4 type M {inside outside}} 248 | addUserKnob {6 show_distance l "show distance" -STARTLINE} 249 | show_distance true 250 | addUserKnob {7 threshold t "threshold of color distance to visualize. 1.0 is the edge of the gamut boundary. 0.0 is all colors." R 0 1.2} 251 | threshold 1 252 | addUserKnob {7 shd_rolloff l "shd rolloff" R 0 0.1} 253 | shd_rolloff 0.03 254 | addUserKnob {4 bg t "choose bg to put behind result colors" M {checkerboard "solid color"}} 255 | addUserKnob {41 color T Fill1.color} 256 | } 257 | Input { 258 | inputs 0 259 | name Input 260 | xpos -40 261 | ypos -285 262 | } 263 | Dot { 264 | name Dot1 265 | note_font "Helvetica Bold" 266 | note_font_size 24 267 | note_font_color 0xff 268 | xpos -6 269 | ypos -174 270 | } 271 | set N12fbbc60 [stack 0] 272 | Dot { 273 | name Dot2 274 | note_font "Helvetica Bold" 275 | note_font_size 24 276 | note_font_color 0xff 277 | xpos -116 278 | ypos -174 279 | } 280 | set N164fb290 [stack 0] 281 | Dot { 282 | name Dot5 283 | note_font "Helvetica Bold" 284 | note_font_size 24 285 | note_font_color 0xff 286 | xpos -226 287 | ypos -174 288 | } 289 | set N16500160 [stack 0] 290 | Expression { 291 | channel0 {rgba.red rgba.green rgba.blue none} 292 | expr0 max(r,g,b) 293 | name achromatic 294 | note_font Helvetica 295 | xpos -370 296 | ypos -129 297 | } 298 | set N16505060 [stack 0] 299 | push $N16500160 300 | Merge2 { 301 | inputs 2 302 | operation minus 303 | bbox B 304 | Achannels rgb 305 | Bchannels rgb 306 | output rgb 307 | name Merge2 308 | note_font Helvetica 309 | xpos -260 310 | ypos -129 311 | } 312 | push $N16505060 313 | Expression { 314 | temp_name0 c_r 315 | temp_expr0 1-r 316 | temp_name1 c_g 317 | temp_expr1 1-g 318 | temp_name2 c_b 319 | temp_expr2 1-b 320 | expr0 1-(c_r<(1-thr)?c_r:(1-thr)+thr*tanh(((c_r-(1-thr))/thr))) 321 | expr1 1-(c_g<(1-thr)?c_g:(1-thr)+thr*tanh(((c_g-(1-thr))/thr))) 322 | expr2 1-(c_b<(1-thr)?c_b:(1-thr)+thr*tanh(((c_b-(1-thr))/thr))) 323 | name toe 324 | note_font Helvetica 325 | xpos -370 326 | ypos -81 327 | disable {{parent.shd_rolloff==0}} 328 | addUserKnob {20 Params_tab l Params} 329 | addUserKnob {7 thr t "complement of threshold"} 330 | thr {{parent.shd_rolloff}} 331 | } 332 | Merge2 { 333 | inputs 2 334 | operation divide 335 | bbox B 336 | Achannels rgb 337 | Bchannels rgb 338 | output rgb 339 | name Merge3 340 | note_font Helvetica 341 | xpos -260 342 | ypos -81 343 | } 344 | set N16531310 [stack 0] 345 | Dot { 346 | name Dot4 347 | note_font "Helvetica Bold" 348 | note_font_size 24 349 | note_font_color 0xff 350 | xpos -226 351 | ypos 66 352 | } 353 | push $N16531310 354 | push $N164fb290 355 | MergeExpression { 356 | inputs 2 357 | temp_name0 limit 358 | temp_expr0 threshold 359 | temp_name1 sat 360 | temp_expr1 max(Ar,Ag,Ab) 361 | expr0 type?(sat>=limit?Br:0):sat<=limit?Br:0 362 | expr1 type?(sat>=limit?Bg:0):sat<=limit?Bg:0 363 | expr2 type?(sat>=limit?Bb:0):sat<=limit?Bb:0 364 | expr3 type?(sat>limit?1:0):satR : \[value rmax]\nG : \[value gmax]\nB : \[value bmax]" 444 | note_font Helvetica 445 | selected true 446 | xpos 7440 447 | ypos 6869 448 | addUserKnob {20 MaxRGB_tab l MaxRGB} 449 | addUserKnob {22 calculate t "calculate the max pixel value in red, green, and blue color components." T "def get_max(ct_node, frame):\n ch = ct_node.name().lower()\[0]\n nuke.execute(ct_node, frame, frame)\n maxval = max(ct_node\['maxlumapixvalue'].getValue())\n kmax = n\['\{0\}max'.format(ch)]\n kpos = n\['\{0\}pos'.format(ch)]\n kmax.setValueAt(maxval, frame)\n for i in range(2):\n kpos.setValueAt(ct_node\['maxlumapixdata'].getValue()\[i], frame, i)\n \n\nn = nuke.thisNode()\ncurframe = n\['curframe'].getValue()\nif curframe:\n first_frame = nuke.frame()\n last_frame = nuke.frame()\nelse:\n first_frame = n.frameRange().first()\n last_frame = n.frameRange().last()\n\nct_nodes = \[nuke.toNode('\{0\}.\{1\}MAX'.format(n.fullName(), c)) for c in \['R', 'G', 'B']]\n\nfor ch in \[c.name().lower()\[0] for c in ct_nodes]:\n kmax = n\['\{0\}max'.format(ch)]\n kpos = n\['\{0\}pos'.format(ch)]\n for k in \[kmax, kpos]:\n k.clearAnimated()\n if not curframe:\n k.setAnimated()\n\nif curframe:\n for ct_node in ct_nodes:\n get_max(ct_node, first_frame)\n\n#else: # threading issues... not implemented for now\n# for frame in range(first_frame, last_frame+1):\n# for ct_node in ct_nodes:\n# threading.Thread(group=None, target=get_max, args=(ct_node, frame)).start()" +STARTLINE} 450 | addUserKnob {22 clear -STARTLINE T "n = nuke.thisNode()\nfor c in \['r', 'g', 'b']:\n kmax = n\['\{0\}max'.format(c)]\n kpos = n\['\{0\}pos'.format(c)]\n for k in \[kmax, kpos]:\n k.setValue(0)\n k.clearAnimated()"} 451 | addUserKnob {6 curframe l "current frame" t "Calculate only the current frame. If false, will prompt calculate input framerange." -STARTLINE} 452 | curframe true 453 | addUserKnob {7 rmax t "maximum red value" R 0 50} 454 | rmax 1.215420485 455 | addUserKnob {12 rpos l "" t "rmax position" -STARTLINE} 456 | rpos {2 0} 457 | addUserKnob {7 gmax t "maximum green value" R 0 50} 458 | gmax 1.09990406 459 | addUserKnob {12 gpos l "" t "gmax position" -STARTLINE} 460 | gpos {2 0} 461 | addUserKnob {7 bmax t "maximum blue value" R 0 50} 462 | bmax 1.005128622 463 | addUserKnob {12 bpos l "" t "bmax position" -STARTLINE} 464 | bpos {1 0} 465 | } 466 | Input { 467 | inputs 0 468 | name Input 469 | xpos -40 470 | ypos -10 471 | } 472 | CurveTool { 473 | operation "Max Luma Pixel" 474 | channels {rgba.red -rgba.green -rgba.blue none} 475 | ROI {0 0 {width} {height}} 476 | autocropdata {480 270 1440 810} 477 | maxlumapixdata {{curve x1 209 209 209 2 513 513 513 513 513 385 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 2 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513 513} {curve x1 1175 1175 1175 0 994 994 994 994 994 686 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 0 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994 994}} 478 | maxlumapixvalue {{curve x1 1.03131032 1.361329675 1.03131032 1.215420485 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 1.016959906 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 1.059028029 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962 37.13138962} {curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} 0} 479 | minlumapixdata {{curve x1 1 1 1 0 389 389 389 389 389 998 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 0 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389} {curve x1 0 0 0 0 1040 1040 1040 1040 1040 210 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 0 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040}} 480 | minlumapixvalue {{curve x1 0 0 0 0 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 0 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 0 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463 -0.4703712463} {curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} 0} 481 | name RMAX 482 | tile_color 0x84000000 483 | note_font Helvetica 484 | xpos -40 485 | ypos 39 486 | } 487 | CurveTool { 488 | operation "Max Luma Pixel" 489 | channels {-rgba.red rgba.green -rgba.blue none} 490 | ROI {0 0 {width} {height}} 491 | autocropdata {480 270 1440 810} 492 | maxlumapixdata {{curve x1 894 50 50 2 1347 1347 1347 1347 1347 259 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 1347 x48 2} {curve x1 1274 9 9 0 896 896 896 896 896 459 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 896 x48 0}} 493 | maxlumapixvalue {{curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 x48 0} {curve x1 1.143916965 1.778251767 1.347160339 1.09990406 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 1.138906717 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 39.63381577 x48 1.201209426} {curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 x48 0}} 494 | minlumapixdata {{curve x1 0 0 0 1 389 389 389 389 389 1234 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 389 x48 1} {curve x1 0 0 0 0 1040 1040 1040 1040 1040 220 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 1040 x48 0}} 495 | minlumapixvalue {{curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 x48 0} {curve x1 0 0 0 0 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 0 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 -2.469618797 x48 0} {curve x1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 x48 0}} 496 | name GMAX 497 | tile_color 0x84000000 498 | note_font Helvetica 499 | xpos -40 500 | ypos 87 501 | } 502 | CurveTool { 503 | operation "Max Luma Pixel" 504 | channels {-rgba.red -rgba.green rgba.blue none} 505 | ROI {0 0 {width} {height}} 506 | autocropdata {480 270 1440 810} 507 | maxlumapixdata {{curve x1 531 7 7 1 x10 1235 x48 1} {curve x1 985 41 41 0 x10 221 x48 0}} 508 | maxlumapixvalue {{curve x1 0 0 0 0 x10 0 x48 0} {curve x1 0 0 0 0 x10 0 x48 0} {curve x1 0.9455359578 1.744389296 1.321506977 1.005128622 x10 1.041050911 x48 1.24509275}} 509 | minlumapixdata {{curve x1 3 3 3 2 x10 0 x48 2} {curve x1 0 0 0 0 x10 0 x48 0}} 510 | minlumapixvalue {{curve x1 0 0 0 0 x10 0 x48 0} {curve x1 0 0 0 0 x10 0 x48 0} {curve x1 0 0 0 0 x10 0 x48 0}} 511 | name BMAX 512 | tile_color 0x84000000 513 | note_font Helvetica 514 | xpos -40 515 | ypos 135 516 | } 517 | Output { 518 | name Output 519 | xpos -40 520 | ypos 182 521 | } 522 | end_group 523 | -------------------------------------------------------------------------------- /docs/doc-flame.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flame Matchbox shader install location 4 | OSX: `/opt/Autodesk/preseets/2019/matchbox/shaders` 5 | Linux: `??` 6 | 7 | Scratch Matchbox shader install location 8 | Windows: `C:\ProgramData\Assimilator\plugin64\Matchbox` 9 | OSX: `??` -------------------------------------------------------------------------------- /docs/doc-fusion.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/docs/doc-fusion.md -------------------------------------------------------------------------------- /docs/doc-nuke.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/docs/doc-nuke.md -------------------------------------------------------------------------------- /docs/doc-resolve.md: -------------------------------------------------------------------------------- 1 | ![Resolve UI](/images/screenshots/GamutCompress_resolve-ui.png) 2 | 3 | A DCTL for Blackmagic Resolve is also provided. This is based on the [initial version written by Nick Shaw](https://github.com/nick-shaw/gamut_mapping). Huge thanks to him for putting that together! He also has a Matchbox shader in his repo which might be of use in Baselight or Flame. 4 | 5 | 6 | 7 | ### Usage 8 | ![GamutCompress Nuke UI](/images/screenshots/GamutCompress_nuke-ui.png) 9 | 10 | Threshold is the percentage of the outer gamut to affect. A value of 0.2 will compress out of gamut values into the outer 20% of the gamut. 11 | 12 | Max Distance is the distance past the gamut boundary to map to the gamut boundary. For example, a value of 0.2 will compress distance values of 1.2 to a value of 1.0. Individual controls are given for each color component (`cyan, magenta, and yellow`). These controls can be used to tune the look of the compression creatively, or to optimize for different source gamuts. Alexa Wide Gamut and RedWideGamutRGB have very different maximum possible distances for each of the color components, because their primaries are very different. 13 | 14 | Method specifies the type of compression curve to use. Currently the master branch only has a simple Reinhard compression curve enabled, but if you check out the dev branch there are a few different experimental options. 15 | 16 | Inverting the gamut compression exactly is also possible. This workflow might be necessary in visual effects pipelines where the source plates need to be delivered back to the client exactly as they came in. There should be an excess of caution used with this workflow however. If highly saturated colors in the outer 20% of the color gamut are uncompressed, the results might be very extreme and unpredictable. Efforts should be undertaken to watch this eventuality very closely if this workflow is used. 17 | 18 | ![Resolve UI](/images/screenshots/GamutCompress_resolve-ui.png) 19 | 20 | A DCTL for Blackmagic Resolve is also provided. This is based on the [initial version written by Nick Shaw](https://github.com/nick-shaw/gamut_mapping). Huge thanks to him for putting that together! He also has a Matchbox shader in his repo which might be of use in Baselight or Flame. 21 | -------------------------------------------------------------------------------- /docs/gamut-compress-algorithm.md: -------------------------------------------------------------------------------- 1 | # The Gamut Compression Algorithm 2 | 3 | ## How it works 4 | The gamut compression algorithm is actually very simple. 5 | 6 | It works in a scene-linear floating-point domain. Because the scene-linear data has not been transformed into a display-referred colorspace yet, perceptual attributes like luminance, hue, and saturation don't yet have meaning. Therefore the algorithm works purely with the RGB color components. It is similar in nature to a despill algorithm. 7 | 8 | The gamut compression algorithm runs per-pixel, and is controlled with a small set of parameters. These parameters control how much of the core "confidence gamut" to protect, how much to compress each of the color components, and the shape of the compression curve. 9 | 10 | ## Representations 11 | To achieve this gamut compression, we need to construct a few different representations of the input image. 12 | - **Achromatic** 13 | Similar to luminance, this represents the achromatic or neutral axis of the color cube. Achromatic is constructed by taking the maximum of the R, G, and B color components. 14 | `achromatic = max(r,g,b)` 15 | Constructing the neutral axis this way allows us to leave one of the color components unaffected through the gamut mapping transformation, which makes inversion possible. 16 | - **Distance** 17 | Similar in concept to saturation, distance represents the distance of a color component from the achromatic axis. 18 | `distance = (achromatic - rgb) / achromatic`. 19 | One could think about this as the euclidean distance, but in one dimension this is simply the absolute value. And this can be represented even more simply by subtracting the color component from the achromatic value. Since the result will never be negative, the absolute value calculation is not necessary. 20 | This calculation gives us the distance, or the "inverse rgb ratios". However we still have a problem. We don't know where the gamut boundary is. For our distance value to be useful, we need it to represent in concrete terms what is in gamut and what is out of gamut. 21 | Fortunately this is a simple fix. All we need to do is normalize the distance values by dividing by achromatic. This gives us a distance which is 1.0 at the gamut boundary. 22 | 23 | ## Pictures Tell 1,000 Words 24 | This is all very abstract, so let's explain these concepts with some pictures. 25 | 26 | ![Colorwheel CIE 1931 xy - ACEScg](/images/screenshots/colorwheel_acescg_1931_xy.png) 27 | 28 | Here is a CIE 1931 xy chromaticity diagram. I've taken a colorwheel and increased the saturation equally in all directions. I've plotted this colorhweel assuming the input RGB values are ACEScg. I've plotted the triangle of the ACEScg gamut as well. 29 | 30 | RGB values within the ACEScg triangle are positive. RGB values outside of the triangle have negative values in one or two of their components. If you look at the pixel value being sampled just outside of the blue primary, the R and G components are negative while the B component is positive. These negative components are the problematic ones we are trying to fix by mapping them back inside the bounds of the gamut. 31 | 32 | Note that the xy chromaticity diagram is a simplified 2-dimensional representation of this inherently 3-dimensional data. However it serves to illustrate the concept in a simple way. 33 | 34 | ![Colorwheel Distance](/images/screenshots/colorwheel_acescg_distance_1931_xy.png) 35 | 36 | Here is the same image but transformed into the the distance representation described above. Values inside of the ACEScg triangle are between 0 and 1, with zero being the CIE xy location of the whitepoint of the image (D60, or `0.32168 0.33767` in xy chromaticity coordinates). This is the location of the achromatic axis. 37 | 38 | So inside the triangle we are all good. But outside the triangle is where the problematic values are. These values have a distance that is greater than 1. 39 | 40 | ![Colorwheel Distance Gamma Down](/images/screenshots/colorwheel_acescg_distance_gamma_down_1931_xy.png) 41 | 42 | Here you can see the same distance image, but with a strong gamma down. You can see the values near the gamut boundary are near 1. 43 | 44 | ## Compression 45 | So we need to take distance values above 1, and compress them down to 1. To do this we can use a compression function. A function which is linear (y=x) below a specified threshold. Above that threshold y values are mapped lower in a smooth curve. 46 | 47 | In order to effectively compress values though, we need space to compress them into. If we compress values above 1 to 1, that is the same thing as clipping those values. We want a smooth rolloff of our curve that evenly distributes the compressed values into the space between a threshold value and the gamut boundary. The threshold is where the curve starts compressing, and values below that threshold won't be affected. 48 | 49 | Out of gamut values don't tend to have huge distance values. The size of the distance values varies depending on the original camera gamut. For example Alexa Wide Gamut can have larger distance values near the blue primary, but usually no out of gamut values around the red primary. This is because of the shape of the camera gamut compared to ACEScg. RedWideGamutRGB is more likely to have out of gamut values around the red primary because of it's distance compared to the red primary of ACEScg. 50 | 51 | If we carelessly compress infinite distance to the gamut boundary, this makes very innefficient usage of the compression space between the threshold and the gamut boundary. We might be wasting 99% of it! 52 | 53 | Therefore it is very important to carefully choose how far beyond the gamut boundary we are compressing _to_ the gamut boundary. 54 | 55 | We control this with the Max Distance Limit parameters. These are split based on the color components so that we have precise control to optimize for the source camera gamut. 56 | 57 | In addition to efficient usage of the compression space, the distance limits affect the output RGB ratios for the compressed out of gamut colors. Once the image is run through a display rendering transform, these ratios have a big impact on the hue. Ideally we would be compressing out of gamut values into plausible colors. When the distance limits are set according to the source camera gamut, the hue of those compressed colors is more similar to the color rendering pipeline of the original camera data. 58 | 59 | We also want control control over the agressiveness of the compression curve. Aggressiveness can be thought of here as how quickly the curve transitions from linear to compressed. A less aggressive curve that transitions more quickly will result in compressed values being mapped closer to the threshold, and therefore the colors will appear less saturated. 60 | 61 | ![Less Aggressive Compression Function Curve](/images/screenshots/compression_function.png) 62 | 63 | Here's an example of a less aggressive compression curve. 64 | 65 | ![Aggressive Function Curve](/images/screenshots/compression_function_aggressive.png) 66 | 67 | And here's an example of a more aggressive curve. 68 | 69 | A more aggressive curve will push compressed values further out towards the gamut boundary. These colors will stay very saturated once rendered through a display transform. 70 | 71 | To control the aggressiveness of the curve we can use the power parameter. Higher power settings have a more aggressive curve and result in the compressed color values being closer to the gamut boundary and more saturated. 72 | 73 | There is [an interactive plot](https://www.desmos.com/calculator/54aytu7hek) of the compression curve that is being used, if you would like to play around with the parameters and get a feel for what effect they have on the shape of the curve. 74 | 75 | ## Getting Back to Reality 76 | Finally once we have our compressed distance, we can reconstruct our RGB image from our achromatic and newly compressed distance representations. The math for this is just as simple as getting here: 77 | 78 | `rgb = achromatic - compressed_distance * achromatic` 79 | 80 | 81 | If you've read this far and don't feel too overwhelmed, feel free to read through [how to use the tool](/docs/gamut-compress-documentation.md). -------------------------------------------------------------------------------- /docs/gamut-compress-documentation.md: -------------------------------------------------------------------------------- 1 | # GamutCompress Usage & Workflow 2 | 3 | This is written assuming you have read and understand the [how it works document](/docs/gamut-compress-algorithm.md). 4 | 5 | 6 | # Goals Usages Needs 7 | The needs of a tool depends on the context in which it is used. I see 3 main contexts in which gamut compression could be used: ( there may be more) 8 | 9 | **VFX Vendor** 10 | + Goal 11 | * Remove problematic negative RGB color components to reduce issues with image manipulation. 12 | + Usage 13 | * Gamut compression applied to plates on ingest, reversed on delivery. 14 | + Needs 15 | * Exact inverse transform. 16 | * Plausible color rendering to enable good decision making in image manipulation. 17 | * Fine control and ability to optimize and customize for a specific circumstance. Per source camera, per sequence, or even per shot if needed. 18 | * Tools for calculating and visualizing distances of source images or source gamuts, to aid in customization and optimization. 19 | 20 | **DI Suite** 21 | + Goal 22 | * To creatively manipulate out of gamut colors into a pleasing result. 23 | + Usage 24 | * Applied in a creative look-driven context for final image output. 25 | + Needs 26 | * Creative flexibility. 27 | * Intuitive parameters. 28 | * Plausible, good looking result. 29 | * Exact inverse transform not needed in most circumstances since this transform would be applied prior to final output. However it might might be necessary for collaboration with a VFX facility. 30 | 31 | **Display Rendering Transform** 32 | + Goal 33 | To aide in the rendering of out of gamut colors on a display. 34 | + Usage 35 | Applied in scene-referred space before the display rendering transform. 36 | (This algorithm only works in scene-referred, and is not suitable for more complex display-referred gamut mapping). 37 | + Needs 38 | * Handle any and all possible source imagery. 39 | * Plausible good looking result. 40 | * No exposed or adjustable parameters. 41 | 42 | With that outline of possible goals and needs, we will now talk about how to use the tool, and make some workflow reccomendations. 43 | 44 | 45 | 46 | # Parameters 47 | ![GamutCompress Nuke UI](/images/screenshots/GamutCompress_nuke-ui.png) 48 | 49 | Here is a description of what each of the GamutCompress parameters do. 50 | 51 | 52 | ## Threshold 53 | Treshold controls the percentage of the outer gamut to affect. A value of 0.2 will compress out of gamut values into the outer 20% of the gamut. The inner 80% of the gamut core will not be affected. In the Nuke nodes you are able to adjust these per color component. For example if you wanted to protect reds and greens a little more than blue, you could set the threshold a bit higher for blue. 54 | 55 | 56 | ## Power 57 | Control the aggressiveness of the compression curve. Higher power values result in more compressed values distributed closer to the gamut boundary. This will increase the "color purity" or "saturation" once the image has been rendered through a display transform. 58 | 59 | A power value of 1 is equal to a Reinhard compression curve. Higher values have a smoother transition and approach C2 continuity. 60 | 61 | Be careful of higher power values if an accurate inverse transform is important to you. Higher power values cause the compression curve to go more flat. Depending on the precision of your processing space, a flatter curve is more likely to result in quantization errors. This will result in differences between the inverse transform and the original. 62 | 63 | 64 | ## Shadow Rolloff 65 | The shadow rolloff parameter smoothly reduces the effect of the gamut compression below a specified threshold achromatic value. 66 | 67 | We must divide the inverse RGB ratios by the achromatic value to get a distance value of 1 at the gamut boundary. When the achromatic value gets very small, this can have a very big effect on the resulting distance. The bigger the distance value, the more likely it is to have quantization errors in the inverse direction. 68 | 69 | The closer the luminance of an RGB value gets to 0, the less our eyes perceive it as color. 70 | 71 | The purpose of this parameter is to reduce invertibility issues in shadow grain. Avoiding modifications to shadow grain may also be desireable in a VFX pipeline to avoid causing issues with shadow matching in composites, when the inverse gamut compression transform is applied. 72 | 73 | When this parameter is used, the algorithm is no longer exposure invariant. That is, when applying the gamut compression on an image, and on the same image exposed up by 6 stops, there will be a small difference in shadow grain. If exposure invariance is important to you feel free to disable this by setting the shadow rolloff to 0. 74 | 75 | 76 | ## Max Distance Limits 77 | Max Distance is the distance outside of the gamut boundary to compress to the gamut boundary. For example, a value of 0.2 will compress distance values of 1.2 to a value of 1.0. 78 | 79 | Individual controls are given for each color component. They are named cyan magenta and yellow because they actually control the max distance from the edge of the triangle, not the corner. For example, boosting the value of the cyan distance limit will increase the distance from which values are mapped to the edge between the blue and green corners of the triangle (to use simple 2 dimensional terminology). 80 | 81 | The distance limits also control the RGB ratios of the compressed values, which affects the apparent hue, once the image has been rendered. It may be desireable to get a close match to the original camera color-rendering. In order to do this, a good starting point is to set the max distance parameters according to the original source gamut. There is a utility called [CalculateDistance](/utilities/CalculateDistance.nk) which can aide in this calculation. It can calculate the maximum possible distance for each color component given a processing gamut and a source gamut. Or you can use an input image source to calculate from. 82 | 83 | You should try for a good set of default values that handles as much variety of source imagery as possible. Exceptions may be necessary, but this should be the starting point. 84 | 85 | 86 | ## Inverse Direction 87 | As hinted at above, exact inversion of the gamut compression is possible. 88 | 89 | Using inverse gamut compression should be avoided at all costs if possible. There is a high likelihood of breakage and things going horrible wrong. 90 | 91 | With that disclaimer out of the way, inverse gamut compression may be a necessity in some circumstances such as in VFX pipelines. 92 | 93 | If the working gamut of the VFX pipeline is different than the gamut of the original footage from the client, normally a transform would be applied on plate ingest to convert the source gamut to the working gamut. Work would ocurr in the working gamut. CG would be rendered. Compositing would be done. 94 | 95 | On delivery, there would be an inverse transform to convert working gamut back to source gamut. This would result in an exact match between the footage from the client and the comp delivered from the VFX vendor, except where work has been done. 96 | 97 | Forward and inverse gamut compression has good results when performed on the same image. If new highly saturated color values are introduced to the gamut compressed image, the results may become extreme and unpredictable when inverse gamut compression is applied. 98 | 99 | Therefore it is very important to be warey of this and use an excess of caution. Steps should be taken to carefully monitor any colors that are created in the region between the threshold distance and the gamut boundary. 100 | 101 | There is a tool called [VisualizeDistance](/utilities/VisualizeDistance.nk) which may help with this. It calculates the distance of the input image, and shows values visually that are beyond a specified threshold. -------------------------------------------------------------------------------- /images/aces_gamut_mapping_vwg_images_ap0/download_images_here: -------------------------------------------------------------------------------- 1 | https://www.dropbox.com/sh/jbng5hfi6ofqlp8/AABLGGxd9PWYMxFAadHK5mspa?dl=0 -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_colorwheel_acescg.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6001715ba041972df80b3026687458464cd7da88d0755e9eb39a64589a797915 3 | size 1396529 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_plot_aces-ap1-gamut.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:03e43ffe59216a82df5b79b179a4696b9fba9716a50e06715a73a501aa081c16 3 | size 86849 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_plot_arri-alexa-widegamut.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c7d017162c0c75913db76bd86b4856b49b6de31d7ab6592cba839297c7dab54c 3 | size 122769 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_plot_filmlight-e-gamut.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0b1be6694542e997b5885c257c7580b80c9b0d4cc1baf28025b0842e12751e61 3 | size 134646 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_plot_grid.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:eb150989b8f61c8e6d0e48f06ac1fa6c1c078d7989bb998aeeac0c8176fb36b5 3 | size 32466 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_plot_spectrum-locus.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c38c7a8f272d175ccbf6fa88f1d3a70fd751aa3c7f88f3bc5d9c473dc155eceb 3 | size 114852 4 | -------------------------------------------------------------------------------- /images/cie_1931_xy_chromaticity_diagram_plots/cie_1931_xy_points_acescg.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:76e4417a7e5f648aa679d5488dda2e7825c76e673ffc186b892973cc9ae06d78 3 | size 587898 4 | -------------------------------------------------------------------------------- /images/collage.acescg.exr: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5f9b1567325601460eb196dfb6507a42fd95c4b820231cd90f8d65ad732e9e4d 3 | size 12032259 4 | -------------------------------------------------------------------------------- /images/collage.rrt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/collage.rrt.jpg -------------------------------------------------------------------------------- /images/collage_compressed.rrt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/collage_compressed.rrt.jpg -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram.png -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram_aces_awg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram_aces_awg.png -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram_cameras.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram_cameras.png -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram_displays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram_displays.png -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram_pointers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram_pointers.png -------------------------------------------------------------------------------- /images/screenshots/CIE1931_xy_chromaticity_diagram_rec709.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/CIE1931_xy_chromaticity_diagram_rec709.png -------------------------------------------------------------------------------- /images/screenshots/GamutCompress_nuke-ui-calc_distance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/GamutCompress_nuke-ui-calc_distance.png -------------------------------------------------------------------------------- /images/screenshots/GamutCompress_nuke-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/GamutCompress_nuke-ui.png -------------------------------------------------------------------------------- /images/screenshots/GamutCompress_resolve-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/GamutCompress_resolve-ui.png -------------------------------------------------------------------------------- /images/screenshots/Light-source-SPD-curves-for-Ablue-LED-Bwhite-LED-Cwhite-CFL-and-Dyellow-CFL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/Light-source-SPD-curves-for-Ablue-LED-Bwhite-LED-Cwhite-CFL-and-Dyellow-CFL.png -------------------------------------------------------------------------------- /images/screenshots/bluebar_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/bluebar_image.png -------------------------------------------------------------------------------- /images/screenshots/bluebar_image_aces_vs_arri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/bluebar_image_aces_vs_arri.png -------------------------------------------------------------------------------- /images/screenshots/bluebar_image_and_plot_acescg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/bluebar_image_and_plot_acescg.png -------------------------------------------------------------------------------- /images/screenshots/bluebar_image_and_plot_awg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/bluebar_image_and_plot_awg.png -------------------------------------------------------------------------------- /images/screenshots/bluebar_image_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/bluebar_image_plot.png -------------------------------------------------------------------------------- /images/screenshots/camera_spectral_sensitivities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/camera_spectral_sensitivities.png -------------------------------------------------------------------------------- /images/screenshots/colorwheel_acescg_1931_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/colorwheel_acescg_1931_xy.png -------------------------------------------------------------------------------- /images/screenshots/colorwheel_acescg_distance_1931_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/colorwheel_acescg_distance_1931_xy.png -------------------------------------------------------------------------------- /images/screenshots/colorwheel_acescg_distance_gamma_down_1931_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/colorwheel_acescg_distance_gamma_down_1931_xy.png -------------------------------------------------------------------------------- /images/screenshots/compression_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/compression_function.png -------------------------------------------------------------------------------- /images/screenshots/compression_function_aggressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedypod/gamut-compress/18c979252faacaf144f86cdb62c0ee3d303bf6f5/images/screenshots/compression_function_aggressive.png -------------------------------------------------------------------------------- /utilities/CalculateDistance.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | push $cut_paste_input 3 | Group { 4 | name CalculateDistance 5 | addUserKnob {20 GamutCalculateDistance_tab l GamutCalculateDistance} 6 | addUserKnob {7 shd_rolloff l "shd rolloff" t "smoothly reduce distance calculations below the specified achromatic (luminance) threshold." R 0 0.1} 7 | shd_rolloff 0.008 8 | addUserKnob {18 distance t "maximum distance found" R 1 2} 9 | distance {1 1 1} 10 | addUserKnob {6 distance_panelDropped l "panel dropped state" -STARTLINE +HIDDEN} 11 | addUserKnob {41 gamut t "The current gamut in which we are working." T Gamut.gamut_in} 12 | addUserKnob {41 src_gamut l "source gamut" t "The source gamut from which this image was transformed." -STARTLINE T Gamut.gamut_out} 13 | addUserKnob {6 use_input_image l "use input image" t "Use the input image to calculate the max distance instead of the specified gamuts." +STARTLINE} 14 | addUserKnob {22 calc_max_limit l "Calculate Max Distance" t "Calculate the max distance given current gamut and source gamut." T "def get_max(ct_node):\n max_knob = ct_node\['maxlumapixvalue']\n ct_node\['maxlumapixdata'].clearAnimated()\n ct_node\['minlumapixdata'].clearAnimated()\n ct_node\['minlumapixvalue'].clearAnimated()\n max_knob.clearAnimated()\n nuke.execute(ct_node, frame, frame)\n maxval = max(max_knob.getValue())\n maxval = round(maxval, 4)\n return maxval\n \nn = nuke.thisNode()\nframe = nuke.frame()\ndist = n\['distance']\ndist.clearAnimated()\n#dist.split()\n\nct_nodes = \[nuke.toNode('\{0\}.\{1\}max'.format(n.fullName(), c)) for c in \['r', 'g', 'b']]\n\nfor i, ct_node in enumerate(ct_nodes):\n maxval = get_max(ct_node)\n dist.setValue(maxval, i)" +STARTLINE} 15 | addUserKnob {22 set_selected_node l "Setup Selected GamutCompress" t "Set the max distance knobs on the selected GamutCompress node." T "def setup():\n if 'cyan' not in gc.knobs():\n nuke.message('Please select a GamutCompress node to populate.')\n return\n d = n\['distance']\n dist_knobs = \['cyan', 'magenta', 'yellow']\n for i, k in enumerate(dist_knobs):\n gc\[k].setValue(max(0, d.getValue(i)-1))\n\nn = nuke.thisNode()\nnuke.root().begin()\ngc = nuke.selectedNode()\n\nsetup()" +STARTLINE} 16 | } 17 | Input { 18 | inputs 0 19 | name Input 20 | xpos -40 21 | ypos -34 22 | } 23 | Dot { 24 | name Dot4 25 | xpos -6 26 | ypos 66 27 | } 28 | set N39bc7100 [stack 0] 29 | Output { 30 | name Output 31 | xpos -40 32 | ypos 254 33 | } 34 | push $N39bc7100 35 | Group { 36 | inputs 0 37 | name RGB 38 | xpos 180 39 | ypos -34 40 | } 41 | Constant { 42 | inputs 0 43 | color {0 0 0 0} 44 | format "4 1 0 0 4 1 1 rgbw" 45 | name Constant3 46 | note_font "Bitstream Vera Sans" 47 | xpos 510 48 | ypos -34 49 | postage_stamp false 50 | } 51 | Reformat { 52 | type "to box" 53 | box_width 3 54 | box_height 2 55 | box_fixed true 56 | name Reformat1 57 | xpos 510 58 | ypos -10 59 | } 60 | Rectangle { 61 | area {0 0 1 2} 62 | color {1 0 0 0} 63 | name Rectangle1 64 | note_font "Bitstream Vera Sans" 65 | xpos 510 66 | ypos 14 67 | } 68 | Rectangle { 69 | area {1 0 2 2} 70 | color {0 1 0 0} 71 | name Rectangle2 72 | note_font "Bitstream Vera Sans" 73 | xpos 510 74 | ypos 38 75 | } 76 | Rectangle { 77 | area {2 0 3 2} 78 | color {0 0 1 0} 79 | name Rectangle3 80 | note_font "Bitstream Vera Sans" 81 | xpos 510 82 | ypos 62 83 | } 84 | Output { 85 | name Output 86 | xpos 510 87 | ypos 110 88 | } 89 | end_group 90 | Group { 91 | name Gamut 92 | xpos 180 93 | ypos 14 94 | addUserKnob {20 GamutToXYZ_tab l GamutToXYZ} 95 | addUserKnob {4 gamut_in l "gamut in" t "Choose source gamut" M {ACES ACEScg "Filmlight E-Gamut" Rec709 Rec2020 P3D60 P3D65 "Arri AlexaWideGamut" REDWideGamutRGB "GoPro Protune Native" CanonCinemaGamut SonySGamut SonySGamut3Cine PanasonicVGamut "DJI D-Gamut" BMDWideGamutGen4 AdobeWideGamutRGB ProPhotoRGB}} 96 | gamut_in ACEScg 97 | addUserKnob {4 gamut_out l "gamut out" t "Choose destination gamut" M {ACES ACEScg "Filmlight E-Gamut" Rec709 Rec2020 P3D60 P3D65 "Arri AlexaWideGamut" REDWideGamutRGB "GoPro Protune Native" CanonCinemaGamut SonySGamut SonySGamut3Cine PanasonicVGamut "DJI D-Gamut" BMDWideGamutGen4 AdobeWideGamutRGB ProPhotoRGB}} 98 | gamut_out "Arri AlexaWideGamut" 99 | addUserKnob {6 invert +STARTLINE} 100 | invert true 101 | } 102 | Input { 103 | inputs 0 104 | name Input 105 | xpos -40 106 | ypos -130 107 | } 108 | Dot { 109 | name Dot1 110 | xpos -6 111 | ypos -78 112 | } 113 | set N2a9bd220 [stack 0] 114 | ColorMatrix { 115 | matrix { 116 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 117 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 118 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 119 | } 120 | name ColorMatrix2 121 | label "RGB to XYZ" 122 | xpos 180 123 | ypos 32 124 | addUserKnob {20 Gamut} 125 | addUserKnob {3 which} 126 | which {{parent.gamut_out}} 127 | addUserKnob {12 rxy} 128 | rxy {{parent.mtx.rxy(which)} {parent.mtx.rxy(which)}} 129 | addUserKnob {12 gxy} 130 | gxy {{parent.mtx.gxy(which)} {parent.mtx.gxy(which)}} 131 | addUserKnob {12 bxy} 132 | bxy {{parent.mtx.bxy(which)} {parent.mtx.bxy(which)}} 133 | addUserKnob {12 wxy} 134 | wxy {{parent.mtx.wxy(which)} {parent.mtx.wxy(which)}} 135 | } 136 | ColorMatrix { 137 | matrix { 138 | {0.9872239828 -0.006113247015 0.01595330238} 139 | {-0.007598329335 1.001861453 0.005330037326} 140 | {0.00307257846 -0.00509596616 1.081680536} 141 | } 142 | invert {{!parent.ColorMatrix4.invert}} 143 | name ColorMatrix1 144 | label "CAT: Bradford\n ACES to D65" 145 | xpos 180 146 | ypos 101 147 | disable {{"RGBtoXYZ.wxy == XYZtoRGB.wxy"}} 148 | } 149 | ColorMatrix { 150 | matrix { 151 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 152 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 153 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 154 | } 155 | invert true 156 | name ColorMatrix3 157 | label "XYZ to RGB" 158 | xpos 180 159 | ypos 176 160 | addUserKnob {20 Gamut} 161 | addUserKnob {3 which} 162 | which {{parent.gamut_in}} 163 | addUserKnob {12 rxy} 164 | rxy {{parent.mtx.rxy(which)} {parent.mtx.rxy(which)}} 165 | addUserKnob {12 gxy} 166 | gxy {{parent.mtx.gxy(which)} {parent.mtx.gxy(which)}} 167 | addUserKnob {12 bxy} 168 | bxy {{parent.mtx.bxy(which)} {parent.mtx.bxy(which)}} 169 | addUserKnob {12 wxy} 170 | wxy {{parent.mtx.wxy(which)} {parent.mtx.wxy(which)}} 171 | } 172 | push $N2a9bd220 173 | ColorMatrix { 174 | matrix { 175 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 176 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 177 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 178 | } 179 | name RGBtoXYZ 180 | xpos -260 181 | ypos 38 182 | addUserKnob {20 Gamut} 183 | addUserKnob {3 which} 184 | which {{parent.gamut_in}} 185 | addUserKnob {12 rxy} 186 | rxy {{parent.mtx.rxy(which)} {parent.mtx.rxy(which)}} 187 | addUserKnob {12 gxy} 188 | gxy {{parent.mtx.gxy(which)} {parent.mtx.gxy(which)}} 189 | addUserKnob {12 bxy} 190 | bxy {{parent.mtx.bxy(which)} {parent.mtx.bxy(which)}} 191 | addUserKnob {12 wxy} 192 | wxy {{parent.mtx.wxy(which)} {parent.mtx.wxy(which)}} 193 | } 194 | ColorMatrix { 195 | matrix { 196 | {0.9874471426 -0.005980737507 0.01595583558} 197 | {-0.00739388587 1.001675606 0.005319938064} 198 | {0.003088310361 -0.005131930113 1.081959367} 199 | } 200 | invert {{"parent.RGBtoXYZ.wxy == 0.3127"}} 201 | name ColorMatrix4 202 | label "CAT: Bradford\n D60 to D65" 203 | xpos -260 204 | ypos 101 205 | disable {{"RGBtoXYZ.wxy == XYZtoRGB.wxy"}} 206 | } 207 | ColorMatrix { 208 | matrix { 209 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 210 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 211 | {{parent.mtx.matrix(which)} {parent.mtx.matrix(which)} {parent.mtx.matrix(which)}} 212 | } 213 | invert true 214 | name XYZtoRGB 215 | xpos -260 216 | ypos 182 217 | addUserKnob {20 Gamut} 218 | addUserKnob {3 which} 219 | which {{parent.gamut_out}} 220 | addUserKnob {12 rxy} 221 | rxy {{parent.mtx.rxy(which)} {parent.mtx.rxy(which)}} 222 | addUserKnob {12 gxy} 223 | gxy {{parent.mtx.gxy(which)} {parent.mtx.gxy(which)}} 224 | addUserKnob {12 bxy} 225 | bxy {{parent.mtx.bxy(which)} {parent.mtx.bxy(which)}} 226 | addUserKnob {12 wxy} 227 | wxy {{parent.mtx.wxy(which)} {parent.mtx.wxy(which)}} 228 | } 229 | Switch { 230 | inputs 2 231 | which {{parent.invert}} 232 | name switch_direction 233 | xpos -40 234 | ypos 303 235 | } 236 | Output { 237 | name Output 238 | xpos -40 239 | ypos 374 240 | } 241 | push $N2a9bd220 242 | ColorMatrix { 243 | matrix { 244 | {{curve 0.9525524378 0.6624541879 0.7053968906 0.4123907983 0.6369580626 0.5049495697 0.4865709841 0.6380076408 0.7352752686 0.5022571683 0.7160496712 0.7064827085 0.5990839601 0.6796444654 0.6481720209 0.6065810919 0.7165006995 0.7976718545} {curve 0 0.1340042055 0.1640413404 0.3575843275 0.1446169019 0.2646814585 0.2656676769 0.2147038579 0.06860940903 0.2929667532 0.1296834797 0.1288010478 0.2489254922 0.1522114277 0.1940581352 0.2203479856 0.1010205746 0.1351878047} {curve 9.367863095e-05 0.1561876982 0.08101774752 0.180480808 0.1688809693 0.1830150485 0.1982172877 0.09774444997 0.1465712637 0.1552320272 0.1047228053 0.1151721701 0.1024464965 0.1186000481 0.108225815 0.123526901 0.1467743814 0.03133957833}} 245 | {{curve 0.3439664543 0.2722287476 0.2801307142 0.2126390189 0.2627002299 0.237623319 0.2289745659 0.2919537723 0.2866941094 0.1387997568 0.2612613738 0.2709796727 0.2150758505 0.2606855333 0.2830046713 0.2680045366 0.258728236 0.2880405784} {curve 0.7281661034 0.6740817428 0.8202066422 0.7151686549 0.6779980659 0.6891706586 0.6917385459 0.8238410354 0.8429791331 0.910841465 0.8696421385 0.786606431 0.8850684762 0.7748944759 0.8131960034 0.8326833844 0.7246823311 0.7118694782} {curve -0.07213255018 0.05368951708 -0.1003373638 0.07219231874 0.05930171534 0.07320601493 0.07928691059 -0.1157948226 -0.1296732277 -0.04964122549 -0.1309035122 -0.05758608505 -0.1001443192 -0.03558001295 -0.09620071948 -0.1006879359 0.01658944227 8.991353388e-05}} 246 | {{curve -3.863927134e-08 -0.005574660841 -0.1037815213 0.01933082007 0 0 0 0.0027982709 -0.07968087494 0.07801423222 -0.009676366113 -0.009677864611 -0.03206583485 -0.009310216643 -0.01825834997 -0.02941203304 -2.906408625e-08 0} {curve 0 0.004060741514 -0.07290724665 0.1191947311 0.0280726999 0.0449459292 0.04511339962 -0.06703422964 -0.3473432064 -0.3148325086 -0.2364816219 0.004600019194 -0.02765839547 -0.004612449091 -0.08316776901 -0.08659287542 0.05121183768 -1.262213711e-08} {curve 1.008825183 1.010339141 1.265746474 0.950532198 1.060985088 0.9638792276 1.043944359 1.153293729 1.51608181 1.325875998 1.335215807 1.094135642 1.148782015 1.102980375 1.190483928 1.205062628 0.7738927603 0.8248898983}} 247 | } 248 | name mtx 249 | label "\n" 250 | xpos -40 251 | ypos 38 252 | addUserKnob {20 Params} 253 | addUserKnob {12 rxy} 254 | rxy {{curve 0.7347 0.713 0.8 0.64 0.708 0.68 0.68 0.684 0.780308 0.69848046 0.74 0.73 0.766 0.73 0.71 0.7177 0.7347 0.734699} {curve 0.2653 0.293 0.3177 0.33 0.292 0.32 0.32 0.313 0.304253 0.19302645 0.27 0.28 0.275 0.28 0.31 0.3171 0.2653 0.265301}} 255 | addUserKnob {12 gxy} 256 | gxy {{curve 0 0.165 0.18 0.3 0.17 0.265 0.265 0.221 0.121595 0.32955538 0.17 0.14 0.225 0.165 0.21 0.228 0.1152 0.159597} {curve 1 0.83 0.9 0.6 0.797 0.69 0.69 0.848 1.493994 1.02459662 1.14 0.855 0.8 0.84 0.88 0.8616 0.8264 0.840403}} 257 | addUserKnob {12 bxy} 258 | bxy {{curve 0.0001 0.128 0.065 0.15 0.131 0.15 0.15 0.0861 0.095612 0.10844263 0.08 0.1 0.089 0.1 0.09 0.1006 0.1566 0.036598} {curve -0.077 0.044 -0.0805 0.06 0.046 0.06 0.06 -0.102 -0.084589 -0.03467857 -0.1 -0.05 -0.087 -0.03 -0.08 -0.082 0.0177 0.000105}} 259 | addUserKnob {12 wxy} 260 | wxy {{curve 0.32168 0.32168 0.3127 0.3127 0.3127 0.32168 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127 0.3127 0.3457 0.345704} {curve 0.33767 0.33767 0.329 0.329 0.329 0.33767 0.329 0.329 0.329 0.329 0.329 0.329 0.329 0.329 0.329 0.329 0.3585 0.35854}} 261 | } 262 | end_group 263 | Switch { 264 | inputs 2 265 | which {{parent.use_input_image}} 266 | name Switch_distance_source 267 | xpos 180 268 | ypos 62 269 | } 270 | Group { 271 | name ColorDistance 272 | xpos 180 273 | ypos 110 274 | addUserKnob {20 ColorDistance} 275 | addUserKnob {7 shd_rolloff l "shd rolloff" R 0 0.1} 276 | shd_rolloff {{parent.shd_rolloff}} 277 | } 278 | Input { 279 | inputs 0 280 | name Input 281 | xpos -40 282 | ypos -274 283 | } 284 | Dot { 285 | name Dot5 286 | xpos -6 287 | ypos -222 288 | } 289 | set N225d8930 [stack 0] 290 | Dot { 291 | name Dot1 292 | xpos -116 293 | ypos -222 294 | } 295 | Expression { 296 | channel0 {rgba.red rgba.green rgba.blue none} 297 | expr0 max(r,g,b) 298 | name achromatic 299 | xpos -150 300 | ypos -178 301 | } 302 | set N225e2510 [stack 0] 303 | Expression { 304 | temp_name0 c_r 305 | temp_expr0 1-r 306 | temp_name1 c_g 307 | temp_expr1 1-g 308 | temp_name2 c_b 309 | temp_expr2 1-b 310 | expr0 1-(c_r<(1-thr)?c_r:(1-thr)+thr*tanh(((c_r-(1-thr))/thr))) 311 | expr1 1-(c_g<(1-thr)?c_g:(1-thr)+thr*tanh(((c_g-(1-thr))/thr))) 312 | expr2 1-(c_b<(1-thr)?c_b:(1-thr)+thr*tanh(((c_b-(1-thr))/thr))) 313 | name toe 314 | xpos -150 315 | ypos -130 316 | disable {{parent.shd_rolloff==0}} 317 | addUserKnob {20 Params_tab l Params} 318 | addUserKnob {7 thr t "complement of threshold"} 319 | thr {{parent.shd_rolloff}} 320 | } 321 | Expression { 322 | expr0 abs(r) 323 | expr1 abs(g) 324 | expr2 abs(b) 325 | channel3 none 326 | name abs 327 | xpos -150 328 | ypos -82 329 | } 330 | push $N225e2510 331 | push $N225d8930 332 | Merge2 { 333 | inputs 2 334 | operation minus 335 | bbox B 336 | Achannels rgb 337 | Bchannels rgb 338 | output rgb 339 | name Merge2 340 | xpos -40 341 | ypos -178 342 | } 343 | MergeExpression { 344 | inputs 2 345 | expr0 Ar==0?0:Br/Ar 346 | expr1 Ag==0?0:Bg/Ag 347 | expr2 Ab==0?0:Bb/Ab 348 | channel3 none 349 | name MergeDivide 350 | xpos -40 351 | ypos -82 352 | } 353 | Output { 354 | name Output 355 | xpos -40 356 | ypos -10 357 | } 358 | end_group 359 | CurveTool { 360 | operation "Max Luma Pixel" 361 | channels {rgba.red -rgba.green -rgba.blue none} 362 | ROI {0 0 {width} {height}} 363 | autocropdata {480 270 1440 810} 364 | maxlumapixdata {{curve} {curve}} 365 | maxlumapixvalue {{curve} {curve} 0} 366 | minlumapixdata {{curve} {curve}} 367 | minlumapixvalue {{curve} {curve} 0} 368 | name rmax 369 | tile_color 0x84000000 370 | xpos 180 371 | ypos 158 372 | } 373 | CurveTool { 374 | operation "Max Luma Pixel" 375 | channels {-rgba.red rgba.green -rgba.blue none} 376 | ROI {0 0 {width} {height}} 377 | autocropdata {480 270 1440 810} 378 | maxlumapixdata {{curve x1 2} {curve x1 0}} 379 | maxlumapixvalue {{curve x1 0} {curve x1 0.9845341444} 0} 380 | minlumapixdata {{curve x1 1} {curve x1 0}} 381 | minlumapixvalue {{curve x1 0} {curve x1 0} 0} 382 | name gmax 383 | tile_color 0x84000000 384 | xpos 180 385 | ypos 182 386 | } 387 | CurveTool { 388 | operation "Max Luma Pixel" 389 | channels {-rgba.red -rgba.green rgba.blue none} 390 | ROI {0 0 {width} {height}} 391 | autocropdata {480 270 1440 810} 392 | maxlumapixdata {{curve x1 0} {curve x1 0}} 393 | maxlumapixvalue {{curve x1 0} {curve x1 0} 0.9951567054} 394 | minlumapixdata {{curve x1 2} {curve x1 0}} 395 | minlumapixvalue {{curve x1 0} {curve x1 0} 0} 396 | name bmax 397 | tile_color 0x84000000 398 | xpos 180 399 | ypos 206 400 | } 401 | end_group 402 | -------------------------------------------------------------------------------- /utilities/VisualizeDistance.nk: -------------------------------------------------------------------------------- 1 | set cut_paste_input [stack 0] 2 | push $cut_paste_input 3 | Group { 4 | name VisualizeDistance 5 | addUserKnob {20 VisualizeDistance} 6 | addUserKnob {4 type M {inside outside}} 7 | type outside 8 | addUserKnob {6 show_distance l "show distance" -STARTLINE} 9 | addUserKnob {7 threshold t "threshold of color distance to visualize. 1.0 is the edge of the gamut boundary. 0.0 is all colors." R 0 1.2} 10 | threshold 1 11 | addUserKnob {7 shd_rolloff l "shd rolloff" R 0 0.1} 12 | shd_rolloff 0.008 13 | addUserKnob {4 bg t "choose bg to put behind result colors" M {checkerboard "solid color"}} 14 | addUserKnob {41 color T Fill1.color} 15 | } 16 | Input { 17 | inputs 0 18 | name Input 19 | xpos -40 20 | ypos -250 21 | } 22 | Dot { 23 | name Dot1 24 | note_font "Helvetica Bold" 25 | note_font_size 24 26 | note_font_color 0xff 27 | xpos -6 28 | ypos -198 29 | } 30 | set N402f2fa0 [stack 0] 31 | Dot { 32 | name Dot2 33 | note_font "Helvetica Bold" 34 | note_font_size 24 35 | note_font_color 0xff 36 | xpos -116 37 | ypos -198 38 | } 39 | set N2a8a6560 [stack 0] 40 | Dot { 41 | name Dot5 42 | note_font "Helvetica Bold" 43 | note_font_size 24 44 | note_font_color 0xff 45 | xpos -226 46 | ypos -198 47 | } 48 | set N2a90f4c0 [stack 0] 49 | Dot { 50 | name Dot6 51 | note_font "Helvetica Bold" 52 | note_font_size 24 53 | note_font_color 0xff 54 | xpos -336 55 | ypos -198 56 | } 57 | Expression { 58 | channel0 {rgba.red rgba.green rgba.blue none} 59 | expr0 max(r,g,b) 60 | channel1 none 61 | channel2 none 62 | channel3 none 63 | name achromatic 64 | note_font Helvetica 65 | xpos -370 66 | ypos -153 67 | } 68 | set N2a92aeb0 [stack 0] 69 | Expression { 70 | temp_name0 c_r 71 | temp_expr0 1-r 72 | temp_name1 c_g 73 | temp_expr1 1-g 74 | temp_name2 c_b 75 | temp_expr2 1-b 76 | expr0 1-(c_r<(1-thr)?c_r:(1-thr)+thr*tanh(((c_r-(1-thr))/thr))) 77 | expr1 1-(c_g<(1-thr)?c_g:(1-thr)+thr*tanh(((c_g-(1-thr))/thr))) 78 | expr2 1-(c_b<(1-thr)?c_b:(1-thr)+thr*tanh(((c_b-(1-thr))/thr))) 79 | name toe 80 | note_font Helvetica 81 | xpos -370 82 | ypos -118 83 | disable {{parent.shd_rolloff==0}} 84 | addUserKnob {20 Params_tab l Params} 85 | addUserKnob {7 thr t "complement of threshold"} 86 | thr {{parent.shd_rolloff}} 87 | } 88 | Expression { 89 | expr0 abs(r) 90 | expr1 abs(g) 91 | expr2 abs(b) 92 | name abs 93 | xpos -370 94 | ypos -82 95 | } 96 | push $N2a92aeb0 97 | push $N2a90f4c0 98 | Merge2 { 99 | inputs 2 100 | operation minus 101 | bbox B 102 | Achannels rgb 103 | Bchannels rgb 104 | output rgb 105 | name Merge2 106 | note_font Helvetica 107 | xpos -260 108 | ypos -153 109 | } 110 | MergeExpression { 111 | inputs 2 112 | expr0 Ar==0?0:Br/Ar 113 | expr1 Ag==0?0:Bg/Ag 114 | expr2 Ab==0?0:Bb/Ab 115 | channel3 none 116 | name MergeDivide 117 | xpos -260 118 | ypos -82 119 | } 120 | set N39140210 [stack 0] 121 | Dot { 122 | name Dot4 123 | note_font "Helvetica Bold" 124 | note_font_size 24 125 | note_font_color 0xff 126 | xpos -226 127 | ypos 42 128 | } 129 | push $N39140210 130 | push $N2a8a6560 131 | MergeExpression { 132 | inputs 2 133 | temp_name0 limit 134 | temp_expr0 threshold 135 | temp_name1 sat 136 | temp_expr1 max(Ar,Ag,Ab) 137 | expr0 type?(sat>=limit?Br:0):sat<=limit?Br:0 138 | expr1 type?(sat>=limit?Bg:0):sat<=limit?Bg:0 139 | expr2 type?(sat>=limit?Bb:0):sat<=limit?Bb:0 140 | expr3 type?(sat>limit?1:0):sat