|
| 1 | +defmodule Image.TestSupport do |
| 2 | + import ExUnit.Assertions |
| 3 | + alias Vix.Vips.Image, as: Vimage |
| 4 | + |
| 5 | + @images_path Path.join(__DIR__, "images") |
| 6 | + @validate_path Path.join(__DIR__, "validate") |
| 7 | + @acceptable_similarity 1.1 |
| 8 | + |
| 9 | + @dialyzer {:nowarn_function, {:assert_files_equal, 2}} |
| 10 | + def assert_files_equal(expected, result) do |
| 11 | + assert File.read!(expected) == File.read!(result) |
| 12 | + end |
| 13 | + |
| 14 | + @dialyzer {:nowarn_function, {:assert_images_equal, 2}} |
| 15 | + @dialyzer {:nowarn_function, {:assert_images_equal, 3}} |
| 16 | + |
| 17 | + def assert_images_equal(calculated_image, validate, similarity \\ @acceptable_similarity) |
| 18 | + |
| 19 | + def assert_images_equal(%Vimage{} = calculated_image, validate, similarity) |
| 20 | + when is_binary(validate) do |
| 21 | + validate_image = Image.open!(validate, access: :random) |
| 22 | + compare_images(calculated_image, validate_image, similarity) |
| 23 | + end |
| 24 | + |
| 25 | + def assert_images_equal(calculated, validate, similarity) |
| 26 | + when is_binary(calculated) and is_binary(validate) do |
| 27 | + validate_image = Image.open!(validate, access: :random) |
| 28 | + calculated_image = Image.open!(calculated, access: :random) |
| 29 | + |
| 30 | + compare_images(calculated_image, validate_image, similarity) |
| 31 | + end |
| 32 | + |
| 33 | + def assert_images_equal(%Vimage{} = calculated, %Vimage{} = validate, similarity) do |
| 34 | + compare_images(calculated, validate, similarity) |
| 35 | + end |
| 36 | + |
| 37 | + def image_path(name) do |
| 38 | + Path.join(@images_path, name) |
| 39 | + end |
| 40 | + |
| 41 | + def validate_path(name) do |
| 42 | + Path.join(@validate_path, name) |
| 43 | + end |
| 44 | + |
| 45 | + # From: https://github.com/libvips/libvips/discussions/2232 |
| 46 | + # Calculate a single number for the match between two images, calculate the sum |
| 47 | + # of squares of differences, |
| 48 | + @dialyzer {:nowarn_function, {:compare_images, 3}} |
| 49 | + def compare_images(calculated_image, validate_image, acceptable_similarity) do |
| 50 | + alias Image.Math |
| 51 | + validate_path = Image.filename(validate_image) |
| 52 | + |
| 53 | + {calculated_image, validate_image} = |
| 54 | + if Vimage.format(calculated_image) == Vimage.format(validate_image) do |
| 55 | + {calculated_image, validate_image} |
| 56 | + else |
| 57 | + { |
| 58 | + Vix.Vips.Operation.cast!(calculated_image, :VIPS_FORMAT_UCHAR), |
| 59 | + Vix.Vips.Operation.cast!(validate_image, :VIPS_FORMAT_UCHAR) |
| 60 | + } |
| 61 | + end |
| 62 | + |
| 63 | + similarity = |
| 64 | + calculated_image |
| 65 | + |> Math.subtract!(validate_image) |
| 66 | + |> Math.pow!(2) |
| 67 | + |> Vix.Vips.Operation.avg!() |
| 68 | + |
| 69 | + if similarity < acceptable_similarity do |
| 70 | + assert true |
| 71 | + else |
| 72 | + path = String.replace(validate_path, "validate", "did_not_match") |
| 73 | + |
| 74 | + comparison_image = |
| 75 | + Vix.Vips.Operation.relational!( |
| 76 | + calculated_image, |
| 77 | + validate_image, |
| 78 | + :VIPS_OPERATION_RELATIONAL_EQUAL |
| 79 | + ) |
| 80 | + |
| 81 | + Image.write!(comparison_image, path) |
| 82 | + |
| 83 | + flunk( |
| 84 | + "Calculated image did not match pre-existing validation image. " <> |
| 85 | + "Similarity score was #{inspect(similarity)}. " <> |
| 86 | + "See the image at #{path} for the image diff." |
| 87 | + ) |
| 88 | + end |
| 89 | + end |
| 90 | +end |
0 commit comments