diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c5b59..a73ad20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## master +* `Source.new_from_memory` keeps a reference to the memory area, which libvips + aliases rather than copies + ## Version 2.3.0 (2025-12-10) * move library_name out of the global namespace and into FFI [jcupitt] diff --git a/lib/vips/source.rb b/lib/vips/source.rb index a252697..4bc04e4 100644 --- a/lib/vips/source.rb +++ b/lib/vips/source.rb @@ -70,6 +70,9 @@ def self.new_from_file(filename) # Create a new source from an area of memory. Memory areas can be # strings, arrays and so forth -- anything that supports bytesize. # + # libvips does not copy the memory area, so the source keeps a reference + # to it and will hold it alive for as long as the source is alive. + # # Pass sources to {Image.new_from_source} to load images from # them. # @@ -79,10 +82,14 @@ def self.new_from_memory(data) ptr = Vips.vips_source_new_from_memory data, data.bytesize raise Vips::Error if ptr.null? - # FIXME do we need to keep a ref to the underlying memory area? what - # about Image.new_from_buffer? Does that need a secret ref too? + source = Vips::Source.new ptr - Vips::Source.new ptr + # vips_source_new_from_memory() aliases the memory area rather than + # copying it, so we must keep a secret ref to stop it being freed while + # the source is alive. See {Image.new_from_memory}. + source.references << data + + source end end end diff --git a/spec/connection_spec.rb b/spec/connection_spec.rb index 35f3c25..cefee30 100644 --- a/spec/connection_spec.rb +++ b/spec/connection_spec.rb @@ -26,6 +26,21 @@ expect(source) end + it "can load an image from a memory source after the caller drops its ref" do + source = Vips::Source.new_from_memory File.binread(simg("wagon.jpg")) + + # the secret ref inside source is now the only thing keeping the string + # alive ... GC to try to trigger a segv if new_from_memory didn't take one + GC.start + + image = Vips::Image.new_from_source source, "" + + expect(image.width).to eq(685) + expect(image.height).to eq(478) + expect(image.bands).to eq(3) + expect(image.avg).to be_within(0.001).of(109.789) + end + it "sources have filenames and nicks" do source = Vips::Source.new_from_file simg("wagon.jpg")