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