Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
13 changes: 10 additions & 3 deletions lib/vips/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
15 changes: 15 additions & 0 deletions spec/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down