1if not modules then modules = { } end modules ['attr-col'] = {
2 version = 1.001,
3 comment = "companion to attr-col.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
9
10
11
12
13
14
15local type, tonumber = type, tonumber
16local concat = table.concat
17local min, max, floor, mod = math.min, math.max, math.floor, math.mod
18
19local attributes = attributes
20local nodes = nodes
21local utilities = utilities
22local logs = logs
23local backends = backends
24local storage = storage
25local context = context
26local tex = tex
27
28local variables = interfaces.variables
29local v_yes <const> = variables.yes
30local v_no <const> = variables.no
31
32local p_split_comma = lpeg.tsplitat(",")
33local p_split_colon = lpeg.splitat(":")
34local lpegmatch = lpeg.match
35
36local allocate = utilities.storage.allocate
37local setmetatableindex = table.setmetatableindex
38
39local report_attributes = logs.reporter("attributes","colors")
40local report_colors = logs.reporter("colors","support")
41local report_transparencies = logs.reporter("transparencies","support")
42
43
44
45
46
47
48
49local states = attributes.states
50local registrations = backends.registrations
51local nodeinjections = backends.nodeinjections
52
53local unsetvalue <const> = attributes.unsetvalue
54
55local enableaction = nodes.tasks.enableaction
56local setaction = nodes.tasks.setaction
57
58local registerstorage = storage.register
59local formatters = string.formatters
60
61local interfaces = interfaces
62local implement = interfaces.implement
63
64local texgetattribute = tex.getattribute
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96attributes.colors = attributes.colors or { }
97local colors = attributes.colors
98
99local a_color <const> = attributes.private('color')
100local a_selector <const> = attributes.private('colormodel')
101
102colors.data = allocate()
103colors.values = colors.values or { }
104colors.registered = colors.registered or { }
105colors.weightgray = true
106colors.attribute = a_color
107colors.selector = a_selector
108colors.default = 1
109colors.main = nil
110colors.triggering = true
111colors.supported = true
112colors.model = "all"
113
114local data = colors.data
115local values = colors.values
116local registered = colors.registered
117
118local cmykrgbmode = 0
119
120local numbers = attributes.numbers
121local list = attributes.list
122
123registerstorage("attributes/colors/values", values, "attributes.colors.values")
124registerstorage("attributes/colors/registered", registered, "attributes.colors.registered")
125
126directives.register("colors.cmykrgbmode", function(v) cmykrgbmode = tonumber(v) or 0 end)
127
128local f_colors = {
129 rgb = formatters["r:%s:%s:%s"],
130 cmyk = formatters["c:%s:%s:%s:%s"],
131 gray = formatters["s:%s"],
132 spot = formatters["p:%s:%s:%s:%s"],
133}
134
135local models = {
136 [variables.none] = unsetvalue,
137 black = unsetvalue,
138 bw = unsetvalue,
139 all = 1,
140 gray = 2,
141 rgb = 3,
142 cmyk = 4,
143}
144
145local function rgbtocmyk(r,g,b)
146 if not r then
147 return 0, 0, 0, 1
148 else
149 return 1-r, 1-g, 1-b, 0
150 end
151end
152
153local function cmyktorgb(c,m,y,k)
154 if not c then
155 return 0, 0, 0
156 elseif cmykrgbmode == 1 then
157 local d = 1.0 - k
158 return 1.0 - min(1.0,c*d+k), 1.0 - min(1.0,m*d+k), 1.0 - min(1.0,y*d+k)
159 else
160 return 1.0 - min(1.0,c +k), 1.0 - min(1.0,m +k), 1.0 - min(1.0,y +k)
161 end
162end
163
164local function rgbtogray(r,g,b)
165 if not r then
166 return 0
167 end
168 local w = colors.weightgray
169 if w == true then
170 return .30*r + .59*g + .11*b
171 elseif not w then
172 return r/3 + g/3 + b/3
173 else
174 return w[1]*r + w[2]*g + w[3]*b
175 end
176end
177
178local function cmyktogray(c,m,y,k)
179 return rgbtogray(cmyktorgb(c,m,y,k))
180end
181
182
183
184
185
186
187
188
189local function hsvtorgb(h,s,v)
190 if s > 1 then
191 s = 1
192 elseif s < 0 then
193 s = 0
194 elseif s == 0 then
195 return v, v, v
196 end
197 if v > 1 then
198 s = 1
199 elseif v < 0 then
200 v = 0
201 end
202 if h < 0 then
203 h = 0
204 elseif h >= 360 then
205 h = mod(h,360)
206 end
207 local hd = h / 60
208 local hi = floor(hd)
209 local f = hd - hi
210 local p = v * (1 - s)
211 local q = v * (1 - f * s)
212 local t = v * (1 - (1 - f) * s)
213 if hi == 0 then
214 return v, t, p
215 elseif hi == 1 then
216 return q, v, p
217 elseif hi == 2 then
218 return p, v, t
219 elseif hi == 3 then
220 return p, q, v
221 elseif hi == 4 then
222 return t, p, v
223 elseif hi == 5 then
224 return v, p, q
225 else
226 print("error in hsv -> rgb",h,s,v)
227 return 0, 0, 0
228 end
229end
230
231local function rgbtohsv(r,g,b)
232 local offset, maximum, other_1, other_2
233 if r >= g and r >= b then
234 offset, maximum, other_1, other_2 = 0, r, g, b
235 elseif g >= r and g >= b then
236 offset, maximum, other_1, other_2 = 2, g, b, r
237 else
238 offset, maximum, other_1, other_2 = 4, b, r, g
239 end
240 if maximum == 0 then
241 return 0, 0, 0
242 end
243 local minimum = other_1 < other_2 and other_1 or other_2
244 if maximum == minimum then
245 return 0, 0, maximum
246 end
247 local delta = maximum - minimum
248 return (offset + (other_1-other_2)/delta)*60, delta/maximum, maximum
249end
250
251local function graytorgb(s)
252 return 1-s, 1-s, 1-s
253end
254
255local function hsvtogray(h,s,v)
256 return rgb_to_gray(hsv_to_rgb(h,s,v))
257end
258
259local function graytohsv(s)
260 return 0, 0, s
261end
262
263local function hwbtorgb(hue,black,white)
264 local r, g, b = hsvtorgb(hue,1,.5)
265 local f = 1 - white - black
266 return f * r + white, f * g + white , f * b + white
267end
268
269colors.rgbtocmyk = rgbtocmyk
270colors.rgbtogray = rgbtogray
271colors.cmyktorgb = cmyktorgb
272colors.cmyktogray = cmyktogray
273colors.rgbtohsv = rgbtohsv
274colors.hsvtorgb = hsvtorgb
275colors.hwbtorgb = hwbtorgb
276colors.hsvtogray = hsvtogray
277colors.graytohsv = graytohsv
278
279
280
281
282
283
284
285function colors.gray(s)
286 return { 2, s, s, s, s, 0, 0, 0, 1-s }
287end
288
289function colors.rgb(r,g,b)
290 local s = rgbtogray(r,g,b)
291 local c, m, y, k = rgbtocmyk(r,g,b)
292 return { 3, s, r, g, b, c, m, y, k }
293end
294
295function colors.cmyk(c,m,y,k)
296 local s = cmyktogray(c,m,y,k)
297 local r, g, b = cmyktorgb(c,m,y,k)
298 return { 4, s, r, g, b, c, m, y, k }
299end
300
301
302
303
304
305function colors.spot(parent,f,d,p)
306
307 if type(p) == "number" then
308 local n = list[numbers.color][parent]
309 if n then
310 local v = values[n]
311 if v then
312
313 local c, m, y, k = p*v[6], p*v[7], p*v[8], p*v[9]
314 local r, g, b = cmyktorgb(c,m,y,k)
315 local s = cmyktogray(c,m,y,k)
316 return { 5, s, r, g, b, c, m, y, k, parent, f, d, p }
317 end
318 end
319 else
320
321 local ps = lpegmatch(p_split_comma,p)
322 local ds = lpegmatch(p_split_comma,d)
323 local c, m, y, k = 0, 0, 0, 0
324 local done = false
325 for i=1,#ps do
326 local p = tonumber(ps[i])
327 local d = ds[i]
328 if p and d then
329 local n = list[numbers.color][d]
330 if n then
331 local v = values[n]
332 if v then
333 c = c + p*v[6]
334 m = m + p*v[7]
335 y = y + p*v[8]
336 k = k + p*v[9]
337 done = true
338 end
339 end
340 end
341 end
342 if done then
343 local r, g, b = cmyktorgb(c,m,y,k)
344 local s = cmyktogray(c,m,y,k)
345 local f = tonumber(f)
346 return { 5, s, r, g, b, c, m, y, k, parent, f, d, p }
347 end
348 end
349 return { 5, .5, .5, .5, .5, 0, 0, 0, .5, parent, f, d, p }
350end
351
352local graycolor = nodeinjections.graycolor
353local rgbcolor = nodeinjections.rgbcolor
354local cmykcolor = nodeinjections.cmykcolor
355local spotcolor = nodeinjections.spotcolor
356
357updaters.register("backends.injections.latebindings",function()
358 local nodeinjections = backends.nodeinjections
359 graycolor = nodeinjections.graycolor
360 rgbcolor = nodeinjections.rgbcolor
361 cmykcolor = nodeinjections.cmykcolor
362 spotcolor = nodeinjections.spotcolor
363end)
364
365local function extender(colors,key)
366 if colors.supported and key == "none" then
367 local d = graycolor(0)
368 colors.none = d
369 return d
370 end
371end
372
373local function reviver(data,n)
374 if colors.supported then
375 local v = values[n]
376 local d
377 if not v then
378 local gray = graycolor(0)
379 d = { gray, gray, gray, gray }
380 report_attributes("unable to revive color %a",n)
381 else
382 local model = colors.forcedmodel(v[1])
383 if model == 2 then
384 local gray = graycolor(v[2])
385 d = { gray, gray, gray, gray }
386 elseif model == 3 then
387 local gray = graycolor(v[2])
388 local rgb = rgbcolor(v[3],v[4],v[5])
389 local cmyk = cmykcolor(v[6],v[7],v[8],v[9])
390 d = { rgb, gray, rgb, cmyk }
391 elseif model == 4 then
392 local gray = graycolor(v[2])
393 local rgb = rgbcolor(v[3],v[4],v[5])
394 local cmyk = cmykcolor(v[6],v[7],v[8],v[9])
395 d = { cmyk, gray, rgb, cmyk }
396 elseif model == 5 then
397 local spot = spotcolor(v[10],v[11],v[12],v[13])
398
399 d = { spot, spot, spot, spot }
400 end
401 end
402 data[n] = d
403 return d
404 end
405end
406
407setmetatableindex(colors, extender)
408setmetatableindex(colors.data, reviver)
409
410function colors.filter(n)
411 return concat(data[n],":",5)
412end
413
414
415
416
417
418
419
420
421
422
423function colors.setmodel(name,weightgray)
424 if weightgray == true or weightgray == v_yes then
425 weightgray = true
426 elseif weightgray == false or weightgray == v_no then
427 weightgray = false
428 else
429 local r, g, b = lpegmatch(p_split_colon,weightgray)
430 if r and g and b then
431 weightgray = { r, g, b }
432 else
433 weightgray = true
434 end
435 end
436 local default = models[name] or 1
437
438 colors.model = name
439 colors.default = default
440 colors.weightgray = weightgray
441
442
443
444 local forced = colors.forced
445
446 if forced == nil then
447
448 colors.forced = default
449 elseif forced == false then
450
451 elseif forced ~= default then
452
453 colors.forced = false
454 else
455
456 end
457 return default
458end
459
460function colors.register(name, colorspace, ...)
461 local stamp = f_colors[colorspace](...)
462 local color = registered[stamp]
463 if not color then
464 color = #values + 1
465 values[color] = colors[colorspace](...)
466 registered[stamp] = color
467
468 end
469 if name then
470 list[a_color][name] = color
471 end
472 return registered[stamp]
473end
474
475function colors.value(id)
476 return values[id]
477end
478
479attributes.colors.handler = nodes.installattributehandler {
480 name = "color",
481 namespace = colors,
482 initializer = states.initialize,
483 finalizer = states.finalize,
484 processor = states.selective,
485 resolver = function() return colors.main end,
486}
487
488function colors.enable(value)
489 setaction("shipouts","attributes.colors.handler",not (value == false or not colors.supported))
490end
491
492function colors.forcesupport(value)
493 colors.supported = value
494 report_colors("color is %ssupported",value and "" or "not ")
495 colors.enable(value)
496end
497
498function colors.toattributes(name)
499 local mc = list[a_color][name]
500 local mm = texgetattribute(a_selector)
501 return (mm == unsetvalue and 1) or mm or 1, mc or list[a_color][1] or unsetvalue
502end
503
504
505
506local a_transparency <const> = attributes.private('transparency')
507
508attributes.transparencies = attributes.transparencies or { }
509local transparencies = attributes.transparencies
510transparencies.registered = transparencies.registered or { }
511transparencies.data = allocate()
512transparencies.values = transparencies.values or { }
513transparencies.triggering = true
514transparencies.attribute = a_transparency
515transparencies.supported = true
516
517local registered = transparencies.registered
518local data = transparencies.data
519local values = transparencies.values
520local f_transparency = formatters["%s:%s"]
521
522registerstorage("attributes/transparencies/registered", registered, "attributes.transparencies.registered")
523registerstorage("attributes/transparencies/values", values, "attributes.transparencies.values")
524
525local register_transparency = registrations.transparency
526local inject_transparency = nodeinjections.transparency
527
528updaters.register("backends.injections.latebindings",function()
529 register_transparency = backends.registrations.transparency
530 inject_transparency = backends.nodeinjections.transparency
531end)
532
533function transparencies.register(name,a,t,force)
534
535
536
537
538 local stamp = f_transparency(a,t)
539 local n = registered[stamp]
540 if not n then
541 n = #values + 1
542 values[n] = { a, t }
543 registered[stamp] = n
544 if force then
545 register_transparency(n,a,t)
546 end
547 elseif force and not data[n] then
548 register_transparency(n,a,t)
549 end
550 if name then
551 list[a_transparency][name] = n
552 end
553 return registered[stamp]
554end
555
556local function extender(transparencies,key)
557 if colors.supported and key == "none" then
558 local d = inject_transparency(0)
559 transparencies.none = d
560 return d
561 end
562end
563
564local function reviver(data,n)
565 if n and transparencies.supported then
566 local v = values[n]
567 local d
568 if not v then
569 d = inject_transparency(0)
570 else
571 d = inject_transparency(n)
572 register_transparency(n,v[1],v[2])
573 end
574 data[n] = d
575 return d
576 else
577 return ""
578 end
579end
580
581setmetatableindex(transparencies,extender)
582setmetatableindex(transparencies.data,reviver)
583
584
585
586function transparencies.value(id)
587 return values[id]
588end
589
590attributes.transparencies.handler = nodes.installattributehandler {
591 name = "transparency",
592 namespace = transparencies,
593 initializer = states.initialize,
594 finalizer = states.finalize,
595 processor = states.process,
596}
597
598function transparencies.enable(value)
599 setaction("shipouts","attributes.transparencies.handler",not (value == false or not transparencies.supported))
600end
601
602function transparencies.forcesupport(value)
603 transparencies.supported = value
604 report_transparencies("transparency is %ssupported",value and "" or "not ")
605 transparencies.enable(value)
606end
607
608function transparencies.toattribute(name)
609 return list[a_transparency][name] or unsetvalue
610end
611
612
613
614attributes.colorintents = attributes.colorintents or { }
615local colorintents = attributes.colorintents
616colorintents.data = allocate()
617
618colorintents.attribute = attributes.private('colorintent')
619
620colorintents.registered = allocate {
621 overprint = 1,
622 knockout = 2,
623}
624
625local inject_overprint = nodeinjections.injectoverprint
626local inject_knockout = nodeinjections.injectknockout
627
628updaters.register("backends.injections.latebindings",function()
629 local nodeinjections = backends.nodeinjections
630 inject_overprint = nodeinjections.injectoverprint
631 inject_knockout = nodeinjections.injectknockout
632end)
633
634local data = colorintents.data
635local registered = colorintents.registered
636
637local function extender(colorintents,key)
638 if key == "none" then
639 local d = data[2]
640 colorintents.none = d
641 return d
642 end
643end
644
645local function reviver(data,n)
646 if n == 1 then
647 local d = inject_overprint()
648 data[1] = d
649 return d
650 elseif n == 2 then
651 local d = inject_knockout()
652 data[2] = d
653 return d
654 end
655end
656
657setmetatableindex(colorintents, extender)
658setmetatableindex(colorintents.data, reviver)
659
660function colorintents.register(stamp)
661 return registered[stamp] or registered.overprint
662end
663
664colorintents.handler = nodes.installattributehandler {
665 name = "colorintent",
666 namespace = colorintents,
667 initializer = states.initialize,
668 finalizer = states.finalize,
669 processor = states.process,
670}
671
672function colorintents.enable()
673 enableaction("shipouts","attributes.colorintents.handler")
674end
675
676
677
678implement { name = "enablecolor", onlyonce = true, actions = colors.enable }
679implement { name = "enabletransparency", onlyonce = true, actions = transparencies.enable }
680implement { name = "enablecolorintents", onlyonce = true, actions = colorintents.enable }
681
682
683
684implement { name = "registercolorintent", actions = { colorintents .register, context }, arguments = "string" }
685 |