lib/color/conversion/rainbow.ex

defmodule Fledex.Color.Conversion.Rainbow do
  import Bitwise
  use Fledex.Color.Types
  alias Fledex.Color.Utils

  @k255 255
  @k171 171
  @k170 170
  @k85   85

  @spec hsv2rgb(hsv, (rgb -> rgb)) :: rgb
  def hsv2rgb({h,s,v}, extra_color_correction) do
    determine_rgb(h)
      |> extra_color_correction.()
      |> desaturate(s)
      |> scale_brightness(v)
  end

  @spec determine_rgb(byte) :: rgb
  defp determine_rgb(h) do
    main = {(h &&& 0x80) > 0, (h &&& 0x40) > 0, (h &&& 0x20) > 0}
    offset = h &&& 0x1F
    offset8 = offset <<< 3

    third = Utils.scale8(offset8, Kernel.trunc(256/3))
    twothird = Utils.scale8(offset8, Kernel.trunc((256*2) / 3))
    build_rgb(main, third, twothird)
  end

  @spec build_rgb({boolean, boolean, boolean}, byte, byte) :: rgb
  defp build_rgb({:false,:false,:false}, third, _twothird) do
    {@k255 - third, third, 0}
  end
  defp build_rgb({:false, :false, :true}, third, _twothird) do
    {@k171, @k85 + third, 0}
  end
  defp build_rgb({:false, :true, :false}, third, twothird) do
    {@k171 - twothird, @k170 + third, 0}
  end
  defp build_rgb({:false, :true, :true}, third, _twothird) do
    {0, @k255 - third, third}
  end
  defp build_rgb({:true, :false, :false}, _third, twothird) do
    {0, @k171 - twothird, @k85 + twothird}
  end
  defp build_rgb({:true, :false, :true}, third, _twothird) do
    {third, 0, @k255 - third}
  end
  defp build_rgb({:true, :true, :false}, third, _twothird) do
    {@k85 + third, 0, @k171 - third}
  end
  defp build_rgb({:true, :true, :true}, third, _twothird) do
    {@k170 + third, 0, @k85 - third}
  end

  @spec desaturate(rgb, byte) :: rgb
  defp desaturate({r, g, b}, 255), do: {r, g, b}
  defp desaturate(_, 0), do: {255, 255, 255}
  defp desaturate({r,g,b}, s) do
    desat = 255 - s
    desat = Utils.scale8(desat, desat, true)
    satscale = 255 - desat
    {r,g,b} = Utils.nscale8({r,g,b}, satscale, true)

    {r+desat, g+desat, b+desat}
  end

  # scales the brightness (the v part of HSV, aka HSB)
  @spec scale_brightness(rgb, byte) :: rgb
  defp scale_brightness(rgb, 255), do: rgb
  defp scale_brightness(_, v) when ((v*v) >>> 8) == 0, do: {0, 0, 0}
  defp scale_brightness({r,g,b}, v) do
    val = Utils.scale8(v, v, true)
    Utils.nscale8({r,g,b}, val, true)
  end
end