colo-icc.lua /size: 9691 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['colo-icc'] = {
2    version   = 1.000,
3    comment   = "companion to colo-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local char, byte, gsub, match, format, strip = string.char, string.byte, string.gsub, string.match, string.format, string.strip
10local readstring, readnumber = io.readstring, io.readnumber
11local band = bit32.band
12local next = next
13
14local colors = attributes and attributes.colors or { } -- when used in mtxrun
15
16local report_colors = logs.reporter("colors","icc")
17
18local R, Cs, lpegmatch = lpeg.R, lpeg.Cs, lpeg.match
19
20local invalid = R(char(0)..char(31))
21local cleaned = invalid^0 * Cs((1-invalid)^0)
22
23function colors.iccprofile(filename,verbose)
24    local fullname = resolvers.findfile(filename,"icc") or ""
25    if fullname == "" then
26        local locate = resolvers.finders.byscheme -- not in mtxrun
27        if locate then
28            fullname = locate("loc",filename)
29        end
30    end
31    if fullname == "" then
32        report_colors("profile %a cannot be found",filename)
33        return nil, false
34    end
35    local f = io.open(fullname,"rb")
36    if not f then
37        report_colors("profile %a cannot be loaded",fullname)
38        return nil, false
39    end
40    local header =  {
41        size               = readnumber(f,4),
42        cmmtype            = readnumber(f,4),
43        version            = readnumber(f,4),
44        deviceclass        = strip(readstring(f,4)),
45        colorspace         = strip(readstring(f,4)),
46        connectionspace    = strip(readstring(f,4)),
47        datetime           = {
48            year    = readnumber(f,2),
49            month   = readnumber(f,2),
50            day     = readnumber(f,2),
51            hour    = readnumber(f,2),
52            minutes = readnumber(f,2),
53            seconds = readnumber(f,2),
54        },
55        filesignature      = strip(readstring(f,4)),
56        platformsignature  = strip(readstring(f,4)),
57        options            = readnumber(f,4),
58        devicemanufacturer = strip(readstring(f,4)),
59        devicemodel        = strip(readstring(f,4)),
60        deviceattributes   = readnumber(f,4),
61        renderingintent    = readnumber(f,4),
62        illuminantxyz      = {
63            x = readnumber(f,4),
64            y = readnumber(f,4),
65            z = readnumber(f,4),
66        },
67        profilecreator     = readnumber(f,4),
68        id                 = strip(readstring(f,16)),
69    }
70    local tags = { }
71    for i=1,readnumber(f,128,4) do
72        tags[readstring(f,4)] = {
73            offset = readnumber(f,4),
74            length = readnumber(f,4),
75        }
76    end
77    local o = header.options
78    header.options =
79        o == 0 and "embedded"  or
80        o == 1 and "dependent" or "unknown"
81    local d = header.deviceattributes
82    header.deviceattributes = {
83        [band(d,1) ~= 0 and "transparency" or "reflective"] = true,
84        [band(d,2) ~= 0 and "mate"         or "glossy"    ] = true,
85        [band(d,3) ~= 0 and "negative"     or "positive"  ] = true,
86        [band(d,4) ~= 0 and "bw"           or "color"     ] = true,
87    }
88    local r = header.renderingintent
89    header.renderingintent =
90        r == 0 and "perceptual" or
91        r == 1 and "relative"   or
92        r == 2 and "saturation" or
93        r == 3 and "absolute"   or "unknown"
94    for tag, spec in next, tags do
95        if tag then
96            local offset, length = spec.offset, spec.length
97            local variant = readstring(f,offset,4)
98            if variant == "text" or variant == "desc" then
99                local str = readstring(f,length-4)
100                tags[tag] = {
101                    data    = str,
102                    cleaned = lpegmatch(cleaned,str),
103                }
104            else
105                if verbose then
106                    report_colors("ignoring tag %a or type %a in profile %a",tag,variant,fullname)
107                end
108                tags[tag] = nil
109            end
110        end
111    end
112    f:close()
113    local profile = {
114        filename = filename,
115        fullname = fullname,
116        header   = header,
117        tags     = tags,
118    }
119    report_colors("profile %a loaded",fullname)
120    return profile, true
121end
122
123-- This is just some fun stuff I decided to check out when I was making sure that
124-- the 2020 metafun manual could be processed with lmtx 2021. Color conversion has
125-- been part of ConTeXt from the start but it has been extended to the less commonly
126-- used color spaces. We already do some CIE but didn't have lab converters to play
127-- with (although I had some MetaPost done for a friend long ago). So, when we moved
128-- to lmtx it made sense to also move some into the core. When searching for info
129-- I ran into some formulas for lab/xyz: http://www.easyrgb.com/en/math.php and
130-- http://www.brucelindbloom.com/ are useful resources. I didn't touch existing
131-- code (as it works ok).
132--
133-- local illuminants = { -- 2=CIE 1931 10=CIE 1964
134--     A   = { [2] = { 109.850, 100,  35.585 }, [10] = { 111.144, 100,  35.200 } }, -- incandescent/tungsten
135--     B   = { [2] = {  99.093, 100,  85.313 }, [10] = {  99.178, 100,  84.349 } }, -- old direct sunlight at noon
136--     C   = { [2] = {  98.074, 100, 118.232 }, [10] = {  97.285, 100, 116.145 } }, -- old daylight
137--     D50 = { [2] = {  96.422, 100,  82.521 }, [10] = {  96.720, 100,  81.427 } }, -- icc profile pcs
138--     D55 = { [2] = {  95.682, 100,  92.149 }, [10] = {  95.799, 100,  90.926 } }, -- mid-morning daylight
139--     D65 = { [2] = {  95.047, 100, 108.883 }, [10] = {  94.811, 100, 107.304 } }, -- daylight, srgb, adobe-rgb
140--     D75 = { [2] = {  94.972, 100, 122.638 }, [10] = {  94.416, 100, 120.641 } }, -- north sky daylight
141--     E   = { [2] = { 100.000, 100, 100.000 }, [10] = { 100.000, 100, 100.000 } }, -- equal energy
142--     F1  = { [2] = {  92.834, 100, 103.665 }, [10] = {  94.791, 100, 103.191 } }, -- daylight fluorescent
143--     F2  = { [2] = {  99.187, 100,  67.395 }, [10] = { 103.280, 100,  69.026 } }, -- cool fluorescent
144--     F3  = { [2] = { 103.754, 100,  49.861 }, [10] = { 108.968, 100,  51.965 } }, -- white fluorescent
145--     F4  = { [2] = { 109.147, 100,  38.813 }, [10] = { 114.961, 100,  40.963 } }, -- warm white fluorescent
146--     F5  = { [2] = {  90.872, 100,  98.723 }, [10] = {  93.369, 100,  98.636 } }, -- daylight fluorescent
147--     F6  = { [2] = {  97.309, 100,  60.191 }, [10] = { 102.148, 100,  62.074 } }, -- lite white fluorescent
148--     F7  = { [2] = {  95.044, 100, 108.755 }, [10] = {  95.792, 100, 107.687 } }, -- daylight fluorescent, d65 simulator
149--     F8  = { [2] = {  96.413, 100,  82.333 }, [10] = {  97.115, 100,  81.135 } }, -- sylvania f40, d50 simulator
150--     F9  = { [2] = { 100.365, 100,  67.868 }, [10] = { 102.116, 100,  67.826 } }, -- cool white fluorescent
151--     F10 = { [2] = {  96.174, 100,  81.712 }, [10] = {  99.001, 100,  83.134 } }, -- ultralume 50, philips tl85
152--     F11 = { [2] = { 100.966, 100,  64.370 }, [10] = { 103.866, 100,  65.627 } }, -- ultralume 40, philips tl84
153--     F12 = { [2] = { 108.046, 100,  39.228 }, [10] = { 111.428, 100,  40.353 } }, -- ultralume 30, philips tl83
154-- }
155--
156-- local D65  = illuminants.D65
157-- local D652 = {  95.047, 100, 108.883 }
158--
159-- local function labref(illuminate,observer)
160--     local r = illuminants[illuminant or "D65"] or D65
161--     return r[observer or 2] or r[2] or D652
162-- end
163--
164-- This is hardly useful but nice for metafun demos:
165
166local D652 = {  95.047, 100, 108.883 }
167
168local function xyztolab(x,y,z,mapping)
169    if not mapping then
170        mapping = D652
171    end
172    x = x / mapping[1]
173    y = y / mapping[2]
174    z = z / mapping[3]
175    x = (x > 0.008856) and x^(1/3) or (7.787 * x) + (16/116)
176    y = (y > 0.008856) and y^(1/3) or (7.787 * y) + (16/116)
177    z = (z > 0.008856) and z^(1/3) or (7.787 * z) + (16/116)
178    return
179        116 * y  - 16,
180        500 * (x - y),
181        200 * (y - z)
182end
183
184local function labtoxyz(l,a,b,mapping)
185    if not mapping then
186        mapping = D652
187    end
188    local y = (l + 16) / 116
189    local x = a / 500 + y
190    local z = y - b / 200
191    return
192        mapping[1] * ((x^3 > 0.008856) and x^3 or (x - 16/116) / 7.787),
193        mapping[2] * ((y^3 > 0.008856) and y^3 or (y - 16/116) / 7.787),
194        mapping[3] * ((z^3 > 0.008856) and z^3 or (z - 16/116) / 7.787)
195end
196
197local function xyztorgb(x,y,z) -- D65/2°
198    local r = (x *  3.2404542 + y * -1.5371385 + z * -0.4985314) / 100
199    local g = (x * -0.9692660 + y *  1.8760108 + z *  0.0415560) / 100
200    local b = (x *  0.0556434 + y * -0.2040259 + z *  1.0572252) / 100
201    r = (r > 0.0031308) and (1.055 * r^(1/2.4) - 0.055) or (12.92 * r)
202    g = (g > 0.0031308) and (1.055 * g^(1/2.4) - 0.055) or (12.92 * g)
203    b = (b > 0.0031308) and (1.055 * b^(1/2.4) - 0.055) or (12.92 * b)
204    if r < 0 then r = 0 elseif r > 1 then r = 1 end
205    if g < 0 then g = 0 elseif g > 1 then g = 1 end
206    if b < 0 then b = 0 elseif b > 1 then b = 1 end
207    return r, g, b
208end
209
210local function rgbtoxyz(r,g,b)
211    r = 100 * ((r > 0.04045) and ((r + 0.055)/1.055)^2.4 or (r / 12.92))
212    g = 100 * ((g > 0.04045) and ((g + 0.055)/1.055)^2.4 or (g / 12.92))
213    b = 100 * ((b > 0.04045) and ((b + 0.055)/1.055)^2.4 or (b / 12.92))
214    return
215        r * 0.4124 + g * 0.3576 + b * 0.1805,
216        r * 0.2126 + g * 0.7152 + b * 0.0722,
217        r * 0.0193 + g * 0.1192 + b * 0.9505
218end
219
220local function labtorgb(l,a,b,mapping)
221    return xyztorgb(labtoxyz(l,a,b,mapping))
222end
223
224colors.xyztolab = xyztolab
225colors.labtoxyz = labtoxyz
226colors.xyztorgb = xyztorgb
227colors.rgbtoxyz = rgbtoxyz
228colors.labtorgb = labtorgb
229