Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add PrimariesInset tool #1

Merged
merged 51 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
98866fa
feat(primaries_inset): add CIExyPoint node
MrLixm Oct 21, 2023
2146ce4
feat(primaries_inset): add GamutPlot node
MrLixm Oct 21, 2023
4adaede
feat(primaries_inset): GamutPlot: add red primary
MrLixm Oct 21, 2023
9bedfc1
feat(primaries_inset): GamutPlot: add green primary
MrLixm Oct 21, 2023
20e74e5
feat(primaries_inset): GamutPlot: add blue primary
MrLixm Oct 21, 2023
1e6dbef
feat(primaries_inset): GamutPlot: add whitepoint
MrLixm Oct 21, 2023
67ff9c1
feat(primaries_inset): add GamutInset
MrLixm Oct 22, 2023
578f5db
feat(gamut-convert): add first version python module
MrLixm Oct 22, 2023
d70c51a
fix(primaries_inset): GamuInset: updated some names
MrLixm Oct 22, 2023
893fefe
feat(primaries_inset): add PrimariesInset
MrLixm Oct 22, 2023
5b51173
feat(primaries_inset): PrimariesInset: set parent link
MrLixm Oct 22, 2023
f369d57
fix(primaries_inset): remove endGroup call
MrLixm Oct 22, 2023
8ca151a
feat(primaries_inset): PrimariesInset: add clone knob matrix/primaries
MrLixm Oct 22, 2023
ebdeee0
feat(primaries_inset): PrimariesInset: add empty knobChanged
MrLixm Oct 22, 2023
4c3e5d3
feat(primaries_inset): add knoc changed callback script
MrLixm Oct 22, 2023
daa44b1
feat(primaries_inset): add build script
MrLixm Oct 22, 2023
f1db707
feat(primaries_inset): add build instructions
MrLixm Oct 22, 2023
713fa4a
chore: add colour to dependencies
MrLixm Oct 22, 2023
7f8660a
fix(primaries_inset): close Output group by default
MrLixm Oct 22, 2023
1f738d4
fix(primaries_inset): matrix default as identity
MrLixm Oct 22, 2023
e8105bc
fix(primaries_inset): matrix need to be inverted
MrLixm Oct 22, 2023
abd1779
feat(primaries_inset): add generate-preset util script
MrLixm Oct 22, 2023
f198510
feat(primaries_inset): add preset script
MrLixm Oct 22, 2023
f055235
feat(primaries_inset): add preset knobs
MrLixm Oct 22, 2023
3510181
feat(primaries_inset): add node's preset feature to build
MrLixm Oct 22, 2023
367ead4
feat(primaries_inset): add pretty title to gizmo
MrLixm Oct 22, 2023
59d9301
!refacto(primaries_inset): GamutInset: add whitepoint shift
MrLixm Oct 22, 2023
de54d8a
refacto(primaries_inset): PrimariesInset: apply change of GamutInset
MrLixm Oct 22, 2023
3cb55da
feat(primaries_inset): PrimariesInset: add whitepoint offset controls
MrLixm Oct 22, 2023
8bf7a19
refacto(primaries_inset): GamutInset: use edited whitepoint
MrLixm Oct 22, 2023
0ce9693
refacto(primaries_inset): PrimairesInset: apply GamutInset changes
MrLixm Oct 22, 2023
dc55cb1
feat(primaries_inset): PrimairesInset: add node color
MrLixm Oct 22, 2023
e56b717
feat(primaries_inset): add build node
MrLixm Oct 22, 2023
b7b26f3
chore(primaries_inset): add README
MrLixm Oct 22, 2023
4a7d0fc
feat(primary_inset): add blinkscript implementation
MrLixm Oct 28, 2023
f5655de
refacto(primary_inset): directory structure
MrLixm Oct 28, 2023
f908834
refacto(primary_inset): build improve code structure
MrLixm Oct 28, 2023
45ebe49
feat(primaries_inset): add plot blink script
MrLixm Nov 11, 2023
8ebe2d8
fix(primaries_inset): bug in Nuke 15.0
MrLixm Nov 11, 2023
74163c1
fix(primaries_inset): compat with nuke-14; blink name
MrLixm Nov 11, 2023
38bba83
feat([primaries_Inset): add default values to blink param
MrLixm Nov 11, 2023
c97df6a
refacto(primaries_inset): delete non-blink nodes; update build process
MrLixm Nov 11, 2023
c217057
refacto(primaries_inset): Gizmo: move Plot section
MrLixm Nov 11, 2023
91ed1dd
feat(primaries_inset): add whitepoint offset
MrLixm Nov 11, 2023
f133642
refacto(primaries_inset): update build instructions
MrLixm Nov 11, 2023
55409e8
refacto(primaries_inset): update README
MrLixm Nov 11, 2023
5945c91
refacto(primaries_inset): Gizmo: better label whitepoint options
MrLixm Nov 11, 2023
ee53d68
feat(primaries_inset): add images to README
MrLixm Nov 11, 2023
8373614
chore(primaries_inset): update build gizmo
MrLixm Nov 11, 2023
bb7884d
refacto(gamut-convert): removed
MrLixm Nov 11, 2023
3be468b
chore: upgrade version to 0.2.0
MrLixm Nov 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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