Triadic Color Generator
Mix.install([
{:kino, "~> 0.16.0"},
{:chameleon, "~> 2.5"}
])
Introduction
A color triad consists of three colors that are evenly spaced around the hue circle. They usually appear quite pleasing.
This workbook implements a variant whereby two colors are given and a third one is produced to lie equally far on the hue cirle from the two input colors. Because two such colors exist, this workbook will produce both.
Note: Let it be known that arcs in SVG takes some getting used to, especially when combined with gradients.
Support Code
defmodule Canvas do
@dim 320
use GenServer
# interface
def start(frame) do
GenServer.start(__MODULE__, frame, name: __MODULE__)
end
def update(color1, color2) do
GenServer.cast(__MODULE__, {:update, color1, color2})
end
# callbacks
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_cast({:update, color1, color2}, frame = state) do
Kino.Frame.clear(frame)
Kino.Frame.render(frame, [color1, color2])
Kino.Frame.render(frame, render(color1, color2))
{:noreply, state}
end
# helpers
def render_color(color) do
%Chameleon.HSV{h: h} = Chameleon.convert(color, Chameleon.HSV)
{x, y} = gen_coords_for_angle(h)
"""
"""
end
def render_color_info(color, offset) do
%Chameleon.HSV{h: h, s: s, v: v} = Chameleon.convert(color, Chameleon.HSV)
%Chameleon.RGB{r: r, g: g, b: b} = Chameleon.convert(color, Chameleon.RGB)
"""
hex:#{color}
rgb:(#{r},#{g},#{b})
hsv:(#{h},#{s},#{v})
"""
end
def find_third_color(color1, color2, rotate\\0) do
%Chameleon.HSV{h: h1, s: s1, v: v1} = Chameleon.convert(color1, Chameleon.HSV)
%Chameleon.HSV{h: h2, s: s2, v: v2} = Chameleon.convert(color2, Chameleon.HSV)
h = rem(h1 + div(h2-h1,2) + rotate, 360)
s = s1 + (s2-s1)/2
v = v1 + (v2-v1)/2
%Chameleon.Hex{hex: hex} = Chameleon.HSV.new(h, s, v) |> Chameleon.convert(Chameleon.Hex)
"##{hex}"
end
def render(color1, color2) do
color3 = find_third_color(color1, color2)
color4 = find_third_color(color1, color2, 180)
%Chameleon.Hex{hex: c0} = Chameleon.HSV.new( 0, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c60} = Chameleon.HSV.new( 60, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c120} = Chameleon.HSV.new(120, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c180} = Chameleon.HSV.new(180, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c240} = Chameleon.HSV.new(240, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c300} = Chameleon.HSV.new(300, 100, 100) |> Chameleon.convert(Chameleon.Hex)
%Chameleon.Hex{hex: c360} = Chameleon.HSV.new( 0, 100, 100) |> Chameleon.convert(Chameleon.Hex)
r = 0.4*@dim
{x0, y0} = gen_coords_for_angle(0)
{x60, y60} = gen_coords_for_angle(60)
{x120, y120} = gen_coords_for_angle(120)
{x180, y180} = gen_coords_for_angle(180)
{x240, y240} = gen_coords_for_angle(240)
{x300, y300} = gen_coords_for_angle(300)
{x360, y360} = gen_coords_for_angle(360)
"""
#{render_color(color1)}
#{render_color(color2)}
#{render_color(color3)}
#{render_color(color4)}
#{render_color_info(color1, 0)}
#{render_color_info(color2, 1)}
#{render_color_info(color3, 2)}
#{render_color_info(color4, 3)}
"""
|> Kino.Image.new(:svg)
end
def gen_coords_for_angle(angle) do
{
@dim/2+0.4*@dim*:math.cos(angle/360*2*:math.pi),
@dim/2+0.4*@dim*:math.sin(angle/360*2*:math.pi)
}
end
end
Interface
elements = [
color1: Kino.Input.color("Color 1:", default: "#008080"),
color2: Kino.Input.color("Color 2:", default: "#800080")
]
form = Kino.Control.form(elements, submit: "Send", report_changes: true)
frame = Kino.Frame.new()
canvas_pid =
case Canvas.start(frame) do
{:ok, pid} -> pid
{:error, {:already_started, pid}} -> pid
end
for event <- Kino.Control.stream(form) do
%{data: %{color1: color1, color2: color2}, type: _type, origin: _origin} = event
Canvas.update(color1, color2)
end