defmodule Vix.Operator do @moduledoc """ Provides implementation of basic math operators for Vix Image. Useful to improve the readability of complex Image processing operation pipelines. ```elixir def foo do use Vix.Operator black = Operation.black!(100, 100, bands: 3) white = Operation.invert!(black) white == white + black grey = black + 125 # same as [125] grey = black + [125] # same as [125, 125, 125], since the image contains 3 bands grey = black + [125, 125, 125] end ``` """ alias Vix.Vips.Image alias Vix.Vips.Operation import Kernel, except: [+: 2, -: 2, *: 2, /: 2] @doc false defmacro __using__(_opts) do quote do import Kernel, except: [+: 2, -: 2, *: 2, /: 2] import Vix.Operator end end @doc """ Perform addition operation of an image and a number, an array or an another image. * if argument is a number or a list of only one number, then the same number is used for all image bands for the operation. Example `img + 255`, `img + [255]` * if array size matches image bands, then each array element is added to respective band. Example `red = Operation.black!(100, 100, bands: 3) + [125, 0, 0]` * if array size is more than image bands and image only contains one band then the output will be a multi band image with each array element mapping to one band * if argument is an image, then `Vix.Vips.Operation.add!/2` operation will be performed When none of the argument is an image then delegates to `Kernel.+/2` """ @spec Image.t() + Image.t() :: Image.t() @spec Image.t() + number() :: Image.t() @spec number() + Image.t() :: Image.t() @spec Image.t() + [number()] :: Image.t() @spec [number()] + Image.t() :: Image.t() @spec number() + number() :: number() def a + b do add(a, b) end @doc """ Perform multiplication operation of an image and a number, an array or an another image. * if argument is a number or a list of only one number, then the same number is used for all image bands for the operation. Example `img * 2`, `img + [2]` * if array size matches image bands, then each array element is added to respective band * if array size is more than image bands and image only contains one band then the output will be a multi band image with each array element mapping to one band * if argument is an image, then `Vix.Vips.Operation.multiply!/2` operation will be performed When none of the argument is an image then delegates to `Kernel.*/2` """ @spec Image.t() * Image.t() :: Image.t() @spec Image.t() * number() :: Image.t() @spec number() * Image.t() :: Image.t() @spec Image.t() * [number()] :: Image.t() @spec [number()] * Image.t() :: Image.t() @spec number() * number() :: number() def a * b do mul(a, b) end @doc """ Perform subtraction operation of an image and a number, an array or an another image. * if argument is a number or a list of only one number, then the same number is used for all image bands for the operation. Example `img - 125`, `img - [125]` * if array size matches image bands, then each array element is added to respective band * if array size is more than image bands and image only contains one band then the output will be a multi band image with each array element mapping to one band * if argument is an image, then `Vix.Vips.Operation.subtract!/2` operation will be performed When none of the argument is an image then delegates to `Kernel.-/2` """ @spec Image.t() - Image.t() :: Image.t() @spec Image.t() - number() :: Image.t() @spec number() - Image.t() :: Image.t() @spec Image.t() - [number()] :: Image.t() @spec [number()] - Image.t() :: Image.t() @spec number() - number() :: number() def a - b do sub(a, b) end @doc """ Perform division operation of an image and a number, an array or an another image. * if argument is a number or a list of only one number, then the same number is used for all image bands for the operation. Example `img / 2`, `img / [2]` * if array size matches image bands, then each array element is added to respective band * if array size is more than image bands and image only contains one band then the output will be a multi band image with each array element mapping to one band * if argument is an image, then `Vix.Vips.Operation.divide!/2` operation will be performed When none of the argument is an image then delegates to `Kernel.//2` """ @spec Image.t() / Image.t() :: Image.t() @spec Image.t() / number() :: Image.t() @spec number() / Image.t() :: Image.t() @spec Image.t() / [number()] :: Image.t() @spec [number()] / Image.t() :: Image.t() @spec number() / number() :: number() def a / b do divide(a, b) end defmacrop when_number_list(arg, do: block) do quote do if Enum.all?(unquote(arg), &is_number/1) do unquote(block) else raise ArgumentError, "list elements must be a number, got: #{inspect(unquote(arg))}" end end end # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp add(a, b) do cond do image?(a) && image?(b) -> Operation.add!(a, b) image?(a) && is_number(b) -> add(a, [b]) is_number(a) && image?(b) -> add(b, [a]) is_list(a) && image?(b) -> add(b, a) image?(a) && is_list(b) -> when_number_list b do Operation.linear!(a, [1.0], b) end true -> Kernel.+(a, b) end end # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp mul(a, b) do cond do image?(a) && image?(b) -> Operation.multiply!(a, b) image?(a) && is_number(b) -> mul(a, [b]) is_number(a) && image?(b) -> mul(b, [a]) is_list(a) && image?(b) -> mul(b, a) image?(a) && is_list(b) -> when_number_list b do Operation.linear!(a, b, [+0.0]) end true -> Kernel.*(a, b) end end # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp sub(a, b) do cond do image?(a) && image?(b) -> Operation.subtract!(a, b) image?(a) && is_number(b) -> sub(a, [b]) is_number(a) && image?(b) -> sub([a], b) is_list(a) && image?(b) -> when_number_list a do # a - b = (b * -1) + a Operation.linear!(b, [-1.0], a) end image?(a) && is_list(b) -> when_number_list b do Operation.linear!(a, [1.0], Enum.map(b, &(-&1))) end true -> Kernel.-(a, b) end end # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp divide(a, b) do cond do image?(a) && image?(b) -> Operation.divide!(a, b) image?(a) && is_number(b) -> divide(a, [b]) is_number(a) && image?(b) -> divide([a], b) is_list(a) && image?(b) -> when_number_list a do # a / b = (b^-1) * a = (1 / b) * a b |> Operation.math2_const!(:VIPS_OPERATION_MATH2_POW, [-1.0]) |> Operation.linear!(a, [+0.0]) end image?(a) && is_list(b) -> when_number_list b do Operation.linear!(a, Enum.map(b, &(1 / &1)), [+0.0]) end true -> Kernel./(a, b) end end defp image?(%Image{}), do: true defp image?(_), do: false end