Current section

Files

Jump to
cssex lib structs hsla.ex
Raw

lib/structs/hsla.ex

defmodule CSSEx.HSLA do
alias CSSEx.Unit
@moduledoc """
Struct and helper functions for generating HSLA values.
"""
defstruct h: %Unit{value: 0, unit: nil},
s: %Unit{value: 0, unit: "%"},
l: %Unit{value: 0, unit: "%"},
a: 1
@type t() :: %CSSEx.HSLA{
h: non_neg_integer,
s: non_neg_integer,
l: non_neg_integer,
a: non_neg_integer
}
@colors CSSEx.Helpers.Colors.colors_tuples()
@doc """
Accepts any value in the form of a binary `"hsla(0, 10%, 20%, 0.5)"` or `"hsl(0, 10%, 20%)"`, any hexadecimal representation in binary in the form of `"#xxx"`, `"#xxxx"`, `"#xxxxxx"` or `"#xxxxxxxx"`, rgb/a as `"rgba(100,100,100,0.1)"` or `"rgb(10,20,30)"`, or any literal color name defined as web colors (CSSEx.Colors) - returns a `%CSSEx.HSLA{}` struct.
"""
@spec new_hsla(String.t()) :: {:ok, %CSSEx.HSLA{}} | {:error, term}
def new_hsla(<<"hsla", values::binary>>) do
case Regex.run(~r/\((.+),(.+),(.+),(.+)\)/, values) do
[_, h, s, l, a] ->
new(
String.trim(h),
String.trim(s),
String.trim(l),
String.trim(a)
)
_ ->
{:error, :invalid}
end
end
def new_hsla(<<"hsl", values::binary>>) do
case Regex.run(~r/\((.+),(.+),(.+)\)/, values) do
[_, h, s, l] ->
new(
String.trim(h),
String.trim(s),
String.trim(l),
"1"
)
_ ->
{:error, :invalid}
end
end
def new_hsla(<<"rgb", _::binary>> = full) do
case CSSEx.RGBA.new_rgba(full) do
{:ok, rgba} -> from_rgba(rgba)
error -> error
end
end
def new_hsla(<<"#", _::binary>> = full) do
case CSSEx.RGBA.new_rgba(full) do
{:ok, rgba} -> from_rgba(rgba)
error -> error
end
end
Enum.each(@colors, fn [color, rgba] ->
def new_hsla(unquote(color)) do
case CSSEx.RGBA.new_rgba(unquote(rgba)) do
{:ok, new_rgba} -> from_rgba(new_rgba)
error -> error
end
end
end)
@doc """
Converts an existing `%CSSEx.RGBA{}` struct into a `%CSSEx.HSLA{}` struct.
Taken from https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
"""
@spec from_rgba(%CSSEx.RGBA{}) :: {:ok, %CSSEx.HSLA{}}
def from_rgba(%CSSEx.RGBA{r: r, g: g, b: b, a: a}) do
n_r = r / 255
n_g = g / 255
n_b = b / 255
{min, max} = Enum.min_max([n_r, n_g, n_b])
luminance = (min + max) / 2 * 100
saturation =
case {min, max} do
{same, same} -> 0
{min, max} when luminance <= 50 -> (max - min) / (max + min) * 100
{min, max} -> (max - min) / (2 - max - min) * 100
end
hue =
case {n_r, n_g, n_b} do
{same, same, same} -> 0
{n_r, n_g, n_b} when n_r > n_g and n_r > n_b -> (n_g - n_b) / (max - min)
{n_r, n_g, n_b} when n_g > n_r and n_g > n_b -> 2 + (n_b - n_r) / (max - min)
{n_r, n_g, _n_b} -> 4 + (n_r - n_g) / (max - min)
end
hue_2 =
case hue * 60 do
new_hue when new_hue < 0 -> new_hue + 360
new_hue -> new_hue
end
{:ok,
%__MODULE__{
h: %Unit{value: round(hue_2), unit: nil},
s: %Unit{value: round(saturation), unit: "%"},
l: %Unit{value: round(luminance), unit: "%"},
a: a
}}
end
@doc """
Generates a `%CSSEx.HSLA{}` wrapped in an :ok tuple, from the values of h, s, l, and alpha. All values are treated as decimal.
"""
def new(h, s, l, a),
do: {
:ok,
%__MODULE__{
h: new_hue(h),
s: new_saturation(s),
l: new_luminance(l),
a: alpha_value(a, 10)
}
}
@doc false
def new_hue(val) when is_binary(val) do
case Integer.parse(val, 10) do
{parsed, _} -> valid_hue_val(parsed)
:error -> %Unit{value: 0, unit: nil}
end
end
def new_hue(val) when is_integer(val) or is_float(val), do: valid_hue_val(val)
@doc false
def new_saturation(val) when is_binary(val) do
case Integer.parse(val, 10) do
{parsed, _} -> valid_saturation_val(parsed)
:error -> %Unit{value: 0, unit: "%"}
end
end
def new_saturation(val) when is_integer(val) or is_float(val),
do: valid_saturation_val(val)
@doc false
def new_luminance(val) when is_binary(val) do
case Integer.parse(val, 10) do
{parsed, _} -> valid_luminance_val(parsed)
:error -> %Unit{value: 0, unit: "%"}
end
end
def new_luminance(val) when is_integer(val) or is_float(val),
do: valid_luminance_val(val)
@doc false
def alpha_value(val, 10) do
case Float.parse(val) do
{parsed, _} -> valid_alpha_val(parsed)
:error -> 1
end
end
@doc false
def valid_hue_val(n) when n <= 360 and n >= 0, do: %Unit{value: n, unit: nil}
def valid_hue_val(_n), do: %Unit{value: 0, unit: nil}
@doc false
def valid_saturation_val(n) when n <= 100 and n >= 0, do: %Unit{value: n, unit: "%"}
def valid_saturation_val(_n), do: %Unit{value: 0, unit: "%"}
@doc false
def valid_luminance_val(n) when n <= 100 and n >= 0, do: %Unit{value: n, unit: "%"}
def valid_luminance_val(_n), do: %Unit{value: 0, unit: "%"}
@doc false
def valid_alpha_val(n) when n > 0 and n <= 1, do: n
def valid_alpha_val(_n), do: 1
end
defimpl String.Chars, for: CSSEx.HSLA do
def to_string(%CSSEx.HSLA{
h: %CSSEx.Unit{value: h},
s: %CSSEx.Unit{value: s} = su,
l: %CSSEx.Unit{value: l} = lu,
a: a
}),
do: "hsla(#{round(h)},#{%{su | value: round(s)}},#{%{lu | value: round(l)}},#{a})"
end