Skip to content

Commit

Permalink
Modifies protocols to support external color modules
Browse files Browse the repository at this point in the history
  • Loading branch information
supersimple committed May 13, 2018
1 parent 4e502e5 commit 05ae1d5
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 191 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ 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.convert(Chameleon.Hex.new("FFFFFF"), Chameleon.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.Color.convert(Chameleon.Hex.new("F69292"), Chameleon.Pantone) -> {:error, "No keyword match could be found for that hex value."}`

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(Chameleon.Keyword.new("Reddish-Blue", Chameleon.Hex)`

## Caveat(s)
Pantone is designed to be used on printed work only. As such, it is disingenuous to say a
Expand All @@ -33,7 +32,7 @@ The package can be installed by adding `chameleon` to your list of dependencies
```elixir
def deps do
[
{:chameleon, "~> 1.0.0"}
{:chameleon, "~> 2.0.0"}
]
end
```
Expand Down
48 changes: 13 additions & 35 deletions lib/chameleon.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,35 @@ defmodule Chameleon do
It currently supports: Hex, RGB, CMYK, HSL, Pantone, and Keywords.
## Use
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}`
Example: `Chameleon.convert(Chameleon.Hex.new("FFFFFF"), Chameleon.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.Converter.convert(%Chameleon.Color.new(%{hex: "F69292"}), :pantone) -> {:error, "F69292"}`
Example: `Chameleon.Color.convert(Chameleon.Hex.new("F69292"), Chameleon.Pantone) -> {:error, "No keyword match could be found for that hex value."}`
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.convert(%Chameleon.Color.new(%{keyword: "Reddish-Blue"}, :hex)`
Example: `Chameleon.convert(Chameleon.Keyword.new("Reddish-Blue", Chameleon.Hex)`
"""

@doc """
This is the only public interface available.
Handles conversion from the input color struct to the requested output color model.
## Examples
iex> input = Chameleon.Color.new(%{hex: "000000"})
iex> Chameleon.Converter.convert(input, :keyword)
iex> input = Chameleon.Hex.new("000000")
iex> Chameleon.convert(input, Chameleon.Keyword)
%Chameleon.Keyword{keyword: "black"}
iex> input = Chameleon.Color.new(%{keyword: "black"})
iex> Chameleon.Converter.convert(input, :cmyk)
iex> input = Chameleon.Keyword.new("black")
iex> Chameleon.convert(input, Chameleon.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
Kernel.apply(input_module(input_model), convert_function(output_model), [value])
|> response(value)
end

defp response(output, input_value) when is_tuple(output) do
{:error, input_value}
end

defp response(output, _input_value) do
{:ok, output}
end

defp input_module(input_model) do
case input_model do
:rgb -> Chameleon.Rgb
:cmyk -> Chameleon.Cmyk
:hex -> Chameleon.Hex
:pantone -> Chameleon.Pantone
:keyword -> Chameleon.Keyword
:hsl -> Chameleon.Hsl
_ -> {:error, "Please pass in the input model as an atom."}
end
end
def convert(%{__struct__: color_model} = c, color_model), do: c

defp convert_function(output_model) do
("to_" <> Atom.to_string(output_model))
|> String.to_atom()
def convert(input_color, output) do
convert_struct = Module.concat(input_color.__struct__, output)
conversion = %{__struct__: convert_struct, from: input_color}
Chameleon.Color.convert(conversion)
end
end
71 changes: 57 additions & 14 deletions lib/chameleon/cmyk.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
defmodule Chameleon.Cmyk.Chameleon.Hex do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: cmyk}) do
Chameleon.Cmyk.to_hex(cmyk)
end
end
end

defmodule Chameleon.Cmyk.Chameleon.Rgb do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: cmyk}) do
Chameleon.Cmyk.to_rgb(cmyk)
end
end
end

defmodule Chameleon.Cmyk.Chameleon.Hsl do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: cmyk}) do
Chameleon.Cmyk.to_hsl(cmyk)
end
end
end

defmodule Chameleon.Cmyk.Chameleon.Keyword do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: cmyk}) do
Chameleon.Cmyk.to_keyword(cmyk)
end
end
end

defmodule Chameleon.Cmyk.Chameleon.Pantone do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: cmyk}) do
Chameleon.Cmyk.to_pantone(cmyk)
end
end
end

defmodule Chameleon.Cmyk do
@enforce_keys [:c, :m, :y, :k]
defstruct @enforce_keys

def new(c, m, y, k), do: %__MODULE__{c: c, m: m, y: y, k: k}

@doc """
Converts a cmyk color to its rgb value.
Expand All @@ -17,7 +69,7 @@ defmodule Chameleon.Cmyk do
g = round(Float.round(255.0 * (1.0 - m) * (1.0 - k)))
b = round(Float.round(255.0 * (1.0 - y) * (1.0 - k)))

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

@doc """
Expand All @@ -31,7 +83,7 @@ defmodule Chameleon.Cmyk do
def to_hsl(cmyk) do
cmyk
|> to_rgb()
|> Chameleon.Converter.convert(:hsl)
|> Chameleon.convert(Chameleon.Hsl)
end

@doc """
Expand All @@ -45,7 +97,7 @@ defmodule Chameleon.Cmyk do
def to_hex(cmyk) do
cmyk
|> to_rgb()
|> Chameleon.Converter.convert(:hex)
|> Chameleon.convert(Chameleon.Hex)
end

@doc """
Expand All @@ -59,7 +111,7 @@ defmodule Chameleon.Cmyk do
def to_pantone(cmyk) do
cmyk
|> to_hex()
|> Chameleon.Converter.convert(:pantone)
|> Chameleon.convert(Chameleon.Pantone)
end

@doc """
Expand All @@ -73,15 +125,6 @@ defmodule Chameleon.Cmyk do
def to_keyword(cmyk) do
cmyk
|> to_rgb()
|> Chameleon.Converter.convert(:keyword)
|> Chameleon.convert(Chameleon.Keyword)
end
end

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
42 changes: 4 additions & 38 deletions lib/chameleon/color.ex
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
defmodule Chameleon.Color do
defprotocol Chameleon.Color do
@moduledoc """
Chameleon.Color
Converts color inputs into supported structs.
Performs the conversions from one color model to another.
"""

@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
@spec convert(struct()) :: struct()
def convert(conversion)
end
7 changes: 0 additions & 7 deletions lib/chameleon/converter.ex

This file was deleted.

73 changes: 58 additions & 15 deletions lib/chameleon/hex.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,61 @@
defmodule Chameleon.Hex.Chameleon.Rgb do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: hex}) do
Chameleon.Hex.to_rgb(hex)
end
end
end

defmodule Chameleon.Hex.Chameleon.Cmyk do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: hex}) do
Chameleon.Hex.to_cmyk(hex)
end
end
end

defmodule Chameleon.Hex.Chameleon.Hsl do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: hex}) do
Chameleon.Hex.to_hsl(hex)
end
end
end

defmodule Chameleon.Hex.Chameleon.Keyword do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: hex}) do
Chameleon.Hex.to_keyword(hex)
end
end
end

defmodule Chameleon.Hex.Chameleon.Pantone do
defstruct [:from]

defimpl Chameleon.Color do
def convert(%{from: hex}) do
Chameleon.Hex.to_pantone(hex)
end
end
end

defmodule Chameleon.Hex do
alias Chameleon.Util

@enforce_keys [:hex]
defstruct @enforce_keys

def new(hex), do: %__MODULE__{hex: hex}

@doc """
Converts a hex color to its rgb value.
Expand Down Expand Up @@ -38,7 +90,7 @@ defmodule Chameleon.Hex do
keyword_to_hex_map()
|> Enum.find(fn {_k, v} -> v == String.downcase(long_hex) end)
|> case do
{keyword, _hex} -> Chameleon.Color.new(%{keyword: keyword})
{keyword, _hex} -> Chameleon.Keyword.new(keyword)
_ -> {:error, "No keyword match could be found for that hex value."}
end
end
Expand All @@ -54,7 +106,7 @@ defmodule Chameleon.Hex do
def to_hsl(hex) do
hex
|> to_rgb()
|> Chameleon.Converter.convert(:hsl)
|> Chameleon.convert(Chameleon.Hsl)
end

@doc """
Expand All @@ -71,8 +123,8 @@ defmodule Chameleon.Hex do
pantone_to_hex_map()
|> Enum.find(fn {_k, v} -> v == String.upcase(long_hex) end)
|> case do
{pantone, _hex} -> Chameleon.Color.new(%{pantone: pantone})
_ -> {:error, "No keyword match could be found for that hex value."}
{pantone, _hex} -> Chameleon.Pantone.new(pantone)
_ -> {:error, "No pantone match could be found for that color value."}
end
end

Expand All @@ -87,7 +139,7 @@ defmodule Chameleon.Hex do
def to_cmyk(hex) do
hex
|> to_rgb()
|> Chameleon.Converter.convert(:cmyk)
|> Chameleon.convert(Chameleon.Cmyk)
end

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

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

defp do_to_rgb(_list) do
Expand All @@ -122,12 +174,3 @@ defmodule Chameleon.Hex do
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 05ae1d5

Please sign in to comment.