vips 8.10.5 → 8.12.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,8 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
+ require "set"
8
9
 
9
10
  module Vips
10
11
  private
@@ -12,22 +13,22 @@ module Vips
12
13
  attach_function :vips_operation_new, [:string], :pointer
13
14
 
14
15
  # We may well block during this (eg. if it's avg, or perhaps jpegsave), and
15
- # libvips might trigger some signals which ruby has handles for.
16
+ # libvips might trigger some signals which ruby has handles for.
16
17
  #
17
- # We need FFI to drop the GIL lock during this call and reacquire it when
18
+ # We need FFI to drop the GIL lock during this call and reacquire it when
18
19
  # the call ends, or we'll deadlock.
19
- attach_function :vips_cache_operation_build, [:pointer], :pointer,
20
+ attach_function :vips_cache_operation_build, [:pointer], :pointer,
20
21
  blocking: true
21
22
  attach_function :vips_object_unref_outputs, [:pointer], :void
22
23
 
23
24
  callback :argument_map_fn, [:pointer,
24
- GObject::GParamSpec.ptr,
25
- ArgumentClass.ptr,
26
- ArgumentInstance.ptr,
27
- :pointer, :pointer], :pointer
25
+ GObject::GParamSpec.ptr,
26
+ ArgumentClass.ptr,
27
+ ArgumentInstance.ptr,
28
+ :pointer, :pointer], :pointer
28
29
  attach_function :vips_argument_map, [:pointer,
29
- :argument_map_fn,
30
- :pointer, :pointer], :pointer
30
+ :argument_map_fn,
31
+ :pointer, :pointer], :pointer
31
32
 
32
33
  OPERATION_SEQUENTIAL = 1
33
34
  OPERATION_NOCACHE = 4
@@ -45,14 +46,26 @@ module Vips
45
46
  # everything we know about it. This is used for doc generation as well as
46
47
  # call.
47
48
  class Introspect
48
- attr_reader :name, :description, :flags, :args, :required_input,
49
- :optional_input, :required_output, :optional_output, :member_x,
50
- :method_args
49
+ attr_reader :name, :description, :flags, :args, :required_input,
50
+ :optional_input, :required_output, :optional_output, :member_x,
51
+ :method_args, :vips_name, :destructive, :doc_optional_input,
52
+ :doc_optional_output
51
53
 
52
54
  @@introspect_cache = {}
53
55
 
54
56
  def initialize name
55
- @op = Operation.new name
57
+ # if there's a trailing "!", this is a destructive version of an
58
+ # operation
59
+ if name[-1] == "!"
60
+ @destructive = true
61
+ # strip the trailing "!"
62
+ @vips_name = name[0...-1]
63
+ else
64
+ @destructive = false
65
+ @vips_name = name
66
+ end
67
+
68
+ @op = Operation.new @vips_name
56
69
  @args = []
57
70
  @required_input = []
58
71
  @optional_input = {}
@@ -66,12 +79,14 @@ module Vips
66
79
  # names can include - as punctuation, but we always use _ in
67
80
  # Ruby
68
81
  arg_name = pspec[:name].tr("-", "_")
69
- args << {
70
- :arg_name => arg_name,
71
- :flags => flags,
72
- :gtype => pspec[:value_type]
82
+ @args << {
83
+ arg_name: arg_name,
84
+ flags: flags,
85
+ gtype: pspec[:value_type]
73
86
  }
74
87
  end
88
+
89
+ nil
75
90
  end
76
91
 
77
92
  @args.each do |details|
@@ -79,26 +94,27 @@ module Vips
79
94
  flags = details[:flags]
80
95
 
81
96
  if (flags & ARGUMENT_INPUT) != 0
82
- if (flags & ARGUMENT_REQUIRED) != 0 &&
83
- (flags & ARGUMENT_DEPRECATED) == 0
97
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
98
+ (flags & ARGUMENT_DEPRECATED) == 0
84
99
  @required_input << details
85
100
  else
86
101
  # we allow deprecated optional args
87
102
  @optional_input[arg_name] = details
88
103
  end
89
104
 
90
- # MODIFY INPUT args count as OUTPUT as well
91
- if (flags & ARGUMENT_MODIFY) != 0
92
- if (flags & ARGUMENT_REQUIRED) != 0 &&
93
- (flags & ARGUMENT_DEPRECATED) == 0
105
+ # MODIFY INPUT args count as OUTPUT as well in non-destructive mode
106
+ if (flags & ARGUMENT_MODIFY) != 0 &&
107
+ !@destructive
108
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
109
+ (flags & ARGUMENT_DEPRECATED) == 0
94
110
  @required_output << details
95
111
  else
96
112
  @optional_output[arg_name] = details
97
113
  end
98
114
  end
99
115
  elsif (flags & ARGUMENT_OUTPUT) != 0
100
- if (flags & ARGUMENT_REQUIRED) != 0 &&
101
- (flags & ARGUMENT_DEPRECATED) == 0
116
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
117
+ (flags & ARGUMENT_DEPRECATED) == 0
102
118
  @required_output << details
103
119
  else
104
120
  # again, allow deprecated optional args
@@ -106,6 +122,16 @@ module Vips
106
122
  end
107
123
  end
108
124
  end
125
+
126
+ # in destructive mode, the first required input arg must be MODIFY and
127
+ # must be an image
128
+ if @destructive
129
+ if @required_input.length < 1 ||
130
+ @required_input[0][:flags] & ARGUMENT_MODIFY == 0 ||
131
+ @required_input[0][:gtype] != IMAGE_TYPE
132
+ raise Vips::Error, "operation #{@vips_name} is not destructive"
133
+ end
134
+ end
109
135
  end
110
136
 
111
137
  # Yard comment generation needs a little more introspection. We add this
@@ -113,10 +139,12 @@ module Vips
113
139
  # we can.
114
140
  def add_yard_introspection name
115
141
  @name = name
116
- @description = Vips::vips_object_get_description @op
117
- @flags = Vips::vips_operation_get_flags @op
142
+ @description = Vips.vips_object_get_description @op
143
+ @flags = Vips.vips_operation_get_flags @op
118
144
  @member_x = nil
119
145
  @method_args = []
146
+ @doc_optional_input = {}
147
+ @doc_optional_output = {}
120
148
 
121
149
  @args.each do |details|
122
150
  arg_name = details[:arg_name]
@@ -125,20 +153,31 @@ module Vips
125
153
 
126
154
  details[:yard_name] = arg_name == "in" ? "im" : arg_name
127
155
  pspec = @op.get_pspec arg_name
128
- details[:blurb] = GObject::g_param_spec_get_blurb pspec
156
+ details[:blurb] = GObject.g_param_spec_get_blurb pspec
129
157
 
130
- if (flags & ARGUMENT_INPUT) != 0 &&
131
- (flags & ARGUMENT_REQUIRED) != 0 &&
132
- (flags & ARGUMENT_DEPRECATED) == 0
133
- # the first required input image is the thing we will be a method
158
+ if (flags & ARGUMENT_INPUT) != 0 &&
159
+ (flags & ARGUMENT_REQUIRED) != 0 &&
160
+ (flags & ARGUMENT_DEPRECATED) == 0
161
+ # the first required input image is the thing we will be a method
134
162
  # of
135
- if @member_x == nil && gtype == IMAGE_TYPE
136
- @member_x = details
163
+ if @member_x.nil? && gtype == IMAGE_TYPE
164
+ @member_x = details
137
165
  else
138
166
  @method_args << details
139
167
  end
140
168
  end
141
169
  end
170
+
171
+ # and make the arg sets to document by filtering out deprecated args
172
+ @optional_input.each do |arg_name, details|
173
+ next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
174
+ @doc_optional_input[details[:arg_name]] = details
175
+ end
176
+
177
+ @optional_output.each do |arg_name, details|
178
+ next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
179
+ @doc_optional_output[details[:arg_name]] = details
180
+ end
142
181
  end
143
182
 
144
183
  def self.get name
@@ -150,7 +189,6 @@ module Vips
150
189
  introspect.add_yard_introspection name
151
190
  introspect
152
191
  end
153
-
154
192
  end
155
193
 
156
194
  class Operation < Object
@@ -176,7 +214,7 @@ module Vips
176
214
  # allow init with a pointer so we can wrap the return values from
177
215
  # things like _build
178
216
  if value.is_a? String
179
- value = Vips::vips_operation_new value
217
+ value = Vips.vips_operation_new value
180
218
  raise Vips::Error if value.null?
181
219
  end
182
220
 
@@ -184,66 +222,65 @@ module Vips
184
222
  end
185
223
 
186
224
  def build
187
- op = Vips::vips_cache_operation_build self
225
+ op = Vips.vips_cache_operation_build self
188
226
  if op.null?
189
- Vips::vips_object_unref_outputs self
227
+ Vips.vips_object_unref_outputs self
190
228
  raise Vips::Error
191
229
  end
192
230
 
193
- return Operation.new op
231
+ Operation.new op
194
232
  end
195
233
 
196
234
  def argument_map &block
197
- fn = Proc.new do |_op, pspec, argument_class, argument_instance, _a, _b|
235
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
198
236
  block.call pspec, argument_class, argument_instance
199
237
  end
200
-
201
- Vips::vips_argument_map self, fn, nil, nil
238
+ Vips.vips_argument_map self, fn, nil, nil
202
239
  end
203
240
 
204
- # Search an object for the first element to match a predicate. Search
241
+ # Search an object for the first element to match a predicate. Search
205
242
  # inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
206
243
  def self.flat_find object, &block
207
244
  if object.respond_to? :each
208
- object.each do |x|
209
- result = flat_find x, &block
210
- return result if result != nil
245
+ object.each do |x|
246
+ result = flat_find x, &block
247
+ return result unless result.nil?
211
248
  end
212
- else
213
- return object if yield object
249
+ elsif yield object
250
+ return object
214
251
  end
215
252
 
216
- return nil
253
+ nil
217
254
  end
218
255
 
219
256
  # expand a constant into an image
220
257
  def self.imageize match_image, value
221
- return value if value.is_a? Image
258
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
222
259
 
223
260
  # 2D array values become tiny 2D images
224
261
  # if there's nothing to match to, we also make a 2D image
225
- if (value.is_a?(Array) && value[0].is_a?(Array)) ||
226
- match_image == nil
227
- return Image.new_from_array value
262
+ if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
263
+ Image.new_from_array value
228
264
  else
229
265
  # we have a 1D array ... use that as a pixel constant and
230
266
  # expand to match match_image
231
- return match_image.new_from_image value
267
+ match_image.new_from_image value
232
268
  end
233
269
  end
234
270
 
235
271
  # set an operation argument, expanding constants and copying images as
236
272
  # required
237
- def set name, value, match_image, flags, gtype
273
+ def set name, value, match_image, flags, gtype, destructive
238
274
  if gtype == IMAGE_TYPE
239
- value = Operation::imageize match_image, value
275
+ value = Operation.imageize match_image, value
240
276
 
241
- if (flags & ARGUMENT_MODIFY) != 0
242
- # make sure we have a unique copy
277
+ # in non-destructive mode, make sure we have a unique copy
278
+ if (flags & ARGUMENT_MODIFY) != 0 &&
279
+ !destructive
243
280
  value = value.copy.copy_memory
244
281
  end
245
282
  elsif gtype == ARRAY_IMAGE_TYPE
246
- value = value.map { |x| Operation::imageize match_image, x }
283
+ value = value.map { |x| Operation.imageize match_image, x }
247
284
  end
248
285
 
249
286
  super name, value
@@ -319,8 +356,8 @@ module Vips
319
356
  # the constant value 255.
320
357
 
321
358
  def self.call name, supplied, optional = {}, option_string = ""
322
- GLib::logger.debug("Vips::VipsOperation.call") {
323
- "name = #{name}, supplied = #{supplied}, " +
359
+ GLib.logger.debug("Vips::VipsOperation.call") {
360
+ "name = #{name}, supplied = #{supplied}, " \
324
361
  "optional = #{optional}, option_string = #{option_string}"
325
362
  }
326
363
 
@@ -329,20 +366,21 @@ module Vips
329
366
  required_output = introspect.required_output
330
367
  optional_input = introspect.optional_input
331
368
  optional_output = introspect.optional_output
369
+ destructive = introspect.destructive
332
370
 
333
371
  unless supplied.is_a? Array
334
- raise Vips::Error, "unable to call #{name}: " +
335
- "argument array is not an array"
372
+ raise Vips::Error, "unable to call #{name}: " \
373
+ "argument array is not an array"
336
374
  end
337
375
  unless optional.is_a? Hash
338
- raise Vips::Error, "unable to call #{name}: " +
339
- "optional arguments are not a hash"
376
+ raise Vips::Error, "unable to call #{name}: " \
377
+ "optional arguments are not a hash"
340
378
  end
341
379
 
342
380
  if supplied.length != required_input.length
343
- raise Vips::Error, "unable to call #{name}: " +
344
- "you supplied #{supplied.length} arguments, " +
345
- "but operation needs " + "#{required_input.length}."
381
+ raise Vips::Error, "unable to call #{name}: " \
382
+ "you supplied #{supplied.length} arguments, " \
383
+ "but operation needs #{required_input.length}."
346
384
  end
347
385
 
348
386
  # all supplied_optional keys should be in optional_input or
@@ -352,8 +390,8 @@ module Vips
352
390
 
353
391
  unless optional_input.has_key?(arg_name) ||
354
392
  optional_output.has_key?(arg_name)
355
- raise Vips::Error, "unable to call #{name}: " +
356
- "unknown option #{arg_name}"
393
+ raise Vips::Error, "unable to call #{name}: " \
394
+ "unknown option #{arg_name}"
357
395
  end
358
396
  end
359
397
 
@@ -362,17 +400,58 @@ module Vips
362
400
  #
363
401
  # look inside array and hash arguments, since we may be passing an
364
402
  # array of images
365
- match_image = flat_find(supplied) { |value| value.is_a? Image }
403
+ #
404
+ # also enforce the rules around mutable and non-mutable images
405
+ match_image = nil
406
+ flat_find(supplied) do |value|
407
+ if match_image
408
+ # no non-first image arg can ever be mutable
409
+ if value.is_a?(MutableImage)
410
+ raise Vips::Error, "unable to call #{name}: " \
411
+ "only the first image argument can be mutable"
412
+ end
413
+ elsif destructive
414
+ if value.is_a?(Image)
415
+ raise Vips::Error, "unable to call #{name}: " \
416
+ "first image argument to a destructive " \
417
+ "operation must be mutable"
418
+ elsif value.is_a?(MutableImage)
419
+ match_image = value
420
+ end
421
+ elsif value.is_a?(MutableImage)
422
+ # non destructive operation, so no mutable images
423
+ raise Vips::Error, "unable to call #{name}: " \
424
+ "must not pass mutable images to " \
425
+ "non-destructive operations"
426
+ elsif value.is_a?(Image)
427
+ match_image = value
428
+ end
366
429
 
367
- op = Operation.new name
430
+ # keep looping
431
+ false
432
+ end
433
+
434
+ op = Operation.new introspect.vips_name
368
435
 
369
436
  # set any string args first so they can't be overridden
370
- if option_string != nil
371
- if Vips::vips_object_set_from_string(op, option_string) != 0
437
+ unless option_string.nil?
438
+ if Vips.vips_object_set_from_string(op, option_string) != 0
372
439
  raise Vips::Error
373
440
  end
374
441
  end
375
442
 
443
+ # collect a list of all input references here
444
+ references = Set.new
445
+
446
+ add_reference = lambda do |x|
447
+ if x.is_a?(Vips::Image)
448
+ x.references.each do |i|
449
+ references << i
450
+ end
451
+ end
452
+ false
453
+ end
454
+
376
455
  # set all required inputs
377
456
  required_input.each_index do |i|
378
457
  details = required_input[i]
@@ -381,7 +460,8 @@ module Vips
381
460
  gtype = details[:gtype]
382
461
  value = supplied[i]
383
462
 
384
- op.set arg_name, value, match_image, flags, gtype
463
+ flat_find value, &add_reference
464
+ op.set arg_name, value, match_image, flags, gtype, destructive
385
465
  end
386
466
 
387
467
  # set all optional inputs
@@ -395,16 +475,27 @@ module Vips
395
475
  flags = details[:flags]
396
476
  gtype = details[:gtype]
397
477
 
398
- op.set arg_name, value, match_image, flags, gtype
478
+ flat_find value, &add_reference
479
+ op.set arg_name, value, match_image, flags, gtype, destructive
399
480
  end
400
481
  end
401
482
 
402
483
  op = op.build
403
484
 
485
+ # attach all input refs to output x
486
+ set_reference = lambda do |x|
487
+ if x.is_a? Vips::Image
488
+ x.references += references
489
+ end
490
+ false
491
+ end
492
+
404
493
  # get all required results
405
494
  result = []
406
495
  required_output.each do |details|
407
- result << op.get(details[:arg_name])
496
+ value = details[:arg_name]
497
+ flat_find value, &set_reference
498
+ result << op.get(value)
408
499
  end
409
500
 
410
501
  # fetch all optional ones
@@ -413,7 +504,9 @@ module Vips
413
504
  arg_name = key.to_s
414
505
 
415
506
  if optional_output.has_key? arg_name
416
- optional_results[arg_name] = op.get arg_name
507
+ value = op.get arg_name
508
+ flat_find value, &set_reference
509
+ optional_results[arg_name] = value
417
510
  end
418
511
  end
419
512
 
@@ -425,11 +518,11 @@ module Vips
425
518
  result = nil
426
519
  end
427
520
 
428
- GLib::logger.debug("Vips::Operation.call") { "result = #{result}" }
521
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
429
522
 
430
- Vips::vips_object_unref_outputs op
523
+ Vips.vips_object_unref_outputs op
431
524
 
432
- return result
525
+ result
433
526
  end
434
527
  end
435
528
  end
data/lib/vips/region.rb CHANGED
@@ -4,12 +4,12 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
8
 
9
9
  module Vips
10
10
  attach_function :vips_region_new, [:pointer], :pointer
11
11
 
12
- if Vips::at_least_libvips?(8, 8)
12
+ if Vips.at_least_libvips?(8, 8)
13
13
  attach_function :vips_region_fetch, [:pointer, :int, :int, :int, :int, SizeStruct.ptr], :pointer
14
14
  attach_function :vips_region_width, [:pointer], :int
15
15
  attach_function :vips_region_height, [:pointer], :int
@@ -44,24 +44,24 @@ module Vips
44
44
  end
45
45
 
46
46
  def initialize(name)
47
- ptr = Vips::vips_region_new name
47
+ ptr = Vips.vips_region_new name
48
48
  raise Vips::Error if ptr.null?
49
49
 
50
50
  super ptr
51
51
  end
52
52
 
53
53
  def width
54
- Vips::vips_region_width self
54
+ Vips.vips_region_width self
55
55
  end
56
56
 
57
57
  def height
58
- Vips::vips_region_height self
58
+ Vips.vips_region_height self
59
59
  end
60
60
 
61
61
  # Fetch a region filled with pixel data.
62
62
  def fetch(left, top, width, height)
63
63
  len = Vips::SizeStruct.new
64
- ptr = Vips::vips_region_fetch self, left, top, width, height, len
64
+ ptr = Vips.vips_region_fetch self, left, top, width, height, len
65
65
  raise Vips::Error if ptr.null?
66
66
 
67
67
  # wrap up as an autopointer
data/lib/vips/source.rb CHANGED
@@ -4,10 +4,10 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
8
 
9
9
  module Vips
10
- if Vips::at_least_libvips?(8, 9)
10
+ if Vips.at_least_libvips?(8, 9)
11
11
  attach_function :vips_source_new_from_descriptor, [:int], :pointer
12
12
  attach_function :vips_source_new_from_file, [:pointer], :pointer
13
13
  attach_function :vips_source_new_from_memory, [:pointer, :size_t], :pointer
@@ -42,26 +42,26 @@ module Vips
42
42
  #
43
43
  # Pass sources to {Image.new_from_source} to load images from
44
44
  # them.
45
- #
45
+ #
46
46
  # @param descriptor [Integer] the file descriptor
47
47
  # @return [Source] the new Vips::Source
48
48
  def self.new_from_descriptor(descriptor)
49
- ptr = Vips::vips_source_new_from_descriptor descriptor
49
+ ptr = Vips.vips_source_new_from_descriptor descriptor
50
50
  raise Vips::Error if ptr.null?
51
51
 
52
52
  Vips::Source.new ptr
53
53
  end
54
54
 
55
- # Create a new source from a file name.
55
+ # Create a new source from a file name.
56
56
  #
57
57
  # Pass sources to {Image.new_from_source} to load images from
58
58
  # them.
59
- #
59
+ #
60
60
  # @param filename [String] the name of the file
61
61
  # @return [Source] the new Vips::Source
62
62
  def self.new_from_file(filename)
63
63
  raise Vips::Error, "filename is nil" if filename.nil?
64
- ptr = Vips::vips_source_new_from_file filename
64
+ ptr = Vips.vips_source_new_from_file filename
65
65
  raise Vips::Error if ptr.null?
66
66
 
67
67
  Vips::Source.new ptr
@@ -72,18 +72,17 @@ module Vips
72
72
  #
73
73
  # Pass sources to {Image.new_from_source} to load images from
74
74
  # them.
75
- #
76
- # @param data [String] memory area
75
+ #
76
+ # @param data [String] memory area
77
77
  # @return [Source] the new Vips::Source
78
78
  def self.new_from_memory(data)
79
- ptr = Vips::vips_source_new_from_memory data, data.bytesize
79
+ ptr = Vips.vips_source_new_from_memory data, data.bytesize
80
80
  raise Vips::Error if ptr.null?
81
81
 
82
- # FIXME do we need to keep a ref to the underlying memory area? what
82
+ # FIXME do we need to keep a ref to the underlying memory area? what
83
83
  # about Image.new_from_buffer? Does that need a secret ref too?
84
84
 
85
85
  Vips::Source.new ptr
86
86
  end
87
-
88
87
  end
89
88
  end
@@ -4,14 +4,14 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
8
 
9
9
  module Vips
10
- if Vips::at_least_libvips?(8, 9)
10
+ if Vips.at_least_libvips?(8, 9)
11
11
  attach_function :vips_source_custom_new, [], :pointer
12
12
  end
13
13
 
14
- # A source you can attach action signal handlers to to implement
14
+ # A source you can attach action signal handlers to to implement
15
15
  # custom input types.
16
16
  #
17
17
  # For example:
@@ -23,7 +23,7 @@ module Vips
23
23
  # image = Vips::Image.new_from_source source
24
24
  # ```
25
25
  #
26
- # (just an example -- of course in practice you'd use {Source#new_from_file}
26
+ # (just an example -- of course in practice you'd use {Source#new_from_file}
27
27
  # to read from a named file)
28
28
  class SourceCustom < Vips::Source
29
29
  module SourceCustomLayout
@@ -44,7 +44,7 @@ module Vips
44
44
  end
45
45
 
46
46
  def initialize
47
- pointer = Vips::vips_source_custom_new
47
+ pointer = Vips.vips_source_custom_new
48
48
  raise Vips::Error if pointer.null?
49
49
 
50
50
  super pointer
@@ -60,7 +60,7 @@ module Vips
60
60
  def on_read &block
61
61
  signal_connect "read" do |buf, len|
62
62
  chunk = block.call len
63
- return 0 if chunk == nil
63
+ return 0 if chunk.nil?
64
64
  bytes_read = chunk.bytesize
65
65
  buf.put_bytes(0, chunk, 0, bytes_read)
66
66
  chunk.clear
@@ -70,7 +70,7 @@ module Vips
70
70
  end
71
71
 
72
72
  # The block is executed to seek the source. The interface is exactly as
73
- # IO::seek, ie. it should take an offset and whence, and return the
73
+ # IO::seek, ie. it should take an offset and whence, and return the
74
74
  # new read position.
75
75
  #
76
76
  # This handler is optional -- if you do not attach a seek handler,
@@ -85,6 +85,5 @@ module Vips
85
85
  block.call offset, whence
86
86
  end
87
87
  end
88
-
89
88
  end
90
89
  end