defmodule Image.Color do
@moduledoc """
Functions to manage image color and color conversion.
"""
@typedoc """
Reference to an ICC color profile
* `:none` means no profile
* `:cmyk`, `:srgb` and `:p3` refer to the built-in color profiles
* `Path.t()` means any file system path. If the path is a relative
path then is will be loaded from the systems profile directory.
"""
@type icc_profile :: :none | :cmyk | :srgb | :p3 | Path.t()
@typedoc """
An rbg color expressed as a list of numbers.
The number of list elements and the type
varies depending on the image format, colorspace
and dimensions.
For a common `sRGB` image it will be a list of
three of four images. If the fourth number is provided
it will be considered as an alpha transparency band.
"""
@type rgb_color :: [number()]
@inbuilt_profiles [:none, :srgb, :cmyk, :p3]
@doc """
Guards whether a given value can be interpreted
as a color value.
"""
defguard is_color(color)
when (is_number(color) and color > 0) or (is_list(color) and length(color) == 3)
@doc """
Guards whether a given profile is one of the inbuilt
profiles.
"""
defguard is_inbuilt_profile(profile) when profile in @inbuilt_profiles
@doc """
Returns the list of color profiles built into
`libvips`.
"""
def inbuilt_profiles, do: @inbuilt_profiles
@doc """
Returns a boolean indicating if the given
profile is known and can be used for image
operations.
"""
def known_icc_profile?(profile) when profile in @inbuilt_profiles do
true
end
def known_icc_profile?(path) do
case Vix.Vips.Operation.profile_load(path) do
{:ok, _} -> true
_other -> false
end
end
@priv_dir :code.priv_dir(:image) |> List.to_string()
@path Path.join(@priv_dir, "color_map.csv")
@color_map File.read!(@path)
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, ", "))
|> Enum.map(fn [name, hex] ->
<<"#", r::bytes-2, g::bytes-2, b::bytes-2>> = hex
rgb = [String.to_integer(r, 16), String.to_integer(g, 16), String.to_integer(b, 16)]
{String.downcase(name), hex: hex, rgb: rgb}
end)
|> Map.new()
@doc """
Returns a mapping from CSS color names to CSS hex values
and RGB triplets as a list.
"""
def color_map do
@color_map
end
def rgb_color!(color) when is_binary(color) do
case color do
<<"#", r::bytes-2, g::bytes-2, b::bytes-2>> ->
[String.to_integer(r, 16), String.to_integer(g, 16), String.to_integer(b, 16)]
color ->
color_map()
|> Map.fetch!(normalize(color))
|> Keyword.fetch!(:rgb)
end
end
def rgb_color!(color) when is_color(color) do
color
end
@opacity 255
def rgba_color!(color, a \\ @opacity)
def rgba_color!(color, _a) when color in [:none, :transparent] do
[0.0, 0.0, 0.0, 1.0]
end
def rgba_color!(color, a) when is_binary(color) and is_integer(a) do
[r, g, b] = rgb_color!(color)
[r, g, b, a]
end
def rgba_color!(color, a) when is_binary(color) and is_float(a) and a >= 0.0 and a <= 1.0 do
a = round(@opacity * a)
[r, g, b] = rgb_color!(color)
[r, g, b, a]
end
def rgba_color!([r, g, b], a) do
[r, g, b, a]
end
def normalize(color) do
color
|> to_string()
|> String.downcase()
|> String.replace(["_", "-", " "], "")
end
end