Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

add extended targetcustom support #337

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
32 changes: 24 additions & 8 deletions example/connection.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
#!/usr/bin/ruby

require "vips"
require "down/http"
# gem install down
require "down"

# byte_source = File.open ARGV[0], "rb"
# eg. https://images.unsplash.com/photo-1491933382434-500287f9b54b
byte_source = Down::Http.open(ARGV[0])
byte_source = Down::open(ARGV[0])

source = Vips::SourceCustom.new
source.on_read do |length|
puts "reading #{length} bytes ..."
puts "source: reading #{length} bytes ..."
byte_source.read length
end
source.on_seek do |offset, whence|
puts "seeking to #{offset}, #{whence}"
puts "source: seeking to #{offset}, #{whence}"
byte_source.seek(offset, whence)
end

byte_target = File.open ARGV[1], "wb"
byte_target = File.open ARGV[1], "w+b"

target = Vips::TargetCustom.new
target.on_write { |chunk| byte_target.write(chunk) }
target.on_finish { byte_target.close }
target.on_write do |chunk|
puts "target: writing #{chunk.length} bytes ..."
byte_target.write(chunk)
end
target.on_read do |length|
puts "target: reading #{length} bytes ..."
byte_target.read length
end
target.on_seek do |offset, whence|
puts "target: seeking to #{offset}, #{whence}"
byte_target.seek(offset, whence)
end
target.on_end do
puts "target: ending"
byte_target.close
end

image = Vips::Image.new_from_source source, "", access: :sequential
image.write_to_target target, ".jpg"
image.write_to_target target, ARGV[2]
8 changes: 6 additions & 2 deletions lib/vips/image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,12 @@ def self.new_from_array array, scale = 1, offset = 0
def new_from_image value
pixel = (Vips::Image.black(1, 1) + value).cast(format)
image = pixel.embed 0, 0, width, height, extend: :copy
image.copy interpretation: interpretation, xres: xres, yres: yres,
xoffset: xoffset, yoffset: yoffset
image.copy \
interpretation: interpretation,
xres: xres,
yres: yres,
xoffset: xoffset,
yoffset: yoffset
end

# Write this image to a file. Save options may be encoded in the
Expand Down
8 changes: 8 additions & 0 deletions lib/vips/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ class Progress < FFI::Struct
end
end

MARSHAL_END = proc do |handler|
FFI::Function.new(:int, [:pointer, :pointer]) do |i, cb|
# this can't throw an exception, so no catch is necessary
handler.call
end
end

MARSHAL_FINISH = proc do |handler|
FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
# this can't throw an exception, so no catch is necessary
Expand All @@ -123,6 +130,7 @@ class Progress < FFI::Struct
read: MARSHAL_READ,
seek: MARSHAL_SEEK,
write: MARSHAL_WRITE,
end: MARSHAL_END,
finish: MARSHAL_FINISH
}

Expand Down
57 changes: 56 additions & 1 deletion lib/vips/targetcustom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,63 @@ def on_write &block
end
end

# The block is executed to read data from the target. The interface is
# exactly as IO::read, ie. it takes a maximum number of bytes to read and
# returns a string of bytes from the target, or nil if the target is already
# at end of file.
#
# This handler is optional and only needed for image formats which have
# to be able to read their own output, like TIFF.
#
# @yieldparam length [Integer] Read and return up to this many bytes
# @yieldreturn [String] Up to length bytes of data, or nil for EOF
def on_read &block
# target read added in 8.13
if Vips.at_least_libvips?(8, 13)
signal_connect "read" do |buf, len|
chunk = block.call len
return 0 if chunk.nil?
bytes_read = chunk.bytesize
buf.put_bytes(0, chunk, 0, bytes_read)
chunk.clear

bytes_read
end
end
end

# The block is executed to seek the target. The interface is exactly as
# IO::seek, ie. it should take an offset and whence, and return the
# new read position.
#
# This handler is optional and only needed for image formats which have
# to be able to read their own output, like TIFF.
#
# @yieldparam offset [Integer] Seek offset
# @yieldparam whence [Integer] Seek whence
# @yieldreturn [Integer] the new read position, or -1 on error
def on_seek &block
# target seek added in 8.13
if Vips.at_least_libvips?(8, 13)
signal_connect "seek" do |offset, whence|
block.call offset, whence
end
end
end

# The block is executed at the end of write. It should do any necessary
# finishing action, such as closing a file.
# finishing action, such as closing a file, and return 0 on sucess and -1
# on error
#
# @yieldreturn [Integer] 0 on sucess, or -1 on error
def on_end &block
signal_name = Vips.at_least_libvips?(8, 13) ? "end" : "finish"
signal_connect signal_name do
block.call
end
end

# Deprecated name for libvips before 8.13
def on_finish &block
signal_connect "finish" do
block.call
Expand Down
73 changes: 50 additions & 23 deletions spec/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
it "can load an image from filename source" do
source = Vips::Source.new_from_file simg("wagon.jpg")
image = Vips::Image.new_from_source source, ""
real = Vips::Image.new_from_file simg("wagon.jpg")

expect(image)
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)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end
end

Expand Down Expand Up @@ -76,13 +77,14 @@
filename = timg("x4.png")
target = Vips::Target.new_to_file filename
image.write_to_target target, ".png"
real = Vips::Image.new_from_file simg("wagon.jpg")

image = Vips::Image.new_from_file filename
expect(image)
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)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end

it "can save an image to a memory target" do
Expand Down Expand Up @@ -114,47 +116,72 @@
source.on_read { |length| file.read length }
source.on_seek { |offset, whence| file.seek(offset, whence) }
image = Vips::Image.new_from_source source, ""
real = Vips::Image.new_from_file simg("wagon.jpg")

expect(image)
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)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end

it "on_seek is optional" do
file = File.open simg("wagon.jpg"), "rb"
source = Vips::SourceCustom.new
source.on_read { |length| file.read length }
image = Vips::Image.new_from_source source, ""
real = Vips::Image.new_from_file simg("wagon.jpg")

expect(image)
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)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end

it "can create a user output stream" do
it "can create a custom target" do
target = Vips::TargetCustom.new

expect(target)
end

it "can write an image to a user output stream" do
it "can write an image to a custom target" do
filename = timg("x5.png")
file = File.open filename, "wb"
target = Vips::TargetCustom.new
target.on_write { |chunk| file.write(chunk) }
target.on_finish { file.close }
target.on_end { file.close }
image = Vips::Image.new_from_file simg("wagon.jpg")
image.write_to_target target, ".png"

image = Vips::Image.new_from_file filename
real = Vips::Image.new_from_file simg("wagon.jpg")
expect(image)
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)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end
end

RSpec.describe Vips::TargetCustom, version: [8, 13] do
it "can write to a custom target as TIFF" do
filename = timg("x6.tif")
file = File.open filename, "w+b"
target = Vips::TargetCustom.new
target.on_write { |chunk| file.write(chunk) }
target.on_read { |length| file.read length }
target.on_seek { |offset, whence| file.seek(offset, whence) }
target.on_end { file.close; 0 }
image = Vips::Image.new_from_file simg("wagon.jpg")
image.write_to_target target, ".tif"

image = Vips::Image.new_from_file filename
real = Vips::Image.new_from_file simg("wagon.jpg")
expect(image)
expect(image.width).to eq(real.width)
expect(image.height).to eq(real.height)
expect(image.bands).to eq(real.bands)
expect(image.avg).to eq(real.avg)
end
end