Skip to content

Commit c9c7b56

Browse files
authored
Merge pull request #296 from sled/bugfix/sled-295-ffi-pointer-size
Fix FFI::Pointer handling in Image#new_from_memory and Image#new_from_memory_copy
2 parents fe124c4 + fed73ec commit c9c7b56

File tree

2 files changed

+120
-3
lines changed

2 files changed

+120
-3
lines changed

lib/vips/image.rb

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ def self.p2str(pointer)
7878
class Image < Vips::Object
7979
alias_method :parent_get_typeof, :get_typeof
8080

81+
# FFI sets a pointer's size to this magic value if the size of the memory
82+
# chunk the pointer points to is unknown to FFI.
83+
UNKNOWN_POINTER_SIZE = FFI::Pointer.new(1).size
84+
private_constant :UNKNOWN_POINTER_SIZE
85+
8186
private
8287

8388
# the layout of the VipsImage struct
@@ -326,6 +331,24 @@ def self.new_from_buffer data, option_string, **opts
326331
# image.width, image.height, image.bands, image.format
327332
# ```
328333
#
334+
# Creating a new image from a memory pointer:
335+
#
336+
# ```
337+
# ptr = FFI::MemoryPointer.new(:uchar, 10*10)
338+
# # => #<FFI::MemoryPointer address=0x00007fc236db31d0 size=100>
339+
# x = Vips::Image.new_from_memory(ptr, 10, 10, 1, :uchar)
340+
# ```
341+
#
342+
# Creating a new image from an address only pointer:
343+
#
344+
# ```
345+
# ptr = call_to_external_c_library(w: 10, h: 10)
346+
# # => #<FFI::Pointer address=0x00007f9780813a00>
347+
# ptr_slice = ptr.slice(0, 10*10)
348+
# # => #<FFI::Pointer address=0x00007f9780813a00 size=100>
349+
# x = Vips::Image.new_from_memory(ptr_slice, 10, 10, 1, :uchar)
350+
# ```
351+
#
329352
# {new_from_memory} keeps a reference to the array of pixels you pass in
330353
# to try to prevent that memory from being freed by the Ruby GC while it
331354
# is being used.
@@ -340,13 +363,23 @@ def self.new_from_buffer data, option_string, **opts
340363
# @param format [Symbol] band format
341364
# @return [Image] the loaded image
342365
def self.new_from_memory data, width, height, bands, format
343-
size = data.bytesize
344-
345366
# prevent data from being freed with JRuby FFI
346367
if defined?(JRUBY_VERSION) && !data.is_a?(FFI::Pointer)
347368
data = ::FFI::MemoryPointer.new(:char, data.bytesize).write_bytes data
348369
end
349370

371+
if data.is_a?(FFI::Pointer)
372+
# A pointer needs to know about the size of the memory it points to.
373+
# If you have an address-only pointer, use the .slice method to wrap
374+
# the pointer in a size aware pointer.
375+
if data.size == UNKNOWN_POINTER_SIZE
376+
raise Vips::Error, "size of memory is unknown"
377+
end
378+
size = data.size
379+
else
380+
size = data.bytesize
381+
end
382+
350383
format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
351384
vi = Vips.vips_image_new_from_memory data, size,
352385
width, height, bands, format_number
@@ -373,7 +406,17 @@ def self.new_from_memory data, width, height, bands, format
373406
# @return [Image] the loaded image
374407
def self.new_from_memory_copy data, width, height, bands, format
375408
format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
376-
vi = Vips.vips_image_new_from_memory_copy data, data.bytesize,
409+
410+
if data.is_a?(FFI::Pointer)
411+
if data.size == UNKNOWN_POINTER_SIZE
412+
raise Vips::Error, "size of memory is unknown"
413+
end
414+
size = data.size
415+
else
416+
size = data.bytesize
417+
end
418+
419+
vi = Vips.vips_image_new_from_memory_copy data, size,
377420
width, height, bands, format_number
378421
raise Vips::Error if vi.null?
379422
new(vi)

spec/image_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,47 @@ def has_jpeg?
4444
expect(x.avg).to eq(128)
4545
end
4646

47+
it "can load an image from memory by memory pointer" do
48+
data = FFI::MemoryPointer.new(:uchar, 16 * 16)
49+
data.put_array_of_uchar(0, Array.new(16 * 16, 128))
50+
51+
x = Vips::Image.new_from_memory data, 16, 16, 1, :uchar
52+
53+
# GC to try to trigger a segv if data hasn't been reffed by
54+
# new_from_memory
55+
GC.start
56+
57+
expect(x.width).to eq(16)
58+
expect(x.height).to eq(16)
59+
expect(x.bands).to eq(1)
60+
expect(x.avg).to eq(128)
61+
end
62+
63+
it "can load an image from memory by size aware address pointer" do
64+
memory = FFI::MemoryPointer.new(:uchar, 16 * 16)
65+
memory.put_array_of_uchar(0, Array.new(16 * 16, 128))
66+
67+
data = FFI::Pointer.new(memory)
68+
# JRuby's FFI implementation looses the size information
69+
data = data.slice(0, 16 * 16) if defined?(JRUBY_VERSION)
70+
71+
x = Vips::Image.new_from_memory data, 16, 16, 1, :uchar
72+
73+
# GC to try to trigger a segv if data hasn't been reffed by
74+
# new_from_memory
75+
GC.start
76+
77+
expect(x.width).to eq(16)
78+
expect(x.height).to eq(16)
79+
expect(x.bands).to eq(1)
80+
expect(x.avg).to eq(128)
81+
end
82+
83+
it "throws an error when trying to load an image from memory with unknown size" do
84+
data = FFI::Pointer.new(1)
85+
expect { Vips::Image.new_from_memory(data, 16, 16, 1, :uchar) }.to raise_error(Vips::Error)
86+
end
87+
4788
it "can load an image from memory and copy" do
4889
image = Vips::Image.black(16, 16) + 128
4990
data = image.write_to_memory
@@ -57,6 +98,39 @@ def has_jpeg?
5798
expect(x.avg).to eq(128)
5899
end
59100

101+
it "can load and copy an image from memory by memory pointer" do
102+
data = FFI::MemoryPointer.new(:uchar, 16 * 16)
103+
data.put_array_of_uchar(0, Array.new(16 * 16, 128))
104+
105+
x = Vips::Image.new_from_memory_copy data, 16, 16, 1, :uchar
106+
107+
expect(x.width).to eq(16)
108+
expect(x.height).to eq(16)
109+
expect(x.bands).to eq(1)
110+
expect(x.avg).to eq(128)
111+
end
112+
113+
it "can load and copy an image from memory by size aware address pointer" do
114+
memory = FFI::MemoryPointer.new(:uchar, 16 * 16)
115+
memory.put_array_of_uchar(0, Array.new(16 * 16, 128))
116+
117+
data = FFI::Pointer.new(memory)
118+
# JRuby's FFI implementation looses the size information
119+
data = data.slice(0, 16 * 16) if defined?(JRUBY_VERSION)
120+
121+
x = Vips::Image.new_from_memory_copy data, 16, 16, 1, :uchar
122+
123+
expect(x.width).to eq(16)
124+
expect(x.height).to eq(16)
125+
expect(x.bands).to eq(1)
126+
expect(x.avg).to eq(128)
127+
end
128+
129+
it "throws an error when trying to load and copy from memory with unknown size" do
130+
data = FFI::Pointer.new(1)
131+
expect { Vips::Image.new_from_memory_copy(data, 16, 16, 1, :uchar) }.to raise_error(Vips::Error)
132+
end
133+
60134
if has_jpeg?
61135
it "can save an image to a buffer" do
62136
image = Vips::Image.black(16, 16) + 128

0 commit comments

Comments
 (0)