Packages
image
0.48.1
0.72.0
0.71.0
0.70.0
0.69.0
0.68.0
0.67.0
0.67.0-dev
retired
0.66.0
0.65.0
0.64.0
0.63.0
0.62.1
0.62.0
0.61.1
0.61.0
0.60.0
0.59.3
0.59.2
0.59.1
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.2
0.55.1
retired
0.55.0
0.54.4
0.54.3
0.54.2
0.54.1
0.54.0
0.53.0
0.52.3
0.52.2
0.52.1
0.52.0
retired
0.51.0
0.50.0
0.49.0
0.48.1
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.2
0.43.1
0.43.0
0.42.0
0.41.0
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.0
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.31.0
0.30.0
0.29.0
0.28.2
0.28.1
0.28.0
0.27.0
0.26.0
0.25.1
0.25.0
0.24.1
0.24.0
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
retired
0.19.0
0.18.1
0.18.0
0.17.0
0.16.0
0.15.0
0.14.4
0.14.2
0.14.1
0.14.0
retired
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.10.0-rc.0
retired
0.9.0
retired
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
An approachable image processing library primarily based upon Vix and libvips that is NIF-based, fast, multi-threaded, pipelined and has a low memory footprint.
Current section
Files
Jump to
Current section
Files
lib/image/color.ex
defmodule Image.Color do
@moduledoc """
Functions to manage image color and color conversion.
"""
@priv_dir :code.priv_dir(:image) |> List.to_string()
@css_color_path Path.join(@priv_dir, "color/css_colors.csv")
@additional_color_path Path.join(@priv_dir, "color/additional_colors.csv")
@external_resource @additional_color_path
@max_opacity 255
@min_opacity 0
@css_colors File.read!(@css_color_path)
@additional_colors File.read!(@additional_color_path)
@color_map (@css_colors <> "\n" <> @additional_colors)
|> String.split("\n", trim: true)
|> Enum.reject(&String.starts_with?(&1, "#"))
|> 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()
@css_color Map.keys(@color_map)
|> Enum.map(&String.to_atom/1)
@greyscale_color_map @color_map
|> Enum.filter(fn {_name, [hex: _hex, rgb: [r, g, b]]} ->
if r == b && r == g, do: true, else: false
end)
|> Map.new()
# TODO Put back when we validate greyscale colors
# @greyscale_color Map.keys(@greyscale_color_map)
# |> Enum.map(&String.to_atom/1)
@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()] | number()
@typedoc """
A color can be expressed as a list of numbers or
as a CSS color name in atom or string format.
"""
@type t :: rgb_color | atom() | String.t()
@typedoc """
A transparency value which is one of the atoms `:none`,
`:transparent` or `:opaque`. Or an integer between `0` and
`255` where 0 is transparent and 255 is opaque. Or a float
in the range `0.0` to `1.0` that is converted to the range
`0` to `255`.
"""
@type transparency :: :none | :transparent | :opaque | non_neg_integer() | float()
@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()
@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) in 3..5) or
color in @css_color
@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
@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
@doc """
Returns a mapping from CSS color names to CSS hex values
and RGB triplets as a list - but only for greyscal colors.
"""
def greyscale_color_map do
@greyscale_color_map
end
@doc """
Converts a color name or RGB value
to a hex string.
### Arguments
`color` which can be specified as a single integer
which or a list of integers representing the color.
The color can also be supplied as a CSS color name as a
string or atom. For example: `:misty_rose`. See
`Image.Color.color_map/0` and `Image.Color.rgb_color/1`.
### Returns
* `{:ok, #RRGGBB}` string or
* `{:error, reason}`.
### Examples
iex> Image.Color.rgb_to_hex(:green)
{:ok, "#008000"}
iex> Image.Color.rgb_to_hex([10,20,30])
{:ok, "#A141E"}
"""
def rgb_to_hex(color) do
with {:ok, color} <- validate_color(color) do
case color do
[color] -> {:ok, convert_to_hex(color, color, color)}
[r, g, b] -> {:ok, convert_to_hex(r, g, b)}
end
end
end
@doc """
Converts a hex color string to an RGB
list.
### Arguments
`color` is a hex string representing an RGB color. It has
the form `#RRGGBB`.
### Returns
* `{:ok, [r, g, b]}` or
* `{:error, reason}`.
### Examples
iex> Image.Color.validate_color "#0000FF"
{:ok, [0, 0, 255]}
"""
def hex_to_rgb(<<"#", r::bytes-2, g::bytes-2, b::bytes-2>>) do
{:ok, [String.to_integer(r, 16), String.to_integer(g, 16), String.to_integer(b, 16)]}
end
def hex_to_rgb(invalid_color) do
{:error, "Invalid hex color. Found #{inspect(invalid_color)}"}
end
defp convert_to_hex(r, g, b) do
"#" <> to_hex(r) <> to_hex(g) <> to_hex(b)
end
defp to_hex(i) when i >= 10 do
Integer.to_string(i, 16)
end
defp to_hex(i) do
"0" <> Integer.to_string(i, 16)
end
@doc """
Validates a color returning an
`[r, g, b]` triplet or error.
### Arguments
`color` which can be specified as a single integer
which or a list of integers representing the color.
The color can also be supplied as a CSS color name as a
string or atom. For example: `:misty_rose`. See
`Image.Color.color_map/0` and `Image.Color.rgb_color/1`.
### Returns
* `{:ok, [r, g, b]}` or
* `{:error, reason}`
"""
def validate_color(color) do
case rgb_color(color) do
{:ok, [hex: _hex, rgb: rgb]} -> {:ok, rgb}
{:ok, color} when is_list(color) -> {:ok, color}
{:ok, color} when is_integer(color) -> {:ok, [color, color, color]}
other -> other
end
end
@doc """
Returns a transparency value in the range 0 to 255
where 0 means transparent and 255 means opqque.
"""
def validate_transparency(float) when float >= 0.0 and float <= 1.0 do
{:ok, round(@max_opacity * float)}
end
def validate_transparency(int) when int in 0..255 do
{:ok, int}
end
def validate_transparency(:transparent), do: {:ok, @min_opacity}
def validate_transparency(:none), do: {:ok, @min_opacity}
def validate_transparency(:opaque), do: {:ok, @max_opacity}
def validate_transparency(other), do: {:error, "Invalid transparency value. Found #{inspect other}"}
def rgb_color(color) when is_binary(color) or is_atom(color) do
case color do
<<"#", r::bytes-2, g::bytes-2, b::bytes-2>> ->
{:ok, [String.to_integer(r, 16), String.to_integer(g, 16), String.to_integer(b, 16)]}
color ->
case Map.fetch(color_map(), normalize(color)) do
{:ok, color} -> {:ok, color}
:error -> {:error, "Invalid color #{inspect(color)}"}
end
end
end
def rgb_color(color) when is_color(color) do
{:ok, color}
end
def rgb_color!(color) do
case rgb_color(color) do
{:ok, color} -> color
{:error, reason} -> raise ArgumentError, reason
end
end
@doc false
def max_opacity do
@max_opacity
end
@doc false
def min_opacity do
@min_opacity
end
@doc false
def rgba_color!(color, a \\ @max_opacity)
def rgba_color!(color, _a) when color in [:none, :transparent] do
[0, 0, 0, @min_opacity]
end
def rgba_color!(color, a) when is_binary(color) and is_integer(a) do
[r, g, b] =
case rgb_color!(color) do
[hex: _hex, rgb: rgb_color] -> rgb_color
[_r, _g, _b] = rgb_color -> rgb_color
end
[r, g, b, a]
end
def rgba_color!(color, a)
when (is_binary(color) or is_atom(color)) and is_float(a) and a >= 0.0 and a <= 1.0 do
a = round(@max_opacity * a)
rgba_color!(color, a)
end
def rgba_color!(color, a)
when (is_binary(color) or is_atom(color)) and is_integer(a) and a >= 0 do
[r, g, b] = Keyword.get(rgb_color!(color), :rgb, color)
[r, g, b, a]
end
def rgba_color!(color, a) when is_integer(color) and color >= 0 do
rgba_color!([color, color, color], a)
end
def rgba_color!([r, g, b], a) when is_integer(a) and a >= 0 do
[r, g, b, a]
end
def rgba_color!([_r, _g, _b, _a] = color, _alpha) do
color
end
@doc false
def normalize(color) do
color
|> to_string()
|> String.downcase()
|> String.replace(["_", "-", " "], "")
end
end