diff --git a/lib/image.ex b/lib/image.ex index 8565604..2437986 100644 --- a/lib/image.ex +++ b/lib/image.ex @@ -5907,6 +5907,12 @@ defmodule Image do a list of 3 or 4 float values depending on the image color space. + ## Discrete rotation + + When `angle` is a multiple of 90, and all displacement options + are unset, `nil`, `0` or `0.0`, the rotation will be done as a + discrete operation in order to preserve source pixel values. + ## Notes The displacement parameters cause the image canvas to be @@ -5935,10 +5941,41 @@ defmodule Image do def rotate(%Vimage{} = image, angle, options \\ []) when is_number(angle) do with {:ok, options} <- Options.Rotate.validate_options(options) do - Operation.rotate(image, angle, options) + rot_angle = rot_angle(angle, options) + + if rot_angle do + Operation.rot(image, rot_angle) + else + Operation.rotate(image, angle, options) + end + end + end + + defp rot_angle(angle, options) do + if Options.Rotate.no_displacement?(options) do + to_rot_angle(angle) end end + defp to_rot_angle(angle) when is_integer(angle) and rem(angle, 90) == 0 do + angle + |> Integer.mod(360) + |> rot_angle_from_degrees() + end + + defp to_rot_angle(angle) when is_float(angle) and angle == trunc(angle) do + angle + |> trunc() + |> to_rot_angle() + end + + defp to_rot_angle(_angle), do: nil + + defp rot_angle_from_degrees(0), do: :VIPS_ANGLE_D0 + defp rot_angle_from_degrees(90), do: :VIPS_ANGLE_D90 + defp rot_angle_from_degrees(180), do: :VIPS_ANGLE_D180 + defp rot_angle_from_degrees(270), do: :VIPS_ANGLE_D270 + @doc """ Rotate an image clockwise (to the right) by a number of degrees. diff --git a/lib/image/options/rotate.ex b/lib/image/options/rotate.ex index 0b5b739..4195f34 100644 --- a/lib/image/options/rotate.ex +++ b/lib/image/options/rotate.ex @@ -64,4 +64,16 @@ defmodule Image.Options.Rotate do defp invalid_option(option) do "Invalid option or option value: #{inspect(option)}" end + + @doc false + def no_displacement?(options) do + empty_displacement?(options, :idx) and + empty_displacement?(options, :idy) and + empty_displacement?(options, :odx) and + empty_displacement?(options, :ody) + end + + defp empty_displacement?(options, key) do + Keyword.get(options, key, 0) in [nil, 0, 0.0] + end end diff --git a/test/support/validate/Kip_small_rotate-90.jpg b/test/support/validate/Kip_small_rotate-90.jpg index 9e4dc56..4209473 100644 Binary files a/test/support/validate/Kip_small_rotate-90.jpg and b/test/support/validate/Kip_small_rotate-90.jpg differ diff --git a/test/support/validate/Kip_small_rotate90.jpg b/test/support/validate/Kip_small_rotate90.jpg index 94d3c45..5d83ce8 100644 Binary files a/test/support/validate/Kip_small_rotate90.jpg and b/test/support/validate/Kip_small_rotate90.jpg differ