defmodule Evision.Wx do
@moduledoc """
Interact with wxWidgets
"""
# use GenServer
@behaviour :wx_object
@wxVERTICAL 8
@wxEXPAND 8192
@wxFULL_REPAINT_ON_RESIZE 65536
@process_env_key "Evision.Wx.windows"
defp check_wx! do
case Code.ensure_compiled(:wx) do
{:error, _} ->
raise RuntimeError,
":wx is not available, please ensure Erlang was compiled with wxWidgets."
_ ->
:ok
end
end
@spec imshow(String.t(), Evision.Mat.maybe_mat_in()) :: :ok
def imshow(window_name, image) when is_binary(window_name) and is_struct(image, Evision.Mat) do
check_wx!()
windows = Process.get(@process_env_key, %{})
window = Map.get(windows, window_name)
wx_pid =
case window do
nil -> nil
{_, _, _, pid} -> pid
end
window =
if window == nil || !Process.alive?(wx_pid) do
wx_window = :wx.new()
wx_obj =
{:wx_ref, _, :wxFrame, window} =
:wx_object.start(__MODULE__, {window_name, wx_window}, [])
Process.put(@process_env_key, Map.put(windows, window_name, wx_obj))
window
else
{:wx_ref, _, :wxFrame, window} = window
window
end
GenServer.call(window, {:imshow, image})
end
@spec destroyWindow(String.t()) :: :ok
def destroyWindow(window_name) do
check_wx!()
windows = Process.get(@process_env_key, %{})
window = Map.get(windows, window_name)
if window do
try do
:wxFrame.close(window)
catch
:error, {_, {:wxWindow, :close, _}} -> :ok
end
Process.put(@process_env_key, Map.delete(windows, window_name))
end
:ok
end
@spec destroyAllWindows() :: :ok
def destroyAllWindows() do
check_wx!()
windows = Process.get(@process_env_key, %{})
Enum.each(Map.values(windows), fn window ->
try do
:wxFrame.close(window)
catch
:error, {_, {:wxWindow, :close, _}} -> :ok
end
end)
Process.put(@process_env_key, %{})
:ok
end
@impl true
def init({window_name, window}) do
:wx.batch(fn ->
frame = :wxFrame.new(window, 0, String.to_charlist(window_name))
panel = :wxPanel.new(frame, [])
mainSizer = :wxBoxSizer.new(@wxVERTICAL)
sizer = :wxStaticBoxSizer.new(@wxVERTICAL, panel)
canvas = :wxPanel.new(panel, style: @wxFULL_REPAINT_ON_RESIZE)
:wxPanel.connect(canvas, :paint, [:callback])
:wxPanel.connect(canvas, :size)
:wxPanel.connect(canvas, :left_down)
:wxPanel.connect(canvas, :left_up)
:wxPanel.connect(canvas, :motion)
:wxSizer.add(sizer, canvas, flag: @wxEXPAND, proportion: 1)
:wxSizer.add(mainSizer, sizer, flag: @wxEXPAND, proportion: 1)
:wxPanel.setSizer(panel, mainSizer)
:wxSizer.layout(mainSizer)
{w, h} = :wxPanel.getSize(canvas)
bitmap = :wxBitmap.new(max(w, 30), max(h, 30))
:wxFrame.show(frame)
{frame,
%{
window_name: window_name,
window: window,
frame: frame,
panel: panel,
canvas: canvas,
bitmap: bitmap,
image: nil
}}
end)
end
@impl true
def handle_event(
{:wx, _, _, _, {:wxSize, :size, {w, h}, _}},
state = %{canvas: canvas, bitmap: prev, image: image}
) do
if w > 0 and h > 0 do
bitmap = :wxBitmap.new(w, h)
draw_canvas(image, canvas, bitmap)
:wxBitmap.destroy(prev)
{:noreply, %{state | bitmap: bitmap}}
else
{:noreply, state}
end
end
@impl true
def handle_event(_wx, state) do
{:noreply, state}
end
@impl true
def handle_sync_event({:wx, _, _, _, {:wxPaint, :paint}}, _obj, %{
canvas: canvas,
bitmap: bitmap,
image: image
}) do
draw_canvas(image, canvas, bitmap)
:ok
end
@impl true
def handle_sync_event(_wx, _obj, _state) do
:ok
end
@impl true
def handle_call({:imshow, image}, _from, state = %{canvas: canvas, bitmap: bitmap}) do
case draw_canvas(image, canvas, bitmap) do
:ok ->
{:reply, :ok, %{state | image: image}}
:error ->
{:reply, :error, state}
end
end
defp draw_canvas(nil, _canvas, _bitmap) do
:ok
end
defp draw_canvas(image, canvas, bitmap) when is_struct(image, Evision.Mat) do
{canvasW, canvasH} = :wxPanel.getSize(canvas)
canvas_w = trunc(canvasW)
canvas_h = trunc(canvasH)
{image_h, image_w} =
case Evision.Mat.shape(image) do
{h, w} ->
{h, w}
{h, w, c} when c in [1, 3, 4] ->
{h, w}
_ ->
{0, 0}
end
with true <- image_h > 0 and image_w > 0 and canvas_h > 0 and canvas_w > 0,
proportional = min(canvas_w / image_w, canvas_h / image_h),
{target_w, target_h} = {trunc(image_w * proportional), trunc(image_h * proportional)},
resized = Evision.resize(image, {target_w, target_h}),
true <- is_struct(resized, Evision.Mat),
rgb = Evision.cvtColor(resized, Evision.Constant.cv_COLOR_BGR2RGB()),
true <- is_struct(rgb, Evision.Mat),
binary = Evision.Mat.to_binary(rgb),
true <- is_binary(binary) do
img = :wxImage.new(target_w, target_h, binary)
bmp = :wxBitmap.new(img)
memoryDC = :wxMemoryDC.new(bitmap)
:wxDC.clear(memoryDC)
:wxDC.drawBitmap(memoryDC, bmp, {0, 0})
canvasDC = :wxWindowDC.new(canvas)
:wxDC.blit(
canvasDC,
{0, 0},
{:wxBitmap.getWidth(bitmap), :wxBitmap.getHeight(bitmap)},
memoryDC,
{0, 0}
)
:wxWindowDC.destroy(canvasDC)
:wxMemoryDC.destroy(memoryDC)
:wxBitmap.destroy(bmp)
:wxImage.destroy(img)
:ok
else
_ ->
:error
end
end
defp draw_canvas(_, _canvas, _bitmap) do
:ok
end
end