Welcome to WSL!

Make yourself at home, but before posting, please may I ask you to read the following topics.

Posting 101
Server space, screenshots, and you

Thank you!

PS. please pretty please:


[DEV] Tetrahedral interpolation (from the Yedlin demo)

Where the future is being made, today.

Welcome to the WSL development corner!

In this forum, please post your development projects. You get kudos and feedback here.
Topics ideally have preset prefixes, and this is what they (might) mean:

  • [DEV] - very much work in progress, don't build a business on this, could go anywhere
  • [BETA] - should kinda do what it's supposed to do, please test, give feedback
  • [RC] - this may end up in Reactor soon, polishing up, now's the time for last minute thoughts
  • [ABD] - died a premature death, sadness, will not see the light of day ever (unless someone picks up the scraps)

Once a development project has been released (hurray), topics can be marked as - you guessed it - [RELEASED] :cheer:

Development topics only, please. For generic questions, how-to's, questions and inquiries about existing tools etc, please go to the appropriate other forums.
User avatar
Posts: 7
Joined: Sat May 30, 2020 5:55 am

[DEV] Tetrahedral interpolation (from the Yedlin demo)


Post by PieterDumoulin » Fri Aug 14, 2020 5:03 am

Hi everyone!

I was hoping to get some help with the writing of a Fuse that is above my head for the moment.

The Fuse is based on the tetra Blink script written by Steve Yedlin, snippets of the code can be seen in his display prep demo followup:

http://yedlin.net/DisplayPrepDemo/DispP ... owup.html

The script is shown around 14min 36s

I made a quick transcription that has most of the code, but some of it is missing.

The first intention of the script is to rearrange the color rendition to a subtractive model, so there are no sliders or adjustments that need values.

But it would also be possible to make it as a modifier, where you can target a specific hue and adjust the saturation levels, as shown at the end of the discussion (around 1h mark).

I already had some help so there is a basis to start from, but I'm new to coding myself so I hope some of you can join in and help me with this.


Added in 47 seconds:
Blink script:

kernel Tetra : ImageComputationKernel<ePixelWise>
Image<eRead, eAccessPoint, eEdgeClamped> src; // the input image
Image>eWrite> dst; // the output image

float3 red;
float3 grn;
float3 blu;
float3 cyn;
float3 mag;
float3 yel;
bool inv;

void define () {
defineParam(red, "red", float3 (1.f.0.f.0.f));
defineParam(grn, "grn", float3 (0.f.1.f.0.f));
defineParam(blu, "blu", float3 (0.f.0.f.1.f));
defineParam(cyn, "cyn", float3 (0.f.1.f.1.f));
defineParam(mag, "mag", float3 (1.f.0.f.1.f));
defineParam(yel, "yel", float3 (1.f.1.f.0.f));

float determinantMinor ( int rowHeight, int columnWidht, float3x3 matrix)

int y1 = rowHeight == 0 ? 1 : 0;
int y2 =

int x1 = columnWidht == 0 ? 1 : 0;
int x2 = columnWidht == 2 ? 1 : 2;

return (matrix{y1}{x1} * matrix{y2}{x2}) - (matrix{y1}{x2}) * matrix {y2}{x1});

float determinant (float3x3 thMatrix)
return (theMatrix{0} {0} * determinantMinor (0.f,0.f,theMatrix))
- (theMatrix{0} {1} * determinantMinor (0.f.1.f.theMatrix))
+ (theMatrix{0} {2} * determinantMinor (0.f,2.f,theMatrix))


float3x3 matrixInverse (float3x3 input)
float det = determinant (input)

// if the determinant is basically zero, then it is zero...
// if (ABS (det) < 1e-2)
// {
// memset (output, 0, sizeof(&output));
// return false;
// }
float3x3 output
int x,y;
for (y = 0; y<3; y++)

output{y}{x} = determinantMinor (x,y,input) * (1.f / det);
if( 1 == ((x+y) & 2) )
output {y}{x} = - output {y}{x};
return output

//A function that returns the rotational distance of a point (around the gray diagonal) from red

float pt_ang(float3triplet , float3 corners [6]) {

75 - 92 missing

float b_ang = atan2 (1.4142f,1.f)-pi/2f;
float3 tr2;
tr2.x = tr1.x * cos(b_ang) + tr1.y * sin (b_ang);
tr2.y = tri.x * (-sin(b_ang)) + tr1.y * cos (b_ang);
tr2.z = tr1.z;
float3 rc2;
rc2.x = rc1.x * cos(b_ang) + rc1.y * sin(b_ang);
rc2.y = rc1.x * (-sin(b_ang)) + rc1.y *cos(b_ang);
rc2.z = rc1.z;

// now rotate on the now vertical gray axis so that red is at -pi rotation angle
float f_ang = atan2(rc2.x,rc2.z) - pi;
float3 tr3;
tr3.x = tr2.x * cos(g_ang) + tr2.z * (-sin(g_ang));
tr3.y = tr2.y;
tr3.z = tr2.x * sin(g_ang) + tr2.z * cos(g_ang);
float3 rc3;
rc3.x = rc2.x * cos(g_ang) + rc2.z * (-sin(g_ang));
rc3.y = rc2.y;
rc3.z = rc2.x * sin(g_ang) + rc2.z * cos(g_ang);

return atan2 (tr3.x , tr.z) - atan2(rc3.x , rc3.z);


//A function that finds which of the 6 tetrahedrons a point is contained in,
//even if the cube has been deformed
int tetra_region(float3 rgb, float3 corners{6}){

float pi = 3.14159265f;

float ang = pt_ang(rgb , corners);

float corner_ang(6);
for (int 1 = 0; 1 < 6; ++1) {
corner_angle = pt_ang (corners {i}, corners);

int region = 5;
for (int i = 0; i < 5; ++i) {
if ( ang>=corner_angs(i) && ang < corner_angs (i+i) ) {
region = i;

return region;


float tetra ( float3 triplet , float3 corners [6]){

float r = triplet.x;

147 - 152 missing

float3 yel = corners[1];
float3 grn = corners[2];
float3 cyn = corners[3];
float3 blu = corners[4];
float3 mag = corners[5];

if (r>g) {
if (g>b) (
return r*red + g*(yel-red) + b*(wht-yel);
else if (r>b) {
return r*red + g*(whit-mag) + b*(mag-red);

168 - 173 missing

if (b>g) {
return r*(wht-cyn) + g*(cyn-blu) + b*blu;
else if (b>r) {
return r*(wht-cyn) + g*grn + b*(cyn-grn);
else {
return r*(yel-grn) + g*grn + b*(wht-yel);

float3 inv_tetra ( float3 triplet , float3 corners[6]){

float r = triplet.x;
float g = triplet.y;
float b = triplet.z;

float3 wht = float3(1.f.1.f,1.f);
float3 red = corners[0];
float3 yel = corners[1];
float3 grn = corners[2];
float3 cyn = corners[3];
float3 blu = corners[4];
float3 mag = corners[5];

int region = tetra_region (triplet,corners);
float3 cR;
float3 cG;
float3 cB;

if (region == 0) {}
cR = red;
cG = yel-red;
cB = wht-yel;
else if (region == 1){
cR = yel-grn;
cG = grn;
cB = wht-yel
else if (region == 2){
cR = wht-cyn;
cG = grn;
cB = cyn-grn;
else if (region == 3){

227 - 231 Missing

else if (region == 4){
cR = mag-blu;
cG = wht-mag;
cB = blu;
else {
cR = red;
cG = wht-mag;
cB = mag-red;

float3x3 matrix = float3x3(cR.x.cR.y,cR.z.cG.x,cG.y,cG.z,cB.x,cB.x,cB.y,cB.z);
float3x3 inverse = matrixInverse(matrix);

cR.x = inverse[0][0];
cR.y = inverse[0][1];
cR.z = inverse[0][2];

252 - 255 missing

cB.x = inverse[2][0];
cB.y = inverse[2][1];
cB.z = inverse[2][2];

return (r * cR + g * cG + b * cB);


void process () {
// Read the input image

SampleType (src) input = src();

float3 rgb;
rgb.x = input.x;
rgb.y = input.y;
rgb.z = input.z;

float3 corners [6];

corners[0] = red;
corners[1] = yel;
corners[2] = grn;
corners[3] = cyn;
corners[4] = blu;
corners[5] = mag;

float output = tetra ( rgb , corners );
if (inv) {
output = inv_tetra( rgb , corners);

//Write the result tot the output image
dst() = float4 ( output.x, output.y , output.z , input.w);



Added in 1 minute 38 seconds:
Fuse basis:

function Process(req)
local img = InImage:GetValue(req)

local red = { 1, 0, 0 }
local grn = { 0, 1, 0 }
local blu = { 0, 0, 1 }
local cyn = { 0, 1, 1 }
local mag = { 1, 0, 1 }
local yel = { 1, 1, 0 }

local inv

This is a block comment, so you can just copy-paste all of this without having
to worry about removing my commentary.

Next we have some function declarations. We usually don't want to nest these
inside our Process function. It will work, so long as you never call the function
prior to it being declared, but it's not good practice. Look to the bottom of the
fuse for the functions.

Of course, nothing in the code is actually calling these functions, and there is
one call to a function that doesn't exist here. So nothing will happen in this fuse
as it currently exists

local matrix = {cR.x, cR.y, cR.z, cG.x, cG.y, cG.z, cB.x, cB.y, cB.z}
local inverse = matrixInverse(matrix)

We create a matrix here, then invert it, but nothing is being done with the matrix,
so there's not yet much point to it. We'll also need to invoke the matrixutils
library in order for the inversion to work, or write our own. As it stands, this
Fuse will not compile due to the absence of matrixInverse() and tetra_region().

OutImage:Set(req, img) --img has not been defined, which will likely cause Fusion to crash

end -- End of Process()

--[[-- Comments from the fuse:
By calculating the sine and cosine of the angle, you can get the point one unit away from the origin at
the given rotation. Just multiply the coordinates by whatever distance you need. Cosine corresponds
to X and sine corresponds to Y.

In other words:

Decide on the angle.
Calculate X by getting the cosine of the angle.
Calculate Y by getting the sine of the angle.
Multiply X and Y by the distance.
Offset X and Y by your "source" point.

function pt_ang(triplet, corners)

local b_ang = math.atan2(1.4142, 1)-math.pi/2
Most trig functions are contained in the math library, and we have to use the
table notation to access them. math.sin(), math.exp(), etc.

I am not sure why b_ang is calculated this way. With no variables present,
it would be much more efficient to just set it equal to -0.61548422949032.
In any case, atan2() returns the angle in radians between the x axis and a ray
drawn from the origin through the point (y, x). Note that it's not (x,y); the
coordinate is reversed. Then we subtract a quarter revolution, forcing the angle
to be somewhere in Quadrants I or IV (+x)

local tr2 = {}
-- If we're going to put a table in a variable, we need to call the constructor like this first.

This is the first place where the lack of information in the source code is
harming us. We don't yet know what should be in tr1 or rc1. We'll have to try
to guess from context, which is currently lacking. We'll see if it becomes
clearer later on. I suspect that tr1 is the argument 'triplet' and rc1 is
'corners', but I can't be certain.

tr2.x = tr1.x * math.cos(b_ang) + tr1.y * math.sin(b_ang)
tr2.y = tr1.x * -math.sin(b_ang) + tr1.y * cos(b_ang)
tr2.z = tr1.z

local rc2 = {}
rc2.x = rc1.x * math.cos(b_ang) + rc1.y * math.sin(b_ang)
rc2.y = rc1.x * -math.sin(b_ang) + rc1.y * math.cos(b_ang)
rc2.z = rc1.z

-- The above lines are simply rotations around the z (blue) axis

-- now rotate on the now vertical gray axis so that red is at -pi rotation angle
-- (Bryan) This was f_ang in your fuse. I suspect it's suppose to be g_ang to match
-- all of the lines that follow.

local g_ang = math.atan2(rc2.x, rc2.z) - math.pi
local tr3 = {}
tr3.x = tr2.x * math.cos(g_ang) + tr2.z * -math.sin(g_ang)
tr3.y = tr2.y
tr3.z = tr2.x * math.sin(g_ang) + tr2.z * math.cos(g_ang)

local rc3 = {}
rc3.x = rc2.x * math.cos(g_ang) + rc2.z * -math.sin(g_ang)
rc3.y = rc2.y
rc3.z = rc2.x * math.sin(g_ang) + rc2.z * cos(g_ang)

-- Those were rotations around the y (green) axis

-- We return a scalar value, but I'm not sure what exactly is happening here.
-- I'll have to do some graphing, maybe, to figure it out.
return math.atan2(tr3.x, tr3.z) - math.atan2(rc3.x, rc3.z)

end -- End of pt_ang()

A function that finds which of the 6 tetrahedrons a point is contained in,
even if the cube has been deformed

function tetra(triplet, corners)
-- Guessing the rest of the color variables are supposed to be
-- declared here, as they are in inv_tetra.
local b = triplet.z
local wht = (1, 1, 1)
local red = corners[0]
local yel = corners[1]
local grn = corners[2]
local cyn = corners[3]
local blu = corners[4]
local mag = corners[5]

In Lua, if statements take the form of:
if <condition> then
<do stuff>

They can, of course, be nested, as we see below, and they
can use elseif to provide alternate conditions.

The indentation is not syntactically required, but it makes
it much easier to read the code. For very short statements,
the entire thing can be put one line:

if <condition> then <do stuff> end

The if can also use Boolean logic:
if <condition1> AND <condition2> then
<both conditions must be true for this code to execute>

if not <condition1> then
<this executes if the condition is false or nil>

if r>g then
if g>b then
return r*red + g*(yel-red) + b*(wht-yel) --r>g>b
elseif r>b then
return r*red + g*(wht-mag) + b*(mag-red) --r>b>g

if b>r then
return r*(mag-blu) + g*(wht-mag) + b*blu -- b>g>r

if b>g then
return r*(wht-cyn) + g*(cyn-blu) + b*blu -- b>g>r
elseif b>r then
return r*(wht-cyn) + g*grn + b*(cyn-grn) -- g>b>r
return r*(yel-grn) + g*grn + b*(wht-yel) -- g>r>b

end -- End of tetra()

function inv_tetra(triplet, corners)
local r = triplet.x
local g = triplet.y
local b = triplet.z

local red = corners[0]
local yel = corners[1]
local grn = corners[2]
local cyn = corners[3]
local blu = corners[4]
local mag = corners[5]

local region = tetra_region(triplet, corners)

local cR
local cG
local cB

if region == 0 then
cR = red
cG = yel-red
cB = wht-yel
elseif region == 1 then
cR = yel-grn
cG = grn
cB = wht-yel
elseif region == 2 then
cR = wht-cyn
cG = grn
cB = cyn-grn
elseif region == 3 then
cR = wht-cyn
cG = cyn-blu
cB = blu
elseif region == 4 then
cR = mag-blu
cG = wht-mag
cB = blu
cR = red
cG = wht-mag
cB = mag-red

end -- End of inv_tetra()

User avatar
Posts: 50
Joined: Wed Nov 15, 2017 6:47 am
Answers: 1
Location: Europe
Been thanked: 1 time

Re: [DEV] Tetrahedral interpolation (from the Yedlin demo)


Post by cinewrangler » Sun Aug 16, 2020 1:52 am

Your version of matrixInverse() in the Blink script looks wrong, it is missing a for() loop over 'x'.

The missing code snippets in the tetrahedral interpolation are easy to fill in. Basically such a code determines which of the six tetrahedrons that make up the cube your RGB pixel falls into. Each tetrahedron has four corners (i.e. it touches four out of the eight corners of the full cube). - So just take a piece of paper and draw a cube and label the corners with the corresponding colors (hint: one corner is black and one is white and these stay constant in the Blink code, i.e. can not be moved around)... then it is easy to see which tetrahedron corresponds to the piece of code you're missing there.

User avatar
Posts: 1
Joined: Fri Oct 16, 2020 8:18 am

Re: [DEV] Tetrahedral interpolation (from the Yedlin demo)


Post by cayubal » Fri Oct 16, 2020 8:26 am

This might help for the tetra part (see pages 56 and 57 of that PDF): https://www.filmlight.ltd.uk/pdf/whitep ... areLib.pdf
Wondering if that code might be turned into a DCTL for use in Resolve :wink:

User avatar
Posts: 1
Joined: Sun Oct 18, 2020 5:56 pm

Re: [DEV] Tetrahedral interpolation (from the Yedlin demo)


Post by zecko » Sun Oct 18, 2020 6:06 pm

watching the development here with great interest! I watched his demo and came away wondering the same thing.