Here is the code for a 2D FFT based rotation.

`fftpos`

is extracted from PhysicalOptics.jl and I copied it for simplicity.

I didn’t include it here, but it should work pretty much the same with CUDA (tested it last week).

```
using FFTW
function shear(arr, Δx)
ĩ = 2
c = eltype(arr)(2π * Δx)
ϕ_1D_shift = c .* rfftfreq(size(arr)[ĩ], one(eltype(arr)))'
ϕ_shift_strength = fftpos(one(eltype(arr)), size(arr)[2])
ϕ_2D = exp.(1im .* ϕ_1D_shift .* ϕ_shift_strength)
arr_ft = rfft(arr, [ĩ])
return irfft(arr_ft .* ϕ_2D, size(arr)[ĩ], [ĩ])
end
function rotate(arr, θ)
α = -tan(θ/2)
β = sin(θ)
arr = shear(arr, α * size(arr)[1])
arr = permutedims(arr, (2,1))
arr = shear(arr, β * size(arr)[1])
arr = permutedims(arr, (2,1))
arr = shear(arr, α * size(arr)[1])
return arr
end
```

Here is a full Pluto example:

##
Full Example

```
### A Pluto.jl notebook ###
# v0.12.19
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
el
end
end
# ╔═╡ 7560fc84-6862-11eb-0f99-bfa76ae8a694
using Revise, FFTW, FFTResampling, TestImages, Colors, PlutoUI
# ╔═╡ 16e4aa72-6863-11eb-35ad-21c3ba47ebad
function fftpos(l, N)
if N % 2 == 0
dx = l / N
return range(-l/2, l/2-dx, length=N)
else
return range(-l/2, l/2, length=N)
end
end
# ╔═╡ 79d641c0-6862-11eb-15db-77d9548980c1
function shear(arr, Δx)
ĩ = 2
c = eltype(arr)(2π * Δx)
ϕ_1D_shift = c .* rfftfreq(size(arr)[ĩ], one(eltype(arr)))'
ϕ_shift_strength = fftpos(one(eltype(arr)), size(arr)[2])
ϕ_2D = exp.(1im .* ϕ_1D_shift .* ϕ_shift_strength)
arr_ft = rfft(arr, [ĩ])
return irfft(arr_ft .* ϕ_2D, size(arr)[ĩ], [ĩ])
end
# ╔═╡ 0323a81a-6864-11eb-1c5f-eb449060c54b
function rotate(arr, θ)
α = -tan(θ/2)
β = sin(θ)
arr = shear(arr, α * size(arr)[1])
arr = permutedims(arr, (2,1))
arr = shear(arr, β * size(arr)[1])
arr = permutedims(arr, (2,1))
arr = shear(arr, α * size(arr)[1])
return arr
end
# ╔═╡ 79b6251e-6862-11eb-0260-a71fc759825b
begin
img = Float32.(testimage("fabip_gray_256"))
img_pad = FFTResampling.center_set!(zeros(eltype(img), (400, 400)), img)
end
# ╔═╡ 4c4f8fde-6866-11eb-3e26-1b5be5dfd4fa
md"""
$(@bind θ Slider(-180:180))
"""
# ╔═╡ b20c01c8-6866-11eb-33c1-89d7c45484ed
img_s = rotate(img_pad, θ / 180 * π)
# ╔═╡ b4b405c6-6866-11eb-1b55-173146524f32
md"""
$ \theta= $ $(θ)°
"""
# ╔═╡ 95d4846c-6863-11eb-11c5-5568bca3e754
Gray.(img_s)
# ╔═╡ Cell order:
# ╠═7560fc84-6862-11eb-0f99-bfa76ae8a694
# ╠═16e4aa72-6863-11eb-35ad-21c3ba47ebad
# ╠═79d641c0-6862-11eb-15db-77d9548980c1
# ╠═0323a81a-6864-11eb-1c5f-eb449060c54b
# ╠═79b6251e-6862-11eb-0260-a71fc759825b
# ╠═b20c01c8-6866-11eb-33c1-89d7c45484ed
# ╠═4c4f8fde-6866-11eb-3e26-1b5be5dfd4fa
# ╠═b4b405c6-6866-11eb-1b55-173146524f32
# ╠═95d4846c-6863-11eb-11c5-5568bca3e754
```