defmodule Vix.Vips.MutableImage do
defstruct [:pid]
alias __MODULE__
alias Vix.Type
alias Vix.Vips.Image
@moduledoc """
Vips Mutable Image
See `Vix.Vips.Image.mutate/2`
"""
alias Vix.Nif
@behaviour Type
@typedoc """
Represents a mutable instance of VipsImage
"""
@type t() :: %MutableImage{pid: pid}
@impl Type
def typespec do
quote do
unquote(__MODULE__).t()
end
end
@impl Type
def default(nil) do
raise "default/1 for Vix.Vips.MutableImage is not supported"
end
@impl Type
def to_nif_term(%Image{} = image, data) do
Image.to_nif_term(image, data)
end
def to_nif_term(%MutableImage{}, _data) do
raise "to_nif_term/2 for Vix.Vips.MutableImage is not supported"
end
@impl Type
def to_erl_term(_term) do
raise "to_erl_term/1 for Vix.Vips.MutableImage is not supported"
end
# Create mutable image
@doc false
@spec new(Vix.Vips.Image.t()) :: {:ok, __MODULE__.t()} | {:error, term()}
def new(%Image{} = image) do
GenServer.start_link(__MODULE__, image)
|> wrap_type()
end
@doc """
Return the number of bands of a mutable image.
"""
def bands(%MutableImage{pid: pid}) do
GenServer.call(pid, :bands)
end
@doc """
Return the width of a mutable image.
"""
def width(%MutableImage{pid: pid}) do
GenServer.call(pid, :width)
end
@doc """
Return the height of a mutable image.
"""
def height(%MutableImage{pid: pid}) do
GenServer.call(pid, :height)
end
@doc """
Return a boolean indicating if a mutable image
has an alpha band.
"""
def has_alpha?(%MutableImage{pid: pid}) do
GenServer.call(pid, :has_alpha?)
end
@doc """
Return the shape of the umage as
`{width, height, bands}`.
"""
def shape(%MutableImage{pid: pid}) do
GenServer.call(pid, :shape)
end
@doc """
Set the value of existing metadata item on an image. Value is converted to match existing value GType
"""
@spec update(__MODULE__.t(), String.t(), term()) :: :ok | {:error, term()}
def update(%MutableImage{pid: pid}, name, value) do
GenServer.call(pid, {:update, name, value})
end
@supported_gtype ~w(gint guint gdouble gboolean gchararray VipsArrayInt VipsArrayDouble VipsArrayImage VipsRefString VipsBlob VipsImage VipsInterpolate)a
@doc """
Create a metadata item on an image of the specified type.
Vix converts value to specified GType
Supported GTypes
#{Enum.map(@supported_gtype, fn type -> " * `#{inspect(type)}`\n" end)}
"""
@spec set(__MODULE__.t(), String.t(), atom(), term()) :: :ok | {:error, term()}
def set(%MutableImage{pid: pid}, name, type, value) do
if type in @supported_gtype do
type = to_string(type)
GenServer.call(pid, {:set, name, type, cast_value(type, value)})
else
{:error, "invalid gtype. Supported types are #{inspect(@supported_gtype)}"}
end
end
@doc """
Remove a metadata item from an image.
"""
@spec remove(__MODULE__.t(), String.t()) :: :ok | {:error, term()}
def remove(%MutableImage{pid: pid}, name) do
GenServer.call(pid, {:remove, name})
end
@doc """
Returns metadata from the image
"""
@spec get(__MODULE__.t(), String.t()) :: {:ok, term()} | {:error, term()}
def get(%MutableImage{pid: pid}, name) do
GenServer.call(pid, {:get, name})
end
@doc false
def to_image(%MutableImage{pid: pid}) do
GenServer.call(pid, :to_image)
end
@doc false
def stop(%MutableImage{pid: pid}) do
GenServer.stop(pid, :normal)
end
use GenServer
@impl true
def init(image) do
case Image.copy_memory(image) do
{:ok, copy} -> {:ok, %{image: copy}}
{:error, error} -> {:stop, error}
end
end
@impl true
def handle_call({:update, name, value}, _from, %{image: image} = state) do
{:reply, Nif.nif_image_update_metadata(image.ref, name, value), state}
end
@impl true
def handle_call({:set, name, type, value}, _from, %{image: image} = state) do
{:reply, Nif.nif_image_set_metadata(image.ref, name, type, value), state}
end
@impl true
def handle_call({:remove, name}, _from, %{image: image} = state) do
{:reply, Nif.nif_image_remove_metadata(image.ref, name), state}
end
@impl true
def handle_call({:get, name}, _from, %{image: image} = state) do
{:reply, Image.header_value(image, name), state}
end
@impl true
def handle_call(:to_image, _from, %{image: image} = state) do
{:reply, Image.copy_memory(image), state}
end
@impl true
def handle_call({:operation, callback}, _from, %{image: image} = state) do
{:reply, callback.(image), state}
end
@impl true
def handle_call(:width, _from, %{image: image} = state) do
{:reply, {:ok, Image.width(image)}, state}
end
@impl true
def handle_call(:height, _from, %{image: image} = state) do
{:reply, {:ok, Image.height(image)}, state}
end
@impl true
def handle_call(:bands, _from, %{image: image} = state) do
{:reply, {:ok, Image.bands(image)}, state}
end
@impl true
def handle_call(:has_alpha?, _from, %{image: image} = state) do
{:reply, {:ok, Image.has_alpha?(image)}, state}
end
@impl true
def handle_call(:shape, _from, %{image: image} = state) do
width = Image.width(image)
height = Image.height(image)
bands = Image.bands(image)
{:reply, {:ok, {width, height, bands}}, state}
end
defp wrap_type({:ok, pid}), do: {:ok, %MutableImage{pid: pid}}
defp wrap_type(value), do: value
defp cast_value(type, value) do
Vix.Type.to_nif_term(type, value, nil)
end
end