libvips / ruby-vips

Ruby extension for the libvips image processing library.
MIT License
837 stars 60 forks source link

Can't figure out how to rotate around coordinates, and there is some error in docs #366

Open Nakilon opened 1 year ago

Nakilon commented 1 year ago

I need to rotate this image around the center of the circle with known coordinates

temp

[x,y,angle]
[398, 290, -2.4995608216061647]
img.rotate(angle * 180 / Math::PI, idx: x, idy: y, odx: x, ody: y)

but it looks wrong, the position has shifted

temp

how do I do it without shifting the resulting circle?

Also the https://www.libvips.org/API/current/libvips-resample.html#vips-rotate doc has the ody arg twice.

vips-8.11.3-Wed Aug 11 09:29:27 UTC 2021
Vips::VERSION "2.1.4"
Nakilon commented 1 year ago

After playing around I suppose rotation shifts the image, i.e. you don't know the odx/ody ahead, because it all shifts to hold the top left corner pixel inside the image. It feels wrong.

jcupitt commented 1 year ago

Hi @Nakilon,

Also the https://www.libvips.org/API/current/libvips-resample.html#vips-rotate doc has the ody arg twice.

Oops! Thanks, I patched it.

I'll make you a rotate example, I have some code somewhere.

jcupitt commented 1 year ago

Sorry, just to be clear, you'd like an image the same width and height as the input, and with the circle in the same position, but the circle should be rotated so the leaf points roughly upwards, is that right?

jcupitt commented 1 year ago

Here's a thing that does ^^^ my guess.

#!/usr/bin/ruby

require 'vips'

x, y, angle = 398, 290, -2.4995608216061647

image = Vips::Image.new_from_file ARGV[0]

# add an alpha so we can see the image edges
image = image.add_alpha

# the [[a, b], [c, d]] affine transform we want (simple rotate)
a = Math.cos(angle)
b = -Math.sin(angle)
c = -b
d = a

# point (x, y) in the input goes to this coordinate in the output
x_after = a * x + b * y
y_after = c * x + d * y

# therefore to keep (x, y) in the same place, we need to move the output area
# by the difference
dx = x_after - x
dy = y_after - y

image = image.affine [a, b, c, d], oarea: [dx, dy, image.width, image.height]

image.write_to_file ARGV[1]

I see:

$ ./naklion.rb ~/pics/naklion.png x2.png

To make:

x2

You don't need the alpha, but it's an easy way to see where the image edges are.

jcupitt commented 1 year ago

... perhaps rotate should have an oarea parameter. It would make this a little simpler.

Nakilon commented 1 year ago

Yes, you understood right. Thank you. I predicted it would need to calculate these sinuses by yourself. And yes, it's very inconvinient. As a user of this method I don't expect the rotation to change the size of image, because it's kind of unpredictable. After have no luck with it I ended up with just pixel-by-pixel projecting by myself ..(

Vips::Image.bandjoin( img.bandsplit.map do |band|
  array = band.to_a
  Vips::Image.new_from_array( array.map.with_index do |row, i|
    row.map.with_index do |c, j|
      (inner+outer)/2 < Math.hypot(x-j, y-i) ? c : ((
        ii = i - y
        jj = j - x
        array[jj * Math::sin(angle) + ii * Math::cos(angle) + y]\
             [jj * Math::cos(angle) - ii * Math::sin(angle) + x]
      ))
    end
  end ).cast :uchar
end )

otherwise I would need kind of reverse engineer this transformation method.

Nakilon commented 1 year ago

The goal was to transform this image to this image