-
Notifications
You must be signed in to change notification settings - Fork 1
/
cie1931.go
323 lines (274 loc) · 9.74 KB
/
cie1931.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright (c) 2021-2022 David Vogel
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package emission
import (
"fmt"
"math"
)
// CIE1931xyYAbs represents a color in the CIE 1931 XYZ color space with an absolute luminance in lumen.
//
// An equal-energy radiator would result in X == Y == Z.
type CIE1931XYZAbs struct {
X, Y, Z float64
}
var (
_ Value = &CIE1931XYZAbs{}
_ ValueReceiver = &CIE1931XYZAbs{}
)
// IntoDCS implements the Value interface.
func (c CIE1931XYZAbs) IntoDCS(cp ColorProfile) DCSVector {
return cp.XYZToDCS(c)
}
// FromDCS implements the ValueReceiver interface.
func (c *CIE1931XYZAbs) FromDCS(cp ColorProfile, v DCSVector) error {
var err error
if *c, err = cp.DCSToXYZ(v); err != nil {
return fmt.Errorf("failed to convert from DCS to %T: %w", c, err)
}
return nil
}
// Relative returns the color in the CIE 1931 XYZ color space with relative luminance.
//
// maxLuminance defines the highest possible luminance in lumen.
func (c CIE1931XYZAbs) Relative(maxLuminance float64) CIE1931XYZRel {
return CIE1931XYZRel{c.X / maxLuminance, c.Y / maxLuminance, c.Z / maxLuminance}
}
// CIE1931xyYAbs returns the color in the CIE 1931 xyY color space with an absolute luminance in lumen.
func (c CIE1931XYZAbs) CIE1931xyYAbs() CIE1931xyYAbs {
sum := c.X + c.Y + c.Z
return CIE1931xyYAbs{
X: c.X / sum,
Y: c.Y / sum,
LuminanceY: c.Y,
}
}
// Sum returns the sum of c and all colors.
func (c CIE1931XYZAbs) Sum(colors ...CIE1931XYZAbs) CIE1931XYZAbs {
result := c
for _, color := range colors {
result.X += color.X
result.Y += color.Y
result.Z += color.Z
}
return result
}
// Scaled returns c scaled by the scalar s.
func (c CIE1931XYZAbs) Scaled(s float64) CIE1931XYZAbs {
return CIE1931XYZAbs{c.X * s, c.Y * s, c.Z * s}
}
// CrossProd returns the cross product between two color vectors.
func (c CIE1931XYZAbs) CrossProd(c2 CIE1931XYZAbs) CIE1931XYZAbs {
return CIE1931XYZAbs{c.Y*c2.Z - c.Z*c2.Y, c.Z*c2.X - c.X*c2.Z, c.X*c2.Y - c.Y*c2.X}
}
// CIE1931XYZRel represents a color in the CIE 1931 XYZ color space with a relative luminance.
//
// Luminance Y is in the range of [0, 1].
//
// The absolute brightness depends on the device/module that this emission value is rendered on.
// A luminance value Y of 1 corresponds with the full light output.
//
// An equal-energy radiator would result in X == Y == Z.
type CIE1931XYZRel struct {
X, Y, Z float64
}
var (
_ Value = &CIE1931XYZRel{}
_ ValueReceiver = &CIE1931XYZRel{}
)
// IntoDCS implements the Value interface.
func (c CIE1931XYZRel) IntoDCS(cp ColorProfile) DCSVector {
maxLuminance := cp.WhitePoint().Y
return c.Absolute(maxLuminance).IntoDCS(cp)
}
// FromDCS implements the ValueReceiver interface.
func (c *CIE1931XYZRel) FromDCS(cp ColorProfile, v DCSVector) error {
var res CIE1931XYZAbs
if err := res.FromDCS(cp, v); err != nil {
return err
}
maxLuminance := cp.WhitePoint().Y
*c = res.Relative(maxLuminance)
return nil
}
// TransformRGB writes the color into the given RGB color space.
//
// var rgbColor StandardRGB
// c.TransformRGB(&rgbColor) // Writes result into rgbColor.
func (c CIE1931XYZRel) TransformRGB(rgbColor RGB) {
rgbColor.FromCIE1931XYZRel(c)
}
// Absolute returns the color in the CIE 1931 XYZ color space with absolute luminance in lumen.
//
// maxLuminance defines the highest possible luminance in lumen.
func (c CIE1931XYZRel) Absolute(maxLuminance float64) CIE1931XYZAbs {
return CIE1931XYZAbs{c.X * maxLuminance, c.Y * maxLuminance, c.Z * maxLuminance}
}
// CIE1931xyYRel returns the color in the CIE 1931 xyY color space with a relative luminance.
func (c CIE1931XYZRel) CIE1931xyYRel() CIE1931xyYRel {
sum := c.X + c.Y + c.Z
return CIE1931xyYRel{
X: c.X / sum,
Y: c.Y / sum,
LuminanceY: c.Y,
}
}
// ClampedUniform returns XYZ scaled by a single scaling factor in a way so that every component is below or equal to one.
//
// CIE1931XYZRel{1.1, 0.9, -0.1} --> CIE1931XYZRel{1.0, 0.818, -0.091}
func (c CIE1931XYZRel) ClampedUniform() CIE1931XYZRel {
scale := 1.0
if neededScale := 1.0 / c.X; c.X > 1.0 && scale > neededScale {
scale = neededScale
}
if neededScale := 1.0 / c.Y; c.Y > 1.0 && scale > neededScale {
scale = neededScale
}
if neededScale := 1.0 / c.Z; c.Z > 1.0 && scale > neededScale {
scale = neededScale
}
return c.Scaled(scale)
}
// Scaled returns c scaled by the scalar s.
func (c CIE1931XYZRel) Scaled(s float64) CIE1931XYZRel {
return CIE1931XYZRel{c.X * s, c.Y * s, c.Z * s}
}
// Distance returns the euclidean distance between c and c2.
//
// This distance doesn't represent perceptual difference of two colors.
// See the method CIE1976LABDistance() for a better metric.
func (c CIE1931XYZRel) Distance(c2 CIE1931XYZRel) float64 {
xDiff := c.X - c2.X
yDiff := c.Y - c2.Y
zDiff := c.Z - c2.Z
return math.Sqrt(xDiff*xDiff + yDiff*yDiff + zDiff*zDiff)
}
// CIE1976LABDistance returns the euclidean distance between c and c2 in the CIE 1976 L*a*b* color space.
// This is a good measure for perceptual difference of two colors.
//
// The distance is officially called ΔE*ab (with ab being in subscript) or just ΔE*.
// A value of about 2.3 is just noticeable.
func (c CIE1931XYZRel) CIE1976LABDistance(c2 CIE1931XYZRel, whitePoint CIE1931XYZRel) float64 {
cLAB := c.CIE1976LAB(whitePoint)
c2LAB := c2.CIE1976LAB(whitePoint)
return cLAB.Distance(c2LAB)
}
// CIE1976LABDistanceSqr returns the squared euclidean distance between c and c2 in the CIE 1976 L*a*b* color space.
//
// See c.CIE1976LABDistance() for details.
func (c CIE1931XYZRel) CIE1976LABDistanceSqr(c2 CIE1931XYZRel, whitePoint CIE1931XYZRel) float64 {
cLAB := c.CIE1976LAB(whitePoint)
c2LAB := c2.CIE1976LAB(whitePoint)
return cLAB.DistanceSqr(c2LAB)
}
// CIE1976LAB returns the color transformed into the CIE 1976 L*a*b* color space with the given white point.
func (c CIE1931XYZRel) CIE1976LAB(whitePoint CIE1931XYZRel) CIE1976LAB {
// Using the intent of the CIE standard, not the numbers published by the CIE. See http://www.brucelindbloom.com/LContinuity.html.
const delta = 6.0 / 29
f := func(t float64) float64 {
if t > delta*delta*delta {
return math.Pow(t, 1.0/3)
} else {
return t/(3*delta*delta) + 4.0/29
}
}
return CIE1976LAB{
L: 116*f(c.Y/whitePoint.Y) - 16,
A: 500 * (f(c.X/whitePoint.X) - f(c.Y/whitePoint.Y)),
B: 200 * (f(c.Y/whitePoint.Y) - f(c.Z/whitePoint.Z)),
WhitePoint: whitePoint,
}
}
// CIE1931xyYAbs represents a color in the CIE 1931 xyY color space with an absolute luminance in lumen.
//
// An equal-energy radiator would result in x == y == 1/3.
type CIE1931xyYAbs struct {
X, Y float64 // x, y in the range of [0, 1]
LuminanceY float64 // Luminance Y in lumen.
}
var (
_ Value = &CIE1931xyYAbs{}
_ ValueReceiver = &CIE1931xyYAbs{}
)
// IntoDCS implements the Value interface.
func (c CIE1931xyYAbs) IntoDCS(cp ColorProfile) DCSVector {
return cp.XYZToDCS(c.CIE1931XYZAbs())
}
// FromDCS implements the ValueReceiver interface.
func (c *CIE1931xyYAbs) FromDCS(cp ColorProfile, v DCSVector) error {
if xyzColor, err := cp.DCSToXYZ(v); err != nil {
return fmt.Errorf("failed to convert from DCS to %T: %w", c, err)
} else {
sum := xyzColor.X + xyzColor.Y + xyzColor.Z
*c = CIE1931xyYAbs{xyzColor.X / sum, xyzColor.Y / sum, xyzColor.Y}
}
return nil
}
// Relative returns the color in the CIE 1931 xyY color space with relative luminance.
//
// maxLuminance defines the highest possible luminance in lumen.
func (c CIE1931xyYAbs) Relative(maxLuminance float64) CIE1931xyYRel {
return CIE1931xyYRel{c.X, c.Y, c.LuminanceY / maxLuminance}
}
// CIE1931XYZAbs returns the color in the CIE 1931 XYZ color space with absolute luminance in lumens.
func (c CIE1931xyYAbs) CIE1931XYZAbs() CIE1931XYZAbs {
return CIE1931XYZAbs{
(c.X * c.LuminanceY) / c.Y,
c.LuminanceY,
(1 - c.X - c.Y) * c.LuminanceY / c.Y,
}
}
// Scaled returns c scaled by the scalar s.
func (c CIE1931xyYAbs) Scaled(s float64) CIE1931xyYAbs {
return CIE1931xyYAbs{c.X, c.Y, c.LuminanceY * s}
}
// CIE1931xyYRel represents a color in the CIE 1931 xyY color space with a relative luminance.
//
// LuminanceY is in the range of [0, 1].
//
// The absolute brightness depends on the device/module that this emission value is rendered on.
// A luminance value Y of 1 corresponds with the full light output.
//
// An equal-energy radiator would result in x == y == 1/3.
type CIE1931xyYRel struct {
X, Y float64 // x, y in the range of [0, 1]
LuminanceY float64 // Relative luminance Y in the range of [0, 1].
}
var (
_ Value = &CIE1931xyYRel{}
_ ValueReceiver = &CIE1931xyYRel{}
)
// IntoDCS implements the Value interface.
func (c CIE1931xyYRel) IntoDCS(cp ColorProfile) DCSVector {
maxLuminance := cp.WhitePoint().Y
return c.Absolute(maxLuminance).IntoDCS(cp)
}
// FromDCS implements the ValueReceiver interface.
func (c *CIE1931xyYRel) FromDCS(cp ColorProfile, v DCSVector) error {
var res CIE1931xyYAbs
if err := res.FromDCS(cp, v); err != nil {
return err
}
maxLuminance := cp.WhitePoint().Y
*c = res.Relative(maxLuminance)
return nil
}
// Absolute returns the color in the CIE 1931 xyY color space with absolute luminance in lumen.
//
// maxLuminance defines the highest possible luminance in lumen.
func (c CIE1931xyYRel) Absolute(maxLuminance float64) CIE1931xyYAbs {
return CIE1931xyYAbs{c.X, c.Y, c.LuminanceY * maxLuminance}
}
// CIE1931XYZRel returns the color in the CIE 1931 XYZ color space with relative luminance.
func (c CIE1931xyYRel) CIE1931XYZRel() CIE1931XYZRel {
return CIE1931XYZRel{
(c.X * c.LuminanceY) / c.Y,
c.LuminanceY,
(1 - c.X - c.Y) * c.LuminanceY / c.Y,
}
}
// Scaled returns c scaled by the scalar s.
func (c CIE1931xyYRel) Scaled(s float64) CIE1931xyYRel {
return CIE1931xyYRel{c.X, c.Y, c.LuminanceY * s}
}