vips 8.8.4 → 8.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e63e0ca10ce7e3c41bfda60bea2a13f12d13937c5ccab8be832648ed3d4aa6c
4
- data.tar.gz: 9e40c91344b418e24aed13952f335bb5b6a08dd015e675597e656650ce4bd2a9
3
+ metadata.gz: 5603f19612f6051e2ec146f82ef9ac652b0790a1f00499fcea3d27eb4141c144
4
+ data.tar.gz: 3ad98dc9631a08e364181f4edc10716059e997cb8a86c507e61d1d46a1cfe907
5
5
  SHA512:
6
- metadata.gz: 85aa8d1d02d8d91cdc33a2b012a2340f7b93aed72e3e6081ed3012ed7d0a8f605ec64a0a523d77a1add4cb6be15dfe2982d72d8770226c09547b39d3d6680145
7
- data.tar.gz: de62d6c3d576d3d0c34e0f391a6e720412152aafe672db9f2df148d34b948059f23402e2af4393421129efbfb2d414fb93b74bd1c50e0599fc367f73adf5fc83
6
+ metadata.gz: ccef89e9e98babf11c0b07073700c0406cc296c6b2a6f3f92ee87b1fbb89ac17edf32fc48f4366b3f776e1ca239174c1147fa457c1362eb2ca3883fb3b0acf2d
7
+ data.tar.gz: 9970c5801392bf405dc75f2b8e3edc174b597d90d3e1e184b08c7a9362ba0feea0c9d68bad012a924f390a776ef36a78160d7364da637e001f1660742052543f
@@ -41,6 +41,7 @@ matrix:
41
41
  - rvm: 2.6
42
42
  os: osx
43
43
  env: PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
44
+ - rvm: 2.7
44
45
  - rvm: truffleruby
45
46
  - rvm: jruby-head
46
47
  - rvm: ruby-head
data/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # Vips
2
2
 
3
+ Programs that use `vips` don't manipulate images directly, instead they create pipelines of image processing operations building on a source image. When the end of the pipe is connected to a destination, the whole pipeline executes at once, streaming the image in parallel from source to destination a section at a time. Because `ruby-vips` is parallel, it's quick, and because it doesn't need to keep entire images in memory, it's light.
4
+
3
5
  This gem is a backwards compatible fork of `ruby-vips` but also includes (and compiles) the [libvips] source code.
4
6
 
5
7
  [![Build Status](https://secure.travis-ci.org/ioquatix/vips.svg)](http://travis-ci.org/ioquatix/vips)
6
8
 
7
- [libvips]: https://jcupitt.github.io/libvips
9
+ [libvips]: https://libvips.github.io/libvips
8
10
 
9
11
  ## Installation
10
12
 
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'vips'
4
+
5
+ file = File.open ARGV[0], "rb"
6
+ source = Vips::SourceCustom.new
7
+ source.on_read { |length| file.read length }
8
+ # this method is optional
9
+ # source.on_seek { |offset, whence| file.seek(offset, whence) }
10
+
11
+ dest = File.open ARGV[1], "wb"
12
+ target = Vips::TargetCustom.new
13
+ target.on_write { |chunk| dest.write(chunk) }
14
+ target.on_finish { dest.close }
15
+
16
+ image = Vips::Image.new_from_source source, "", access: :sequential
17
+ image.write_to_target target, ".png"
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'vips'
4
+
5
+ image = Vips::Image.black 1, 100000
6
+ image.set_progress true
7
+
8
+ def progress_to_s(name, progress)
9
+ puts "#{name}:"
10
+ puts " progress.run = #{progress[:run]}"
11
+ puts " progress.eta = #{progress[:eta]}"
12
+ puts " progress.tpels = #{progress[:tpels]}"
13
+ puts " progress.npels = #{progress[:npels]}"
14
+ puts " progress.percent = #{progress[:percent]}"
15
+ end
16
+
17
+ image.signal_connect :preeval do |progress|
18
+ progress_to_s("preeval", progress)
19
+ end
20
+
21
+ image.signal_connect :eval do |progress|
22
+ progress_to_s("eval", progress)
23
+ image.set_kill(true) if progress[:percent] > 50
24
+ end
25
+
26
+ image.signal_connect :posteval do |progress|
27
+ progress_to_s("posteval", progress)
28
+ end
29
+
30
+ image.avg
@@ -10,6 +10,29 @@ require 'logger'
10
10
  # This module uses FFI to make a simple layer over the glib and gobject
11
11
  # libraries.
12
12
 
13
+ # Generate a library name for ffi.
14
+ #
15
+ # Platform notes:
16
+ # linux:
17
+ # Some distros allow "libvips.so", but only if the -dev headers have been
18
+ # installed. To work everywhere, you must include the ABI number.
19
+ # Confusingly, the file extension is not at the end. ffi adds the "lib"
20
+ # prefix.
21
+ # mac:
22
+ # As linux, but the extension is at the end and is added by ffi.
23
+ # windows:
24
+ # The ABI number must be included, but with a hyphen. ffi does not add a
25
+ # "lib" prefix or a ".dll" suffix.
26
+ def library_name(name, abi_number)
27
+ if FFI::Platform.windows?
28
+ "lib#{name}-#{abi_number}.dll"
29
+ elsif FFI::Platform.mac?
30
+ "#{name}.#{abi_number}"
31
+ else
32
+ "#{name}.so.#{abi_number}"
33
+ end
34
+ end
35
+
13
36
  module GLib
14
37
  class << self
15
38
  attr_accessor :logger
@@ -19,13 +42,7 @@ module GLib
19
42
 
20
43
  extend FFI::Library
21
44
 
22
- if FFI::Platform.windows?
23
- glib_libname = 'libglib-2.0-0.dll'
24
- else
25
- glib_libname = 'glib-2.0'
26
- end
27
-
28
- ffi_lib glib_libname
45
+ ffi_lib library_name('glib-2.0', 0)
29
46
 
30
47
  attach_function :g_malloc, [:size_t], :pointer
31
48
 
@@ -117,13 +134,7 @@ end
117
134
  module GObject
118
135
  extend FFI::Library
119
136
 
120
- if FFI::Platform.windows?
121
- gobject_libname = 'libgobject-2.0-0.dll'
122
- else
123
- gobject_libname = 'gobject-2.0'
124
- end
125
-
126
- ffi_lib gobject_libname
137
+ ffi_lib library_name('gobject-2.0', 0)
127
138
 
128
139
  # we can't just use ulong, windows has different int sizing rules
129
140
  if FFI::Platform::ADDRESS_SIZE == 64
@@ -155,7 +166,7 @@ require 'vips/gobject'
155
166
  require 'vips/gvalue'
156
167
 
157
168
  # This module provides a binding for the [libvips image processing
158
- # library](https://jcupitt.github.io/libvips/).
169
+ # library](https://libvips.github.io/libvips/).
159
170
  #
160
171
  # # Example
161
172
  #
@@ -201,6 +212,9 @@ require 'vips/gvalue'
201
212
  # memory buffers, create images that wrap C-style memory arrays, or make images
202
213
  # from constants.
203
214
  #
215
+ # Use {Source} and {Image.new_from_source} to load images from any data
216
+ # source, for example URIs.
217
+ #
204
218
  # The next line:
205
219
  #
206
220
  # ```ruby
@@ -242,6 +256,9 @@ require 'vips/gvalue'
242
256
  # suffix. You can also write formatted images to memory buffers, or dump
243
257
  # image data to a raw memory array.
244
258
  #
259
+ # Use {Target} and {Image#write_to_target} to write formatted images to
260
+ # any data sink, for example URIs.
261
+ #
245
262
  # # How it works
246
263
  #
247
264
  # The binding uses [ruby-ffi](https://github.com/ffi/ffi) to open the libvips
@@ -393,12 +410,12 @@ require 'vips/gvalue'
393
410
  # # Automatic YARD documentation
394
411
  #
395
412
  # The bulk of these API docs are generated automatically by
396
- # {Vips::generate_yard}. It examines
413
+ # {Vips::Yard::generate}. It examines
397
414
  # libvips and writes a summary of each operation and the arguments and options
398
415
  # that that operation expects.
399
416
  #
400
417
  # Use the [C API
401
- # docs](https://jcupitt.github.io/libvips/API/current)
418
+ # docs](https://libvips.github.io/libvips/API/current)
402
419
  # for more detail.
403
420
  #
404
421
  # # Enums
@@ -423,6 +440,55 @@ require 'vips/gvalue'
423
440
  # If you want to avoid the copies, you'll need to call drawing operations
424
441
  # yourself.
425
442
  #
443
+ # # Progress
444
+ #
445
+ # You can attach signal handlers to images to watch computation progress. For
446
+ # example:
447
+ #
448
+ # ```ruby
449
+ # image = Vips::Image.black 1, 100000
450
+ # image.set_progress true
451
+ #
452
+ # def progress_to_s(name, progress)
453
+ # puts "#{name}:"
454
+ # puts " run = #{progress[:run]}"
455
+ # puts " eta = #{progress[:eta]}"
456
+ # puts " tpels = #{progress[:tpels]}"
457
+ # puts " npels = #{progress[:npels]}"
458
+ # puts " percent = #{progress[:percent]}"
459
+ # end
460
+ #
461
+ # image.signal_connect :preeval do |progress|
462
+ # progress_to_s("preeval", progress)
463
+ # end
464
+ #
465
+ # image.signal_connect :eval do |progress|
466
+ # progress_to_s("eval", progress)
467
+ # image.set_kill(true) if progress[:percent] > 50
468
+ # end
469
+ #
470
+ # image.signal_connect :posteval do |progress|
471
+ # progress_to_s("posteval", progress)
472
+ # end
473
+ #
474
+ # image.avg
475
+ # ```
476
+ #
477
+ # The `:eval` signal will fire for every tile that is processed. You can stop
478
+ # progress with {Image#set_kill} and processing will end with an exception.
479
+ #
480
+ # User streams
481
+ #
482
+ # You can make your own input and output stream objects with {SourceCustom} and
483
+ # {TargetCustom}. For example:
484
+ #
485
+ # ```ruby
486
+ # file = File.open "some/file", "rb"
487
+ # source = Vips::SourceCustom.new
488
+ # source.on_read { |length| file.read length }
489
+ # image = Vips::Image.new_from_source source, "", access: "sequential"
490
+ # ```
491
+ #
426
492
  # # Overloads
427
493
  #
428
494
  # The wrapper defines the usual set of arithmetic, boolean and relational
@@ -462,7 +528,7 @@ module Vips
462
528
  if FFI::Platform.windows?
463
529
  vips_libname = 'libvips-42.dll'
464
530
  else
465
- vips_libname = File.expand_path(FFI::map_library_name('vips'), __dir__)
531
+ vips_libname = File.expand_path(FFI.map_library_name('vips'), __dir__)
466
532
  end
467
533
 
468
534
  ffi_lib vips_libname
@@ -609,7 +675,7 @@ module Vips
609
675
  LIBRARY_VERSION = Vips::version_string
610
676
 
611
677
  # libvips has this arbitrary number as a sanity-check upper bound on image
612
- # size. It's sometimes useful for know whan calculating image ratios.
678
+ # size. It's sometimes useful to know when calculating scale factors.
613
679
  MAX_COORD = 10000000
614
680
  end
615
681
 
@@ -617,4 +683,10 @@ require 'vips/object'
617
683
  require 'vips/operation'
618
684
  require 'vips/image'
619
685
  require 'vips/interpolate'
686
+ require 'vips/region'
620
687
  require 'vips/version'
688
+ require 'vips/connection'
689
+ require 'vips/source'
690
+ require 'vips/sourcecustom'
691
+ require 'vips/target'
692
+ require 'vips/targetcustom'
@@ -0,0 +1,46 @@
1
+ # This module provides an interface to the top level bits of libvips
2
+ # via ruby-ffi.
3
+ #
4
+ # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
+ # License:: MIT
6
+
7
+ require 'ffi'
8
+
9
+ module Vips
10
+ if Vips::at_least_libvips?(8, 9)
11
+ attach_function :vips_connection_filename, [:pointer], :string
12
+ attach_function :vips_connection_nick, [:pointer], :string
13
+ end
14
+
15
+ # Abstract base class for connections.
16
+ class Connection < Vips::Object
17
+ # The layout of the VipsRegion struct.
18
+ module ConnectionLayout
19
+ def self.included(base)
20
+ base.class_eval do
21
+ layout :parent, Vips::Object::Struct
22
+ # rest opaque
23
+ end
24
+ end
25
+ end
26
+
27
+ class Struct < Vips::Object::Struct
28
+ include ConnectionLayout
29
+ end
30
+
31
+ class ManagedStruct < Vips::Object::ManagedStruct
32
+ include ConnectionLayout
33
+ end
34
+
35
+ # Get any filename associated with a connection, or nil.
36
+ def filename
37
+ Vips::vips_connection_filename self
38
+ end
39
+
40
+ # Get a nickname (short description) of a connection that could be shown to
41
+ # the user.
42
+ def nick
43
+ Vips::vips_connection_nick self
44
+ end
45
+ end
46
+ end
@@ -73,6 +73,9 @@ module GObject
73
73
  def initialize ptr
74
74
  # GLib::logger.debug("GObject::GObject.initialize") {"ptr = #{ptr}"}
75
75
  @struct = ffi_managed_struct.new ptr
76
+
77
+ # sometimes we need to keep refs across C calls ... hide them here
78
+ @references = []
76
79
  end
77
80
 
78
81
  # access to the casting struct for this class
@@ -111,8 +114,13 @@ module GObject
111
114
  layout :value, GParamSpec.ptr
112
115
  end
113
116
 
114
- attach_function :g_param_spec_get_blurb, [GParamSpec.ptr], :string
117
+ attach_function :g_param_spec_get_blurb, [:pointer], :string
115
118
 
116
119
  attach_function :g_object_ref, [:pointer], :void
117
120
  attach_function :g_object_unref, [:pointer], :void
121
+
122
+ # we just use one gcallback type for every signal, hopefully this is OK
123
+ callback :gcallback, [:pointer], :void
124
+ attach_function :g_signal_connect_data,
125
+ [:pointer, :string, :gcallback, :pointer, :pointer, :int], :long
118
126
  end
@@ -13,6 +13,8 @@ module GObject
13
13
  # gvalue.init GObject::GDOUBLE_TYPE
14
14
  # gvalue.set 3.1415
15
15
  # value = gvalue.get
16
+ # # optional -- drop any ref the gvalue had
17
+ # gvalue.unset
16
18
  # ```
17
19
  #
18
20
  # Lifetime is managed automatically. It doesn't know about all GType values,
@@ -27,7 +29,9 @@ module GObject
27
29
  value = value.to_s if value.is_a? Symbol
28
30
 
29
31
  if value.is_a? String
30
- value = Vips::vips_enum_from_nick "ruby-vips", gtype, value
32
+ # libvips expects "-" as a separator in enum names, but "_" is more
33
+ # convenient for ruby, eg. :b_w
34
+ value = Vips::vips_enum_from_nick "ruby-vips", gtype, value.tr("_", "-")
31
35
  if value == -1
32
36
  raise Vips::Error
33
37
  end
@@ -241,6 +245,14 @@ module GObject
241
245
 
242
246
  return result
243
247
  end
248
+
249
+ # Clear the thing held by a GValue.
250
+ #
251
+ # This happens automatically when a GValue is GCed, but this method can be
252
+ # handy if you need to drop a reference explicitly for some reason.
253
+ def unset
254
+ ::GObject::g_value_unset self
255
+ end
244
256
  end
245
257
 
246
258
  attach_function :g_value_init, [GValue.ptr, :GType], :void
@@ -14,6 +14,9 @@ module Vips
14
14
 
15
15
  attach_function :vips_image_copy_memory, [:pointer], :pointer
16
16
 
17
+ attach_function :vips_image_set_progress, [:pointer, :bool], :void
18
+ attach_function :vips_image_set_kill, [:pointer, :bool], :void
19
+
17
20
  attach_function :vips_filename_get_filename, [:string], :pointer
18
21
  attach_function :vips_filename_get_options, [:string], :pointer
19
22
 
@@ -22,6 +25,11 @@ module Vips
22
25
  attach_function :vips_foreign_find_load_buffer, [:pointer, :size_t], :string
23
26
  attach_function :vips_foreign_find_save_buffer, [:string], :string
24
27
 
28
+ if Vips::at_least_libvips?(8, 9)
29
+ attach_function :vips_foreign_find_load_source, [:pointer], :string
30
+ attach_function :vips_foreign_find_save_target, [:string], :string
31
+ end
32
+
25
33
  attach_function :vips_image_write_to_memory,
26
34
  [:pointer, SizeStruct.ptr], :pointer
27
35
 
@@ -29,20 +37,14 @@ module Vips
29
37
  attach_function :vips_image_get,
30
38
  [:pointer, :string, GObject::GValue.ptr], :int
31
39
 
32
- # vips_image_get_fields was added in libvips 8.5
33
- begin
40
+ if Vips::at_least_libvips?(8, 5)
34
41
  attach_function :vips_image_get_fields, [:pointer], :pointer
35
- rescue FFI::NotFoundError
36
- nil
42
+ attach_function :vips_image_hasalpha, [:pointer], :int
37
43
  end
38
44
 
39
- # vips_addalpha was added in libvips 8.6
40
45
  if Vips::at_least_libvips?(8, 6)
41
46
  attach_function :vips_addalpha, [:pointer, :pointer, :varargs], :int
42
47
  end
43
- if Vips::at_least_libvips?(8, 5)
44
- attach_function :vips_image_hasalpha, [:pointer], :int
45
- end
46
48
 
47
49
  attach_function :vips_image_set,
48
50
  [:pointer, :string, GObject::GValue.ptr], :void
@@ -53,6 +55,8 @@ module Vips
53
55
 
54
56
  attach_function :nickname_find, :vips_nickname_find, [:GType], :string
55
57
 
58
+ attach_function :vips_image_invalidate_all, [:pointer], :void
59
+
56
60
  # turn a raw pointer that must be freed into a self-freeing Ruby string
57
61
  def self.p2str(pointer)
58
62
  pointer = FFI::AutoPointer.new(pointer, GLib::G_FREE)
@@ -67,6 +71,10 @@ module Vips
67
71
  class Image < Vips::Object
68
72
  alias_method :parent_get_typeof, :get_typeof
69
73
 
74
+ def close
75
+ Vips.vips_image_invalidate_all(self)
76
+ end
77
+
70
78
  private
71
79
 
72
80
  # the layout of the VipsImage struct
@@ -115,7 +123,7 @@ module Vips
115
123
 
116
124
  unless Image::complex? image.format
117
125
  if image.bands % 2 != 0
118
- raise Error, "not an even number of bands"
126
+ raise Vips::Error, "not an even number of bands"
119
127
  end
120
128
 
121
129
  unless Image::float? image.format
@@ -145,34 +153,6 @@ module Vips
145
153
  end
146
154
  end
147
155
 
148
- # Write can fail due to no file descriptors and memory can fill if
149
- # large objects are not collected fairly soon. We can't try a
150
- # write and GC and retry on fail, since the write may take a
151
- # long time and may not be repeatable.
152
- #
153
- # GCing before every write would have a horrible effect on
154
- # performance, so as a compromise we GC every @@gc_interval writes.
155
- #
156
- # ruby2.1 introduced a generational GC which is fast enough to be
157
- # able to GC on every write.
158
-
159
- @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1
160
-
161
- @@gc_interval = 100
162
- @@gc_countdown = @@gc_interval
163
-
164
- def write_gc
165
- if @@generational_gc
166
- GC.start full_mark: false
167
- else
168
- @@gc_countdown -= 1
169
- if @@gc_countdown < 0
170
- @@gc_countdown = @@gc_interval
171
- GC.start
172
- end
173
- end
174
- end
175
-
176
156
  public
177
157
 
178
158
  def inspect
@@ -219,13 +199,13 @@ module Vips
219
199
  # load options, for example:
220
200
  #
221
201
  # ```
222
- # image = Vips::new_from_file "fred.jpg[shrink=2]"
202
+ # image = Vips::Image.new_from_file "fred.jpg[shrink=2]"
223
203
  # ```
224
204
  #
225
205
  # You can also supply options as a hash, for example:
226
206
  #
227
207
  # ```
228
- # image = Vips::new_from_file "fred.jpg", shrink: 2
208
+ # image = Vips::Image.new_from_file "fred.jpg", shrink: 2
229
209
  # ```
230
210
  #
231
211
  # The full set of options available depend upon the load operation that
@@ -296,11 +276,50 @@ module Vips
296
276
  # @return [Image] the loaded image
297
277
  def self.new_from_buffer data, option_string, **opts
298
278
  loader = Vips::vips_foreign_find_load_buffer data, data.bytesize
299
- raise Vips::Error if loader == nil
279
+ raise Vips::Error if loader.nil?
300
280
 
301
281
  Vips::Operation.call loader, [data], opts, option_string
302
282
  end
303
283
 
284
+ # Create a new {Image} from a source. Load options may be passed as
285
+ # strings or appended as a hash. For example:
286
+ #
287
+ # ```
288
+ # source = Vips::Source.new_from_file("k2.jpg")
289
+ # image = Vips::Image.new_from_source source, "shrink=2"
290
+ # ```
291
+ #
292
+ # or alternatively:
293
+ #
294
+ # ```
295
+ # image = Vips::Image.new_from_source source, "", shrink: 2
296
+ # ```
297
+ #
298
+ # The options available depend on the file format. Try something like:
299
+ #
300
+ # ```
301
+ # $ vips jpegload_source
302
+ # ```
303
+ #
304
+ # at the command-line to see the available options. Not all loaders
305
+ # support load from source, but at least JPEG, PNG and
306
+ # TIFF images will work.
307
+ #
308
+ # Loading is fast: only enough data is read to be able to fill
309
+ # out the header. Pixels will only be read and decompressed when they are
310
+ # needed.
311
+ #
312
+ # @param source [Vips::Source] the source to load from
313
+ # @param option_string [String] load options as a string
314
+ # @macro vips.loadopts
315
+ # @return [Image] the loaded image
316
+ def self.new_from_source source, option_string, **opts
317
+ loader = Vips::vips_foreign_find_load_source source
318
+ raise Vips::Error if loader.nil?
319
+
320
+ Vips::Operation.call loader, [source], opts, option_string
321
+ end
322
+
304
323
  def self.matrix_from_array width, height, array
305
324
  ptr = FFI::MemoryPointer.new :double, array.length
306
325
  ptr.write_array_of_double array
@@ -317,13 +336,13 @@ module Vips
317
336
  # For example:
318
337
  #
319
338
  # ```
320
- # image = Vips::new_from_array [1, 2, 3]
339
+ # image = Vips::Image.new_from_array [1, 2, 3]
321
340
  # ```
322
341
  #
323
342
  # or
324
343
  #
325
344
  # ```
326
- # image = Vips::new_from_array [
345
+ # image = Vips::Image.new_from_array [
327
346
  # [-1, -1, -1],
328
347
  # [-1, 16, -1],
329
348
  # [-1, -1, -1]], 8
@@ -426,8 +445,6 @@ module Vips
426
445
  end
427
446
 
428
447
  Vips::Operation.call saver, [self, filename], opts, option_string
429
-
430
- write_gc
431
448
  end
432
449
 
433
450
  # Write this image to a memory buffer. Save options may be encoded in
@@ -460,17 +477,52 @@ module Vips
460
477
  option_string = Vips::p2str(Vips::vips_filename_get_options format_string)
461
478
  saver = Vips::vips_foreign_find_save_buffer filename
462
479
  if saver == nil
463
- raise Vips::Error, "No known saver for '#{filename}'."
480
+ raise Vips::Error, "No known buffer saver for '#{filename}'."
464
481
  end
465
482
 
466
483
  buffer = Vips::Operation.call saver, [self], opts, option_string
467
484
  raise Vips::Error if buffer == nil
468
485
 
469
- write_gc
470
-
471
486
  return buffer
472
487
  end
473
488
 
489
+ # Write this image to a target. Save options may be encoded in
490
+ # the format_string or given as a hash. For example:
491
+ #
492
+ # ```ruby
493
+ # target = Vips::Target.new_to_file "k2.jpg"
494
+ # image.write_to_target target, ".jpg[Q=90]"
495
+ # ```
496
+ #
497
+ # or equivalently:
498
+ #
499
+ # ```ruby
500
+ # image.write_to_target target, ".jpg", Q: 90
501
+ # ```
502
+ #
503
+ # The full set of save options depend on the selected saver. Try
504
+ # something like:
505
+ #
506
+ # ```
507
+ # $ vips jpegsave_target
508
+ # ```
509
+ #
510
+ # to see all the available options for JPEG save.
511
+ #
512
+ # @param target [Vips::Target] the target to write to
513
+ # @param format_string [String] save format plus string options
514
+ # @macro vips.saveopts
515
+ def write_to_target target, format_string, **opts
516
+ filename = Vips::p2str(Vips::vips_filename_get_filename format_string)
517
+ option_string = Vips::p2str(Vips::vips_filename_get_options format_string)
518
+ saver = Vips::vips_foreign_find_save_target filename
519
+ if saver == nil
520
+ raise Vips::Error, "No known target saver for '#{filename}'."
521
+ end
522
+
523
+ Vips::Operation.call saver, [self, target], opts, option_string
524
+ end
525
+
474
526
  # Write this image to a large memory buffer.
475
527
  #
476
528
  # @return [String] the pixels as a huge binary string
@@ -485,6 +537,28 @@ module Vips
485
537
  ptr.get_bytes 0, len[:value]
486
538
  end
487
539
 
540
+ # Turn progress signalling on and off.
541
+ #
542
+ # If this is on, the most-downstream image from this image will issue
543
+ # progress signals.
544
+ #
545
+ # @see Object#signal_connect
546
+ # @param state [Boolean] progress signalling state
547
+ def set_progress state
548
+ Vips::vips_image_set_progress self, state
549
+ end
550
+
551
+ # Kill computation of this time.
552
+ #
553
+ # Set true to stop computation of this image. You can call this from a
554
+ # progress handler, for example.
555
+ #
556
+ # @see Object#signal_connect
557
+ # @param kill [Boolean] stop computation
558
+ def set_kill kill
559
+ Vips::vips_image_set_kill self, kill
560
+ end
561
+
488
562
  # Get the `GType` of a metadata field. The result is 0 if no such field
489
563
  # exists.
490
564
  #
@@ -523,10 +597,11 @@ module Vips
523
597
  end
524
598
 
525
599
  gvalue = GObject::GValue.alloc
526
- result = Vips::vips_image_get self, name, gvalue
527
- raise Vips::Error if result != 0
600
+ raise Vips::Error if Vips::vips_image_get(self, name, gvalue) != 0
601
+ result = gvalue.get
602
+ gvalue.unset
528
603
 
529
- gvalue.get
604
+ result
530
605
  end
531
606
 
532
607
  # Get the names of all fields on an image. Use this to loop over all
@@ -571,6 +646,7 @@ module Vips
571
646
  gvalue.init gtype
572
647
  gvalue.set value
573
648
  Vips::vips_image_set self, name, gvalue
649
+ gvalue.unset
574
650
  end
575
651
 
576
652
  # Set the value of a metadata item on an image. The metadata item must
@@ -1059,7 +1135,7 @@ module Vips
1059
1135
  # @option opts [Vips::Interpretation] :compositing_space Composite images in this colour space
1060
1136
  # @option opts [Boolean] :premultiplied Images have premultiplied alpha
1061
1137
  # @return [Image] blended image
1062
- def composite overlay, mode, **opts
1138
+ def composite overlay, mode, **options
1063
1139
  unless overlay.is_a? Array
1064
1140
  overlay = [overlay]
1065
1141
  end
@@ -1071,7 +1147,7 @@ module Vips
1071
1147
  GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x
1072
1148
  end
1073
1149
 
1074
- Vips::Image.composite([self] + overlay, mode, opts)
1150
+ Vips::Image.composite([self] + overlay, mode, **options)
1075
1151
  end
1076
1152
 
1077
1153
  # Return the coordinates of the image maximum.
@@ -1320,30 +1396,27 @@ module Vips
1320
1396
  #
1321
1397
  # @param opts [Hash] Set of options
1322
1398
  # @return [Vips::Image] Output image
1323
- def scaleimage **opts
1324
- Vips::Image.scale self, opts
1399
+ def scaleimage **options
1400
+ Vips::Image.scale self, **options
1325
1401
  end
1326
1402
  end
1327
1403
  end
1328
1404
 
1329
1405
  module Vips
1330
- # This method generates yard comments for all the dynamically bound
1406
+ # This module generates yard comments for all the dynamically bound
1331
1407
  # vips operations.
1332
1408
  #
1333
1409
  # Regenerate with something like:
1334
1410
  #
1335
1411
  # ```
1336
1412
  # $ ruby > methods.rb
1337
- # require 'vips'; Vips::generate_yard
1413
+ # require 'vips'; Vips::Yard.generate
1338
1414
  # ^D
1339
1415
  # ```
1340
1416
 
1341
- def self.generate_yard
1342
- # these have hand-written methods, see above
1343
- no_generate = ["scale", "bandjoin", "composite", "ifthenelse"]
1344
-
1417
+ module Yard
1345
1418
  # map gobject's type names to Ruby
1346
- map_go_to_ruby = {
1419
+ MAP_GO_TO_RUBY = {
1347
1420
  "gboolean" => "Boolean",
1348
1421
  "gint" => "Integer",
1349
1422
  "gdouble" => "Float",
@@ -1351,116 +1424,99 @@ module Vips
1351
1424
  "gchararray" => "String",
1352
1425
  "VipsImage" => "Vips::Image",
1353
1426
  "VipsInterpolate" => "Vips::Interpolate",
1427
+ "VipsConnection" => "Vips::Connection",
1428
+ "VipsSource" => "Vips::Source",
1429
+ "VipsTarget" => "Vips::Target",
1430
+ "VipsSourceCustom" => "Vips::SourceCustom",
1431
+ "VipsTargetCustom" => "Vips::TargetCustom",
1354
1432
  "VipsArrayDouble" => "Array<Double>",
1355
1433
  "VipsArrayInt" => "Array<Integer>",
1356
1434
  "VipsArrayImage" => "Array<Image>",
1357
1435
  "VipsArrayString" => "Array<String>",
1358
1436
  }
1359
1437
 
1360
- generate_operation = lambda do |gtype, nickname, op|
1361
- op_flags = op.get_flags
1362
- return if (op_flags & OPERATION_DEPRECATED) != 0
1363
- return if no_generate.include? nickname
1364
-
1365
- description = Vips::vips_object_get_description op
1366
-
1367
- # find and classify all the arguments the operator can take
1368
- required_input = []
1369
- optional_input = []
1370
- required_output = []
1371
- optional_output = []
1372
- member_x = nil
1373
- op.argument_map do |pspec, argument_class, _argument_instance|
1374
- arg_flags = argument_class[:flags]
1375
- next if (arg_flags & ARGUMENT_CONSTRUCT) == 0
1376
- next if (arg_flags & ARGUMENT_DEPRECATED) != 0
1377
-
1378
- name = pspec[:name].tr("-", "_")
1379
- # 'in' as a param name confuses yard
1380
- name = "im" if name == "in"
1381
- gtype = pspec[:value_type]
1382
- fundamental = GObject::g_type_fundamental gtype
1383
- type_name = GObject::g_type_name gtype
1384
- if map_go_to_ruby.include? type_name
1385
- type_name = map_go_to_ruby[type_name]
1386
- end
1387
- if fundamental == GObject::GFLAGS_TYPE ||
1388
- fundamental == GObject::GENUM_TYPE
1389
- type_name = "Vips::" + type_name[/Vips(.*)/, 1]
1390
- end
1391
- blurb = GObject::g_param_spec_get_blurb pspec
1392
- value = {
1393
- name: name,
1394
- flags: arg_flags,
1395
- gtype: gtype,
1396
- type_name: type_name,
1397
- blurb: blurb
1398
- }
1399
-
1400
- if (arg_flags & ARGUMENT_INPUT) != 0
1401
- if (arg_flags & ARGUMENT_REQUIRED) != 0
1402
- # note the first required input image, if any ... we
1403
- # will be a method of this instance
1404
- if !member_x && gtype == Vips::IMAGE_TYPE
1405
- member_x = value
1406
- else
1407
- required_input << value
1408
- end
1409
- else
1410
- optional_input << value
1411
- end
1412
- end
1438
+ # these have hand-written methods, see above
1439
+ NO_GENERATE = ["scale", "bandjoin", "composite", "ifthenelse"]
1413
1440
 
1414
- # MODIFY INPUT args count as OUTPUT as well
1415
- if (arg_flags & ARGUMENT_OUTPUT) != 0 ||
1416
- ((arg_flags & ARGUMENT_INPUT) != 0 &&
1417
- (arg_flags & ARGUMENT_MODIFY) != 0)
1418
- if (arg_flags & ARGUMENT_REQUIRED) != 0
1419
- required_output << value
1420
- else
1421
- optional_output << value
1422
- end
1423
- end
1441
+ # these are aliased (appear under several names)
1442
+ ALIAS = ["crop"]
1443
+
1444
+ # turn a gtype into a ruby type name
1445
+ def self.gtype_to_ruby gtype
1446
+ fundamental = GObject::g_type_fundamental gtype
1447
+ type_name = GObject::g_type_name gtype
1448
+
1449
+ if MAP_GO_TO_RUBY.include? type_name
1450
+ type_name = MAP_GO_TO_RUBY[type_name]
1451
+ end
1452
+
1453
+ if fundamental == GObject::GFLAGS_TYPE ||
1454
+ fundamental == GObject::GENUM_TYPE
1455
+ type_name = "Vips::" + type_name[/Vips(.*)/, 1]
1424
1456
  end
1425
1457
 
1458
+ type_name
1459
+ end
1460
+
1461
+ def self.generate_operation introspect
1462
+ return if (introspect.flags & OPERATION_DEPRECATED) != 0
1463
+ return if NO_GENERATE.include? introspect.name
1464
+
1465
+ method_args = introspect.method_args
1466
+ required_output = introspect.required_output
1467
+ optional_input = introspect.optional_input
1468
+ optional_output = introspect.optional_output
1469
+
1426
1470
  print "# @!method "
1427
- print "self." unless member_x
1428
- print "#{nickname}("
1429
- print required_input.map { |x| x[:name] }.join(", ")
1430
- print ", " if required_input.length > 0
1471
+ print "self." unless introspect.member_x
1472
+ print "#{introspect.name}("
1473
+ print method_args.map{ |x| x[:yard_name] }.join(", ")
1474
+ print ", " if method_args.length > 0
1431
1475
  puts "**opts)"
1432
1476
 
1433
- puts "# #{description.capitalize}."
1477
+ puts "# #{introspect.description.capitalize}."
1478
+
1479
+ method_args.each do |details|
1480
+ yard_name = details[:yard_name]
1481
+ gtype = details[:gtype]
1482
+ blurb = details[:blurb]
1434
1483
 
1435
- required_input.each do |arg|
1436
- puts "# @param #{arg[:name]} [#{arg[:type_name]}] #{arg[:blurb]}"
1484
+ puts "# @param #{yard_name} [#{gtype_to_ruby(gtype)}] #{blurb}"
1437
1485
  end
1438
1486
 
1439
1487
  puts "# @param opts [Hash] Set of options"
1440
- optional_input.each do |arg|
1441
- puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " +
1442
- "#{arg[:blurb]}"
1488
+ optional_input.each do |arg_name, details|
1489
+ yard_name = details[:yard_name]
1490
+ gtype = details[:gtype]
1491
+ blurb = details[:blurb]
1492
+
1493
+ puts "# @option opts [#{gtype_to_ruby(gtype)}] :#{yard_name} " +
1494
+ "#{blurb}"
1443
1495
  end
1444
- optional_output.each do |arg|
1445
- print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}"
1446
- puts " Output #{arg[:blurb]}"
1496
+ optional_output.each do |arg_name, details|
1497
+ yard_name = details[:yard_name]
1498
+ gtype = details[:gtype]
1499
+ blurb = details[:blurb]
1500
+
1501
+ print "# @option opts [#{gtype_to_ruby(gtype)}] :#{yard_name}"
1502
+ puts " Output #{blurb}"
1447
1503
  end
1448
1504
 
1449
1505
  print "# @return ["
1450
1506
  if required_output.length == 0
1451
1507
  print "nil"
1452
1508
  elsif required_output.length == 1
1453
- print required_output.first[:type_name]
1509
+ print gtype_to_ruby(required_output.first[:gtype])
1454
1510
  else
1455
1511
  print "Array<"
1456
- print required_output.map { |x| x[:type_name] }.join(", ")
1512
+ print required_output.map{ |x| gtype_to_ruby(x[:gtype]) }.join(", ")
1457
1513
  print ">"
1458
1514
  end
1459
1515
  if optional_output.length > 0
1460
1516
  print ", Hash<Symbol => Object>"
1461
1517
  end
1462
1518
  print "] "
1463
- print required_output.map { |x| x[:blurb] }.join(", ")
1519
+ print required_output.map{ |x| x[:blurb] }.join(", ")
1464
1520
  if optional_output.length > 0
1465
1521
  print ", " if required_output.length > 0
1466
1522
  print "Hash of optional output items"
@@ -1470,30 +1526,42 @@ module Vips
1470
1526
  puts ""
1471
1527
  end
1472
1528
 
1473
- generate_class = lambda do |gtype, _|
1474
- nickname = Vips::nickname_find gtype
1529
+ def self.generate
1530
+ alias_gtypes = {}
1531
+ ALIAS.each do |name|
1532
+ gtype = Vips::type_find "VipsOperation", name
1533
+ alias_gtypes[gtype] = name
1534
+ end
1475
1535
 
1476
- if nickname
1477
- begin
1478
- # can fail for abstract types
1479
- op = Vips::Operation.new nickname
1480
- rescue Vips::Error
1481
- nil
1536
+ generate_class = lambda do |gtype, _|
1537
+ if alias_gtypes.key? gtype
1538
+ name = alias_gtypes[gtype]
1539
+ else
1540
+ name = Vips::nickname_find gtype
1482
1541
  end
1483
1542
 
1484
- generate_operation.(gtype, nickname, op) if op
1485
- end
1543
+ if name
1544
+ begin
1545
+ # can fail for abstract types
1546
+ introspect = Vips::Introspect.get_yard name
1547
+ rescue Vips::Error
1548
+ nil
1549
+ end
1486
1550
 
1487
- Vips::vips_type_map gtype, generate_class, nil
1488
- end
1551
+ generate_operation(introspect) if introspect
1552
+ end
1553
+
1554
+ Vips::vips_type_map gtype, generate_class, nil
1555
+ end
1489
1556
 
1490
- puts "module Vips"
1491
- puts " class Image"
1492
- puts ""
1557
+ puts "module Vips"
1558
+ puts " class Image"
1559
+ puts ""
1493
1560
 
1494
- generate_class.(GObject::g_type_from_name("VipsOperation"), nil)
1561
+ generate_class.(GObject::g_type_from_name("VipsOperation"), nil)
1495
1562
 
1496
- puts " end"
1497
- puts "end"
1563
+ puts " end"
1564
+ puts "end"
1565
+ end
1498
1566
  end
1499
1567
  end