Module: Colors::Convert

Defined in:
lib/colors/convert.rb

Constant Summary collapse

RGB2XYZ =

RGB -> ???

[
  [  0.41239079926595948129,  0.35758433938387796373,  0.18048078840183428751 ],
  [  0.21263900587151035754,  0.71516867876775592746,  0.07219231536073371500 ],
  [  0.01933081871559185069,  0.11919477979462598791,  0.95053215224966058086 ]
]
R_xyY =

sRGB reference points

[0.64r, 0.33r, 1r]
G_xyY =
[0.30r, 0.60r, 1r]
B_xyY =
[0.15r, 0.06r, 1r]
D65_xyY =
[0.3127r, 0.3290r, 1r]
R_XYZ =
xyy_to_xyz(*R_xyY)
G_XYZ =
xyy_to_xyz(*G_xyY)
B_XYZ =
xyy_to_xyz(*B_xyY)
D65_XYZ =
xyy_to_xyz(*D65_xyY)
M_P =
[
  [R_XYZ[0], G_XYZ[0], B_XYZ[0]],
  [R_XYZ[1], G_XYZ[1], B_XYZ[1]],
  [R_XYZ[2], G_XYZ[2], B_XYZ[2]]
]
M_S =
dot_product(matrix_inv(M_P), D65_XYZ)
M_RGB_XYZ =
(0 ... 3).map do |i|
  (0 ... 3).map {|j| (M_S[j] * M_P[i][j]).round(4) }
end
M_XYZ_RGB =
matrix_inv(M_RGB_XYZ).map do |row|
  row.map {|v| v.round(4) }
end

Class Method Summary collapse

Class Method Details

.closest_xterm256_gray_index(r, g, b) ⇒ Object



192
193
194
# File 'lib/colors/convert.rb', line 192

def closest_xterm256_gray_index(r, g, b)
  ((255*(r + g + b) - 24)/30.0).round.clamp(0, 23)
end

.closest_xterm256_rgb_index(x) ⇒ Object



184
185
186
# File 'lib/colors/convert.rb', line 184

def closest_xterm256_rgb_index(x)
  ([x*255 - 55, 0].max / 40.0).round
end

.degree_to_radian(d) ⇒ Object

2 * pi / 360



64
65
66
# File 'lib/colors/convert.rb', line 64

def degree_to_radian(d)
  d * DEG2RAD
end

.lch_to_husl(l, c, h) ⇒ Object

LCh -> ???



70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/colors/convert.rb', line 70

def lch_to_husl(l, c, h)
  if l > 99.9999999 || l < 1e-8
    s = 0r
  else
    mx = max_chroma(l, h)
    s = c / mx * 100r
  end

  h = 0r if c < 1e-8

  [h, s/100r, l/100r]
end

.lch_to_luv(l, c, h) ⇒ Object



83
84
85
86
87
88
# File 'lib/colors/convert.rb', line 83

def lch_to_luv(l, c, h)
  h_rad = degree_to_radian(h)
  u = Math.cos(h_rad).to_r * c
  v = Math.sin(h_rad).to_r * c
  [l, u, v]
end

.lch_to_xyz(l, c, h) ⇒ Object



90
91
92
# File 'lib/colors/convert.rb', line 90

def lch_to_xyz(l, c, h)
  luv_to_xyz(*lch_to_luv(l, c, h))
end

.linear_srgb_to_srgb(r, g, b) ⇒ Object

linear-sRGB -> ???



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/colors/convert.rb', line 96

def linear_srgb_to_srgb(r, g, b)
  [r, g, b].map do |v|
    # the following is an optimization technique for `1.055*v**(1/2.4) - 0.055`.
    # x^y ~= exp(y*log(x)) ~= exp2(y*log2(y)); the middle form is faster
    #
    # See https://github.com/JuliaGraphics/Colors.jl/issues/351#issuecomment-532073196
    # for more detail benchmark in Julia language.
    if v <= 0.0031308
      12.92*v
    else
      1.055 * Math.exp(1/2.4 * Math.log(v)) - 0.055
    end
  end
end

.luv_to_husl(l, u, v) ⇒ Object

Luv -> ???



113
114
115
# File 'lib/colors/convert.rb', line 113

def luv_to_husl(l, u, v)
  lch_to_husl(*luv_to_lch(l, u, v))
end

.luv_to_lch(l, u, v) ⇒ Object



117
118
119
120
121
122
123
# File 'lib/colors/convert.rb', line 117

def luv_to_lch(l, u, v)
  c = Math.sqrt(u*u + v*v).to_r
  hard = Math.atan2(v, u).to_r
  h = hard * 180 / Math::PI.to_r
  h += 360r if h < 0
  [l, c, h]
end

.luv_to_xyz(l, u, v) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/colors/convert.rb', line 125

def luv_to_xyz(l, u, v)
  return [0r, 0r, 0r] if l <= 1e-8

  wp_u, wp_v = WHITE_POINT_D65.uv_values
  var_u = u / (13 * l) + wp_u
  var_v = v / (13 * l) + wp_v
  y = if l < 8
        l / XYZ::KAPPA
      else
        ((l + 16r) / 116r)**3
      end
  x = -(9 * y * var_u) / ((var_u - 4) * var_v - var_u * var_v)
  z = (9 * y - (15 * var_v * y) - (var_v * x)) / (3 * var_v)
  [x, y, z]
end

.max_chroma(l, h) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/colors/convert.rb', line 26

def max_chroma(l, h)
  h_rad = degree_to_radian(h)
  sin_h = Math.sin(h_rad).to_r
  cos_h = Math.cos(h_rad).to_r

  max = Float::INFINITY
  luminance_bounds(l).each do |line|
    len = line[1] / (sin_h - line[0] * cos_h)
    max = len if 0 <= len && len < max
  end
  max
end

.rgb_to_greyscale(r, g, b) ⇒ Object



153
154
155
156
157
# File 'lib/colors/convert.rb', line 153

def rgb_to_greyscale(r, g, b)
  r, g, b = srgb_to_linear_srgb(r, g, b)
  a = RGB2XYZ[1]
  (a[0]*r + a[1]*g + a[2]*b).clamp(0, 1)
end

.rgb_to_xterm256(r, g, b) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/colors/convert.rb', line 159

def rgb_to_xterm256(r, g, b)
  i = closest_xterm256_rgb_index(r)
  j = closest_xterm256_rgb_index(g)
  k = closest_xterm256_rgb_index(b)

  r0 = xterm256_rgb_index_to_rgb_value(i)
  g0 = xterm256_rgb_index_to_rgb_value(j)
  b0 = xterm256_rgb_index_to_rgb_value(k)
  d0 = (r - r0)**2 + (g - g0)**2 + (b - b0)**2

  l = closest_xterm256_gray_index(r, g, b)
  gr = xterm256_gray_index_to_gray_level(l)
  d1 = (r - gr)**2 + (g - gr)**2 + (b - gr)**2

  if d0 > d1
    xterm256_gray_index_to_code(l)
  else
    xterm256_rgb_indices_to_code(i, j, k)
  end
end

.rgb_to_xyz(r, g, b) ⇒ Object



149
150
151
# File 'lib/colors/convert.rb', line 149

def rgb_to_xyz(r, g, b)
  dot_product(RGB2XYZ, srgb_to_linear_srgb(r, g, b))
end

.srgb_from_linear_srgb(r, g, b) ⇒ Object

sRGB -> ???



206
207
208
209
210
211
212
213
214
215
# File 'lib/colors/convert.rb', line 206

def srgb_from_linear_srgb(r, g, b)
  a = 0.055r
  [r, g, b].map do |v|
    if v < 0.0031308
      12.92r * v
    else
      (1 + a) * v**(1/2.4r) - a
    end
  end
end

.srgb_to_linear_srgb(r, g, b) ⇒ Object



217
218
219
220
221
222
223
224
225
226
# File 'lib/colors/convert.rb', line 217

def srgb_to_linear_srgb(r, g, b)
  a = 0.055r
  [r, g, b].map do |v|
    if v > 0.04045
      ((v + a) / (1 + a)) ** 2.4r
    else
      v / 12.92r
    end
  end
end

.xterm256_gray_index_to_code(i) ⇒ Object



200
201
202
# File 'lib/colors/convert.rb', line 200

def xterm256_gray_index_to_code(i)
  i + 232
end

.xterm256_gray_index_to_gray_level(i) ⇒ Object



188
189
190
# File 'lib/colors/convert.rb', line 188

def xterm256_gray_index_to_gray_level(i)
  (10*i + 8)/255.0
end

.xterm256_rgb_index_to_rgb_value(i) ⇒ Object



180
181
182
# File 'lib/colors/convert.rb', line 180

def xterm256_rgb_index_to_rgb_value(i)
  (i == 0) ? 0 : (40*i + 55)/255.0
end

.xterm256_rgb_indices_to_code(i, j, k) ⇒ Object



196
197
198
# File 'lib/colors/convert.rb', line 196

def xterm256_rgb_indices_to_code(i, j, k)
  6*(6*i + j) + k + 16
end

.xyy_to_xyz(x, y, large_y) ⇒ Object

xyY -> ???



230
231
232
233
234
# File 'lib/colors/convert.rb', line 230

def xyy_to_xyz(x, y, large_y)
  large_x = large_y*x/y
  large_z = large_y*(1 - x - y)/y
  [large_x, large_y, large_z]
end

.xyz_to_rgb(x, y, z) ⇒ Object



265
266
267
268
269
270
271
272
273
# File 'lib/colors/convert.rb', line 265

def xyz_to_rgb(x, y, z)
  r, g, b = dot_product(M_XYZ_RGB, [x, y, z])
  r, g, b = srgb_from_linear_srgb(r, g, b)
  [
    r.clamp(0r, 1r),
    g.clamp(0r, 1r),
    b.clamp(0r, 1r)
  ]
end