CLUT formats, esp. RGB LUT

I know there are different kind of CLUT formats,
all dealing the 3D to 2D transformation in their respective way
(the 2 most-spreaded are offered in G’MIC’s CLUT functions: .cube and HaldCLUT).

But I haven’t found any information about other formats anywhere.

The .cube format obviously is ASCII and by that kind of unusable if larger.
The HaldCLUT is descibed at it’s author’s site having many advantages.

XnViewMP contains several cluts, and also 2 identity cluts
in HCLUT, and also in “RGB LUT” format:


Due to the positioning of the entries the “RGB LUT” is
more than 3 times smaller than the HCLUT,
and can be zip compressed by factor 10, instead of 2 for HCLUT.

As very detailed/ large CLUTs are a problem, the drastic effect
of the order of the entries should be relevant.

My questions:
Are the any information about CLUT formats?
Have these “RGB LUTs” as much advantages as HCLUTs?

  • In G’MIC, a CLUT is stored in memory as a 3-channels 3D volumetric image, which is an image structure natively supported.
  • Converting a CLUT into an HaldCLUT is simple, as an HaldCLUT is just a CLUT where values have been “unrolled” from 3D to 2D. So as long as N = sqrt(w*h*d) is an integer, you can do resize $N,$N,1,3,-1 to convert a CLUT into an HaldCLUT (and save it as a .png file then).
  • G’MIC additionnally has commands input_cube and output_cube to load/save CLUT as .cube files.

And for the moment, that’s all.
But, it is also quite easy to create a command that transforms a CLUT into your “RGB LUT” representation you mention, e.g.:

$ gmic clut summer,64 split z append_tiles , o RGBLUT.png


This will generate a square image, as long as N = sqrt(d) is an integer. Here, for a 64\times 64\times 64 CLUT, you get a 512\times 512 = (64\times 8)\times(64\times 8) RGB LUT.

And to convert back a RGB LUT into a regular 3D CLUT is easy as well:

$ gmic RGBLUT.png split yx,8 append z 

So, basically, once you know that a CLUT in G’MIC is indeed a 3D volumetric color image, you can manage/reorder its geometry as much as you want, using the regular G’MIC operators (e.g. split, append or resize) to do your 3D \leftrightarrow 2D conversion as you wish.


Thank you very much for your detailed information :slight_smile:

I’ve added a new option in the CLUT from After - Before Layers filter, so you can choose now to output a CLUT in the “RGB LUT” format (not sure if that’s the correct name or not).

Code change:

1 Like

Let me hug you! :slight_smile:

I was thinking in that direction too…

Actually I know the main issue with large CLUTs is memory
(that’s why you do that multigrid).
But if there is no other disadvantage (access time?) there is no reason
not to store the CLUT in an way it minimizes storage.
(I don’t know why png - saving lossless at least - is able to produce a
much smaller stream with this “RGB ordering”, and zip even more…).

About that name “RGB CLUT”…
That’s why I asked about all the formats for CLUTs.
I haven’t found any information about that…
(it’s just part of XnViewMP as an example identity CLUT).

Again: Thank you!

Actually, the multi-grid scheme is an essential part of the dense CLUT reconstruction algorithm from a set of keypoints. As I’m using diffusion PDE’s for that task, it would take forever to reconstruct the missing CLUT data without a multi-scale scheme.

In G’MIC, we have tons of pre-defined CLUTs (+1100) stored in a compressed way (list of keypoints), and used in filters Colors / Color Presets and Colors / Simulate Film. With such a number of CLUTs, it’s really important to minimize the storage size for each CLUT. In G’MIC, the 1100 CLUTs are stored in a 4Mb file, while it would take hundreds of Mb otherwise.

Let’s keep this name for the moment, and if someone find a more appropriate name for this format, we’ll change this. I was not aware there were software dealing with those.
I understand why they can be better compressed in PNG though
(PNG defines different ways of ‘predicting’ the next image pixel when compressing an image, to minimize the number of data to store, and it tends to work better for smooth images, which is obviously the case with the “RGB LUT” format, compared to the HaldCLUT one).

For fun, I’ve generated a 4096x4096 image, that contains all existing 8bits/channels RGB colors (and each only once), so: 256x256x256 = 4096x4096 = 16777216 colors.


Still, the image looks like something! (but it is almost a 50Mb .png file, so cannot be attached here).

Looks good ?

Question: How did I generate this image ? :smiley:

Spreading to full colorspace…
I can’t imagine this can be achieved by a LUT.
And being a freshman at G’MIC… I’m out. :wink:

Here’s the code I wrote for generating that image.
I’ve added comments to make it as self-containing as possible.

foo :
  sp colorful,4096  # Reference image to reproduce
  256,256,256,3,[x,y,z] r. 4096,4096,1,3,-1 # Canvas image (contains of all RGB colors)

  +rgb2ycbcr f[-2,-1] "[ 5*i0,i1,i2 ]" # Colorspace used for color comparisons, for Reference and Canvas
  w[1] 800,800

  # Re-order noisy pixels of Image to recreate reference image.
    const i_img = 1;     # Index of canvas image
    const i_ref_m = 2;   # Index of reference image (in transformed colorspace)
    const i_img_m = 3;   # Index of canvas image (in transformed colorspace)

    const w = w#i_img;
    const h = h#i_img;
    p_loss = inf;

    repeat (inf,it,
      x0 = int(u(w))%w; y0 = int(u(h))%h; ref0 = I(#i_ref_m,x0,y0,0); img0 = I(#i_img_m,x0,y0,0);
      x1 = int(u(w))%w; y1 = int(u(h))%h; ref1 = I(#i_ref_m,x1,y1,0); img1 = I(#i_img_m,x1,y1,0);
      diff = norm(ref0 - img0) + norm(ref1 - img1);  # Error if no swap applied
      ndiff = norm(ref0 - img1) + norm(ref1 - img0); # Error if swap applied
      ndiff<diff?( # Smaller error -> does the swap
      !t && !(it%10000000)?( # From time to time, display loss and canvas.
        loss = norm(crop(#i_ref_m)-=crop(#i_img_m));
          p_loss = loss;
        run('w[',i_img,'] cont={*}'); # Stop the algorithm when the display window is closed!
  o res.png # Save resulting canvas

Pas mal pour une éléphante défunte!

46MB is not so bad.
Still ordering the colors differently a png may need to at least 58GB.
But in this thread we like the ordering, that will take only 1/1000 of that

(In lossless-jxl it*s 1/10,000)

Question: How to achieve the maximum “scramble”?
(Mind you - I don’t have the answer :wink: )

If staff is OT - I dare to be too :wink:

1100, stored in a multi-grid manner…?

Being a freshman to G’MIC I didn’t know that.
So the “RGB LUT” format might be of only restricted use.

Still, some anotations from my side maybe:

The storage improvement of it doesn’t only hold for the identity CLUT,
as most CLUTs (“film simulation”) are just slight variations of it.
We can check by using the “RGB LUT” from above by taking it as an HaldLUT
and apply it on Lena:


This belongs mere to the “fun” thread, but to a “typical” CLUT usage :slight_smile:
However: For that kind of alteration the HaldLUT would be as “regular”
as the “RGB LUT” from above.

The LUT format seems to be “OBS Studio compatible LUT”.
Not 100% sure though…
Maybe the same as Photoshop compatible 3D LUT?

And now I’m not so sure anymore if it’s a good idea to support that
LUT format.
Only if it’s that 3D LUT (ending *.3DL) there wouldn’t be
the danger of confusion with HaldLUTs.

As creating this kind of LUT is just a re-ordering of the CLUT values, we cannot really say that we “support this CLUT format”.
But you are right, there are currently no ways to detect if a .png image represents a HaldCLUT or a “RGB LUT”. But I would say, the user has to know what he is manipulating.

If we (anybody out there?) could identify it as of “OBS Studio”/ Photoshop compatible type,
then using the ending *.3dl would make things clear.
XnView calls this format “3D LUT”, being the one used by OpenGL shader.
So probably it is that format, to use with appropriate name extension…(?).

No, it probably is not - see here and here - 3DL seems to be different.
It seems to be an OBS Studio compatible format.
Maybe… could be wrong. :face_vomiting:

To pass a little test I did on files sizes:

The full-coloured level 16 identity CLUT takes

6100 kB as a HALD as png
4700 kB as a HALD as jxl (lossless) (won't do any better anywhere)
  59 kB as an OBS as png
  28 kB as an OBS as jxl (lossless)

As the format’s official name still is unknown, I take it as “OBS”.
And as it’s so nice… I include it (Better I choose OBS format for that :slight_smile: )

Rearranging pixel positions might be a approach to achieve best compressibility of arbitrary images? :slight_smile: