lib/image/options/compare.ex

defmodule Image.Options.Compare do
  @moduledoc """
  Options and option validation for `Image.compare/3`.

  """
  import Image, only: :macros
  alias Image.Color

  @typedoc """
  Options applicable to `Image.compare/3`.

  """
  @type compare_option ::
          {:color, Color.t() | :auto}
          | {:threshold, non_neg_integer()}
          | {:greater_than, Color.t()}
          | {:less_than, Color.t()}
          | {:sigma, float()}
          | {:min_amplitude, float()}

  @type compare_options :: [compare_option()] | map()

  # Ussed by Image.compare/3 and defines the
  # default metric to be used.
  @default_compare_metric :ae

  # When overlaying the difference on the original image,
  # use this color by default.
  @default_difference_color :red

  # Saturation for the base image when used with the
  # difference overlay.
  @default_saturation 1.0

  # Brightness for the base image when used with the
  # difference overlay.
  @default_brightness 1.0

  # How much to boost the colors of the difference
  # pixels.
  @default_boost 1.5

  # The valid comparison metrics
  @valid_metrics [:ae, :mse, :rmse]

  @doc """
  Validate the options for `Image.compare/3`.

  """
  def validate_options(options) when is_list(options) do
    options = Keyword.merge(default_options(), options)

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

      options ->
        {:ok, Map.new(options)}
    end
  end

  defp validate_option({:metric, metric}, options) when metric in @valid_metrics do
    {:cont, options}
  end

  defp validate_option({:saturation, saturation}, options) when is_multiplier(saturation) do
    {:cont, options}
  end

  defp validate_option({:brightness, brightness}, options) when is_multiplier(brightness) do
    {:cont, options}
  end

  defp validate_option({:difference_boost, difference_boost}, options)
       when is_multiplier(difference_boost) do
    {:cont, options}
  end

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

  defp validate_option(option, _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
    [
      metric: @default_compare_metric,
      difference_color: @default_difference_color,
      brightness: @default_brightness,
      saturation: @default_saturation,
      difference_boost: @default_boost
    ]
  end
end