module Colors::Convert

Constants

DEG2RAD

degree -> ???

RGB2XYZ

RGB -> ???

XYZ2RGB

XYZ -> ???

Public Instance Methods

degree_to_radian(d) click to toggle source
# File lib/colors/convert.rb, line 57
def degree_to_radian(d)
  d * DEG2RAD
end
lch_to_husl(l, c, h) click to toggle source

LCh -> ???

# File lib/colors/convert.rb, line 63
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) click to toggle source
# File lib/colors/convert.rb, line 76
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) click to toggle source
# File lib/colors/convert.rb, line 83
def lch_to_xyz(l, c, h)
  luv_to_xyz(*lch_to_luv(l, c, h))
end
linear_srgb_to_srgb(r, g, b) click to toggle source

linear-sRGB -> ???

# File lib/colors/convert.rb, line 89
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) click to toggle source

Luv -> ???

# File lib/colors/convert.rb, line 106
def luv_to_husl(l, u, v)
  lch_to_husl(*luv_to_lch(l, u, v))
end
luv_to_lch(l, u, v) click to toggle source
# File lib/colors/convert.rb, line 110
def luv_to_lch(l, u, v)
  c = Math.sqrt(u*u + v*v).to_r

  if c < 1e-8
    h = 0r
  else
    h = Math.atan2(v, u).to_r * 180/Math::PI.to_r
    h += 360r if h < 0
  end

  [l, c, h]
end
luv_to_xyz(l, u, v) click to toggle source
# File lib/colors/convert.rb, line 123
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) click to toggle source
# File lib/colors/convert.rb, line 19
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_xyz(r, g, b) click to toggle source
# File lib/colors/convert.rb, line 147
def rgb_to_xyz(r, g, b)
  dot_product(RGB2XYZ, srgb_to_linear_srgb(r, g, b))
end
srgb_to_linear_srgb(r, g, b) click to toggle source

sRGB -> ???

# File lib/colors/convert.rb, line 153
def srgb_to_linear_srgb(r, g, b)
  [r, g, b].map do |v|
    if v > 0.04045
      ((v + 0.055r) / 1.055r) ** 2.4r
    else
      v / 12.92r
    end
  end
end
xyz_to_rgb(x, y, z) click to toggle source
# File lib/colors/convert.rb, line 170
def xyz_to_rgb(x, y, z)
  r, g, b = dot_product(XYZ2RGB, [x, y, z])
  r, g, b = srgb_to_linear_srgb(r, g, b)
  [
    r.clamp(0r, 1r),
    g.clamp(0r, 1r),
    b.clamp(0r, 1r)
  ]
end

Private Instance Methods

dot_product(matrix, vector) click to toggle source

Utilities

# File lib/colors/convert.rb, line 9
        def dot_product(matrix, vector)
  matrix.map do |row|
    product = 0.0
    row.zip(vector) do |value1, value2|
      product += value1 * value2
    end
    product
  end
end
luminance_bounds(l) click to toggle source
# File lib/colors/convert.rb, line 32
        def luminance_bounds(l)
  sub1 = (l + 16)**3 / 1560896r
  sub2 = sub1 > XYZ::EPSILON ? sub1 : l/XYZ::KAPPA

  bounds = Array.new(6) { [0r, 0r] }
  0.upto(2) do |ch|
    m1 = XYZ2RGB[ch][0].to_r
    m2 = XYZ2RGB[ch][1].to_r
    m3 = XYZ2RGB[ch][2].to_r

    [0, 1].each do |t|
      top1 = (284517r * m1 - 94839r * m3) * sub2
      top2 = (838422r * m3 + 769860r * m2 + 731718r * m1) * l * sub2 - 769860r * t * l
      bottom = (632260r * m3 - 126452r * m2) * sub2 + 126452r * t

      bounds[ch*2 + t][0] = top1 / bottom
      bounds[ch*2 + t][1] = top2 / bottom
    end
  end
  bounds
end