Skip to content

Combining 3D LUTs with each other in javascript

I am working with 3D color LUTs (color lookup tables) in javascript and I was wondering is there a way to combine two or more 3D LUTs to export them in one single file. Let me explain:

I get .cube (3D color lookup file). I parse it and store parsed color values into an array and apply it to existing image. After that I apply new 3D LUT onto existing (changed) image, and I apply new LUT once more. So now I have original image with 3 different 3D LUTs applied onto each other.

Now, I can successfully export every 3D LUT in separate file and download it, but I don’t know how to combine them into a single .cube file. I believe I need some algorithm for “combining” different LUTs into one file?

This is example how photoshop does it:

LUT1:

0.024536 0.000183 0.000244
0.049103 0.000336 0.000458

LUT2:

0.041260 0.021149 0.009125
0.067230 0.023804 0.009125

COMBINED LUT (result):

0.035034 0.020660 0.009308
0.054810 0.022766 0.009430

Thank you!

Answer

After some research I have found a solution. Essentially, I needed to pipe the output of the first LUT into the input of the second LUT. This requires to have an interpolation function in-program (not just a 3D LUT shader).

Process goes something like this:

  1. Create a new identity LUT of a chosen size (default LUT with no changes)
  2. Iterate through every point of that 3D LUT and pipe the identity color of each point through the first LUT’s ColorFromColor, and then through the second LUT’s ColorFromColor. Store the final value in the new LUT.

Function looks something like this:

function mapColorsFast(out, image, clut, clutMix){
    let od = out.data,
        id = image.data,
        w = out.width,
        h = out.height,
        cd = clut.data,
        cl = Math.floor(Math.pow(clut.width, 1/3)+0.001),
        cs = cl*cl,
        cs1 = cs-1;

    var x0 = 1 - clutMix, x1 = clutMix;
    for(var y = 0; y < h; y++) {
        for(var x = 0; x < w; x++) {
            let i = (y*w+x)*4,
                r = id[i]/255*cs1,
                g = id[i+1]/255*cs1,
                b = id[i+2]/255*cs1,
                a = id[i+3]/255,
                ci = (dither(b)*cs*cs+dither(g)*cs+dither(r))*4;

            od[i] = id[i]*x0 + x1*cd[ci];
            od[i+1] = id[i+1]*x0 + x1*cd[ci+1];
            od[i+2] = id[i+2]*x0 + x1*cd[ci+2];
            od[i+3] = a*255;
        }
    }
}

Function accepts few arguments: out – buffer into which the result is written image – a buffer containing the image in imageData format clut – color LUT that we’re applying to the image clutMix – affecting the strength of the effect (0-1)

In this case, we needed to create identity LUT, save it as image and pass it as image argument to the function, and then apply new LUT onto it. Then we pipe the result again into same function and apply new LUT onto it. We do that for every LUT we want to mix with other LUTs.

I found this function on https://github.com/jwagner/analog-film-emulator/blob/master/src/image-processing.js – Javascript Film Emulation project.

There’s a lot of interesting material to find if you’re working with canvas 2d image processing, and there’s also a working example included: https://29a.ch/film-emulator/

Hope it will help someone in the future!