Packages
vix
0.3.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.1
0.33.0
0.32.0
0.31.1
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.2.1
0.2.0
retired
0.1.0
NIF based bindings for libvips
Current section
Files
Jump to
Current section
Files
lib/vix/vips/operation.ex
defmodule Vix.Vips.OperationHelper do
@moduledoc false
alias Vix.Nif
alias Vix.Type
alias Vix.GObject.GParamSpec
def input_to_nif_terms(args, in_pspec) do
Enum.map(
args,
fn {name, value} ->
param_spec = Map.get(in_pspec, List.to_atom(name))
{name, Type.to_nif_term(value, param_spec)}
end
)
end
def output_to_erl_terms(nif_out_args, required_out_pspec, optional_out_pspec) do
res =
nif_out_args
|> Enum.reduce({[], []}, fn {name, value}, {required, optional} ->
param = List.to_atom(name)
cond do
Map.has_key?(required_out_pspec, param) ->
param_spec = Map.get(required_out_pspec, param)
value = Type.to_erl_term(value, param_spec)
{[value | required], optional}
Map.has_key?(optional_out_pspec, param) ->
param_spec = Map.get(optional_out_pspec, param)
value = Type.to_erl_term(value, param_spec)
{required, [{param, value} | optional]}
true ->
raise "Invalid operation output"
end
end)
case res do
{[], []} ->
:ok
# if it is single value then unwrap and return as single term
{[term], []} ->
{:ok, term}
{required, optional} ->
out =
[optional | required]
|> Enum.reverse()
|> List.to_tuple()
{:ok, out}
end
end
def prepare_doc(desc, required_in, optional_in, required_out, optional_out) do
doc_required_in =
Enum.map_join(required_in, "\n", fn pspec ->
" * #{pspec.param_name} - #{pspec.desc}"
end)
"""
#{String.capitalize(to_string(desc))}
## Arguments
#{doc_required_in}
#{optional_in_doc(optional_in)}
#{output_values_doc(required_out, optional_out)}
"""
end
def reject_unsupported_operations(op_name) do
{_desc, args} = vips_operation_arguments(op_name)
Enum.any?(args, fn %{flags: flags, value_type: value_type} ->
# we do not support mutable operations yet. Skip operations which use un-supported types as arguments
:vips_argument_modify in flags ||
value_type == "VipsSource" ||
value_type == "VipsTarget" ||
value_type == "VipsBlob"
end)
end
def operation_args(name) do
{desc, args} = vips_operation_arguments(name)
args =
Enum.reject(args, fn %{flags: flags} ->
# skip required deprecated arguments, but allow optional deprecated arguments.
# This is similar to ruby-vips.
:vips_argument_required in flags && :vips_argument_deprecated in flags
end)
{input, rest} =
Enum.split_with(args, fn %{flags: flags} ->
:vips_argument_input in flags
end)
{output, _rest} =
Enum.split_with(rest, fn %{flags: flags} ->
:vips_argument_output in flags
end)
{required_input, optional_input} =
Enum.split_with(input, fn %{flags: flags} ->
:vips_argument_required in flags
end)
{required_output, optional_output} =
Enum.split_with(output, fn %{flags: flags} ->
:vips_argument_required in flags
end)
# TODO: support VipsInterpolate and other types
optional_input = Enum.filter(optional_input, &Type.supported?/1)
required_input = Enum.sort_by(required_input, & &1.priority)
required_output = Enum.sort_by(required_output, & &1.priority)
{desc, required_input, optional_input, required_output, optional_output}
end
def function_name(name), do: to_string(name) |> String.downcase() |> String.to_atom()
def type_name(name) do
to_string(name)
|> Macro.underscore()
|> String.to_atom()
|> Macro.var(__MODULE__)
end
def output_typespec(required, optional) do
case {required, optional} do
{[], []} ->
quote do
:ok | {:error, term()}
end
{[pspec], []} ->
quote do
{:ok, unquote(typespec(pspec))} | {:error, term()}
end
{pspec_list, optional} ->
optional_out = optional_args_typespec(optional)
quote do
{:ok, {unquote_splicing(typespec(pspec_list)), unquote(optional_out)}}
| {:error, term()}
end
end
end
def bang_output_typespec(required, optional) do
case {required, optional} do
{[], []} ->
quote do
:ok | no_return()
end
{[pspec], []} ->
quote do
unquote(typespec(pspec)) | no_return()
end
{pspec_list, optional} ->
optional_out = optional_args_typespec(optional)
quote do
{unquote_splicing(typespec(pspec_list)), unquote(optional_out)} | no_return()
end
end
end
def func_typespec(func_name, required_in, optional_in, required_out, optional_out) do
quote do
unquote(func_name)(
unquote_splicing(typespec(required_in)),
unquote(optional_args_typespec(optional_in))
) ::
unquote(output_typespec(required_out, optional_out))
end
end
def bang_func_typespec(func_name, required_in, optional_in, required_out, optional_out) do
quote do
unquote(func_name)(
unquote_splicing(typespec(required_in)),
unquote(optional_args_typespec(optional_in))
) ::
unquote(bang_output_typespec(required_out, optional_out))
end
end
defp vips_operation_arguments(name) do
{description, args} = Nif.nif_vips_operation_get_arguments(name)
args =
Enum.map(args, fn {name, spec_details, priority, flags} ->
{desc, spec_type, value_type, data} = spec_details
%GParamSpec{
param_name: List.to_atom(name),
desc: desc,
spec_type: to_string(spec_type),
value_type: to_string(value_type),
data: data,
priority: priority,
flags: flags
}
end)
{description, args}
end
defp default(pspec) do
if Type.default(pspec) == :unsupported do
""
else
"Default: `#{inspect(Type.default(pspec))}`"
end
end
defp optional_in_doc([]), do: ""
defp optional_in_doc(optional_in) do
optional_args =
Enum.map_join(optional_in, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. #{default(pspec)}"
end)
"""
## Optional
#{optional_args}
"""
end
def output_values_doc([], []), do: ""
def output_values_doc([_], []), do: ""
def output_values_doc(required_out, optional_out) do
required_out_values =
Enum.map_join(required_out, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. (`#{Macro.to_string(typespec(pspec))}`)"
end)
optional_out_values =
Enum.map_join(optional_out, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. (`#{Macro.to_string(typespec(pspec))}`)"
end)
"""
## Returns
Ordered values in the returned tuple
#{required_out_values}
## Additional
Last value of the the output tuple is a keyword list of additional optional output values
#{optional_out_values}
"""
end
defp typespec(%GParamSpec{spec_type: "GParamEnum"} = pspec) do
type_name(pspec.value_type)
end
defp typespec(%GParamSpec{spec_type: "GParamFlags"} = pspec) do
type_name(pspec.value_type)
end
defp typespec(pspec_list) when is_list(pspec_list) do
Enum.map(pspec_list, &typespec/1)
end
defp typespec(pspec), do: Type.typespec(pspec)
defp optional_args_typespec(optional) do
Enum.map(optional, fn pspec ->
{pspec.param_name, typespec(pspec)}
end)
end
end
defmodule Vix.Vips.Operation do
@moduledoc """
Vips Operations
NOTE: Vips operation functions are generated using vips-introspection, so the bindings are up-to-date with vips version installed. Documentation in the hexdocs might *not* match for you.
"""
import Vix.Vips.OperationHelper
alias Vix.Nif
defmodule Error do
defexception [:message]
end
Vix.Nif.nif_vips_enum_list()
|> Enum.map(fn {name, enum} ->
{enum_str_list, _} = Enum.unzip(enum)
@type unquote(type_name(name)) ::
unquote(Enum.reduce(enum_str_list, &{:|, [], [&1, &2]}))
end)
Vix.Nif.nif_vips_flag_list()
|> Enum.map(fn {name, flag} ->
{flag_str_list, _} = Enum.unzip(flag)
@type unquote(type_name(name)) ::
list(unquote(Enum.reduce(flag_str_list, &{:|, [], [&1, &2]})))
end)
Nif.nif_vips_operation_list()
|> Enum.uniq()
|> Enum.reject(&reject_unsupported_operations/1)
|> Enum.map(fn name ->
{desc, required_in, optional_in, required_out, optional_out} = operation_args(name)
func_name = function_name(name)
func_params = Enum.map(required_in, &Macro.var(&1.param_name, __MODULE__))
in_pspec = Map.new(required_in ++ optional_in, &{&1.param_name, &1})
required_out_pspec = Map.new(required_out, &{&1.param_name, &1})
optional_out_pspec = Map.new(optional_out, &{&1.param_name, &1})
func_required_in_values =
Enum.map(required_in, fn pspec ->
{Atom.to_charlist(pspec.param_name), Macro.var(pspec.param_name, __MODULE__)}
end)
@doc """
#{prepare_doc(desc, required_in, optional_in, required_out, optional_out)}
"""
@spec unquote(
func_typespec(
func_name,
required_in,
optional_in,
required_out,
optional_out
)
)
def unquote(func_name)(unquote_splicing(func_params), optional \\ []) do
func_optional_in_values =
Enum.map(optional, fn {name, value} ->
{Atom.to_charlist(name), value}
end)
in_args = unquote(func_required_in_values) ++ func_optional_in_values
nif_in_args = input_to_nif_terms(in_args, unquote(Macro.escape(in_pspec)))
result =
Vix.Nif.nif_vips_operation_call(
unquote(name),
nif_in_args
)
case result do
{:ok, nif_out_args} ->
output_to_erl_terms(
nif_out_args,
unquote(Macro.escape(required_out_pspec)),
unquote(Macro.escape(optional_out_pspec))
)
{:error, term} ->
{:error, term}
end
end
bang_func_name = function_name(String.to_atom("#{name}!"))
@doc """
Same as `#{func_name}/#{length(func_params) + 1}`, except it returns only the value (not a tuple) and raises on error.
"""
@spec unquote(
bang_func_typespec(
bang_func_name,
required_in,
optional_in,
required_out,
optional_out
)
)
def unquote(bang_func_name)(unquote_splicing(func_params), optional \\ []) do
case __MODULE__.unquote(func_name)(unquote_splicing(func_params), optional) do
:ok -> :ok
{:ok, result} -> result
{:error, reason} when is_list(reason) -> raise Error, message: to_string(reason)
{:error, reason} -> raise Error, message: inspect(reason)
end
end
end)
end