Skip to content

Commit

Permalink
Merge pull request #1 from MrLixm/feat-primaries_inset
Browse files Browse the repository at this point in the history
Feat: add PrimariesInset tool
  • Loading branch information
MrLixm authored Nov 11, 2023
2 parents d8215dd + 3be468b commit d735bd4
Show file tree
Hide file tree
Showing 15 changed files with 1,319 additions and 3 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ coverage.xml

# project specific
poetry.lock
build/
build/
# nuke compile blink scripts
*.blink.src
*.blink.desc
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[tool.poetry]
name = "foundry-nuke"
version = "0.1.0"
version = "0.2.0"
description = "Collection of script & resources for Foundry's Nuke software."
authors = ["Liam Collod <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"
python = ">=3.9,<3.12"
colour-science = "0.4.3"

[tool.poetry.dev-dependencies]
black = "*"
Expand Down
175 changes: 175 additions & 0 deletions src/primaries_inset/PrimariesInset.nk

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions src/primaries_inset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Primaries Inset

Create a reshaped version of a colorspace's gamut and apply it on images using
a 3x3 matrix. It is possible to visuallize the gamut transformation in a
CIE xy graph plot.

The reshape transformation is called an "inset" as we are creating a smaller
gamut than the original.

This is the main concept behind [the AgX DRT](https://github.com/MrLixm/AgXc) which
was also [ported to darktable](https://github.com/darktable-org/darktable/pull/15104).

![nuke screenshot with PrimariesInset enable](doc/img/demo-inset-on.png)

And disabled :

![nuke screenshot with PrimariesInset disabled](doc/img/demo-inset-off.png)

The above example is in the context of a traditional ACES workflow (note the
ACES view-transform in the viewer) but could be used with any other "tonemapping".

The PrimaryInset operation works closely with the application of a 1D tonescale curve
(usually reffered as tonemapping) after itself.

If you find the effect of the Inset too strong and just woudl like to fix those
very saturated camera artefact it's possible to apply an outset after the inset :

![nuke screenshot of the outset workflow](doc/img/demo-outset.png)

Unfortunately the workflow require to bake the view transform in the chain
(OCIO Display node) :
1. apply ACES view-transform
2. revert the sRGB EOTF conversion to get back ACEScg value
3. duplicate the first `PrimaryInset` node but check the `Invert` option
4. reapply the sRGB EOTF conversion

## plotting

It is possible to preview the new inset colorspace as a plot in the CIE1931 xy space.

Just check the `show` checkbox next to the `Plot` title.

![nuke gif of the plot being edited interactively](doc/img/demo-plot.gif)

# Instructions

## Install

- Copy/paste the content of [PrimariesInset.nk](PrimariesInset.nk) in any nuke
scene.
- That's it

System :
- PrimariesInset uses:
- python code for `Presets` but works on non-commercial versions.
- blink script but works on non-commercial versions >= 14.0
- The python code _should_ be python 2 compatible but has only been tested on latest
python3 versions of Nuke.

## Usage

- Expect an image to be transformed as input.
- Select the preset corresponding to the input image's colorspace encoding.
- Click the apply button: the _primary X_ and _whitepoint_ knobs are updated.
- In the _Option_ section, start playing with the global inset.
- To see exactly how it affect the original colorspace you can click on `show`
in the _Plot_ section : your image disapear to leave a dark squared canvas with
only a gamut visible.
- Keep playing with the _Options_ to see how it works.

# Developer

See the [./src/](./src) folder for the original files that create the final node.

## TODO

- [ ] fix NO_HANDLE flag issue that doesn't seems to work
Binary file added src/primaries_inset/doc/img/demo-inset-off.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/primaries_inset/doc/img/demo-inset-on.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/primaries_inset/doc/img/demo-outset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/primaries_inset/doc/img/demo-plot.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
229 changes: 229 additions & 0 deletions src/primaries_inset/src/PrimariesInset.blink
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// version 6
kernel InsetPrimaries : ImageComputationKernel<ePixelWise>
{
Image<eRead, eAccessPoint, eEdgeClamped> src;
Image<eWrite> dst;

param:
bool u_invert;
float2 u_src_primary_r;
float2 u_src_primary_g;
float2 u_src_primary_b;
float2 u_src_whitepoint;
float u_inset_r;
float u_inset_g;
float u_inset_b;
float u_rotate_r;
float u_rotate_g;
float u_rotate_b;
float2 u_whitepoint_pre_offset;
float2 u_whitepoint_post_offset;

local:
float pi;

float lerp(float a1, float a2, float amount){
// linear interpolation between 2 values
return (1.0 - amount) * a1 + amount * a2;
}

float2 rotate_point_around(float2 point, float angle, float2 center){
// angle: in radians
// https://stackoverflow.com/a/2259502/13806195

float s = sin(angle);
float c = cos(angle);

// translate point back to origin:
point.x -= center.x;
point.y -= center.y;

// rotate point
float xnew = point.x * c - point.y * s;
float ynew = point.x * s + point.y * c;

// translate point back:
point.x = xnew + center.x;
point.y = ynew + center.y;
return point;
}

float3 mult_f3_by_f3x3(float3 vector, float3x3 matrix) {
return float3(
matrix[0][0] * vector.x + matrix[0][1] * vector.y + matrix[0][2] * vector.z,
matrix[1][0] * vector.x + matrix[1][1] * vector.y + matrix[1][2] * vector.z,
matrix[2][0] * vector.x + matrix[2][1] * vector.y + matrix[2][2] * vector.z
);
}

float3x3 normalised_primary_matrix(
float2 primary_r,
float2 primary_g,
float2 primary_b,
float2 whitepoint
) {
// Calculate the normalized primaries matrix for the specified chromaticities and whitepoint.
// Derived from:
// SMPTE Recommended Practice - Derivation of Basic Television Color Equations
// https://ieeexplore.ieee.org/document/7291155

float3x3 matrix;
// build a 3x3 matrix from the primaries and add a third z axis
matrix[0][0] = primary_r[0];
matrix[0][1] = primary_r[1];
matrix[0][2] = 1.0 - primary_r[0] - primary_r[1];
matrix[1][0] = primary_g[0];
matrix[1][1] = primary_g[1];
matrix[1][2] = 1.0 - primary_g[0] - primary_g[1];
matrix[2][0] = primary_b[0];
matrix[2][1] = primary_b[1];
matrix[2][2] = 1.0 - primary_b[0] - primary_b[1];

float Wz;
Wz = 1.0 - whitepoint[0] - whitepoint[1];
float3 W(whitepoint[0] / whitepoint[1], 1.0, Wz / whitepoint[1]);

float3x3 P(matrix);
P = P.transpose();

float3 C;
C = mult_f3_by_f3x3(W,P.invert());

float3x3 Cm(0,0,0,0,0,0,0,0,0);
Cm[0][0] = C.x;
Cm[1][1] = C.y;
Cm[2][2] = C.z;

float3x3 npm;
npm = P * Cm;
return npm;
}

bool are_chromaticities_identity(
float2 primary_r,
float2 primary_g,
float2 primary_b
) {
return (
primary_r.x == 1.0 && primary_r.y == 0.0 &&
primary_g.x == 0.0 && primary_g.y == 1.0 &&
primary_b.x == 0.0 && primary_b.y == 0.0
);
}

float3x3 get_inset_colorspace(
float2 primary_r,
float2 primary_g,
float2 primary_b,
float2 whitepoint,
float inset_r,
float inset_g,
float inset_b
){
float2 new_primary_r;
float2 new_primary_g;
float2 new_primary_b;

new_primary_r.x = lerp(primary_r.x, whitepoint.x, inset_r);
new_primary_r.y = lerp(primary_r.y, whitepoint.y, inset_r);
new_primary_g.x = lerp(primary_g.x, whitepoint.x, inset_g);
new_primary_g.y = lerp(primary_g.y, whitepoint.y, inset_g);
new_primary_b.x = lerp(primary_b.x, whitepoint.x, inset_b);
new_primary_b.y = lerp(primary_b.y, whitepoint.y, inset_b);

float3x3 out;
out[0][0] = new_primary_r.x;
out[0][1] = new_primary_r.y;
out[1][0] = new_primary_g.x;
out[1][1] = new_primary_g.y;
out[2][0] = new_primary_b.x;
out[2][1] = new_primary_b.y;
return out;

}

void init() {
pi = 3.1415926535f;
}

void process() {

float2 dst_whitepoint = u_src_whitepoint + u_whitepoint_pre_offset;

float3x3 inset_colorspace;
inset_colorspace = get_inset_colorspace(
u_src_primary_r,
u_src_primary_g,
u_src_primary_b,
dst_whitepoint,
u_inset_r,
u_inset_g,
u_inset_b
);

float2 primary_r_inset(inset_colorspace[0][0], inset_colorspace[0][1]);
float2 primary_g_inset(inset_colorspace[1][0], inset_colorspace[1][1]);
float2 primary_b_inset(inset_colorspace[2][0], inset_colorspace[2][1]);

primary_r_inset = rotate_point_around(
primary_r_inset, u_rotate_r * (pi/180), dst_whitepoint
);
primary_g_inset = rotate_point_around(
primary_g_inset, u_rotate_g * (pi/180), dst_whitepoint
);
primary_b_inset = rotate_point_around(
primary_b_inset, u_rotate_b * (pi/180), dst_whitepoint
);

float3x3 dst_to_xyz;

if (
are_chromaticities_identity(
primary_r_inset, primary_g_inset, primary_b_inset
)
) {
dst_to_xyz.setIdentity();
} else {
dst_to_xyz = normalised_primary_matrix(
primary_r_inset,
primary_g_inset,
primary_b_inset,
dst_whitepoint + u_whitepoint_post_offset
);
dst_to_xyz = dst_to_xyz.invert();
}

float3x3 src_to_xyz;

if (
are_chromaticities_identity(
u_src_primary_r, u_src_primary_g, u_src_primary_b
)
) {
src_to_xyz.setIdentity();
} else {
src_to_xyz = normalised_primary_matrix(
u_src_primary_r,
u_src_primary_g,
u_src_primary_b,
u_src_whitepoint
);
}

float3x3 conversion_matrix;
conversion_matrix = dst_to_xyz * src_to_xyz;
if (!(u_invert)){
conversion_matrix = conversion_matrix.invert();
}

float4 rgba = src();
float3 converted_rgb(rgba.x, rgba.y, rgba.z);
converted_rgb = mult_f3_by_f3x3(converted_rgb, conversion_matrix);
dst() = float4(
converted_rgb.x,
converted_rgb.y,
converted_rgb.z,
rgba.w
);
}
};
Loading

0 comments on commit d735bd4

Please sign in to comment.