Skip to content

Commit

Permalink
API change - move to protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
supersimple committed May 12, 2018
1 parent c5e3214 commit 4e502e5
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 189 deletions.
19 changes: 10 additions & 9 deletions lib/chameleon.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,29 @@ defmodule Chameleon do
Chameleon is a utility that converts colors from one model to another.
It currently supports: Hex, RGB, CMYK, HSL, Pantone, and Keywords.
## Use
Conversion requires a color value, an input color model, and an output
color model.
Example: `Chameleon.convert("FFFFFF", :hex, :rgb) -> {:ok, %{r: 255, g: 255, b: 255}}`
Conversion requires an input color struct, and an output color model.
Example: `Chameleon.Converter.convert(%Chameleon.Color.new(%{hex: "FFFFFF"}), :rgb) -> %Chameleon.Rgb{r: 255, g: 255, b: 255}`
If a translation cannot be made, the response will be an error tuple with
the input value returned.
Example: `Chameleon.convert("F69292", :hex, :pantone) -> {:error, "F69292"}`
Example: `Chameleon.Converter.convert(%Chameleon.Color.new(%{hex: "F69292"}), :pantone) -> {:error, "F69292"}`
In this example, there is no pantone value that matches that hex value, but
an error could also be caused by a bad input value;
Example: `Chameleon.convert("Reddish-Blue", :keyword, :hex)`
Example: `Chameleon.Convert.convert(%Chameleon.Color.new(%{keyword: "Reddish-Blue"}, :hex)`
"""

@doc """
This is the only public interface available.
## Examples
iex> Chameleon.convert("000000", :hex, :keyword)
{:ok, "black"}
iex> input = Chameleon.Color.new(%{hex: "000000"})
iex> Chameleon.Converter.convert(input, :keyword)
%Chameleon.Keyword{keyword: "black"}
iex> Chameleon.convert("black", :keyword, :cmyk)
{:ok, %{c: 0, m: 0, y: 0, k: 100}}
iex> input = Chameleon.Color.new(%{keyword: "black"})
iex> Chameleon.Converter.convert(input, :cmyk)
%Chameleon.Cmyk{c: 0, m: 0, y: 0, k: 100}
"""
@spec convert(any, atom, atom) :: tuple()
def convert(value, input_model, output_model) do
Expand Down
60 changes: 31 additions & 29 deletions lib/chameleon/cmyk.ex
Original file line number Diff line number Diff line change
@@ -1,85 +1,87 @@
defmodule Chameleon.Cmyk do
alias Chameleon.{Rgb, Hex, Util}
@enforce_keys [:c, :m, :y, :k]
defstruct @enforce_keys

@doc """
Converts a cmyk color to its rgb value.
## Examples
iex> Chameleon.Cmyk.to_rgb([100, 0, 100, 0])
%{r: 0, g: 255, b: 0}
iex> Chameleon.Cmyk.to_rgb(%Chameleon.Cmyk{c: 100, m: 0, y: 100, k: 0})
%Chameleon.Rgb{r: 0, g: 255, b: 0}
"""
@spec to_rgb(list(integer)) :: list(integer)
@spec to_rgb(struct()) :: struct()
def to_rgb(cmyk) do
adjusted_cmyk = Enum.map(cmyk, fn v -> v / 100.0 end)
[c, m, y, k] = adjusted_cmyk
[c, m, y, k] = Enum.map([cmyk.c, cmyk.m, cmyk.y, cmyk.k], fn v -> v / 100.0 end)

r = round(Float.round(255.0 * (1.0 - c) * (1.0 - k)))
g = round(Float.round(255.0 * (1.0 - m) * (1.0 - k)))
b = round(Float.round(255.0 * (1.0 - y) * (1.0 - k)))

%{r: r, g: g, b: b}
Chameleon.Color.new(%{r: r, g: g, b: b})
end

@doc """
Converts a cmyk color to its hsl value.
## Examples
iex> Chameleon.Cmyk.to_hsl([100, 0, 100, 0])
%{h: 120, s: 100, l: 50}
iex> Chameleon.Cmyk.to_hsl(%Chameleon.Cmyk{c: 100, m: 0, y: 100, k: 0})
%Chameleon.Hsl{h: 120, s: 100, l: 50}
"""
@spec to_hsl(list(integer)) :: list(integer)
@spec to_hsl(struct()) :: struct()
def to_hsl(cmyk) do
cmyk
|> to_rgb()
|> rgb_values()
|> Rgb.to_hsl()
|> Chameleon.Converter.convert(:hsl)
end

@doc """
Converts a cmyk color to its hex value.
## Examples
iex> Chameleon.Cmyk.to_hex([100, 0, 100, 0])
"00FF00"
iex> Chameleon.Cmyk.to_hex(%Chameleon.Cmyk{c: 100, m: 0, y: 100, k: 0})
%Chameleon.Hex{hex: "00FF00"}
"""
@spec to_hex(list(integer)) :: charlist
@spec to_hex(struct()) :: struct()
def to_hex(cmyk) do
cmyk
|> to_rgb()
|> rgb_values()
|> Rgb.to_hex()
|> Chameleon.Converter.convert(:hex)
end

@doc """
Converts a cmyk color to its pantone value.
## Examples
iex> Chameleon.Cmyk.to_pantone([0, 0, 0, 100])
"30"
iex> Chameleon.Cmyk.to_pantone(%Chameleon.Cmyk{c: 0, m: 0, y: 0, k: 100})
%Chameleon.Pantone{pantone: "30"}
"""
@spec to_pantone(list(integer)) :: charlist
@spec to_pantone(struct()) :: struct()
def to_pantone(cmyk) do
cmyk
|> to_hex()
|> Hex.to_pantone()
|> Chameleon.Converter.convert(:pantone)
end

@doc """
Converts a cmyk color to its rgb value.
## Examples
iex> Chameleon.Cmyk.to_keyword([100, 0, 100, 0])
"lime"
iex> Chameleon.Cmyk.to_keyword(%Chameleon.Cmyk{c: 100, m: 0, y: 100, k: 0})
%Chameleon.Keyword{keyword: "lime"}
"""
@spec to_keyword(list(integer)) :: charlist
@spec to_keyword(struct()) :: struct()
def to_keyword(cmyk) do
cmyk
|> to_rgb()
|> rgb_values()
|> Rgb.to_keyword()
|> Chameleon.Converter.convert(:keyword)
end
end

#### Helper Functions #######################################################################

defdelegate rgb_values(rgb_map), to: Util
defimpl Chameleon.Converter, for: Chameleon.Cmyk do
def convert(cmyk, :rgb), do: Chameleon.Cmyk.to_rgb(cmyk)
def convert(cmyk, :hsl), do: Chameleon.Cmyk.to_hsl(cmyk)
def convert(cmyk, :hex), do: Chameleon.Cmyk.to_hex(cmyk)
def convert(cmyk, :pantone), do: Chameleon.Cmyk.to_pantone(cmyk)
def convert(cmyk, :keyword), do: Chameleon.Cmyk.to_keyword(cmyk)
def convert(cmyk, :cmyk), do: cmyk
end
41 changes: 41 additions & 0 deletions lib/chameleon/color.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Chameleon.Color do
@moduledoc """
Chameleon.Color
Converts color inputs into supported structs.
"""

@doc """
Converts input into valid struct.
## Examples
iex> Chameleon.Color.new(%{hex: "000000"})
%Chameleon.Hex{hex: "000000"}
iex> Chameleon.Color.new(%{c: 0, m: 0, y: 0, k: 100})
%Chameleon.Cmyk{c: 0, m: 0, y: 0, k: 100}
"""
@spec new(map()) :: struct()
def new(%{c: c, m: m, y: y, k: k}), do: %Chameleon.Cmyk{c: c, m: m, y: y, k: k}
def new(%{r: r, g: g, b: b}), do: %Chameleon.Rgb{r: r, g: g, b: b}
def new(%{h: h, s: s, l: l}), do: %Chameleon.Hsl{h: h, s: s, l: l}
def new(%{hex: hex}), do: %Chameleon.Hex{hex: hex}
def new(%{pantone: pantone}), do: %Chameleon.Pantone{pantone: pantone}
def new(%{keyword: keyword}), do: %Chameleon.Keyword{keyword: keyword}
def new(_other), do: argument_error()
def new(), do: argument_error()

defp argument_error do
message = """
A color argument must be included in one of the following formats:
%{c: 0, m: 0, y: 0, k: 0}
%{r: 0, g: 0, b: 0}
%{h: 0, s: 0, l: 0}
%{hex: "000000"}
%{pantone: "30"}
%{keyword: "black"}
"""

Mix.raise(message)
end
end
7 changes: 7 additions & 0 deletions lib/chameleon/converter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defprotocol Chameleon.Converter do
@moduledoc """
Performs the conversions from one color model to another.
"""
@spec convert(struct(), atom()) :: struct()
def convert(base, to)
end
68 changes: 39 additions & 29 deletions lib/chameleon/hex.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
defmodule Chameleon.Hex do
alias Chameleon.{Rgb, Util}
alias Chameleon.Util

@enforce_keys [:hex]
defstruct @enforce_keys

@doc """
Converts a hex color to its rgb value.
## Examples
iex> Chameleon.Hex.to_rgb("FF0000")
%{r: 255, g: 0, b: 0}
iex> Chameleon.Hex.to_rgb(%Chameleon.Hex{hex: "FF0000"})
%Chameleon.Rgb{r: 255, g: 0, b: 0}
iex> Chameleon.Hex.to_rgb("F00")
%{r: 255, g: 0, b: 0}
iex> Chameleon.Hex.to_rgb(%Chameleon.Hex{hex: "F00"})
%Chameleon.Rgb{r: 255, g: 0, b: 0}
"""
@spec to_rgb(charlist) :: list(integer)
@spec to_rgb(struct()) :: struct()
def to_rgb(hex) do
convert_short_hex_to_long_hex(hex)
|> String.split("", trim: true)
Expand All @@ -22,20 +25,20 @@ defmodule Chameleon.Hex do
Converts a hex color to its keyword value.
## Examples
iex> Chameleon.Hex.to_keyword("FF00FF")
"fuchsia"
iex> Chameleon.Hex.to_keyword(%Chameleon.Hex{hex: "FF00FF"})
%Chameleon.Keyword{keyword: "fuchsia"}
iex> Chameleon.Hex.to_keyword("6789FE")
iex> Chameleon.Hex.to_keyword(%Chameleon.Hex{hex: "6789FE"})
{:error, "No keyword match could be found for that hex value."}
"""
@spec to_keyword(charlist) :: charlist
@spec to_keyword(struct()) :: struct()
def to_keyword(hex) do
long_hex = convert_short_hex_to_long_hex(hex)

keyword_to_hex_map()
|> Enum.find(fn {_k, v} -> v == String.downcase(long_hex) end)
|> case do
{keyword, _hex} -> keyword
{keyword, _hex} -> Chameleon.Color.new(%{keyword: keyword})
_ -> {:error, "No keyword match could be found for that hex value."}
end
end
Expand All @@ -44,32 +47,31 @@ defmodule Chameleon.Hex do
Converts a hex color to its hsl value.
## Examples
iex> Chameleon.Hex.to_hsl("FF0000")
%{h: 0, s: 100, l: 50}
iex> Chameleon.Hex.to_hsl(%Chameleon.Hex{hex: "FF0000"})
%Chameleon.Hsl{h: 0, s: 100, l: 50}
"""
@spec to_hsl(charlist) :: list(integer)
@spec to_hsl(struct()) :: struct()
def to_hsl(hex) do
hex
|> to_rgb()
|> rgb_values()
|> Rgb.to_hsl()
|> Chameleon.Converter.convert(:hsl)
end

@doc """
Converts a hex color to its pantone value.
## Examples
iex> Chameleon.Hex.to_pantone("D8CBEB")
"263"
iex> Chameleon.Hex.to_pantone(%Chameleon.Hex{hex: "D8CBEB"})
%Chameleon.Pantone{pantone: "263"}
"""
@spec to_pantone(charlist) :: charlist
@spec to_pantone(struct()) :: struct()
def to_pantone(hex) do
long_hex = convert_short_hex_to_long_hex(hex)

pantone_to_hex_map()
|> Enum.find(fn {_k, v} -> v == String.upcase(long_hex) end)
|> case do
{pantone, _hex} -> pantone
{pantone, _hex} -> Chameleon.Color.new(%{pantone: pantone})
_ -> {:error, "No keyword match could be found for that hex value."}
end
end
Expand All @@ -78,15 +80,14 @@ defmodule Chameleon.Hex do
Converts a hex color to its cmyk value.
## Examples
iex> Chameleon.Hex.to_cmyk("FF0000")
%{c: 0, m: 100, y: 100, k: 0}
iex> Chameleon.Hex.to_cmyk(%Chameleon.Hex{hex: "FF0000"})
%Chameleon.Cmyk{c: 0, m: 100, y: 100, k: 0}
"""
@spec to_cmyk(charlist) :: list(integer)
@spec to_cmyk(struct()) :: struct()
def to_cmyk(hex) do
hex
|> to_rgb()
|> rgb_values()
|> Rgb.to_cmyk()
|> Chameleon.Converter.convert(:cmyk)
end

#### Helper Functions #######################################################################
Expand All @@ -97,27 +98,36 @@ defmodule Chameleon.Hex do
|> Enum.chunk_every(2)
|> Enum.map(fn grp -> Enum.join(grp) |> String.to_integer(16) end)

%{r: r, g: g, b: b}
Chameleon.Color.new(%{r: r, g: g, b: b})
end

defp do_to_rgb(_list) do
{:error, "A hex value must be provided as 3 or 6 characters."}
end

defp convert_short_hex_to_long_hex(hex) do
case String.length(hex) do
case String.length(hex.hex) do
3 ->
hex
hex.hex
|> String.split("", trim: true)
|> Enum.map(fn grp -> String.duplicate(grp, 2) end)
|> Enum.join()

_ ->
hex
hex.hex
end
end

defdelegate pantone_to_hex_map, to: Util
defdelegate keyword_to_hex_map, to: Util
defdelegate rgb_values(rgb_map), to: Util
end

defimpl Chameleon.Converter, for: Chameleon.Hex do
def convert(hex, :cmyk), do: Chameleon.Hex.to_cmyk(hex)
def convert(hex, :hsl), do: Chameleon.Hex.to_hsl(hex)
def convert(hex, :rgb), do: Chameleon.Hex.to_rgb(hex)
def convert(hex, :pantone), do: Chameleon.Hex.to_pantone(hex)
def convert(hex, :keyword), do: Chameleon.Hex.to_keyword(hex)
def convert(hex, :hex), do: hex
end
Loading

0 comments on commit 4e502e5

Please sign in to comment.