Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Triadic Color Generator

triadic-colors.livemd

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