Øyvind Kolås    pippin@gimp·org @hodefoting   patreon  

sRGBz—a minimal sRGB ICC v2 profile

This page is about the size and precision optimizations of sRGBz.icc a 491byte, CC0 licensed, ICC v2, sRGB equivalent profile, useful for ensuring consistent handling of 8bpc images files in environments where ICC v2 profiles are widely supported (like the web).

background

I've been addding the ability to specify the colorimetric characteristics of the RGB color space used to store colors in the pixels to GEGL and GIMP's pixel conversion library, babl. This information can be provided directly to babl - or babl can extract the information from an ICC profile. Babl also has the ability to write such information back out as an ICC profile.

There are many sRGB ICC profiles out there, resulting from slightly different starting points, and different floating point and fixed point representations used in the ICC profile computation/creation process.

Babl upgrades loaded ICC RGB spaces that are within a small error bound equal to standard RGB spaces to be exactly that RGB space.

524bytes tiny sRGB / "c2"

After implementing the matching I stumbled across facebook's Tiny sRGB ICC profile from 2012, which encodes the sRGB space in a 524byte ICCv2 profile. Intended as a minimal but precise enough for display of 8bpc JPEGs images and thumbnails. [Some versions of some web browsers apparently treat untagged images as if they were in the display-space, rather than sRGB, probably yielding worst results on wide-gamut displays, there are other ways of more tersely specifying sRGB for a JPG through fields of exif tags - but that is beyond the scope of this article]

The tiny sRGB ICC profile which calls itself "c2" in its own description is considered by babl similar enough to sRGB to be treated as sRGB, having implemented reading and writing of the ICC format I was curious about how it packed the data in the file and decided to take a close look at how the profile is expressed in terms of the ICC file format, and ended up using the ICC writing code and transform matrix preparation code in babl to create sRGBz from first principles improving on it.

dedup

Given that one of the bulkier parts of ICCv2 sRGB profiles is the LUT it is very reasonable that the sTRC, gTRC and bTRC data tags are aliases for each other. Otherwise the profile would've been 128 bytes larger..

519bytes - alignment

An ICC profile consists of 128bytes header, 12bytes * number of tags with type/size/offset, followed by the tag data itself with each tag data aligned to 4 bytes. By moving the biggest alignment disruptor the 'desc' tag from the beginning to the end, and reducing its length to 1 we can save 5 bytes. This saving by alignment trick only works for one tag, thus leaving room for 3 bytes in the copyright tag, suited for the string "CC0" to indicate a CC0-1.0 license for it.

519bytes - improving precision

The choice of 26 bins is argued to be because this makes the linear segment of the sRGB TRC fit almost exactly the first bins as a nice coincidence; which might be yielding a closer match than the LUT size otherwise provides.

As part of improving my understanding of the LUTs in ICC profiles, I've implemented a brute force solver for the best 16bit unsigned LUTs of various sizes for approximating the sRGB transfer function.

This is a program that first computes an approximate value for all entires of the LUT, then it goes through each entry of the LUT trying all alternative combinations within a small range, keeping the best one. The best one is determined as the value that yields the lowest average/total error when the LUT is used to derive all 256 values relevnt for an 8bpc file. The above procedure is then repeated until the total error no longer decreases decreasing anymore and we've found our optimum.

sizeavg.error LUT values
230.0001470,229,544,1072,1796,2744,3937,5384,7104,9104,11396,13995,16912,20157,23735,27657,31937,36573,41589,46976,52754,58916,65535
240.0001350,219,509,993,1655,2521,3605,4920,6476,8288,10364,12716,15353,18283,21517,25062,28924,33115,37636,42500,47710,53277,59193,65535
250.0001230,210,483,924,1533,2322,3315,4513,5928,7581,9468,11605,14003,16660,19597,22813,26312,30116,34214,38621,43348,48385,53766,59452,65535
260.0001190,203,457,867,1426,2155,3062,4159,5457,6964,8689,10640,12824,15250,17925,20855,24045,27504,31237,35259,39548,44137,49021,54211,59696,65535(This is the tiny sRGB LUT,which also is available under CC0.)
260.0001140,202,455,864,1423,2154,3060,4156,5454,6960,8689,10637,12821,15247,17920,20855,24042,27501,31233,35247,39549,44132,49018,54208,59695,65535
270.0001050,194,429,812,1327,2001,2836,3842,5035,6415,8000,9786,11785,14005,16451,19134,22051,25211,28621,32289,36215,40409,44869,49603,54621,59912,65535
280.0000970,186,410,763,1243,1865,2635,3567,4662,5938,7388,9034,10870,12910,15157,17614,20294,23191,26324,29681,33285,37124,41214,45555,50148,55007,60114,65535}
290.0000900,180,390,720,1166,1743,2457,3319,4333,5509,6851,8366,10060,11938,14007,16271,18737,21406,24286,27379,30689,34222,37981,41970,46195,50657,55366,60307,65535
420.0000410,123,246,410,627,897,1224,1612,2064,2583,3170,3826,4558,5365,6250,7212,8258,9385,10602,11901,13289,14769,16342,18005,19765,21620,23574,25630,27778,30038,32395,34859,37431,40105,42891,45785,48794,51909,55140,58486,61945,65535

Each additional entry in the lookup-table consumes two bytes in the ICC profile, there doesn't seem to be a particular large win in precision around 26 - as perhaps could be expected the overall error continues being decreased with an increased LUT-size, I considered using some of the gained bytes for a higher precision LUT, but the trade-off chosen good - and we were able to improve on the precision while staying at 26 entries.

487bytes - remove blackpoint tag

The tiny sRGB ICC profile contains a black-point tag, odd for a working/storage RGB space but useful if this was a monitor profile. The black-point tag takes up 20 + 12 = 32 bytes in the profile.

If the sRGB space were to have a black point tag, it should be 0, 0, 0 to indicate that 0,0,0 is indeed black. Which is equivalent to dropping the tag altogether.

I experimented with moving some of the tags to the unused/reserved 0 padded bytes in the header - but some ICC parsers protest on such packing and I had to give up on that.

487bytes - improved matrix precision

ICC profiles generated internally by babl have the ICC 15.16 fixed point precision rounding equalized to provide exact conversions of 255,255,255 to CIE Lab 100 0.0 0.0 as well as a completely neutral gray axis.

491bytes human readable name

Thus the smallest ICCv2 profile possible save for the luxury of a human readable name, we can give the profile the description 'z' one char, giving a file size of 487 bytes 487 bytes, with a human readable description/name it becomes 491 bytes.

sum of savings

Saving more than 32bytes per ICC profile over "c2" from facebook, such an ICC profile might be the best way to explicitly color manage 8bit jpg files. Facebook is currently embedding "c2" in every jpeg served, even thumbnails. Summed up the total savings in storage and transfer adds up - preserving monthly mobile quotas. It adds up, just like the monthly donations on patreon adds up to enabling my persistence in making and sharing things investigating and improving digital media.