View Full Version : gamut conversions through Avisynth ?
Pages :
1
2
3
4
5
6
[
7]
8
9
10
11
12
13
14
15
madshi
16th February 2009, 20:46
What is the point of the union and
char parametersData[1];
line?
If you read in (or memory map in) the whole file at once, you can access parametersData directly through the structure this way. It's not necessary, but I think it's useful.
Having the union in the structure, however, only serves the purpose to let everyone know that the lut data is supposed to come "somewhere" after the parametersData, with nothing else (of importance) in between. It's just meant as a source code level explanation of how the file is structured. I wouldn't mind having this removed from the structure, but personally I like it in there just as an explanation.
Why not just make it
I could live with that.
fseek(f,h3d.parametersSize+h3d.lutFileOffset,SEEK_CUR);
I intended "lutFileOffset" to work from the beginning of the file ("fseek(f,h3d.lutFileOffset,SEEK_BEGIN)"). That's more comfortable when working with a memory map.
yesgrey
17th February 2009, 01:04
Why not just make it...
For me it's ok too.
Where the lutOffset starts is indiferent to me. Just let me know what you prefer so I could write the correct value.
The only change I suggest is always filling the lutCompressed Size. If it's compressed, the size is different from the lutUncompressedSize. If it's uncompressed, both have the same value.
yesgrey
17th February 2009, 01:14
I've vote for (3) ...
In order to do that check, the application would create the parameter settings string in RAM and then compare that to what is stored in the 3dlut file's "parametersData". If it matches, the file is ok. If it doesn't match, the file needs to be recreated.
For that, the 1) is the better option, not the 2) nor the 3).
I already coded the 1). Then I realized that could be interesting allowing some comments to help a user identifying a 3DLUT if he opens it in a text editor, but for that, maybe it's better adding a new field to the header to contain a description of the 3DLUT file (for example a "char fileDescription[64]"), and keep in the parametersData only the parameters and respective settings. The 3) could contain a lot of trash, that's why I prefer the option 1), which retains only the essential.
madshi
17th February 2009, 09:58
For that, the 1) is the better option, not the 2) nor the 3).
I disagree.
Look, if a program creates its 3dlut files automatically by calling cr3dlut then this program will have a function like "CreateParameterSettingsString" which it will then feed to cr3dlut. Now if this program wants to check whether a 3dlut file on harddisk has the expected format, it will call "CreateParametersSettingsString" again and compare the result to the "parametersData" string in the 3dlut file. If the two strings are bit perfectly identical, the 3dlut file is "good", otherwise it's "bad". No need to parse the actual "parametersData" string this way. If you do (1) then the program is forced to write complicated code to parse "parametersData" and compare every single parameter to make sure that the 3dlut data was created with the correct parameters...
Why not simply storing the full input file as it is into parametersData? That should be the most simple thing to do. It would also allow the user to add comments to the file. E.g. the user could write "this 3dlut is meant to be used only for the movie XYZ, because it has the colors encoded in the wrong way". If you stored the settings file unchanged and if you then open the 3dlut file in a (hex) editor, you could still see that comment.
Then I realized that could be interesting allowing some comments to help a user identifying a 3DLUT if he opens it in a text editor, but for that, maybe it's better adding a new field to the header to contain a description of the 3DLUT file (for example a "char fileDescription[64]"), and keep in the parametersData only the parameters and respective settings.
I don't think we need a description in the file. IMHO the readme shipping with cr3dlut should be good enough.
yesgrey
17th February 2009, 11:17
If you do (1) then the program is forced to write complicated code to parse "parametersData" and compare every single parameter to make sure that the 3dlut data was created with the correct parameters...
The code is already created in cr3dlut code, but ok, I will do 3). If that application appears probably no one will be messing with the input files anymore, so no problem of breaking them.;)
It would also allow the user to add comments to the file. E.g. the user could write "this 3dlut is meant to be used only for the movie XYZ, because it has the colors encoded in the wrong way". If you stored the settings file unchanged and if you then open the 3dlut file in a (hex) editor, you could still see that comment.
I don't think we need a description in the file. IMHO the readme shipping with cr3dlut should be good enough.
The description I was talking about was like the example you gave above "this 3dlut is meant to be used only for the movie XYZ, because it has the colors encoded in the wrong way". So, I will let the description being put in the input file as a comment that will be kept in the parametersData array.
madshi
17th February 2009, 11:28
Thanks!
So what is missing to finally complete the spec? "lutFileOffset"? We have two different interpretations of what i means.
tritical suggests to interpret it as "number of bytes between end of parametersData and lut data". The code then looks like this:
// sequential file reading:
fseek(f,h3d.parametersSize+h3d.lutFileOffset,SEEK_CUR);
// memory mapping:
lutPtr = (PVOID) ((ULONG_PTR) lutMemoryMap + sizeof(H3DLUT) + lutMemoryMap->parametersSize + lutMemoryMap->lutFileOffset);
I suggest to interpret it as "number of bytes between beginning of file and lut data". Code:
// sequential file reading:
fseek(f,h3d.lutFileOffset,SEEK_BEGIN);
// memory mapping:
lutPtr = (PVOID) ((ULONG_PTR) lutMemoryMap + lutMemoryMap->lutFileOffset);
:)
yesgrey
17th February 2009, 12:05
So what is missing to finally complete the spec? "lutFileOffset"?
Yes.
tritical's interpretation is more logical. Since lutOffset appears right after parametersSize, it's more logical thinking that it's just the space between parametersData end and lutData begin.
But your interpretation is more programmer friendly...;)
For me is indifferent, I'm only creating and writing the file.:)
yesgrey
17th February 2009, 12:09
if a program creates its 3dlut files automatically by calling cr3dlut then this program will...
Are you considering writing that program, or are you only conjecturing...;)
madshi
17th February 2009, 12:15
Are you considering writing that program, or are you only conjecturing...;)
What? Huh? Have you asked me something? I've some strange noise in my ears, so I couldn't hear you. <plugging fingers into my ears> "La la la la la"...
cyberbeing
17th February 2009, 15:54
I have somehow confused myself when I realized cr3dlut has sRGB as the display gamma for the _PC example input files. I found this odd considering almost no one would have their display at a gamma of 2.4.
For a CRT monitor calibrated to a gamma of 2.2 with something like ProfileMaker, EyeOne Match, or ColorEyes, which output/display gamma should I be using?
sRGB (2.4 gamma?):
12.92,0.055,1.0/2.4,0.0031308,0.04045
sRGB modified with a gamma of 2.2 instead of 2.4:
12.92,0.055,1.0/2.2,0.0031308,0.04045
BT.709/BT.601:
4.5,0.099,1.0/(1.0/0.45),0.018,0.081
General 2.2:
1.0,0.0,1.0/2.2,0.0,0.0
or
1.0,0.0,0.45,0.0,0.0
Is there any test I could do to determine which of the above is the correct one to use?
@yesgrey3
Does cr3dlut only accept decimal values or do fractions also work? What is the maximum number of decimal places it will accept?
Also is Display_gamma 9 lv av pv thresh OR Display_gamma 9 lv av pv thresh1 thresh2 correct?
I ask since you have the example as Display_gamma 9 1.0 0.0 0.45 0.0 without a second thresh value in the readme.
yesgrey
17th February 2009, 17:28
"La la la la la"...
A new project always makes us sing...:D
I have somehow confused myself when I realized cr3dlut has sRGB as the display gamma for the _PC example input files. I found this odd considering almost no one would have their display at a gamma of 2.4.
When you look to a transfer function you cannot consider only the gamma value (2.4 in sRGB). In reality, the sRGB transfer function, due to the small linear part at the black levels, has a curvature of almost a pure 1/2.2 power curve. So yes, if your monitor was calibrated to a gamma of 2.2, you could use the sRGB transfer function, that was the reason I have selected it.
You can also use the pure power curve with a 2.2 gamma. Try it, and see what you prefer. There is not an exact value for the gamma; it will depend always of the viewing lighting conditions...
I never liked very much watching stuff in my CRT monitor, always too dark. Since I started using it with rgb3dlut and output set to sRGB, I am liking what I see...:)
Does cr3dlut only accept decimal values or do fractions also work? What is the maximum number of decimal places it will accept?
Also is Display_gamma 9 lv av pv thresh
Only decimals, the number you want, but since it read it as a double, you gain nothing in putting more than 14 decimal places.;)
The gamma values are the specifyed in the ReadMe. I have not followed tritical's usage of thresh1 and thresh2, because thresh2 can be calculated from thresh1. So, I only need one. My thresh is the same as tritical's thresh1.
yesgrey
17th February 2009, 17:35
About the 'lutCompressedSize' and 'lutUncompressedSize', I think we should use them like this:
lutCompressedSize - size of the lutData array inside the 3DLUT file, whether compressed or not.
lutUncompressedSize - size of the lutData array when loaded in memory for usage, outside of the 3DLUT file. It will always be uncompressed.
Do you agree with this, or should we specify the 'lutCompressedSize' only when compressed, and set it to zero when uncompressed?
madshi
17th February 2009, 17:37
A new project always makes us sing...:D
What new project? :p
About the 'lutCompressedSize' and 'lutUncompressedSize', I think we should use them like this:
lutCompressedSize - size of the lutData array inside the 3DLUT file, whether compressed or not.
lutUncompressedSize - size of the lutData array when loaded in memory for usage, outside of the 3DLUT file. It will always be uncompressed.
Do you agree with this
Yes, absolutely.
yesgrey
17th February 2009, 17:52
What new project?
My cr3dlut, of course.:)
"La la la la la!..."
tritical
18th February 2009, 00:21
The offset being from the beginning or after parametersData doesn't matter to me. Using the compressed/uncompressed size for in the lut and in memory is fine also.
yesgrey
18th February 2009, 01:31
Here is another iteration of the file format. It's tritical's suggestion with a few comment lines for describing with a little more detail the lutData array:
// 3D LUT file specification (fileVersion=1):
enum {PAGE_SIZE = 16384};
struct H3DLUT
{
char signature[8]; // file signature; must be: '3DLUT'
int fileVersion; // file format version number
int inputBitDepth[3]; // input bit depth per component (Y,Cb,Cr or R,G,B)
int outputBitDepth; // output bit depth for all components (valid values are 8 and 16)
int inputColorEncoding; // input color encoding standard
int outputColorEncoding; // output color encoding standard
char programName[24]; // name of the program that created the file
long int programVersion; // version of the program that created the file
int parametersSize; // size in bytes of the array parametersData
int lutFileOffset; // number of bytes between the beginning of the file and array lutData
int lutCompressionMethod;// type of compression used if any (0 = none, ...)
int lutCompressedSize; // size in bytes of the array lutData inside the 3DLUT file, whether compressed or not
int lutUncompressedSize; // size in bytes of the array lutData when in memory for usage, outside of the 3DLUT file
int reserved; // reserved for future use
// This header is followed by the char array 'parametersData', of length 'parametersSize',
// and by the array 'lutData', of length 'lutCompressedSize'.
// The array 'lutData' starts 'lutFileOffset' bytes after the beginning of the file,
// and that pos must be on a 16384 byte boundary if 'lutData' is uncompressed.
};
/*
parametersData - char array with size parametersSize that contains an exact copy of the
input file with the parameters settings used for creating the 3DLUT file
lutData - array with size lutSizeUncompressed that contains the 3dlut output values.
The type used depends on the 'outputBitDepth' field:
- unsigned char, if outputBitDepth = 8
- unsigned short, if outputBitDepth = 16
The offset inside the array is calculated as:
offset = (cr<<(inputBitDepth[1]+inputBitDepth[0])+cb<<(inputBitDepth[0])+y)*3 (YCbCr input)
offset = ( r<<(inputBitDepth[1]+inputBitDepth[0])+ g<<(inputBitDepth[0])+b)*3 (RGB input)
The 'lutUncompressedSize' of the array is calculated as:
lutDim = 3*(2^inputBitDepth[0]*2^inputBitDepth[1]*2^inputBitDepth[2])
lutUncompressedSize = lutDim*1, if outputBitDepth = 8
lutUncompressedSize = lutDim*2, if outputBitDepth = 16
(This specification assumes: char = 1 byte; short = 2 byte; int = 4 byte; long int = 8 byte)
*/
I have changed 'programName' size to [24]. With [32] the struct alignment created an empty space of 4 bytes, due to the long int programVersion. Another way of solving this problem is keeping the [32] size and adding a "#pragma pack(4)" before the struct definition. I think the former is the best; [24] would be enough for a program's name...
Another thing to consider is the long int type. In some compilers, the long int is treated as an int, only 32bit (with mine, for example). I have to change it for long long to get a 64bit integer. Should I change it also in the specification above, or do your compilers work ok with it?
With the new meaning of 'lutCompressedSize' and 'lutUncompressedSize', maybe the names 'lutPackingMethod' 'lutPackedSize' and 'lutUnpackedSize' are more meaningfull...
cyberbeing
18th February 2009, 02:09
When you look to a transfer function you cannot consider only the gamma value (2.4 in sRGB). In reality, the sRGB transfer function, due to the small linear part at the black levels, has a curvature of almost a pure 1/2.2 power curve. So yes, if your monitor was calibrated to a gamma of 2.2, you could use the sRGB transfer function, that was the reason I have selected it.
You can also use the pure power curve with a 2.2 gamma. Try it, and see what you prefer. There is not an exact value for the gamma; it will depend always of the viewing lighting conditions...
I never liked very much watching stuff in my CRT monitor, always too dark. Since I started using it with rgb3dlut and output set to sRGB, I am liking what I see...:)
Aren't you just making things more inaccurate by skewing things from the source gamma? The difference between setting the display gamma to source gamma and setting the display gamma to 2.2 or sRGB is very extreme. Everything has much less contrast.
I never liked very much watching stuff in my CRT monitor, always too dark. Since I started using it with rgb3dlut and output set to sRGB, I am liking what I see...:)
This sounds like more of an issue with your CRT monitor then anything else. Lets forget about CRTs for a second, since you seem to think yours should be treated differently.
If you had a LCD/projector/Plasma calibrated to a gamma of 2.2 which setting for display gamma should you be using to match your calibrated gamma?
I would also still like to see this question answered:
When you calibrate your display to a gamma of 2.2, which gamma curve do apps like Profile Maker, Eye One Match, and Color Eyes calibrate it to? I've never seen an option which can be set in any of these apps, so it seems the question needs to be asked.
2.2 Power Curve?
sRGB Curve adapted to 2.2?
BT.601/BT.709 Curve?
Random 2.2 Curve that doesn't match anything standard?
Are their calibration apps which do let you set a specific type of gamma curve?
Is there any test which can be done to determine which type of gamma curve your calibrated display matches closest? Is there a way to generate your own lv av pv thresh values to match how your TV/Monitor displays gamma exactly?
The reason this came up is because using the sRGB setting my shadows were gray (looked identical to viewing PC levels without conversion) and the whole video was brighter with less contrast overall. The same goes for the 2.2 power curve.
My main concern is if I am actually doing harm rather then good by setting the display gamma any different from the source gamma.
Since the difference is so extreme, doing one or the other must be very wrong. Which one is it? Can it be proven?
leeperry
18th February 2009, 10:08
When you calibrate your display to a gamma of 2.2, which gamma curve do apps like Profile Maker, Eye One Match, and Color Eyes calibrate it to?
you can use Color.HCFR afterwards to spy on your ICC app.
I personally use ARGYLLCMS w/ 2.1 gamma on my CRT, then I always doublecheck with Color.HCFR
I've done numerous tests, it looks better on both my 2.1 CRT/2.2 DLP pj to use the 1.0/0.45 ddcc gamma curve(same as the PS script)
madshi
18th February 2009, 10:56
Here is another iteration of the file format.
Looks fine to me.
programName[24] is fine.
"long long" for programVersion would be fine with me. Another option would be "word programVersion[4]". Use whatever you prefer.
maybe the names 'lutPackingMethod' 'lutPackedSize' and 'lutUnpackedSize' are more meaningfull...
For programmers "packed" is usually used as in "#pragma pack". So I'd prefer "compressed" to avoid confusion...
One final thought: Now we have "lutFileOffset". Maybe we should also use "parametersFileOffset"? I know it's the same as "sizeof(H3DLUT)". But having an extra field "parametersFileOffset" would mean that we would be more flexible. E.g. we could add more fields to the header without breaking anything. Because of that we could also drop "reserved" from the header, because we could later readd as many new fields as we like, simply by adjusting parametersFileOffset and lutFileOffset accordingly...
IanB
18th February 2009, 14:16
I have changed 'programName' size to [24]. With [32] the struct alignment created an empty space of 4 bytes, due to the long int programVersion. Another way of solving this problem is keeping the [32] size and adding a "#pragma pack(4)" before the struct definition. I think the former is the best; [24] would be enough for a program's name...
Another thing to consider is the long int type. In some compilers, the long int is treated as an int, only 32bit (with mine, for example). I have to change it for long long to get a 64bit integer. Should I change it also in the specification above, or do your compilers work ok with it?
With the new meaning of 'lutCompressedSize' and 'lutUncompressedSize', maybe the names 'lutPackingMethod' 'lutPackedSize' and 'lutUnpackedSize' are more meaningfull...Seeing this is a file header, I would suggest you explicitly align all the elements independent of any compiler.You could just shuffle the order a little avoid the problem...
Hint :- Put big things early, put small things late ...
yesgrey
18th February 2009, 16:28
Here is another iteration of the 3DLUT specification:
/* 3D LUT file specification (fileVersion=1):
enum {PAGE_SIZE = 16384};
struct H3DLUT
{
char signature[8]; // file signature; must be: '3DLUT'
int fileVersion; // file format version number
char programName[32]; // name of the program that created the file
unsigned short programVersion[4]; // version of the program that created the file (a.b.c.d -> [3].[2].[1].[0])
int inputBitDepth[3]; // input bit depth per component (Y,Cb,Cr or R,G,B -> [0],[1],[2])
int outputBitDepth; // output bit depth for all components (valid values are 8 and 16)
int inputColorEncoding; // input color encoding standard
int outputColorEncoding; // output color encoding standard
int parametersFileOffset; // number of bytes between the beginning of the file and array parametersData
int parametersSize; // size in bytes of the array parametersData
int lutFileOffset; // number of bytes between the beginning of the file and array lutData
int lutCompressionMethod; // type of compression used if any (0 = none, ...)
int lutCompressedSize; // size in bytes of the array lutData inside the 3DLUT file, whether compressed or not
int lutUncompressedSize; // size in bytes of the array lutData when in memory for usage (outside of the 3DLUT file)
// This header is followed by the char array 'parametersData', of length 'parametersSize',
// and by the array 'lutData', of length 'lutCompressedSize'.
// The array 'parametersData' starts 'parametersFileOffset' bytes after the beginning of the file.
// The array 'lutData' starts 'lutFileOffset' bytes after the beginning of the file,
// and that pos must be on a 16384 byte boundary if 'lutData' is uncompressed.
};
parametersData - char array with size parametersSize that contains an exact copy of the
input file with the parameters settings used for creating the 3DLUT file
lutData - array with size lutSizeUncompressed that contains the 3dlut output values.
The type used depends on the 'outputBitDepth' field:
- unsigned char, if outputBitDepth = 8
- unsigned short, if outputBitDepth = 16
The offset inside the array is calculated as:
offset = (cr<<(inputBitDepth[1]+inputBitDepth[0])+cb<<(inputBitDepth[0])+y)*3 (YCbCr input)
offset = ( r<<(inputBitDepth[1]+inputBitDepth[0])+ g<<(inputBitDepth[0])+b)*3 (RGB input)
The 'lutUncompressedSize' of the array is calculated as:
lutDim = 3*(2^inputBitDepth[0]*2^inputBitDepth[1]*2^inputBitDepth[2])
lutUncompressedSize = lutDim*1, if outputBitDepth = 8
lutUncompressedSize = lutDim*2, if outputBitDepth = 16
(This specification assumes: char = 1 byte; short = 2 byte; int = 4 byte)
*/
I have moved up 'programName' and 'programVersion'. I think it's preferable having them before the file format variables instead of in the middle, as they were. I also have followed madshi suggestion and changed 'programVersion' from long int to unsined short [4]; this also solves the alignement problem, so I have returned to 'programName' size as [32].
Should we also shift positions of these?
From:
int inputBitDepth[3];
int outputBitDepth;
int inputColorEncoding;
int outputColorEncoding;
To:
int inputBitDepth[3];
int inputColorEncoding;
int outputBitDepth;
int outputColorEncoding;
It means output following the input...
You could just shuffle the order a little avoid the problem...
Hint :- Put big things early, put small things late ...
The problem is the 'fileVersion' variable, and I think the right place for it is right after the signature. We can consider that 'programName' and 'programVersion' will never change, and then put 'fileVersion' after them...
Any thoughts?...
madshi
18th February 2009, 16:48
Should we also shift positions of these?
From:
int inputBitDepth[3];
int outputBitDepth;
int inputColorEncoding;
int outputColorEncoding;
To:
int inputBitDepth[3];
int inputColorEncoding;
int outputBitDepth;
int outputColorEncoding;
It means output following the input...
Doesn't matter to me.
the problem is the 'fileVersion' variable, and I think the right place for it is right after the signature. We can consider that 'programName' and 'programVersion' will never change, and then put 'fileVersion' after them...
"programVersion" should change with every new cr3dlut build! Personally, I'd prefer keeping fileVersion directly after the signature because (just like you) I think that is logically the right place.
Can't we simply define the structure with "#pragma pack (1)" and be done with it? Or are there some C++ compilers which don't support byte packing?
Alternatively: Would shorting the signature to 4 byte ("3DLT") help?
yesgrey
18th February 2009, 17:23
"programVersion" should change with every new cr3dlut build!
I was talking about the format, not the value...;)
Can't we simply define the structure with "#pragma pack (1)" and be done with it? Or are there some C++ compilers which don't support byte packing?
"#pragma pack (4)" would be good enough...
Alternatively: Would shorting the signature to 4 byte ("3DLT") help?
Yes, that would help, and we could return to set programVersion to long long...
IanB, what do you think? What's your suggestion to make the alignment compiler independent?
yesgrey
18th February 2009, 17:27
@cyberbeing,
I will look at your questions a little later, when I start working in the custom gamma curves completelly defined by the users, to mimic their display's gamma response. That will be the next step, after completing this 3DLUT file specification thing.;)
IanB
18th February 2009, 21:31
IanB, what do you think? What's your suggestion to make the alignment compiler independent?Assume the size of each element is also it's alignment base for the element and order the elements such that the whole structure is intrinsically aligned.
Don't stuff with any "#pragma pack ..." like options, you just don't know how any given compiler will behave. It also make it easier for others to port the structure to other languages.
The short programVersion[4] trick works for the current windows compilers and probably will work everywhere, but thinking it might not someday costs nothing and hence coding defensively to avoid the issue means never having to say sorry.
madshi
18th February 2009, 21:43
Assume the size of each element is also it's alignment base for the element and order the elements such that the whole structure is intrinsically aligned.
That's fine as long as the reordering makes sense, logically. But I'm not a fan of using an illogical order just to make alignment work.
Don't stuff with any "#pragma pack ..." like options, you just don't know how any given compiler will behave. It also make it easier for others to port the structure to other languages.
I think any language and any compiler (which is any good) has some way to produce byte aligned structures. Because otherwise even calling simple win32 APIs wouldn't work. Sure, "#pragma pack" might not work with every compiler, but then some other option will do the trick. So I don't really see the problem. If the structure is clearly specified as being byte aligned, then I think that's all anybody can ask for.
Of course if it's possible to find a good and logical order of the fields which also happens to be good for alignment, then I'm all for it. But please let us not shuffle the fields to a "random" order just to avoid having to use a compiler option like "#pragma pack".
IanB
19th February 2009, 00:17
Of course if it's possible to find a good and logical order of the fields which also happens to be good for alignment, then I'm all for it. But please let us not shuffle the fields to a "random" order just to avoid having to use a compiler option like "#pragma pack".Well that is the challenge. And you are allowed to manually pad the structure. As I keep saying "Intrinsically aligned".
Anyway for a binary header logical order is somewhat meaningless, but rational ordering like good comments is good programming. Anything that prints the data should of course do so in a sensible order.
yesgrey
19th February 2009, 01:35
Probably this is a very dumb idea, but since the 'signature' field is a char array of 8, and we only use 5, why not use the last to indicate the file version? With a char we can have 127 different versions, I think it would be good enough... this way we can remove the fileVersion field...
We can simply set the fileVersion to be a long long, which would solve the alignment problem...
madshi
19th February 2009, 08:48
Anyway for a binary header logical order is somewhat meaningless, but rational ordering like good comments is good programming.
We have a different way of thinking here, it seems. But maybe we can find a perfect alignment for the 3dlut files without taking a hit on logical ordering. Then we could all be satisfied.
Probably this is a very dumb idea, but since the 'signature' field is a char array of 8, and we only use 5, why not use the last to indicate the file version? With a char we can have 127 different versions, I think it would be good enough... this way we can remove the fileVersion field...
We can simply set the fileVersion to be a long long, which would solve the alignment problem...
I don't like misusing the last byte of a field for another purpose. Using long long for fileVersion would be fine with me, though. But if it solves the alignment problem, I'd like using a 4 byte signature and a simple "int" for fileVersion even better.
Anyway, I'd love to finally get the spec completed. So if either using "long long fileVersion" or a 4 byte signature + 4 byte fileVersion solves all remaining problems, then please just use whatever you like more and let's finally call it finished... :)
yesgrey
19th February 2009, 18:41
So here it goes another iteration... hopefully the last one.;)
// 3DLUT file format specification (fileVersion=1):
enum {PAGE_SIZE = 16384};
struct H3DLUT
{
char signature[4]; // file signature; must be: '3DLT'
int fileVersion; // file format version number
char programName[32]; // name of the program that created the file
long long programVersion; // version number of the program that created the file
int inputBitDepth[3]; // input bit depth per component (Y,Cb,Cr or R,G,B)
int inputColorEncoding; // input color encoding standard
int outputBitDepth; // output bit depth for all components (valid values are 8 and 16)
int outputColorEncoding; // output color encoding standard
int parametersFileOffset; // number of bytes between the beginning of the file and array parametersData
int parametersSize; // size in bytes of the array parametersData
int lutFileOffset; // number of bytes between the beginning of the file and array lutData
int lutCompressionMethod; // type of compression used if any (0 = none, ...)
int lutCompressedSize; // size in bytes of the array lutData inside the file, whether compressed or not
int lutUncompressedSize; // true size in bytes of the array lutData when in memory for usage (outside the file)
// This header is followed by the char array 'parametersData', of length 'parametersSize',
// and by the array 'lutData', of length 'lutCompressedSize'.
// The array 'parametersData' starts 'parametersFileOffset' bytes after the beginning of the file.
// The array 'lutData' starts 'lutFileOffset' bytes after the beginning of the file.
// When creating a 3DLUT file, 'lutData' should be positioned on a 16384 byte boundary.
};
/*
parametersData - char array with size parametersSize that contains an exact copy of the
input file with the parameters settings used for creating the 3DLUT file
lutData - array with size lutSizeUncompressed that contains the 3dlut output values.
The type used depends on the 'outputBitDepth' field:
- unsigned char, if outputBitDepth = 8
- unsigned short, if outputBitDepth = 16
The offset inside the array is calculated as:
offset = (cr<<(inputBitDepth[1]+inputBitDepth[0])+cb<<(inputBitDepth[0])+y)*3 (YCbCr input)
offset = ( r<<(inputBitDepth[1]+inputBitDepth[0])+ g<<(inputBitDepth[0])+b)*3 (RGB input)
The 'lutUncompressedSize' of the array is calculated as:
lutDim = 3*(2^inputBitDepth[0]*2^inputBitDepth[1]*2^inputBitDepth[2])
lutUncompressedSize = lutDim*1, if outputBitDepth = 8
lutUncompressedSize = lutDim*2, if outputBitDepth = 16
(This specification assumes: char = 1 byte; short = 2 byte; int = 4 byte; long long = 8 byte)
*/
I've preferred changing the signature to "3DLT" due to the previously discussed alignment problems. "3DLUT" is the name always used to refer to this type of LUTs, so, since we are specifying a file format, it's better changing the name slightly to avoid confusion when talking about the file format vs the "3D LUT" term. Also, since the name is 3D Lookup Table, "3DLT" are the real initials, so I agreed with madshi and also voted for "3DLT". With this change, I think we should also change the file extension to "*.3dlt".
Comments are welcome...
madshi
19th February 2009, 19:38
So here it goes another iteration... hopefully the last one.;)
For me it's perfect. No complaints, nor further improvement ideas about the structure itself. Just one minor suggestion about one of the comments:
// and that pos must be on a 16384 byte boundary if 'lutData' is uncompressed.
I'd change the comment slightly to make clear that this page alignment requirement is a requirement which must be met by programs *creating* a 3dlut file and not by programs reading such a file. So maybe:
"when creating a 3dlut file, you should position "lutData" on a 16384 byte boundary"
Also, since the name is 3D Lookup Table, "3DLT" are the real initials
Tricky... :)
so I agreed with madshi and also voted for "3DLT". With this change, I think we should also change the file extension to "*.3dlt".
In other words we rename the whole concept from "3D lut" to "3D Lookup Table"? I like that! And I agree that the file extension then must be "*.3dlt".
So when is cr3dlut - - ... - scratch that.
So when is cr3dlt version 2.0 coming out? :D
yesgrey
19th February 2009, 19:57
In other words we rename the whole concept from "3D lut" to "3D Lookup Table"?
In reality is almost the same, is "3D LookUp Table"... just sounds better a lut than a lt.:D
I think we should keep the whole concept as 3D LUT, and use 3DLT just as the file format designation...
So when is cr3dlut - - ... - scratch that.
So when is cr3dlt version 2.0 coming out? :D
I have not yet decided if I will change the program's name. My first thought was to do it, but since I think we should keep the concept as 3DLUT, cr3dlut still makes sense, as tritical's rgb3dlut. The only difference is that now our programs are creating/using a 3DLUT in the file format 3DLT.;)
Let's hear how tritical feels about this...
My program is already creating 8 and 16 bit 3DLT files, and I already send some to tritical, so he can test it with his program version. Now I will start working on the specification of the ColorEncoding variables. I want to create a more user friendly specification of it.;)
yesgrey
19th February 2009, 20:02
I'd change the comment slightly to make clear that this page alignment requirement is a requirement which must be met by programs *creating* a 3dlut file
I've edited the specification. See if it's ok to you.
madshi
19th February 2009, 20:19
In reality is almost the same, is "3D LookUp Table"...
Ooops, didn't know that... :)
I think we should keep the whole concept as 3D LUT, and use 3DLT just as the file format designation...
In that case I'd prefer using "*.3dlut" as file extension. Because if "cr3dlut" and "rgb3dlut" both keep using the full name "3dlut" then so should the file extension, I think. It would be strange if the tool "cr(eate)3dlut" would create a "3dlt" file!
(It doesn't matter that the "U" is missing in the file header "signature" field. No normal end user would ever care - or even notice.)
I've edited the specification. See if it's ok to you.
I'm wondering whether the first 2 comment lines are needed at all. I think without those it would still be clear. Maybe only these 3 lines?
// The array 'parametersData' starts 'parametersFileOffset' bytes after the beginning of the file.
// The array 'lutData' starts 'lutFileOffset' bytes after the beginning of the file.
// When creating a 3DLT file, you should position "lutData" on a 16384 byte boundary.
I'd like "When creating" to be in a new line. And I've removed the "if 'lutData' is uncompressed" because I think it wouldn't harm if "lutData" was always aligned, regardless of whether it's compressed or not. It would keep things simple to always align instead of only aligning under specific circumstances. What do you think?
yesgrey
19th February 2009, 21:00
In that case I'd prefer using "*.3dlut" as file extension...
(It doesn't matter that the "U" is missing in the file header "signature" field. No normal end user would ever care - or even notice.)
Ok, so I will revert the file format name to 3DLUT.
It will be similar to the BMP file format, which has a signature of "BM", and we all keep calling it BMP...;)
About your other suggestions, I agree with all, except removing of the two first comment lines - I think it's better keep them.
I will edit the specification accordingly.
madshi
19th February 2009, 21:29
Great!
Any objections from tritical or IanB about the latest revision of the file format?
tritical
19th February 2009, 21:30
yesgrey3, my program is working with the lut files you sent. Could you send me one with the new header format for testing? If you have your program to the point of generating rgb->yuv tables one of those would be good.
Btw, I've changed the name of my filter to 3dlut, but avisynth doesn't seem to like function names starting with numbers so it is now t3dlut().
IanB
20th February 2009, 01:35
Looks okay, I would put back the outer structure you had in earlier versions as a pseudo structure in the comments to help understand how the parametersData[], the page padding and lutData[] are organised. Use example pseudo numbers if you think it will help.
yesgrey
20th February 2009, 11:04
IanB,
I agree. I will update the spec accordingly.
madshi
25th February 2009, 00:35
Hey guys,
stumbled over this interesting article:
http://www.glennchan.info/articles/technical/chroma/chroma1.htm
As far as I understand, it's written for people who *down*sample chroma. But I still find it interesting. E.g. it seems that chroma upsampling (and probably also any upscaling) should be done in linear color space and not in gamma corrected color space!!
That brings me to the following conclusion: To get perfect quality, we should first remove gamma correction to move the image to linear color space. Then we should upsample chroma and scale the image (if needed). Finally we should do gamut & gamma correction and eventually convert to RGB or YCbCr.
So I guess that means cr3dlut should also support linear YCbCr input in addition to gamma corrected YCbCr and RGB input?
BTW, the article also explains why chroma placement can be "center" instead of "left" even though MPEG2 specifies is should be left.
Edit: It seems that only Y is gamma corrected, while Cb and Cr are linear? But then why does the article say that chroma subsampling should be done linear and not gamma corrected? I'm confused...
yesgrey
25th February 2009, 00:46
So I guess that means cr3dlut should also support linear YCbCr input in addition to gamma corrected YCbCr and RGB input?
It already supports it.;)
I will read the article tomorrow.
tritical
25th February 2009, 08:29
I think the author is saying he thinks you will get better results if you first convert gamma corrected RGB to linear RGB before converting to YCbCr (in that case you technically get "luminance" - Y instead of "luma" - Y') and downsampling. I highly doubt this is the case if you have to store the resulting Y with only 8-bits of precision. In almost all video/image processing, the conversion involves gamma-corrected RGB so you technically get Y'CbCr. Really it makes no difference in terms of chroma upsampling, because unless the person somehow converted to Y'CbCr, downsampled, and then undid gamma correction (which I'm not even sure how you would go about doing as the gamma correction we're talking about, performing the inverse of the transfer function of an rgb display, is really an RGB only concept) you'd want to do the upsampling in the same system (linear or non-linear) that the downsampling was performed in.
Now whether it is better to perform image processing operations in linear RGB instead of gamma corrected RGB is another question. The main thing you have to remember is that a person's perceptual response to luminance, or lightness, is non-linear (close to a 0.4 power function). So if you have limited precision (like 256 steps in 8-bit processing) in which to store intermediate values, you'll get much better results if you operate in a non-linear space close to a 0.4 power function. Otherwise, at the low end you have only a few samples covering a large perceptual difference range, and at the high end you have a lot of samples covering a small perceptual difference range. It just happens that most monitors have a transfer function roughly equal to a 2.2 power curve. So when you perform gamma correction to adjust for the monitor's transfer function by taking x^(1/2.2) you actually get pretty close to the 0.4 power function of human perception. Therefore, working with Y'CbCr (Y' derived from gamma corrected RGB) works pretty well. Of course, what you could do is have each operation undo the gamma correction (storing the result at higher precision), do its work in higher precision, and then reapply gamma before storing the result in limited precision.
I think 12 bits or more of precision, per color component, is required to avoid banding issues with linear rgb.
madshi
25th February 2009, 09:25
I think the author is saying he thinks you will get better results if you first convert gamma corrected RGB to linear RGB before converting to YCbCr (in that case you technically get "luminance" - Y instead of "luma" - Y') and downsampling. I highly doubt this is the case if you have to store the resulting Y with only 8-bits of precision.
Is it easily possible to convert YCbCr to Y'CbCr? Maybe what he was suggesting was to do "R'G'B' -> RGB -> YCbCr -> chroma subsampling -> Y'CbCr"? Don't know if that makes any sense. Of course if he really wanted to end up with YCbCr instead of Y'CbCr that would make no sense at all.
tritical
25th February 2009, 10:23
Is it easily possible to convert YCbCr to Y'CbCr?
You'd have to go YCbCr -> RGB -> R'G'B' -> Y'CbCr.
After reading it again, I'm fairly certain he means to do R'G'B -> RGB -> YCbCr because when you compute Y' using R'G'B' you aren't computing the true "luminance". So when you store the difference components as Cb = B'-Y' and Cr = R'-Y', introduce errors into them through downsampling/upsampling,and then rederive R'G'B', errors in Cb/Cr will cause some change in the luminance. Whereas if you store the true luminance, Y, then errors you introduce into Cb/Cr (Cb = B-Y and Cr = R-Y) cannot affect the luminance. The only thing is that he doesn't mention anything about precision. Although, at that point you are inventing your own system which isn't going to be compatible with current software/devices (which he mentions)... so nothing is preventing you from performing some kind of non-linear scaling on the YCbCr values prior to storing them in limited precision.
yesgrey
25th February 2009, 11:51
Edit: It seems that only Y is gamma corrected, while Cb and Cr are linear?
Y, Cb, Cr are not gamma corrected, only R, G, B are gamma corrected.
Y, Cb, Cr are always a linear combination of R, G, B, so, their content will always linearly reflect the way R, G, B are represented. If R, G, B are not gamma corrected, Y, Cb, Cr will also represent the colors in a linear form, if R, G, B are gamma corrected, they will represent the colors in a non-linear form, but not in a gamma corrected form.
Remember the formula:
Y = kr*R + Kg*G + kb*B; where kg = 1 - kr - kb (kr, kb are defined by the standard's specification)
Is it easily possible to convert YCbCr to Y'CbCr?
No. From the description above you can clearly see that:
Y' = kr*pow(R,0.45) + Kg*pow(G,0.45) + kb*pow(B,0.45) is not the same as Y'=pow(Y,0.45) , so you always have to do:
YCbCr->RGB->R'G'B'->Y'CbCr
I think the author is saying he thinks you will get better results if you first convert gamma corrected RGB to linear RGB before converting to YCbCr (in that case you technically get "luminance" - Y instead of "luma" - Y') and downsampling.
Yes, I also agree with this.
I have looked through the source code he supplyed, and in there he always works in the non-linear domain - he never removes the gamma from the images. So, the images he has there were not obtained with the code supplyed, or he hasn't tested it performing the calculations in the linear domain...:confused:
yesgrey
25th February 2009, 12:10
I was a bit late...:)
It's a very interesting paper, but with real world images the difference should be almost none. I am not writing cr3dlut to see red characters in grey backgrounds in all its glory.:D
But we can always try to create a version which works like this:
-Use one 3D LUT to convert from Y'CbCr->YCbCr
-perform chroma upsample in YCbCr
-Use one 3D LUT to convert YCbCr->Y'CbCr (only to apply on upsampled chroma)
-Use one 3D LUT to convert from Y'CbCr->R'G'B' (using original Y' values, and the upsampled CbCr values)
For the upsampling we will need a higher input bit depth 3D LUT for the YCbCr->Y'CbCr to avoid banding...
I can offer myself to create the LUTs.:D
madshi
25th February 2009, 14:13
But we can always try to create a version which works like this:
-Use one 3D LUT to convert from Y'CbCr->YCbCr
-perform chroma upsample in YCbCr
In order to do this we'd have to go Y'CbCr -> R'G'B' -> RGB -> YCbCr, right? But can we go from Y'CbCr 4:2:0 to R'G'B' without upsampling chroma first? I don't think so. Probably we'd have to downsample Y' to make it match CbCr resolution. Then we could convert the "quarter resolution" Y'CbCr intermediate to R'G'B' -> RGB -> YCbCr, and then finally upsample the chroma.
Ouch. I wonder if there'd even be a visible difference. The problem is: We will probably only be able to tell once we tried it... :(
yesgrey
25th February 2009, 15:23
In order to do this we'd have to go Y'CbCr -> R'G'B' -> RGB -> YCbCr, right?
Yes, that's how cr3dlut should build the 3D LUT.
But can we go from Y'CbCr 4:2:0 to R'G'B' without upsampling chroma first?
For a 3D LUT only interest Y'CbCr -> R'G'B'. Remember that a 3D LUT only relates the input and output values; it is applyed at the pixel level, and is always the same for each pixel; it has no relationship with chroma upsampling, that's another different thing...
For converting an 1920x1080 image (2073600 pixels) you should map each pixel through it, so it would be accessed 2073600 times. In Y'CbCr 4:2:0 you have a Y' value for each pixel (2073600 Y' values for the entire image), and a CbCr value for each 2x2 block of pixels (518400 CbCr values for the entire image). The chroma resampling is just to calculate the missing CbCr values.
Ouch. I wonder if there'd even be a visible difference.
If we'll use it with red characters in a grey background, it should be visible...:D
madshi
25th February 2009, 15:43
For converting an 1920x1080 image (2073600 pixels) you should map each pixel through it, so it would be accessed 2073600 times. In Y'CbCr 4:2:0 you have a Y' value for each pixel (2073600 Y' values for the entire image), and a CbCr value for each 2x2 block of pixels (518400 CbCr values for the entire image). The chroma resampling is just to calculate the missing CbCr values.
Trust me, I know what chroma upsampling is.
For a 3D LUT only interest Y'CbCr -> R'G'B'. Remember that a 3D LUT only relates the input and output values; it is applyed at the pixel level, and is always the same for each pixel; it has no relationship with chroma upsampling, that's another different thing...
My point is this: You suggested to do chroma upsampling in YCbCr (instead of Y'CbCr). But our source is Y'CbCr 4:2:0. How can we go from Y'CbCr 4:2:0 to YCbCr 4:2:0? This is not as easy as it seems. Because in order to go from Y'CbCr to YCbCr we have to go through RGB. And doing Y'CbCr 4:2:0 -> RGB 4:4:4 is not possible, I believe. You first have to upsample chroma to 4:4:4 to do Y'CbCr -> R'G'B'. But we want to upsample chroma in YCbCr and not in Y'CbCr. Do you see the problem?
yesgrey
25th February 2009, 17:02
Trust me, I know what chroma upsampling is.
I believe so, but what I wanted to make clear is that to create the 3D LUT does not matter if the color is 4:2:0, 4:1:1, 4:2:2, 4:4:4, etc... it's an independent thing.
How can we go from Y'CbCr 4:2:0 to YCbCr 4:2:0? This is not as easy as it seems.
I think it will be more clear with an example. Feel free to correct me if anything is wrong.;)
Let's see how it would be done using the method "same chroma for all pixels":
Let's call CbCr in Y'CbCr as C' and CbCr in YCbCr as C.
In Y'CbCr 4:2:0 we have Y'11, Y'12, Y'21, Y'22, and we have C'11.
The current method is doing C'12=C'21=C'22=C'11;
What I was proposing was:
-Use a Y'CbCr->YCbCr 3D LUT to find:
Y12=3dlut(Y'12,C'11)
Y21=3dlut(Y'21,C'11)
Y22=3dlut(Y'22,C'11)
C11=3dlut(Y'11,C'11)
-do C12=C21=C22=C11
-Use a YCbCr->Y'CbCr 3D LUT to find:
C'12=3dlut(Y12,C12)
C'21=3dlut(Y21,C21)
C'11=3dlut(Y22,C22)
-Use original Y'11,Y'12,Y'21,Y'22,C'11 with calculated C'12, C'21, C'22.
So, we have to perform two chroma upsamplings. One upsampling when converting Y'C' -> YC because to find the Y values through the 3D LUT we need a C' value. Another when doing it in YCbCr.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.