Packages

A library to provide a Scenic framework driver implementation for the Inky series of eInk displays from Pimoroni. Built on top of the pappersverk/inky library. All in Elixir.

Current section

Files

Jump to
scenic_driver_inky lib scenic_driver_inky.ex
Raw

lib/scenic_driver_inky.ex

defmodule ScenicDriverInky do
use Scenic.ViewPort.Driver
alias Scenic.ViewPort.Driver
require Logger
@dithering_options [:halftone, false]
@color_affinity_options [:high, :low]
@default_color_affinity :low
@default_color_high 180
@default_color_low 75
@default_dithering false
# This driver is not in a hurry, fetch a new FB once a second at most.
# Refresh takes 10+ seconds, this is practically hectic.
@default_refresh_rate 1000
# Lower doesn't make much sense for Inky
@minimal_refresh_rate 100
@impl true
def init(viewport, size, config) do
vp_supervisor = vp_supervisor(viewport)
{:ok, _} = Driver.start_link({vp_supervisor, size, %{module: Scenic.Driver.Nerves.Rpi}})
type = config[:type]
accent = config[:accent]
opts =
cond do
is_map(config[:opts]) -> config[:opts]
true -> %{}
end
dithering =
cond do
config[:dithering] in @dithering_options -> config[:dithering]
true -> @default_dithering
end
interval =
cond do
is_integer(config[:interval]) and config[:interval] > 100 -> config[:interval]
is_integer(config[:interval]) -> @minimal_refresh_rate
true -> @default_refresh_rate
end
color_affinity =
cond do
config[:color_affinity] in @color_affinity_options -> config[:color_affinity]
true -> @default_color_affinity
end
color_low =
cond do
is_integer(config[:color_low]) and config[:color_low] >= 0 -> config[:color_low]
true -> @default_color_low
end
color_high =
cond do
is_integer(config[:color_high]) and config[:color_high] > color_low -> config[:color_high]
true -> @default_color_high
end
{:ok, inky_pid} = Inky.start_link(type, accent, opts)
{width, height} = size
{:ok, cap} = RpiFbCapture.start_link(width: width, height: height, display: 0)
send(self(), :capture)
{:ok,
%{
viewport: viewport,
size: size,
inky_pid: inky_pid,
cap: cap,
last_crc: -1,
dithering: dithering,
color_affinity: color_affinity,
color_high: color_high,
color_low: color_low,
interval: interval
}}
end
@impl true
def handle_info(:capture, state) do
{:ok, frame} = RpiFbCapture.capture(state.cap, :rgb24)
crc = :erlang.crc32(frame.data)
if crc != state.last_crc do
pixel_data = process_pixels(frame.data, state)
Inky.set_pixels(state.inky_pid, pixel_data)
end
Process.send_after(self(), :capture, state.interval)
{:noreply, %{state | last_crc: crc}}
end
defp process_pixels(data, %{
size: {width, _height},
dithering: dithering,
color_high: color_high,
color_low: color_low,
color_affinity: color_affinity
}) do
pixels = for <<r::8, g::8, b::8 <- data>>, do: {r, g, b}
{_, _, pixel_data} =
Enum.reduce(pixels, {0, 0, %{}}, fn pixel, {x, y, pixel_data} ->
pixel = process_color(pixel, x, y, color_high, color_low, color_affinity, dithering)
color = pixel_to_color(pixel)
pixel_data = Map.put(pixel_data, {x, y}, color)
{x, y} =
if x >= width - 1 do
{0, y + 1}
else
{x + 1, y}
end
{x, y, pixel_data}
end)
pixel_data
end
defp process_color({r, g, b}, x, y, color_high, color_low, color_affinity, dithering) do
{
process_color(r, x, y, color_high, color_low, color_affinity, dithering),
process_color(g, x, y, color_high, color_low, color_affinity, dithering),
process_color(b, x, y, color_high, color_low, color_affinity, dithering)
}
end
defp process_color(color_value, x, y, color_high, color_low, color_affinity, dithering)
when is_integer(color_value) do
if color_value > color_high do
255
else
if color_value < color_low do
0
else
dither(dithering, color_affinity, x, y)
end
end
end
defp dither(false, color_affinity, _, _) do
case color_affinity do
:high -> 255
:low -> 0
end
end
defp dither(:halftone, _, x, y) do
draw = 255
blank = 0
odd_x = rem(x, 2) == 0
odd_y = rem(y, 2) == 0
case odd_x do
true ->
case odd_y do
true -> draw
false -> blank
end
false ->
case odd_y do
true -> blank
false -> draw
end
end
end
defp pixel_to_color({0, 0, 0}), do: :black
defp pixel_to_color({255, 255, 255}), do: :white
defp pixel_to_color(_), do: :accent
defp vp_supervisor(viewport) do
[supervisor_pid | _] =
viewport
|> Process.info()
|> get_in([:dictionary, :"$ancestors"])
supervisor_pid
end
end