defmodule Image.BlendMode do @moduledoc """ Functions to define and validate the blending modes that can be applied when composing images. """ @default_blend_mode :VIPS_BLEND_MODE_OVER @vips_blend_mode_list [ :VIPS_BLEND_MODE_CLEAR, :VIPS_BLEND_MODE_SOURCE, :VIPS_BLEND_MODE_OVER, :VIPS_BLEND_MODE_IN, :VIPS_BLEND_MODE_OUT, :VIPS_BLEND_MODE_ATOP, :VIPS_BLEND_MODE_DEST, :VIPS_BLEND_MODE_DEST_OVER, :VIPS_BLEND_MODE_DEST_IN, :VIPS_BLEND_MODE_DEST_OUT, :VIPS_BLEND_MODE_DEST_ATOP, :VIPS_BLEND_MODE_XOR, :VIPS_BLEND_MODE_ADD, :VIPS_BLEND_MODE_SATURATE, :VIPS_BLEND_MODE_MULTIPLY, :VIPS_BLEND_MODE_SCREEN, :VIPS_BLEND_MODE_OVERLAY, :VIPS_BLEND_MODE_DARKEN, :VIPS_BLEND_MODE_LIGHTEN, :VIPS_BLEND_MODE_COLOUR_DODGE, :VIPS_BLEND_MODE_COLOUR_BURN, :VIPS_BLEND_MODE_HARD_LIGHT, :VIPS_BLEND_MODE_SOFT_LIGHT, :VIPS_BLEND_MODE_DIFFERENCE, :VIPS_BLEND_MODE_EXCLUSION ] # Convert the list of modes into a mapping # from an "Elixir friendly" term to the # underlying Vix/libvips terms @blend_mode_map @vips_blend_mode_list |> Enum.map(fn mode -> ["", key] = mode |> to_string |> String.downcase() |> String.split("vips_blend_mode_") {String.to_atom(key), mode} end) |> Map.new() @blend_modes Map.keys(@blend_mode_map) @typedoc """ Blend mode to use when compositing images. See `Image.compose/3`. * `:over` the image shows what you would expect if you held two semi-transparent slides on top of each other. This is the default when composing images * `:clear` where the second image is drawn, the first is removed * `:source` the second image is drawn as if nothing were below * `:in` the first image is removed completely, the second is only drawn where the first was * `:out` the second is drawn only where the first isn't * `:atop` this leaves the first image mostly intact, but mixes both images in the overlapping area * `:dest` leaves the first image untouched, the second is discarded completely * `:dest_over` like `:over`, but swaps the arguments * `:dest_in` like `:in`, but swaps the arguments * `:dest_out` like `:out`, but swaps the arguments * `:dest_atop` like `:atop`, but swaps the arguments * `:xor` something like a difference operator * `:add` a bit like adding the two images * `:saturate` a bit like the darker of the two * `:multiply` at least as dark as the darker of the two inputs * `:screen` at least as light as the lighter of the inputs * `:overlay` multiplies or screens colors, depending on the lightness * `:darken` the darker of each component * `:lighten` the lighter of each component * `:colour_dodge` brighten first by a factor second * `:colour_burn` darken first by a factor of second * `:hard_light` multiply or screen, depending on lightness * `:soft_light` darken or lighten, depending on lightness * `:difference` difference of the two * `:exclusion` somewhat like :difference, but lower-contrast """ @type t :: unquote(Enum.reduce(@blend_modes, &{:|, [], [&1, &2]})) @doc """ Returns the known blending modes. See `t:Image.BlendMode.t/0` for a description of each mode. """ def known_blend_modes do @blend_modes end @doc """ Returns the default blend mode ### Example iex> Image.BlendMode.default_blend_mode :VIPS_BLEND_MODE_OVER """ def default_blend_mode do @default_blend_mode end @doc """ Normalizes and validates a blend mode. ### Argument * `blend_mode` is one of `Image.BlendMode.known_blend_modes/0` as either a `t:String.t/0` or an `atom`. ### Returns * `{:ok, atom_blend_mode}` where `atom_blend_mode` is a valid blend mode for `libvips` * `{:error, reason}` ### Examples iex> Image.BlendMode.validate_blend_mode :clear {:ok, :VIPS_BLEND_MODE_CLEAR} iex> Image.BlendMode.validate_blend_mode "Over" {:ok, :VIPS_BLEND_MODE_OVER} iex> Image.BlendMode.validate_blend_mode :VIPS_BLEND_MODE_XOR {:ok, :VIPS_BLEND_MODE_XOR} iex> Image.BlendMode.validate_blend_mode :woops {:error, {:error, "Unknown blend mode. Found :woops"}} """ @spec validate_blend_mode(t() | nil) :: {:ok, atom()} | {:error, Image.error_message()} def validate_blend_mode(nil) do {:ok, default_blend_mode()} end def validate_blend_mode(blend_mode) when blend_mode in @vips_blend_mode_list do {:ok, blend_mode} end def validate_blend_mode(blend_mode) when is_atom(blend_mode) do case Map.fetch(@blend_mode_map, blend_mode) do {:ok, blend_mode} -> {:ok, blend_mode} :error -> {:error, unknown_blend_mode_error(blend_mode)} end end def validate_blend_mode(blend_mode) when is_binary(blend_mode) do blend_mode |> String.downcase() |> String.to_existing_atom() |> validate_blend_mode() rescue ArgumentError -> {:error, unknown_blend_mode_error(blend_mode)} end defp unknown_blend_mode_error(blend_mode) do {:error, "Unknown blend mode. Found #{inspect(blend_mode)}"} end end