diff --git a/.dialyzer_ignore_warnings b/.dialyzer_ignore_warnings deleted file mode 100644 index 8f42b348..00000000 --- a/.dialyzer_ignore_warnings +++ /dev/null @@ -1,12 +0,0 @@ -# Dialyzer reports a `pattern_match` warning at lib/image.ex:1 -# ("the pattern 'false' can never match the type 'true'") that -# is not locatable to a specific function — dialyxir collapses -# it to line 1 because the source mapping is unavailable. The -# warning originates from a `cond` / `if-else` arm whose -# predicate dialyzer infers to be always-true based on Vix's -# NIF return types (e.g. `Vix.Vips.Image.has_alpha?/1` is -# typed `boolean | no_return()` but the NIF-only inference -# narrows it to `true`). The behaviour is correct at runtime -# — we keep the falsy arms because `has_alpha?/1` does -# return `false` for alpha-less images. -lib/image.ex:1: The pattern 'false' can never match the type 'true' diff --git a/lib/image.ex b/lib/image.ex index ba2382ae..c774759e 100644 --- a/lib/image.ex +++ b/lib/image.ex @@ -7080,7 +7080,6 @@ defmodule Image do {20, 10, 3} """ - @dialyzer {:nowarn_function, {:ripple, 1}} @doc subject: "Operation" @spec ripple(Vimage.t(), Options.Mapim.background_options()) :: @@ -7143,7 +7142,6 @@ defmodule Image do {20, 10, 3} """ - @dialyzer {:nowarn_function, {:ripple!, 1}} @doc subject: "Operation" @spec ripple!(Vimage.t(), Options.Mapim.background_options()) :: Vimage.t() | no_return() @@ -9570,7 +9568,6 @@ defmodule Image do {10, 10, 3} """ - @dialyzer {:nowarn_function, {:to_polar_coordinates, 1}} @doc subject: "Operation" @spec to_polar_coordinates(Vimage.t(), Options.Mapim.background_options()) :: @@ -9621,7 +9618,6 @@ defmodule Image do {10, 10, 3} """ - @dialyzer {:nowarn_function, {:to_polar_coordinates!, 1}} @doc subject: "Operation" @spec to_polar_coordinates!(Vimage.t(), Options.Mapim.background_options()) :: @@ -9670,7 +9666,6 @@ defmodule Image do {10, 10, 3} """ - @dialyzer {:nowarn_function, {:to_rectangular_coordinates, 1}} @doc subject: "Operation" @spec to_rectangular_coordinates(Vimage.t(), Options.Mapim.interpolate_options()) :: @@ -9723,7 +9718,6 @@ defmodule Image do {10, 10, 3} """ - @dialyzer {:nowarn_function, {:to_rectangular_coordinates!, 1}} @doc subject: "Operation" @spec to_rectangular_coordinates!(Vimage.t(), Options.Mapim.interpolate_options()) :: @@ -11419,12 +11413,10 @@ defmodule Image do {:ok, Vimage.t()} | {:error, error()} def drop_shadow(%Vimage{} = image, options \\ []) do color = Keyword.get(options, :color, :black) - opacity = Keyword.get(options, :opacity, 0.5) - sigma = Keyword.get(options, :sigma, 5.0) dx = Keyword.get(options, :dx, 0) - dy = Keyword.get(options, :dy, round(sigma * 2)) - with :ok <- validate_drop_shadow_args(opacity, sigma) do + with {:ok, {opacity, sigma}} <- validate_drop_shadow_args(options) do + dy = Keyword.get(options, :dy, round(sigma * 2)) image = if has_alpha?(image), do: image, else: add_alpha!(image, :opaque) width = width(image) height = height(image) @@ -11472,24 +11464,29 @@ defmodule Image do end end - defp validate_drop_shadow_args(opacity, sigma) do + defp validate_drop_shadow_args(options) do + opacity = Keyword.get(options, :opacity, 0.5) + sigma = Keyword.get(options, :sigma, 5.0) + cond do not (is_number(opacity) and opacity >= 0.0 and opacity <= 1.0) -> {:error, %Image.Error{ - message: ":opacity must be a number in [0.0, 1.0]", - reason: ":opacity must be a number in [0.0, 1.0]" + reason: :invalid_option, + value: {:opacity, opacity}, + message: ":opacity must be a number in [0.0, 1.0]. Found #{inspect(opacity)}" }} not (is_number(sigma) and sigma > 0.0) -> {:error, %Image.Error{ - message: ":sigma must be a positive number", - reason: ":sigma must be a positive number" + reason: :invalid_option, + value: {:sigma, sigma}, + message: ":sigma must be a positive number. Found #{inspect(sigma)}" }} true -> - :ok + {:ok, {opacity, sigma}} end end @@ -12551,11 +12548,6 @@ defmodule Image do """ - # For some reason dialyzer thinks Vix.Vips.Image.write_to_tensor/1 - # can only return `{:error, _}`. - @dialyzer {:nowarn_function, {:to_nx, 1}} - @dialyzer {:nowarn_function, {:to_nx, 2}} - @default_shape :hwb @doc subject: "Matrix", since: "0.5.0" @@ -12642,11 +12634,6 @@ defmodule Image do """ @doc subject: "Matrix", since: "0.27.0" - # Because of the dialyzer issue for to_nx/2, dialyzer then - # thinks this function has no local return. - @dialyzer {:nowarn_function, {:to_nx!, 1}} - @dialyzer {:nowarn_function, {:to_nx!, 2}} - @spec to_nx!(image :: Vimage.t(), options :: Keyword.t()) :: Nx.Tensor.t() | no_return() @@ -13354,7 +13341,6 @@ defmodule Image do true """ - @dialyzer {:nowarn_function, {:to_evision, 2}} @doc subject: "Matrix", since: "0.9.0" @@ -13401,7 +13387,6 @@ defmodule Image do {300, 328, 3} """ - @dialyzer {:nowarn_function, {:from_evision, 1}} @doc subject: "Matrix", since: "0.9.0" @@ -13643,8 +13628,6 @@ defmodule Image do end end - @dialyzer {:nowarn_function, {:compare_by_metric, 4}} - # Mean square error # mse = ((a - b) ** 2).avg() @@ -13712,8 +13695,6 @@ defmodule Image do end end - @dialyzer {:nowarn_function, {:format_size, 1}} - defp format_size(image) do case Image.BandFormat.nx_format(image) do {:ok, {:u, size}} -> {:ok, round(:math.pow(2, size))} @@ -13969,7 +13950,6 @@ defmodule Image do upright. """ - @dialyzer {:nowarn_function, {:skew_angle, 1}} @doc subject: "Operation" @spec skew_angle(Vimage.t()) :: float() diff --git a/lib/image/complex.ex b/lib/image/complex.ex index ee55e870..1bb8bd7b 100644 --- a/lib/image/complex.ex +++ b/lib/image/complex.ex @@ -9,14 +9,10 @@ defmodule Image.Complex do defguard float_format(format) when format in [:VIPS_FORMAT_FLOAT, :VIPS_FORMAT_DOUBLE] defguard even(bands) when rem(bands, 2) === 0 - @dialyzer {:nowarn_function, {:polar, 1}} - def polar(%Vimage{} = image) do complex(image, &Operation.complex(&1, :VIPS_OPERATION_COMPLEX_POLAR)) end - @dialyzer {:nowarn_function, {:polar!, 1}} - def polar!(%Vimage{} = image) do case polar(image) do {:ok, polar} -> polar @@ -24,14 +20,10 @@ defmodule Image.Complex do end end - @dialyzer {:nowarn_function, {:rectangular, 1}} - def rectangular(%Vimage{} = image) do complex(image, &Operation.complex(&1, :VIPS_OPERATION_COMPLEX_RECT)) end - @dialyzer {:nowarn_function, {:rectangular!, 1}} - def rectangular!(%Vimage{} = image) do case rectangular(image) do {:ok, image} -> image @@ -39,8 +31,6 @@ defmodule Image.Complex do end end - @dialyzer {:nowarn_function, {:complex, 2}} - defp complex(%Vimage{} = image, fun) do bands = Vimage.bands(image) original_format = Vimage.format(image) @@ -54,8 +44,6 @@ defmodule Image.Complex do # Convert to complex - @dialyzer {:nowarn_function, {:to_complex, 3}} - defp to_complex(%Vimage{} = _image, format, bands) when not complex(format) and not even(bands) do {:error, @@ -85,8 +73,6 @@ defmodule Image.Complex do # Convert from complex - @dialyzer {:nowarn_function, {:from_complex, 4}} - defp from_complex(image, original_format, :VIPS_FORMAT_DPCOMPLEX, bands) when not complex(original_format) do Operation.copy(image, format: :VIPS_FORMAT_DOUBLE, bands: bands) diff --git a/lib/image/enum/band_format.ex b/lib/image/enum/band_format.ex index e6fd5ed9..78b65e98 100644 --- a/lib/image/enum/band_format.ex +++ b/lib/image/enum/band_format.ex @@ -121,8 +121,6 @@ defmodule Image.BandFormat do end end - @dialyzer {:nowarn_function, {:nx_format, 1}} - @doc """ Returns the `Image` format type for an `Nx` format type. diff --git a/lib/image/math.ex b/lib/image/math.ex index b6f2d6c5..eab11115 100644 --- a/lib/image/math.ex +++ b/lib/image/math.ex @@ -423,7 +423,7 @@ defmodule Image.Math do {:ok, Kernel.**(a, b)} end - @spec cos(Vimage.t()) :: {:ok, Vimage.t()} + @spec cos(Vimage.t()) :: {:ok, Vimage.t()} | {:error, Image.error()} def cos(%Vimage{} = image) do Operation.math(image, :VIPS_OPERATION_MATH_COS) end @@ -433,7 +433,7 @@ defmodule Image.Math do {:ok, :math.cos(other)} end - @spec sin(Vimage.t()) :: {:ok, Vimage.t()} + @spec sin(Vimage.t()) :: {:ok, Vimage.t()} | {:error, Image.error()} def sin(%Vimage{} = image) do Operation.math(image, :VIPS_OPERATION_MATH_SIN) end @@ -671,7 +671,7 @@ defmodule Image.Math do Kernel.>(a, b) end - @spec greater_than_or_equal!(Vimage.t(), Image.pixel()) :: Vimage.t() | no_return() + @spec greater_than_or_equal!(Vimage.t(), Vimage.t() | Image.pixel()) :: Vimage.t() | no_return() def greater_than_or_equal!(%Vimage{} = image, value) do case greater_than_or_equal(image, value) do {:ok, image} -> image @@ -684,7 +684,7 @@ defmodule Image.Math do Kernel.>=(a, b) end - @spec equal!(Vimage.t(), Image.pixel()) :: Vimage.t() | no_return() + @spec equal!(Vimage.t(), Vimage.t() | Image.pixel()) :: Vimage.t() | no_return() def equal!(%Vimage{} = image, value) do case equal(image, value) do {:ok, image} -> image @@ -697,7 +697,7 @@ defmodule Image.Math do Kernel.==(a, b) end - @spec not_equal!(Vimage.t(), Image.pixel()) :: Vimage.t() | no_return() + @spec not_equal!(Vimage.t(), Vimage.t() | Image.pixel()) :: Vimage.t() | no_return() def not_equal!(%Vimage{} = image, value) do case not_equal(image, value) do {:ok, image} -> image @@ -710,7 +710,7 @@ defmodule Image.Math do Kernel.!=(a, b) end - @spec add!(Vimage.t(), Image.pixel() | number()) :: Vimage.t() | no_return() + @spec add!(Vimage.t(), Vimage.t() | Image.pixel() | number()) :: Vimage.t() | no_return() def add!(%Vimage{} = image, value) do case add(image, value) do {:ok, image} -> image @@ -731,7 +731,7 @@ defmodule Image.Math do Kernel.+(a, b) end - @spec subtract!(Vimage.t(), Image.pixel()) :: Vimage.t() | no_return() + @spec subtract!(Vimage.t(), Vimage.t() | Image.pixel()) :: Vimage.t() | no_return() def subtract!(%Vimage{} = image, value) do case subtract(image, value) do {:ok, image} -> image @@ -752,7 +752,7 @@ defmodule Image.Math do Kernel.-(a, b) end - @spec multiply!(Vimage.t(), Image.pixel() | number()) :: Vimage.t() | no_return() + @spec multiply!(Vimage.t(), Vimage.t() | Image.pixel() | number()) :: Vimage.t() | no_return() def multiply!(%Vimage{} = image, value) do case multiply(image, value) do {:ok, image} -> image @@ -773,7 +773,7 @@ defmodule Image.Math do Kernel.*(a, b) end - @spec divide!(Vimage.t(), Image.pixel()) :: Vimage.t() | no_return() + @spec divide!(Vimage.t(), Vimage.t() | Image.pixel()) :: Vimage.t() | no_return() def divide!(%Vimage{} = image, value) do case divide(image, value) do {:ok, image} -> image @@ -826,7 +826,7 @@ defmodule Image.Math do end end - @spec pow!(Vimage.t(), number()) :: Vimage.t() | no_return() + @spec pow!(Vimage.t(), Vimage.t() | number()) :: Vimage.t() | no_return() def pow!(%Vimage{} = image, value) do case pow(image, value) do {:ok, image} -> image @@ -839,8 +839,6 @@ defmodule Image.Math do Kernel.**(a, b) end - @dialyzer {:nowarn_function, {:cos!, 1}} - @spec cos!(Vimage.t()) :: Vimage.t() | no_return() def cos!(%Vimage{} = image) do case cos(image) do @@ -849,8 +847,6 @@ defmodule Image.Math do end end - @dialyzer {:nowarn_function, {:sin!, 1}} - @spec sin!(Vimage.t()) :: Vimage.t() | no_return() def sin!(%Vimage{} = image) do case sin(image) do @@ -1068,7 +1064,6 @@ defmodule Image.Math do max_coordinates :: [Image.point(), ...], maybe_overflow :: :maybe_overflow | nil } - @dialyzer {:nowarn_function, maxpos: 2} def maxpos(%Vimage{} = image, n \\ 10) when is_integer(n) do band_format = Image.band_format(image) {:ok, {max, opts}} = Operation.max(image, size: n) @@ -1119,7 +1114,6 @@ defmodule Image.Math do {1.0, [{0, 0}], nil} """ - @dialyzer {:nowarn_function, minpos: 2} @spec minpos(image :: Vimage.t(), n :: non_neg_integer()) :: { maximum :: number(), diff --git a/lib/image/text.ex b/lib/image/text.ex index d4c29a6d..25c8db58 100644 --- a/lib/image/text.ex +++ b/lib/image/text.ex @@ -951,8 +951,6 @@ defmodule Image.Text do # For transparent text we need to render the text in # white which is then converted later to a transparency # mask - @dialyzer {:nowarn_function, {:render_text, 2}} - defp render_text(text, %{text_fill_color: :transparent} = options) do render_text(text, Map.put(options, :text_fill_color, :white)) end diff --git a/mix.exs b/mix.exs index 209d420d..81e3c2d0 100644 --- a/mix.exs +++ b/mix.exs @@ -35,7 +35,6 @@ defmodule Image.MixProject do ] ], dialyzer: [ - ignore_warnings: ".dialyzer_ignore_warnings", plt_add_apps: ~w(mix nx plug evision ex_unit)a ], compilers: Mix.compilers() diff --git a/test/group_b_test.exs b/test/group_b_test.exs index 0803742f..991d8c04 100644 --- a/test/group_b_test.exs +++ b/test/group_b_test.exs @@ -124,13 +124,24 @@ defmodule Image.GroupB.Test do end test "rejects out-of-range :opacity", %{cat: image} do - assert {:error, %Image.Error{}} = Image.drop_shadow(image, opacity: 1.5) - assert {:error, %Image.Error{}} = Image.drop_shadow(image, opacity: -0.1) + assert {:error, %Image.Error{reason: :invalid_option, value: {:opacity, 1.5}}} = + Image.drop_shadow(image, opacity: 1.5) + + assert {:error, %Image.Error{reason: :invalid_option, value: {:opacity, -0.1}}} = + Image.drop_shadow(image, opacity: -0.1) end test "rejects non-positive :sigma", %{cat: image} do - assert {:error, %Image.Error{}} = Image.drop_shadow(image, sigma: 0) - assert {:error, %Image.Error{}} = Image.drop_shadow(image, sigma: -1.0) + assert {:error, %Image.Error{reason: :invalid_option, value: {:sigma, 0}}} = + Image.drop_shadow(image, sigma: 0) + + assert {:error, %Image.Error{reason: :invalid_option, value: {:sigma, -1.0}}} = + Image.drop_shadow(image, sigma: -1.0) + end + + test "rejects non-numeric :sigma when :dy is derived from it", %{cat: image} do + assert {:error, %Image.Error{reason: :invalid_option, value: {:sigma, "5.0"}}} = + Image.drop_shadow(image, sigma: "5.0") end end diff --git a/test/math_coverage_test.exs b/test/math_coverage_test.exs index cc87ba50..64449a05 100644 --- a/test/math_coverage_test.exs +++ b/test/math_coverage_test.exs @@ -489,5 +489,62 @@ defmodule Image.MathCoverage.Test do quotient = Math.divide!(100, grey_image(100)) assert Image.get_pixel!(quotient, 0, 0) == [1.0, 1.0, 1.0] end + + test "divide!/2 with image and list" do + quotient = Math.divide!(grey_image(100), [2, 4, 5]) + assert Image.get_pixel!(quotient, 0, 0) == [50.0, 25.0, 20.0] + end + + test "pow!/2 with two images" do + power = Math.pow!(grey_image(2), grey_image(3)) + assert Image.get_pixel!(power, 0, 0) == [8.0, 8.0, 8.0] + end + end + + describe "comparison bang functions with images" do + test "less_than!/2 with an image and a list" do + comparison = Math.less_than!(grey_image(100), [50, 100, 150]) + assert Image.get_pixel!(comparison, 0, 0) == [0, 0, 255] + end + + test "less_than_or_equal!/2 with two images" do + comparison = Math.less_than_or_equal!(grey_image(100), grey_image(30)) + assert Image.get_pixel!(comparison, 0, 0) == [0, 0, 0] + end + + test "less_than_or_equal!/2 with an image and a list" do + comparison = Math.less_than_or_equal!(grey_image(100), [50, 100, 150]) + assert Image.get_pixel!(comparison, 0, 0) == [0, 255, 255] + end + + test "greater_than!/2 with two images" do + comparison = Math.greater_than!(grey_image(100), grey_image(30)) + assert Image.get_pixel!(comparison, 0, 0) == [255, 255, 255] + end + + test "greater_than!/2 with an image and a list" do + comparison = Math.greater_than!(grey_image(100), [50, 100, 150]) + assert Image.get_pixel!(comparison, 0, 0) == [255, 0, 0] + end + + test "greater_than_or_equal!/2 with two images" do + comparison = Math.greater_than_or_equal!(grey_image(100), grey_image(30)) + assert Image.get_pixel!(comparison, 0, 0) == [255, 255, 255] + end + + test "greater_than_or_equal!/2 with an image and a list" do + comparison = Math.greater_than_or_equal!(grey_image(100), [50, 100, 150]) + assert Image.get_pixel!(comparison, 0, 0) == [255, 255, 0] + end + + test "equal!/2 with two images" do + comparison = Math.equal!(grey_image(100), grey_image(100)) + assert Image.get_pixel!(comparison, 0, 0) == [255, 255, 255] + end + + test "not_equal!/2 with two images" do + comparison = Math.not_equal!(grey_image(100), grey_image(30)) + assert Image.get_pixel!(comparison, 0, 0) == [255, 255, 255] + end end end diff --git a/test/support/image_test_helpers.ex b/test/support/image_test_helpers.ex index f4643238..a9952667 100644 --- a/test/support/image_test_helpers.ex +++ b/test/support/image_test_helpers.ex @@ -49,14 +49,10 @@ defmodule Image.TestSupport do MapSet.member?(@supported_heif_compressions, compression) end - @dialyzer {:nowarn_function, {:assert_files_equal, 2}} def assert_files_equal(expected, result) do assert File.read!(expected) == File.read!(result) end - @dialyzer {:nowarn_function, {:assert_images_equal, 2}} - @dialyzer {:nowarn_function, {:assert_images_equal, 3}} - def assert_images_equal(calculated_image, validate, similarity \\ @acceptable_similarity) def assert_images_equal(%Vimage{} = calculated_image, validate, similarity) @@ -88,7 +84,6 @@ defmodule Image.TestSupport do # From: https://github.com/libvips/libvips/discussions/2232 # Calculate a single number for the match between two images, calculate the sum # of squares of differences, - @dialyzer {:nowarn_function, {:compare_images, 3}} def compare_images(calculated_image, validate_image, acceptable_similarity) do alias Image.Math validate_path = Image.filename(validate_image)