12

I'm new to image processing.

I want to use JavaScript to apply effects to images using LUTs (LookUp Tables) or the corresponding lookup PNGs, something like this:

enter image description here

I have googled a lot and couldn't find an article or any resource which will describe the exact process of pixel transformation using LUTs.

I have found a nice article here, which describes the 1D and 3D LUTs and the differences between them. But its still is not fully clear for me.

I want something like this, which is done for iOS.

P.S. Please do not post links/answers regarding image filtering libs, which are using convolution matrices for effects or for filters.

Update:

Finally! I have got the answer thanks to @abbath. I've created a gist in GitHub, which you can find here.

Karlen Kishmiryan
  • 7,322
  • 5
  • 35
  • 45

2 Answers2

17

I am also new to the topic, but I hope it helps what I've found out so far. Your image (the LUT) is a representation of a 3D array, imagine a 64x64x64 cube. This representation is in 2D, one plane is a 64x64 square. You need 64 piece of this plane, which is why the png has 8x8 squares in it. If you look carefully, the upperleft square has red (R of RGB) on the X axis and green (G) on the Y axis. The blue (B) is the Z axis (I imagine it upwards), which can't be representated in 2D, so it comes on the next 64x64 squares. On the last (lower right) square you can see that it is mostly blue, where the red and green coordinates are 0.

So the next question is how to project an image with this LUT. I have tried it out in Java, in my opinion you will have to lose some information because 64x64x64 is far less than 256x256x256, in this case, you have to divide by 4 each pixel color value.

The steps:

  1. Iterate through the pixels of your original image. In one iteration you will have one pixel of the original image. Get the R, G, B values of that pixel.
  2. Divide the values by four, so you will have a value less than 64.
  3. Get the corresponding pixel from your LUT, as if it was a 64x64x64 cube. So if RGB=(255,0,255) was on the original pixel, than divide it to get (63,0,63), then check the B value, to get the corresponding square on the png, which will be the lower right one (in the case of b=63), and get the r,g=(63,0) pixel of it, which purpleish on your png image.

I checked now on android, with java code, its fast enough in my opinion. Below is the code I've written, I hope it's enough to port it to javascript. (I hope the comments are enough to understand)

public Bitmap applyEffectToBitmap(Bitmap src, Bitmap lutbitmap) {
    //android specific code, storing the LUT image's pixels in an int array
    int lutWidth = lutBitmap.getWidth();
    int lutColors[] = new int[lutWidth * lutBitmap.getHeight()];
    lutBitmap.getPixels(lutColors, 0, lutWidth, 0, 0, lutWidth, lutBitmap.getHeight());

    //android specific code, storing the source image's pixels in an int array
    int srcWidth = src.getWidth();
    int srcHeight = src.getHeight();
    int[] pix = new int[srcWidth * srcHeight];

    src.getPixels(pix, 0, srcWidth, 0, 0, srcWidth, srcHeight);

    int R, G, B;
    //now we can iterate through the pixels of the source image
    for (int y = 0; y < srcHeight; y++){
        for (int x = 0; x < srcWidth; x++) {
            //index: because the pix[] is one dimensional
            int index = y * srcWidth+ x;
            //pix[index] returns a color, we need it's r g b values, thats why the shift operator is used
            int r = ((pix[index] >> 16) & 0xff) / 4;
            int g = ((pix[index] >> 8) & 0xff) / 4;
            int b = (pix[index] & 0xff) / 4;

            //the magic happens here: the 3rd step above: blue pixel describes the 
            //column and row of which square you'll need the pixel from
            //then simply add the r and green value to get the coordinates 
            int lutX = (b % 8) * 64 + r;
            int lutY = (b / 8) * 64 + g;
            int lutIndex = lutY * lutWidth + lutX;


            //same pixel getting as above, but now from the lutColors int array
            R = ((lutColors[lutIndex] >> 16) & 0xff);
            G = ((lutColors[lutIndex] >> 8) & 0xff);
            B = ((lutColors[lutIndex]) & 0xff);


            //overwrite pix array with the filtered values, alpha is 256 in my case, but you can use the original alpha
            pix[index] = 0xff000000 | (R << 16) | (G << 8) | B;
        }
    }
    //at the end of operations pix[] has the transformed pixels sou can set them to your new bitmap
    //some android specific code is here for creating bitmaps
    Bitmap result = Bitmap.createBitmap(srcWidth, srcHeight, src.getConfig());
    result.setPixels(pix, 0, srcWidth, 0, 0, srcWidth, srcHeight);
    return result ;
}

Now I've successfully implemented in javascript too, check for using the Math.floor() functions:

for (var i=0;i<imgData.data.length;i+=4){
    var r=Math.floor(imgData.data[i]/4);
    var g=Math.floor(imgData.data[i+1]/4);
    var b=Math.floor(imgData.data[i+2]/4);    
    var a=255;


    var lutX = (b % 8) * 64 + r;
    var lutY = Math.floor(b / 8) * 64 + g;
    var lutIndex = (lutY * lutWidth + lutX)*4;

    var Rr =  filterData.data[lutIndex];
    var Gg =  filterData.data[lutIndex+1];    
    var Bb =  filterData.data[lutIndex+2];

    imgData.data[i] = Rr;
    imgData.data[i+1] = Gg;
    imgData.data[i+2] = Bb;
    imgData.data[i+3] = 255;

}

Check it here: http://jsfiddle.net/gxu080ve/1/ The lut image is in byte code, sorry for that.

This code only applies for 64x64x64 3DLUT images. The parameters vary if your LUT has other dimensions; the / 4, * 64, % 8, / 8 must be changed for other dimensions, but in this question's scope the LUT is 64x64x64.

abbath
  • 2,444
  • 4
  • 28
  • 40
  • Thank you for your answer. I have some questions: What do you mean when you say `Get the corresponding pixel from your LUT`? What if my image is bigger than the LUT? How to `check the B value`? And after the steps, how I will change the current pixel of my image using the result `(63, 0)`? – Karlen Kishmiryan Aug 12 '14 at 06:21
  • Your image can have any size, you have to iterate through every single pixel of your image. So if you have a fullHD image, you have to iterate through 1080*1920 pixel. In one iteration you will have one pixel value. Get that pixel's r, g and b values. They will be less than 256, but you will need less than 64, so divide all three values by 4. So now you have three values for one pixel, each under 64. Then you go to point 3., and get the corresponding pixel from your LUT image as I described above. I update my answer with some java code first, i hope it helps. – abbath Aug 12 '14 at 20:19
  • I've updated my answer with some java code (android), and rewritten the steps to be a bit more understandable, i hope it helps you in javascript. – abbath Aug 12 '14 at 20:49
  • Thank you @abbath. I will try to port it to JavaScript and will let you know. – Karlen Kishmiryan Aug 13 '14 at 05:46
  • Well. I have implemented the same in JavaScript and tried it. The result is obviously wrong. Did you test your code and did it work as expected? You can see the result here: Before: http://imgur.com/0vcCTK9 and After: http://imgur.com/XLcE9jd. I have used the above LUT png. And also you can check the JavaScript code here: http://pastebin.com/b4Q2W5fM. Maybe I did something wrong. – Karlen Kishmiryan Aug 13 '14 at 07:39
  • I have tested, and worked with your image as well, and the output wasn't the one you have uploaded. Are you sure everything was well implemented to javascript? Could you post your javascript code? Maybe I can see the difference. – abbath Aug 13 '14 at 07:50
  • In javascript try to use something else at getting the red, green, blue values of a pixel. I think there is a method there, sth like red=imgData.data[0]; green=imgData.data[1]; blue=imgData.data[2]; alpha=imgData.data[3]; (http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_canvas_getimagedata_firstpx), maybe this helps too: http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_canvas_getimagedata2 – abbath Aug 13 '14 at 08:07
  • okay, i have implemented on javascript as well. i had to debug a while, but the main bug was in my code, that javascript dividing produces floats, so you have to use Math.floor() everytime where a simple divide was enough java. – abbath Aug 13 '14 at 12:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59275/discussion-between-karlen-kishmiryan-and-abbath). – Karlen Kishmiryan Aug 13 '14 at 12:44
  • thank for your answer. i have same requirement but i have 16*16 LUTs. ex : http://imgur.com/DAWiZFE. how can i change the values with respective ? – user512 Sep 30 '15 at 13:28
  • You'll need to change the dimensions; var lutX = (b % 4) * 16 + r; var lutY = Math.floor(b / 4) * 16 + g; – abbath Oct 09 '15 at 14:18
  • thanks @abbath i tried this but getting ArrayIndexOutOfBoundException at this line R = ((lutColors[lutIndex] >> 16) & 0xff); here length = 4096 and index 32957. i am trying in java please give suggestions/updates in java if u don't mind.. – user512 Oct 12 '15 at 06:28
  • Sure, this afternoon I'll check my code for the 16x16, and post the solution at your question. – abbath Oct 12 '15 at 07:41
  • Sorry didn't have time yet, but will look at soon as I can! – abbath Oct 14 '15 at 08:54
  • I've updated my answer at the other page, as your LUT image is a bit of a special case (uses red-green-blue dimensions in an other order) – abbath Oct 16 '15 at 06:54
  • Thanks on the tons of information in this topic. Does anyone know how to take an intensity of the LUT into account in this JS implementation? I want to be able to choose the intensity with which the LUT is applied from 0 to 100 percent. – jpietzsch Jul 14 '16 at 08:37
  • Well, did you think about changing the alpha of the LUT image before applying it? Ah just realized that won't work. Maybe you should create a LUT with original rgb, and then subtract the color from the original LUT. So you'd get a color in between (doing this for all three dimensions) – abbath Jul 14 '16 at 08:40
1

I'm trying to do something similar in c++. 3D LUTs have 3 columns of values usually between 0 and 1. The columns are r g b respectively and they represent a mapping of values. If you open up one of these files of size nxnxn, you would read each value into it's respective R array or G array or B array. Then to recreate the mapping internally, you would iterate and fill up rgb maps with double/float keys and values

for every b
    for every g
        for every r
            // r g and b are indices
            index = b * n^2 + g * n + r
            Rmap.push((r + 1)/n, Rarray[index])
            // Same for g and b
            Gmap.push((g + 1)/n, Garray[index]
            Bmap.push((b + 1)/n, Barray[index]

Once you have your mappings, you can apply the lookup to every pixel in an image. Say the image had Max color 255, so you take the (r,g,b)/255 of each pixel and apply the lookup then convert back by *255. As far as I know, the convention to calculate values you can't lookup, is to perform trilinear interpolation. I haven't had too much success yet, but this is my two cents.

Rain Zhao
  • 31
  • 7