lib/image/options/warp_perspective.ex

defmodule Image.Options.WarpPerspective do
  @moduledoc """
  Options and option validation for `Image.warp_perspective/4`.

  """
  alias Vix.Vips.Image, as: Vimage
  alias Image.Color

  @typedoc """
  Options for Image.warp_perspective/4.

  """
  @type t :: [warp_perspective_option()]

  @typedoc """
  Options applicable to `Image.embed/4`.

  """
  @type warp_perspective_option :: [
          {:background, Color.t() | :average}
          | {:extend_mode, extend_mode()}
        ]

  @typedoc """
  When extending the canvas the generated
  pixels are determined by this option.

  """
  @type extend_mode :: [
          :black
          | :white
          | :copy
          | :repeat
          | :mirror
          | :background
        ]

  @doc """
  Validate the options for `Image.warp_perspective/4`.

  """
  @spec validate_options(Vimage.t(), Keyword.t()) :: {:ok, Keyword.t()} | {:error, Image.error_message()}
  def validate_options(image, options) when is_list(options) do
    options = Keyword.merge(default_options(), options)

    case Enum.reduce_while(options, options, &validate_option(&1, image, &2)) do
      {:error, value} ->
        {:error, value}

      options ->
        {:ok, options}
    end
  end

  defp validate_option({:background, :average}, image, options) do
    case Image.average(image) do
      color when is_list(color) ->
        options = Keyword.put(options, :background, color)
        {:cont, options}

      {:error, reason} ->
        {:halt, {:error, "Could not get the image average: #{inspect(reason)}"}}
    end
  end

  defp validate_option({:background, color} = option, _image, options) do
    case Color.rgb_color(color) do
      {:ok, hex: _hex, rgb: color} -> {:cont, Keyword.put(options, :background, color)}
      {:ok, color} -> {:cont, Keyword.put(options, :backgrouond, color)}
      _other -> {:halt, invalid_option(option)}
    end
  end

  defp validate_option({:extend_mode, extend}, _image, options) do
    case Image.ExtendMode.validate_extend(extend) do
      {:ok, extend_mode} ->
        options = Keyword.put(options, :extend_mode, extend_mode)
        {:cont, options}

      {:error, reason} ->
        {:halt, {:error, reason}}
    end
  end

  defp validate_option(option, _image, _options) do
    {:halt, {:error, invalid_option(option)}}
  end

  defp invalid_option(option) do
    "Invalid option or option value: #{inspect(option)}"
  end

  defp default_options do
    [
      extend_mode: :black,
      background: :black
    ]
  end
end