ruby-vips icon indicating copy to clipboard operation
ruby-vips copied to clipboard

Rotate leaves black lines along one or more sides of the image.

Open etherbob opened this issue 3 years ago • 4 comments

Describe the bug Rotating in 90º increments leaves a black line on one or more sides of non-square images

To Reproduce Steps to reproduce the behavior:

Using this image: flip-me-for-real

path = '/path/to/flip-me-for-real.jpg'
img = Vips::Image.new_from_file(path)
img.rotate(90).write_to_file('out90.jpg')

Expected behavior

Expect image to output rotated by 90 degrees without additional image content.

Actual Behavior

actual output out90

Screen Shot 2022-06-20 at 10 38 29 AM

Desktop:

  • OS: macOS
  • Version 12.4
  • ruby-vips 2.1.4
  • libvips 8.12.2-Tue Jan 25 09:34:32 UTC 2022

** Server **

  • heroku-stack 20 (Ubuntu 20.04-based)
  • ruby-vips 2.1.4
  • libvips 8.12.2-Tue Jan 25 09:34:32 UTC 2022

etherbob avatar Jun 20 '22 15:06 etherbob

I reported this to the image_processing gem (https://github.com/janko/image_processing/issues/104) but it looks like this might be happening at a lower level than either of these gems. My doing-anything-with-c skills are very much atrophied or I'd take a swing at reproducing directly against libvips.

I also attempted to apply some of the suggestions in https://github.com/libvips/libvips/issues/1051 but would be completely unsurprised to find out I missed a trick somewhere.

etherbob avatar Jun 20 '22 15:06 etherbob

Hi @etherbob,

This is expected behaviour. libvips uses corner convention by default for affine transforms, so a 90 degree rotate will translate the image by 1 pixel.

Your best fix (if you want a no-border 90 degree rotate) is to use the rot90 method. This does a simple pixel swap rotation, so it can only do multiples of 90, but you'll get a borderless result. Something like:

result = image.rot90()

There's also rot180, rot270 and rot. Don't use rot45, that's just for matrix arithmetic.

If you want fractional rotations, then you could use centre convention with eg.:

result = image.rotate(90, idx: 05, idy: 0.5)

Now you'll get symmetrical edges, but the 0.5 pixel displacement will make your image a little fuzzy.

jcupitt avatar Jun 20 '22 17:06 jcupitt

Thank you for the quick response @jcupitt, that did the trick. Do you think it would be possible/reasonable to have ruby-vips switch to the appropriate rot command if the angle is divisible by 90?

etherbob avatar Jun 20 '22 18:06 etherbob

I think flipping between rotate methods depending on the argument value would produce some unfortunate transition effects.

I think the best solution is to have two separate methods: one to do an exact rotate in 90 degree steps, and one to do a free rotation (this is what ruby-vips does now).

Perhaps we can do more to improve rotate, I'll make an issue to look at this again for 8.14.

jcupitt avatar Jun 21 '22 10:06 jcupitt