Feature request: save as floating-point

I have always old code “LCMS”
** lcms2-24 with “cmsgamma.c” from 10 september 2012.
At this date,
#define MAX_NODES_IN_CURVE 4097
as today

And, if I read code correctly, this value (4096) is used for V2 and V4

I found another version of LCMS from october 2010, and always #define MAX_NODES_IN_CURVE 4097

By searching on the web, I found a version 1.17 from 2007 (just at the beginning of RT by Gabor Horwath), the "define as not the same name but it is the same value

#define MAX_KNOTS 4096

Checking, yes, that’s also in LCMS-2.8 code. But “maximum allowed points in the LCMS code” logically doesn’t entail “must use the maximum allowed points”. Though it seems this is really the case when making point curves using LCMS, unless perhaps this can be overridden using code that says “write exactly this information into this tag”.

Also “maximum allowed points by LCMS code” logically doesn’t mean “maximum allowed points according to the ICC specs”. I hastily read through the V4 specs trying to see if there is any such restriction. The only thing I could find is this:

10.5 curveType The curveType contains a 4-byte count value and a one-dimensional table of 2-byte values. When used the byte assignment shall be as given in Table 34.

Table 34 says:

Actual curve values starting with the zeroth entry and ending with the entry n −1 [encoded using] uInt16Number […] *

So I’m guessing the maximum number of points is determined by what can be held in 2-byte values encoded using uInt16Numbers, which I don’t know, but hopefully someone reading this thread does know.

Anyway, being curious to see what would happen, I just made a linear gamma point curve V2 Rec2020 profile with 8193 points, and both ArgyllCMS xicclu and LCMS transicc had no trouble reading the profile and returning correct results when asked to output LCH values, though I only tried a few values up and down the gray scale. Then I opened a color image in GIMP-2.10-rc1 and converted the image to the 8193-point curve TRC, comparing results to also converting to a V4 parametric linear gamma version of the same profile - identical results were produced, and the resulting image can be exported with the profile embedded, and then reopened from disk.

FWIW, which I’m guessing is not much from any practical point of view :slight_smile: .

hmmm… 2^16? :thinking: :slight_smile:

1 Like

Well, the “count value” would be the number of values. If you have no other specified limitation, the largest possible number would be 2^32=4294967296, assuming unsigned integer…

1 Like

Yes, that seems right. I was totally confusing the 2-byte values with the number of possible entries, and so didn’t even quote the right part of table 34:

Count value specifying the number of entries (n) that follow . . . uInt32Number

So that would be up to 2^32 entries, with a maximum value of 2^16 for each entry - yes? no? Here’s a link to the actual specs:

Doing a search for “count value” will take you directly to table 34. I wonder what the V2 specs say, will try to post later unless someone else does so in the meantime.

That wouldn’t make sense. Vice versa it does make sense.
Edit: scratch my comment. I was confused. Thinking …

1 Like

I’m glad I’m not the only person who has a bit of trouble interpreting what the ICC says!

1 Like

If this is indeed what the spec says, it’s pretty straightforward. I work with specifications like this, and it is clear to me that the largest possible table is 8,589,934,596 bytes, 4,294,967,296 16-bit (2-byte) entries, prepended with a 32-bit (5 byte) count. Yes, not very practical, either storage-wise or processing-wise, but that’s the specified capacity. These days, one doesn’t want to be the person who underspecifies the next IP address, which was thought at the time to have plenty of room with 32 bits…

Edit: Okay just actually downloaded the spec so I could see Table 34, but yep, still the case. There’s just 8 bytes on front of that for the signature and some zeros…

What is the problem ?

  1. is it my old profile Rt_sRGB that contains a gamma “sRGB”. In this case, is it the gamma that is in question, or the rest of the profile in which I could make a mistake?
  2. are all V2 profiles generated by LCMS, and in this case, is due to gamma or others ?
  3. are all V2 and V4 profiles generated by LCMS, and in this case, is due to gamma or others ?
  4. or is due to other causes, specific printer driver, etc.

If it is 2) or 3) we must verify that LCMS is the cause before intervening with its designer
if it is only 1) it is easy, replace RT_sRGB by the new RT_sRGB-V2-srgbtrc.icc

if it is 4) find the culprit.

Hi @jdc - If your V2 sRGB TRC is a problem, then so is mine as currently my sRGB V2 profile has 4096 points, the same as current RT profiles, and similarly with the presence of unicode tags.

Given that sometimes the jpegs printed, and sometimes not, and lacking a specific example jpeg that wouldn’t print, plus the same jpeg after it was resaved by the Frontier lab’s own copy of PhotoShop, using their specific color management settings, I don’t think it’s possible to troubleshoot “what’s causing the lab to have trouble with the jpegs”.

Maybe it’s not even the profile. For example, maybe it’s in the jpeg compression settings. Leastways I know GIMP has some jpeg compression options that do cause some software to not be able to read the resulting jpeg.

@cuniek - it might be interesting to download ArgyllCMS and replace the RT sRGB profile in jpegs that you’ve already exported from RT, with ArgyllCMS “sRGB.icm”. The “sRGB.icm” profile is in the “ref” folder if you download from argyllcms.com.

An easy way to replace an embedded profile with another embedded profile is to use exiftool. To do this, put the ArgyllCMS “sRGB.icm” in the folder with the jpegs and then type:

exiftool "-icc_profile<=sRGB.icm" *.jpg

Of course experiment with a throw-away jpeg in its own folder, to see if this is working properly, before deploying on a folder full of important jpegs. Also, this is Linux-specific syntax - I don’t know the syntax for other operating systems.

The same exiftool command simply embeds a profile, if there isn’t any already embedded. Otherwise the current profile is replaced.

There is also of course the option of using the ArgyllCMS sRGB.icm profile as the output profile from within RT.

I’d be very curious if using the ArgyllCMS sRGB.icm profile does solve the problem with printing.

I think the question of whether RT should ship with ICC files using 1024 or 4092 point curves should not be influenced by whether some remote printing place encounters practically undiagnosable random errors using an unknown version of unknown software.

What we should do is to ensure that RawTherapee ships standards-compliant and high quality profiles, and writes standards-compliant JPEG files.

I found no issue with RT’s JPEG files using jpeginfo, a program for testing JPEG file integrity, and no issue with the ICC file I extracted from the JPEG using ExifTool and DisplayCAL. When I altered both in a hex editor errors were found, so the tools are reliable at least to some degree.

2 Likes

Hi All,

I’m the mysterious stranger that emailed @Elle the other day about her sRGB profile and its 4096-point TRC. She pointed me to this thread, and I thought I’d chime in with some info that might be helpful.

The reason I had emailed her was that I was doing some research into point-based TRC accuracy, and I had found that the 4096-point curve in her V2 sRGB profile was less accurate than the 1024-point curve used in most other profiles, particularly in the linear section of the curve at its start. I’ve published that research here: Making a Minimal sRGB ICC Profile Part 2: Curve the Curves - PhotoSauce Blog

My next step is to do some comparisons with profiles I’ve created to normalize all settings except the curve, so I can see what kind of real-world impact the curve accuracy has. The worst-case scenario I could come up with was a conversion from sRGB to something with a much wider gamut, like ProPhoto, which would move the individual colors the furthest. I can compare output when converting from a reference V4 pfofile with the output from V2 profiles with various curve definitions. My initial tests were done using the LCMS tifficc util, and so far, they confirm what I found with my earlier mathematical analysis.

I’m wondering, though, whether there are some implementation details in LCMS that might give bad results or whether there’s something else that has a known better implementation. If anyone knows a better way to test, I’d be most appreciative.

3 Likes

100% agree. But grounds of “more or less accurate sRGB point TRC” does seem relevant. It would be nice to see some hard figures on the accuracy of for example the ArgyllCMS sRGB.icm profile, which has the “usual” V2 1024-point sRGB TRC, compared to the 4096-point curve that seems standard output from using LCMS to make the sRGB point curve TRC, assuming “linear interpolation”, and perhaps also comparing to spreadsheet calculations.

I was similarly wondering whether LCMS code does in fact produce different results in a profile conversion, depending on how many points are in the point-curve TRC, and how different these results might be compared, for example, to output from ArgyllCMS conversions. I’ll try to do some testing and hopefully post results by tomorrow if no-one else has already done so.

Speaking of testing profile conversions using V2 point-curve profiles, this test image might be useful:
http://www.brucelindbloom.com/index.html?RGB16Million.html

This is exactly what I did in the analysis I posted. The 256-, 1024-, and 4096- point curves I evaluated there were all created by simply calculating the true sRGB inverse gamma function for those values, multiplying by 65535, and rounding to the nearest integer. I verified that the 1024- and 4096-point curves I calculated in that manner were an exact match for the ones in every profile I could find with the same number of points. Argyll’s curve is the same as HP’s and mine and every other 1024-point profile. And I can only assume every 4096-point profile has the same curve as well.

The problem with just looking at the raw numbers output from those calculations in a table and comparing them is that the visual impact of a difference changes depending where on the curve it falls. So while it’s tempting to just dump all the values in a spreadsheet and look at them in absolute terms, I’m quite confident that the ΔL* analysis I did is much more meaningful.

And it’s impossible to see the difference until you actually consume all the points in the curve, which means you need at least 12-bit input before you start to really see the weakness in the 4096-point curve. The extra points bring down the accuracy only in that beginning linear portion, but the error becomes quite large.

In the end, it’s a balancing act. More points make the curvy part more accurate but make the linear part less accurate. Since the visual impact is greater for smaller absolute changes in the linear part, it doesn’t take that many points before the balance is tipped. That’s how I ended up with a 212-point curve that has better overall visual accuracy than either 1024 or 4096 points.

That’s very helpful, thanks. I grabbed a few real-world images that should be challenging (anything with very saturated colors and anything with lots of subtle shades of green or blue), but a couple of artificial images are good to have too for completeness.

The challenge I have in using a CMS to do the conversion is finding a real reference. For my mathematical analysis, I was able to use the true sRGB inverse gamma function, so I know for sure that my reference was correct, up to the limit of the 64-bit double float precision I used.

Even with a V4 parametric curve, you can’t get that kind of precision. The reason is that while the piecewise nature of the curve and its logic can be accurately represented in the parametric curve type, the parameters themselves are still limited to 16-bit precision. For example, the 2.4 gamma for the curvy part of the curve, when quantized to s15Fixed16Number format in a profile is stored as 0x00026666, which comes out to 2.399993896484. And the 0.04045 transition from the linear portion to the curve is stored as 0x00000a5b, which is really 0.040451049805. Basically, you can get the function logic right, but all 5 numbers in it are just slightly off.

Where does this 0.0405 comes from ?. In wiki I see

The IEC 61966-2-1 standard uses the rounded value K 0 = 0.04045

You’re absolutely right. That was a typo. I also had the wrong hex value in there for that parameter as well. I have ninja-edited both. I used the correct value in my analysis code but then typed it wrong in my post and then copied that wrong value here :confused:

I’m curious what the error level in that parametric curve is based on those rounding differences, so I’m adding that to my analysis code now. I’ll update my post when I get that completed.

OK … fine … but as we can see in wiki all these roundings are a compromise giving discontinuities

BTW I think the largest errors for 8 bit jpegs occur because integer maths are used for jpeg compression … especially at the stage of RGB to YCbCr to RGB transforms … YCbCr - Wikipedia

I suppose, this needs a specially optimized TRC which will take account of these transformations so that the final error is minimal :wink:

Yeah, JPEG is a whole different matter. Not only does the YCbCr conversion to/from RGB mangle the colors, but then you’ve got chroma subsampling in most images, and then the JPEG quantization that throws out the high-frequency detail. It’s a mess.

I’m testing with TIFF images for that very reason. But yeah, precision goes out the window anyway once JPEG is involved, and that’s the primary use-case for my minimal profiles, at least.

I really only became interested in the highest level of precision achievable so that I can test my findings with that 212-point curve compared to the standard 1024. The differences are so minimal, I’m sure it doesn’t matter, but I really want to know which is truly more accurate when used in a profile with a real image and a real CMS. It’s just really tough to know what to use as a reference to determine that.

Update:
Here are the accuracy stats for standard 256-, 1024-, and 4096-point point-based curves along with the stats for the parametric curve from Elle’s V4 sRGB profile, as well as the stats from the 212-point curve I’ve been testing. The points for that curve are as follows:

0,24,48,72,96,120,144,168,192,217,243,271,300,332,365,400,437,476,517,560,605,652,701,752,805,861,918,978,1040,1104,1170,1239,1310,1383,1459,1537,1617,1700,1785,1873,1962,2055,2150,2247,2348,2450,2555,2663,2774,2886,3002,3120,3242,3365,3492,3620,3753,3887,4025,4164,4308,4453,4602,4753,4908,5065,5225,5388,5554,5723,5895,6070,6248,6429,6613,6799,6990,7182,7379,7578,7780,7985,8194,8406,8620,8838,9060,9284,9512,9742,9976,10214,10454,10698,10945,11196,11449,11706,11967,12230,12497,12768,13042,13319,13599,13884,14171,14462,14756,15054,15356,15660,15969,16280,16596,16915,17237,17563,17893,18226,18562,18903,19247,19594,19946,20300,20659,21021,21387,21756,22130,22506,22887,23271,23660,24051,24447,24846,25250,25657,26067,26482,26900,27323,27749,28178,28612,29050,29491,29937,30386,30840,31296,31758,32222,32692,33165,33641,34122,34607,35096,35589,36086,36586,37092,37600,38113,38631,39151,39677,40206,40740,41277,41819,42364,42914,43468,44026,44589,45155,45726,46301,46880,47463,48051,48642,49238,49838,50443,51051,51664,52281,52903,53528,54159,54793,55431,56075,56722,57373,58030,58690,59354,60024,60697,61375,62057,62744,63435,64130,64830,65535

8-bit input:

Points | Max Error | Mean Error | RMS Error | Max DeltaL | Mean DeltaL | RMS DeltaL | Max RT Error
   212 |  0.001650 |   0.000119 |  0.000361 |   0.005960 |    0.000675 |   0.004825 | 0
   256 |  0.005447 |   0.000210 |  0.000802 |   0.006723 |    0.000931 |   0.005125 | 0
  1024 |  0.008405 |   0.000205 |  0.000996 |   0.006870 |    0.000692 |   0.005180 | 0
  4096 |  0.008405 |   0.000175 |  0.000860 |   0.004818 |    0.000684 |   0.004338 | 0
   V4P |  0.000177 |   0.000034 |  0.000051 |   0.000914 |    0.000414 |   0.001889 | 0

16-bit input

Points | Max Error | Mean Error | RMS Error | Max DeltaL | Mean DeltaL | RMS DeltaL | Max RT Error
   212 |  0.001905 |   0.000130 |  0.000381 |   0.006330 |    0.000715 |   0.000311 | 5
   256 |  0.005447 |   0.000203 |  0.000789 |   0.008539 |    0.000878 |   0.000361 | 6
  1024 |  0.008405 |   0.000223 |  0.001028 |   0.006889 |    0.000743 |   0.000324 | 6
  4096 |  0.192685 |   0.000324 |  0.004697 |   0.006863 |    0.000721 |   0.000324 | 6
   V4P |  0.000182 |   0.000034 |  0.000051 |   0.000919 |    0.000416 |   0.000118 | 0

Basically, the 4096-point curve looks good as long as you don’t use all of its points for lookups/interpolation. Its greater number of points in the curvy portion of the curve give it higher resolution and better stats with 8-bit lookups. But once you use all the points, the high degree of error in the linear part of the curve show up.

The parametric curve is definitely a lot more accurate than any of the point-based curves but still has a surprising amount of error. I haven’t tried tweaking any of the parameter values yet to see if they can be improved. It might be a case like with the XYZ values where changing a couple of them by 1 brings things more into balance.

The things I conclude from these numbers are:

  1. The inaccuracy in the linear portion of the 4096-point curve make it unsuitable for general use
  2. The 212-point curve appears to be a better fit than the standard 1024-point for input at any resolution
  3. The parametric curve, while not a perfect match for the true sRGB inverse gamma function, is the best match that can be expressed in an ICC profile and is good enough to be used as a reference when comparing the point-based curves.

One more update:
I did some fiddling with the parameter values for the V4 parametric curve and was able to improve its accuracy a bit. For reference, here are the values from Elle’s V4 profile compared with the values from the sRGB spec

Param | sRGB Value     | Profile Hex | Profile Decimal | Diff
    g | 2.4            |  0x00026666 |  2.399993896484 | -6.103516e-6
    a | 0.947867298578 |  0x0000f2a7 |  0.947860717773 | -6.580805e-6
    b | 0.052132701422 |  0x00000d59 |  0.052139282227 |  6.580805e-6
    c | 0.077399380805 |  0x000013d0 |  0.077392578125 | -6.802680e-6
    d | 0.04045        |  0x00000a5b |  0.040451049805 |  1.049805e-6

It works out that because both the g and a parameters are lower than their reference values, they combine to increase the error. Adjustments to a have a larger impact on the output numbers, so I found that by leaving that alone and adjusting g upwards, I was able to get a much closer fit to the correct sRGB curve.

Here are the updated stats for comparison (revised version is marked with ^):

Points | Max Error | Mean Error | RMS Error | Max DeltaL | Mean DeltaL | RMS DeltaL | Max RT Error
   V4P |  0.000177 |   0.000034 |  0.000051 |   0.000914 |    0.000414 |   0.001889 | 0
  ^V4P |  0.000088 |   0.000012 |  0.000022 |   0.000252 |    0.000156 |   0.000992 | 0

The g value that gave the best fit turned out to be 0x00026669, which works out to 2.400039672852. The other parameters were already at their best-fit values.

1 Like

I just push a change in branch “testoutputprofile” and add 2 RT_sRGB profiles with gamma sRGB

  1. with a LUT 212 ==> RT_sRGB-V2-srgbtrc212.icc

  2. with a LUT 1024 ==> RT_sRGB-V2-srgbtrc1024.icc

jacques