Conway's Game Of Life in BlinkScript (because why not)

I’ve finally started toying around with BlinkScript. After multiple unsuccessful attempts at understanding the Blink Reference Guide, I am now faced with an interesting enough challenge that I went back to it, for real. I’ve also started working with Guillem Ramisa De Soto, which, to be honest, feels like a huge cheat code (i r winner).

I’m not too far on my journey to mastering the power of the node, but I’m at least confident that I now understand the language well enough to work with it.

So, when I watched Veritasium’s video about math being flawed this weekend, I naturally had to try making Conway’s Game Of Life in BlinkScript.
And so, here it is:

The Setup

feedback_loop_screenshot.png

The main problem of Conway’s Game Of Life is that you need to know the current state of the cell at t to generate the next cells at t+1. On it’s own, that’s perfectly reasonable but you can’t really do that in Nuke. So, I had to render each frame to feed them back to the script via a time offsetted read, thus creating a feedback loop.

The comp looks like that.

Feedback loop logic

  • At frame == 1

    • Render Frame 1

  • At frame >= 2

    • Load frame-1 via the read and the framehold [frame-1] nodes

    • Go through the BlinkScript Node to get the state of the game at frame based on the state of the game at frame-1

    • Render it and start again for frame+1

The Code

kernel TheGameOfLife : public ImageComputationKernel
{
    Image src; 
    Image dst;

    param:
        // Color above which a pixel will be consider as alive
        float whitePoint;

    void init()
    {
        // To get the neighbouring pixels
        src.setRange(-1, -1, 1, 1);
    }

    void process(int2 pos)
    {

        float neighbours[9];
        int neighboursCount = 0;

        // Loop on every pixels neighbouring the current one
        int index = 0;
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {
                neighbours[index] = src(i, j);
                index += 1;
            }
        }
        // Neighbouring pixel coordinates
        // (-1,  1) - (0,  1) - (1,  1)
        // (-1,  0) - (0,  0) - (1,  0)
        // (-1, -1) - (0, -1) - (1, -1)

        // Count the number of neighbouring pixels that are alive
        // Do not count the current pixel to avoid self reference and messed up results
        for (int i = 0; i <= 9; i++) {
            if (i != 4) {
                if (neighbours[i] > whitePoint) {
                    neighboursCount += 1;
                }
            }
        }

        // Output based on the rules of the game
        dst() = 0.0f;

        // Current is alive
        if (neighbours[4] > whitePoint) {
            if (neighboursCount > 3) {
                // Any live cell with more than three live neighbours dies, as if by overpopulation.
                dst() = 0.0f;
            } else if (neighboursCount >= 2) {
                // Any live cell with two or three live neighbours lives on to the next generation.
                dst() = 1.0f;
            } else if (neighboursCount < 2) {
                // Any live cell with fewer than two live neighbours dies, as if by underpopulation.
                dst() = 0.0f;
            }
        // Current is dead
        } else {
            if (neighboursCount == 3) {
                // Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
                dst() = 1.0f;
            }
        }
    }
};

Have fun with it !

Normalize and Blend expressions

Another personal reminder note so that I can actually remember how to do this without having to spend a few dozen minutes googling it…

Normalizing values between 2 bounds

Normalize between 0 and 1

$${x' = \frac{x - \min{x}}{\max{x} - \min{x}}}$$

Normalize between -1 and 1

$${x'' = 2\frac{x - \min{x}}{\max{x} - \min{x}}-1}$$

Normalise between a and b

$${x''' = (b-a)\frac{x - \min{x}}{\max{x} - \min{x}} + a}$$

Nuke node

NoOp { name NormaliseValues selected true xpos -5265 ypos 718 addUserKnob {20 User} addUserKnob {7 val} val 1 addUserKnob {7 minval} minval 0 addUserKnob {7 maxval} maxval 10 addUserKnob {26 "" +STARTLINE} addUserKnob {7 upperbound} upperbound 1 addUserKnob {7 lowerbound} addUserKnob {26 "" +STARTLINE} addUserKnob {7 result} result {{"(upperbound-lowerbound) * ((val - minval) / (maxval - minval)) + lowerbound"}} }

Blend 2 values

With w : [0;1] $$x=Aw + B(1-w)$$

Nuke node

NoOp { name BlendValues selected true xpos -5080 ypos 784 addUserKnob {20 User} addUserKnob {7 val1 R -10 10} addUserKnob {7 val2 R -10 10} val2 1 addUserKnob {7 blender} blender 1 addUserKnob {26 "" +STARTLINE} addUserKnob {7 result R -10 10} result {{"val2 * blender + val1 * (1-blender)"}} }

Extend Format to Bounding box, restore original Format.

This more a personal reminder than anything else but I thought I might as well store it here instead of my notes.

This is intended to ease the use of notes and gizmos that handle out of format pixels poorly. It extends the format to the whole boundingbox and then restores it to its original size, allowing you to do whatever needs doing in the middle.

Read More

Edit a Spherical Map in Nuke

I was recently talking to a colleague interested in VR about 360 spherical maps. He was telling me that editing the maps in nuke wasn't as easy as it is in After Effect. So I made something !

The idea is to take an equirectangular map, remap it to the six square faces of a cube, make the edit and then re-render the result as an equirectangular map.

The trick is to link the SphericalTransform and the related card values so that if SphericalTransform2’s ry is 90, Card2’s rotate y is also 90, 180 on the third, and so on. For the top and bottom faces the rotation is on x.

In the file bellow, I’ve taken the liberty to set the SphericalTransform format to 1/4 of the input map’s width. It' seemed like a good idea seeing how you need 4 faces to go from 1 edge to the other. I didn’t spent that much time on it so the formats are not dynamically set and you’ll have to adjust them to your needs.

You can download the comp file here.

I’ve created a clean copy/paste file without Nuke’s version line so with should be pastable anywhere.

 

Here is a quick edit of the bottom half of the spherical map, made relatively easily as I was working on a non deformed image.

As we can see on the difference map, we unfortunately have some light difference even where I didn’t made any edits. Those are due to the SphericalTransform filtering and the render. I found that Impulse results in the less difference.

Python - sRGB to Linear / Linear to sRGB

A quick note to just so I can keep track of the functions.

def srgb2lin(s):
    if s <= 0.0404482362771082:
        lin = s / 12.92
    else:
        lin = pow(((s + 0.055) / 1.055), 2.4)
    return lin


def lin2srgb(lin):
    if lin > 0.0031308:
        s = 1.055 * (pow(lin, (1.0 / 2.4))) - 0.055
    else:
        s = 12.92 * lin
    return s

Note that I picked up these functions somewhere on stackoverflow, I didn’t come up this any of it…

Sampling deep values

A quick note about sampling deep values in python in nuke.

Today, I was faced with a question that a quick google search could not answer so I thought I'd made a post about it.

How do you sample the values a the deep channel in python?

nuke.sample(node, channel, x, y) doesn't work on deep.

Nuke's help behind what it is, accessing this page took a bit longer than it should have: https://learn.foundry.com/nuke/developers/140/pythonreference/_autosummary/nuke.Node.html#nuke.Node.deepSample

And so, here's how you do it :

node.deepSample(c, x, y, n)

  • c -> deep channel (deep.front, deep.back...) - string

  • x -> position to sample on the x axis - integer

  • y -> position to sample on the y axis - integer

  • n -> index of the sample - integer, -1 for the frontmost