lib/color/conversion/approximate.ex

defmodule Fledex.Color.Conversion.Approximate do
  use Fledex.Color.Types

  @hue_red 0
  @hue_orange 32
  @hue_yellow 64
  @hue_green 96
  @hue_aqua 128
  @hue_blue 160
  @hue_purple 192
  @hue_pink 224

  alias Fledex.Color.Utils

  @spec rgb2hsv(rgb) :: hsv
  def rgb2hsv({r,g,b}) do
    desat = find_desaturation({r,g,b})
    {r,g,b} = {r-desat, g-desat, b-desat}
    s = 255 - desat
    s = if (s != 255) do
      255 - trunc(:math.sqrt((255 - s) * 256));
    else
      s
    end
    if (r + g + b == 0) do
      {0, 0, 255-s}
    else
      {r,g,b} = scale_to_compensate({r,g,b}, s)     # for desaturation
      total = r+g+b
      {r,g,b} = scale_to_compensate({r,g,b}, total) # for small value
      v = if (total > 255) do
        255
      else
        v = qadd8(desat, total)
        if (v != 255), do: :math.sqrt(v*256), else: v
      end
      highest = Enum.max([r,g,b])
      h = case {{r,g,b}, highest} do
        {{r, 0, _b}, r} ->
          ((@hue_purple + @hue_pink) / 2) + Utils.scale8( qsub8(r, 128), fixfrac8(48,128))
        {{r,g,_b}, r} when (r - g) > g ->
          @hue_red + Utils.scale8( g, fixfrac8(32,85))
        {{r,g,_b}, r} ->
          @hue_orange + Utils.scale8( qsub8((g - 85) + (171 - r), 4), fixfrac8(32,85));
        {{r,g,0}, g} ->
          @hue_yellow + ((Utils.scale8( qsub8(171,r), 47) + Utils.scale8( qsub8(g,171), 96))/2)
        {{_r,g,b}, g} when (g-b) > b ->
          @hue_green + Utils.scale8( b, fixfrac8(32,85))
        {{_r,g,b}, g} ->
          @hue_aqua + Utils.scale8( qsub8(b, 85), fixfrac8(8,42))
        {{0,_g,b}, b} ->
          @hue_aqua + ((@hue_blue - @hue_aqua) / 4) + Utils.scale8( qsub8(b, 128), fixfrac8(24,128))
        {{r,_g,b}, b} when (b-r) > r ->
          @hue_blue + Utils.scale8( r, fixfrac8(32,85))
        {{r,_g,b}, b} ->
          @hue_purple + Utils.scale8( qsub8(r, 85), fixfrac8(32,85))
      end
      {h+1, s, v}
    end
  end

  defp fixfrac8(n,d) do
    trunc((n*256)/(d))
  end

  @spec scale_to_compensate(rgb, byte) :: rgb
  defp scale_to_compensate({r,g,b}, s) when s < 255 do
    s = if s == 0, do: 1, else: s
    scaleup = 655535 / (s)
    r = trunc(r*scaleup/256)
    g = trunc(g*scaleup/256)
    b = trunc(b*scaleup/256)
    {r,g,b}
  end
  defp scale_to_compensate({r,g,b}, _s), do: {r,g,b}

  @spec find_desaturation(rgb) :: byte
  defp find_desaturation({r,g,b}) do
    #     // find desaturation
    #     uint8_t desat = 255;
    #     if( r < desat) desat = r;
    #     if( g < desat) desat = g;
    #     if( b < desat) desat = b;
    255
      |> adj_desat(r)
      |> adj_desat(g)
      |> adj_desat(b)
  end

  @spec adj_desat(byte, byte) :: byte
  defp adj_desat(desat, value)
  defp adj_desat(desat, value) when value < desat, do: value
  defp adj_desat(desat, _value), do: desat

  @spec qadd8(byte, byte) :: byte
  defp qadd8(i,j) when i+j>255, do: 255
  defp qadd8(i, j) do
    i + j
  end

  @spec qsub8(byte, byte) :: byte
  defp qsub8(i,j) when i-j<0, do: 0
  defp qsub8(i,j) do
    i - j
  end
end