libvips / lua-vips

Lua binding for the libvips image processing library
MIT License
123 stars 10 forks source link

add something like `mutable` #79

Open jcupitt opened 3 months ago

jcupitt commented 3 months ago

The ruby-vips binding has a nice feature for encapsulating destructive operations, like metadata change or the draw operations:

https://www.libvips.org/2021/03/08/ruby-vips-mutate.html

tldr: you can write eg.:

z = x.mutate do |y|
  1000.times do 
    y.draw_circle! Array.new(3) {rand(255)},
      rand(y.width), rand(y.height), rand(100), fill: true
  end
end
  1. mutate takes an image (x in this case) and makes an instance of a class called MutableImage
  2. This constructor runs copy() to make a private copy of the underlying vips image, and subclasses Image, adding a few new methods such as draw_circle! (ruby uses the ! suffix on method names to indicate destructive operations)
  3. mutate executes the block after, passing in the mutable image (y in this case)
  4. draw_circle! modifies the image you give it by erm drawing a circle ... the first time this happens, libvips will force the iamge into memory, ie. allocate a huge ram buffer and render the image into that
  5. Now the 1000 circles are drawn directly into memory with no copying and no chance of side effects
  6. Finally, the mutable block ends, and mutate uses the modified vips image to build a new instance of the Image class
  7. And this new Image instance is returned (and assigned to z in this code)

So this scheme automates the copying that lua-vips requires right now before you can use destructive operations like metadata changes, or line or circle draw. It has locks to prevent things like set or remove being used on a non-mutable image, so it's always safe.

You see a nice speedup too: that code is more than 10x faster and needs less than 1/10th the memory compared to ruby-vips without mutate.

This would be a nice thing to add to lua-vips. You could perhaps use anonymous functions, so the code above might be:

z = x:mutate(function (y)
  for i = 1, 1000 do
    y:draw_circle(... stuff)
  end
end)

You'd need to add a new mutable image class and some locks to gate access to set, remove, draw_circle, etc.

jcupitt commented 3 months ago

For reference, the ruby-vips mutable image class is here:

https://github.com/libvips/ruby-vips/blob/master/lib/vips/mutableimage.rb

And here's the mutate method:

https://github.com/libvips/ruby-vips/blob/master/lib/vips/image.rb#L800-L819

The thing to call libvips operations has a few ifs for enforcing mutability.

https://github.com/libvips/ruby-vips/blob/master/lib/vips/operation.rb