ruby-vips 2.0.17 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  3. data/.github/workflows/test.yml +80 -0
  4. data/.standard.yml +17 -0
  5. data/.yardopts +0 -1
  6. data/CHANGELOG.md +26 -0
  7. data/Gemfile +3 -1
  8. data/README.md +12 -11
  9. data/Rakefile +13 -21
  10. data/TODO +4 -8
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +6 -6
  13. data/example/connection.rb +18 -9
  14. data/example/daltonize8.rb +6 -6
  15. data/example/draw_lines.rb +30 -0
  16. data/example/example1.rb +4 -4
  17. data/example/example2.rb +6 -6
  18. data/example/example3.rb +5 -5
  19. data/example/example4.rb +2 -2
  20. data/example/example5.rb +4 -4
  21. data/example/inheritance_with_refcount.rb +46 -39
  22. data/example/progress.rb +3 -3
  23. data/example/thumb.rb +6 -6
  24. data/example/trim8.rb +1 -1
  25. data/example/watermark.rb +2 -2
  26. data/example/wobble.rb +1 -1
  27. data/lib/ruby-vips.rb +1 -1
  28. data/lib/vips.rb +127 -76
  29. data/lib/vips/blend_mode.rb +29 -25
  30. data/lib/vips/connection.rb +4 -4
  31. data/lib/vips/gobject.rb +18 -11
  32. data/lib/vips/gvalue.rb +54 -54
  33. data/lib/vips/image.rb +289 -165
  34. data/lib/vips/interpolate.rb +3 -2
  35. data/lib/vips/methods.rb +484 -107
  36. data/lib/vips/mutableimage.rb +173 -0
  37. data/lib/vips/object.rb +86 -93
  38. data/lib/vips/operation.rb +161 -82
  39. data/lib/vips/region.rb +6 -6
  40. data/lib/vips/source.rb +11 -12
  41. data/lib/vips/sourcecustom.rb +7 -8
  42. data/lib/vips/target.rb +12 -13
  43. data/lib/vips/targetcustom.rb +9 -10
  44. data/lib/vips/version.rb +1 -1
  45. data/ruby-vips.gemspec +26 -22
  46. metadata +29 -49
  47. data/.rubocop.yml +0 -22
  48. data/.rubocop_todo.yml +0 -473
  49. data/.travis.yml +0 -57
  50. data/install-vips.sh +0 -26
@@ -0,0 +1,173 @@
1
+ # This module provides an interface to the vips image processing library
2
+ # via ruby-ffi.
3
+ #
4
+ # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
+ # License:: MIT
6
+
7
+ require "ffi"
8
+ require "forwardable"
9
+
10
+ module Vips
11
+ # This class represents a libvips image which can be modified. See
12
+ # {Vips::Image#mutate}.
13
+ class MutableImage < Vips::Object
14
+ extend Forwardable
15
+ alias_method :parent_get_typeof, :get_typeof
16
+ def_instance_delegators :@image, :width, :height, :bands, :format,
17
+ :interpretation, :filename, :xoffset, :yoffset, :xres, :yres, :size,
18
+ :get, :get_typeof, :get_fields
19
+
20
+ # layout is exactly as {Image} (since we are also wrapping a VipsImage
21
+ # object)
22
+ module MutableImageLayout
23
+ def self.included base
24
+ base.class_eval do
25
+ layout :parent, Vips::Object::Struct
26
+ # rest opaque
27
+ end
28
+ end
29
+ end
30
+
31
+ class Struct < Vips::Object::Struct
32
+ include MutableImageLayout
33
+ end
34
+
35
+ class ManagedStruct < Vips::Object::ManagedStruct
36
+ include MutableImageLayout
37
+ end
38
+
39
+ # Get the {Image} this {MutableImage} is modifying. Only use this once you
40
+ # have finished all modifications.
41
+ #
42
+ # This is for internal use only. See {Vips::Image#mutate} for the
43
+ # user-facing interface.
44
+ attr_reader :image
45
+
46
+ # Make a {MutableImage} from a regular {Image}.
47
+ #
48
+ # This is for internal use only. See {Vips::Image#mutate} for the
49
+ # user-facing interface.
50
+ def initialize(image)
51
+ # We take a copy of the regular Image to ensure we have an unshared
52
+ # (unique) object. We forward things like #width and #height to this, and
53
+ # it's the thing we return at the end of the mutate block.
54
+ copy_image = image.copy
55
+
56
+ # Use ptr since we need the raw unwrapped pointer inside the image ...
57
+ # and make the ref that gobject will unref when it finishes.
58
+ # See also the comment on set_type! before changing this.
59
+ pointer = copy_image.ptr
60
+ ::GObject.g_object_ref pointer
61
+ super pointer
62
+
63
+ # and save the copy ready for when we finish mutating
64
+ @image = copy_image
65
+ end
66
+
67
+ def inspect
68
+ "#<MutableImage #{width}x#{height} #{format}, #{bands} bands, #{interpretation}>"
69
+ end
70
+
71
+ def respond_to? name, include_all = false
72
+ # To support keyword args, we need to tell Ruby that final image
73
+ # arguments cannot be hashes of keywords.
74
+ #
75
+ # https://makandracards.com/makandra/
76
+ # 36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments
77
+ return false if name == :to_hash
78
+
79
+ super
80
+ end
81
+
82
+ def respond_to_missing? name, include_all = false
83
+ # Respond to all vips operations by nickname.
84
+ return true if Vips.type_find("VipsOperation", name.to_s) != 0
85
+
86
+ super
87
+ end
88
+
89
+ # Invoke a vips operation with {Vips::Operation#call}, using self as
90
+ # the first input argument. {Vips::Operation#call} will only allow
91
+ # operations that modify self when passed a {MutableImage}.
92
+ #
93
+ # @param name [String] vips operation to call
94
+ # @return result of vips operation
95
+ def method_missing name, *args, **options
96
+ Vips::Operation.call name.to_s, [self, *args], options
97
+ end
98
+
99
+ # Create a metadata item on an image of the specifed type. Ruby types
100
+ # are automatically transformed into the matching glib type (eg.
101
+ # {GObject::GINT_TYPE}), if possible.
102
+ #
103
+ # For example, you can use this to set an image's ICC profile:
104
+ #
105
+ # ```ruby
106
+ # x.set_type! Vips::BLOB_TYPE, "icc-profile-data", profile
107
+ # ```
108
+ #
109
+ # where `profile` is an ICC profile held as a binary string object.
110
+ #
111
+ # @see set!
112
+ # @param gtype [Integer] GType of item
113
+ # @param name [String] Metadata field to set
114
+ # @param value [Object] Value to set
115
+ def set_type! gtype, name, value
116
+ gvalue = GObject::GValue.alloc
117
+ gvalue.init gtype
118
+ gvalue.set value
119
+
120
+ # libvips 8.9.1 had a terrible misfeature which would block metadata
121
+ # modification unless the object had a ref_count of 1. MutableImage
122
+ # will always have a ref_count of at least 2 (the parent gobject keeps a
123
+ # ref, and we keep a ref to the copy ready to return to our caller),
124
+ # so we must temporarily drop the refs to 1 around metadata changes.
125
+ #
126
+ # See https://github.com/libvips/ruby-vips/issues/291
127
+ begin
128
+ ::GObject.g_object_unref ptr
129
+ Vips.vips_image_set self, name, gvalue
130
+ ensure
131
+ ::GObject.g_object_ref ptr
132
+ end
133
+
134
+ gvalue.unset
135
+ end
136
+
137
+ # Set the value of a metadata item on an image. The metadata item must
138
+ # already exist. Ruby types are automatically transformed into the
139
+ # matching {GObject::GValue}, if possible.
140
+ #
141
+ # For example, you can use this to set an image's ICC profile:
142
+ #
143
+ # ```
144
+ # x.set! "icc-profile-data", profile
145
+ # ```
146
+ #
147
+ # where `profile` is an ICC profile held as a binary string object.
148
+ #
149
+ # @see set_type!
150
+ # @param name [String] Metadata field to set
151
+ # @param value [Object] Value to set
152
+ def set! name, value
153
+ set_type! get_typeof(name), name, value
154
+ end
155
+
156
+ # Remove a metadata item from an image.
157
+ #
158
+ # For example:
159
+ #
160
+ # ```
161
+ # x.remove! "icc-profile-data"
162
+ # ```
163
+ #
164
+ # @param name [String] Metadata field to remove
165
+ def remove! name
166
+ # See set_type! for an explanation. Image#remove can't throw an
167
+ # exception, so there's no need to ensure we unref.
168
+ ::GObject.g_object_unref ptr
169
+ Vips.vips_image_remove self, name
170
+ ::GObject.g_object_ref ptr
171
+ end
172
+ end
173
+ end
data/lib/vips/object.rb CHANGED
@@ -4,7 +4,7 @@
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
  private
@@ -21,20 +21,20 @@ module Vips
21
21
  public
22
22
 
23
23
  # some handy gtypes
24
- IMAGE_TYPE = GObject::g_type_from_name "VipsImage"
25
- ARRAY_INT_TYPE = GObject::g_type_from_name "VipsArrayInt"
26
- ARRAY_DOUBLE_TYPE = GObject::g_type_from_name "VipsArrayDouble"
27
- ARRAY_IMAGE_TYPE = GObject::g_type_from_name "VipsArrayImage"
28
- REFSTR_TYPE = GObject::g_type_from_name "VipsRefString"
29
- BLOB_TYPE = GObject::g_type_from_name "VipsBlob"
30
-
31
- BAND_FORMAT_TYPE = Vips::vips_band_format_get_type
32
- INTERPRETATION_TYPE = Vips::vips_interpretation_get_type
33
- CODING_TYPE = Vips::vips_coding_get_type
34
-
35
- if Vips::at_least_libvips?(8, 6)
24
+ IMAGE_TYPE = GObject.g_type_from_name "VipsImage"
25
+ ARRAY_INT_TYPE = GObject.g_type_from_name "VipsArrayInt"
26
+ ARRAY_DOUBLE_TYPE = GObject.g_type_from_name "VipsArrayDouble"
27
+ ARRAY_IMAGE_TYPE = GObject.g_type_from_name "VipsArrayImage"
28
+ REFSTR_TYPE = GObject.g_type_from_name "VipsRefString"
29
+ BLOB_TYPE = GObject.g_type_from_name "VipsBlob"
30
+
31
+ BAND_FORMAT_TYPE = Vips.vips_band_format_get_type
32
+ INTERPRETATION_TYPE = Vips.vips_interpretation_get_type
33
+ CODING_TYPE = Vips.vips_coding_get_type
34
+
35
+ if Vips.at_least_libvips?(8, 6)
36
36
  attach_function :vips_blend_mode_get_type, [], :GType
37
- BLEND_MODE_TYPE = Vips::vips_blend_mode_get_type
37
+ BLEND_MODE_TYPE = Vips.vips_blend_mode_get_type
38
38
  else
39
39
  BLEND_MODE_TYPE = nil
40
40
  end
@@ -45,37 +45,34 @@ module Vips
45
45
  layout :im, :pointer,
46
46
  :run, :int,
47
47
  :eta, :int,
48
- :tpels, :int64_t,
49
- :npels, :int64_t,
50
- :percent, :int,
51
- :start, :pointer
48
+ :tpels, :int64_t,
49
+ :npels, :int64_t,
50
+ :percent, :int,
51
+ :start, :pointer
52
52
  end
53
53
 
54
- # Our signal marshalers.
54
+ # Our signal marshalers.
55
55
  #
56
- # These are functions which take the handler as a param and return a
56
+ # These are functions which take the handler as a param and return a
57
57
  # closure with the right FFI signature for g_signal_connect for this
58
- # specific signal.
58
+ # specific signal.
59
59
  #
60
- # ruby-ffi makes it hard to use the g_signal_connect user data param
60
+ # ruby-ffi makes it hard to use the g_signal_connect user data param
61
61
  # to pass the function pointer through, unfortunately.
62
62
  #
63
63
  # We can't throw exceptions across C, so we must catch everything.
64
64
 
65
- MARSHAL_PROGRESS = Proc.new do |handler|
65
+ MARSHAL_PROGRESS = proc do |handler|
66
66
  FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do |vi, prog, cb|
67
- begin
68
- handler.(Progress.new(prog))
69
- rescue Exception => e
70
- puts "progress: #{e}"
71
- end
67
+ # this can't throw an exception, so no catch is necessary
68
+ handler.call(Progress.new(prog))
72
69
  end
73
70
  end
74
71
 
75
- MARSHAL_READ = Proc.new do |handler|
72
+ MARSHAL_READ = proc do |handler|
76
73
  FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
77
74
  begin
78
- result = handler.(p, len)
75
+ result = handler.call(p, len)
79
76
  rescue Exception => e
80
77
  puts "read: #{e}"
81
78
  result = 0
@@ -85,10 +82,10 @@ module Vips
85
82
  end
86
83
  end
87
84
 
88
- MARSHAL_SEEK = Proc.new do |handler|
85
+ MARSHAL_SEEK = proc do |handler|
89
86
  FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
90
87
  begin
91
- result = handler.(off, whence)
88
+ result = handler.call(off, whence)
92
89
  rescue Exception => e
93
90
  puts "seek: #{e}"
94
91
  result = -1
@@ -98,10 +95,10 @@ module Vips
98
95
  end
99
96
  end
100
97
 
101
- MARSHAL_WRITE = Proc.new do |handler|
98
+ MARSHAL_WRITE = proc do |handler|
102
99
  FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
103
100
  begin
104
- result = handler.(p, len)
101
+ result = handler.call(p, len)
105
102
  rescue Exception => e
106
103
  puts "write: #{e}"
107
104
  result = 0
@@ -111,41 +108,38 @@ module Vips
111
108
  end
112
109
  end
113
110
 
114
- MARSHAL_FINISH = Proc.new do |handler|
111
+ MARSHAL_FINISH = proc do |handler|
115
112
  FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
116
- begin
117
- handler.()
118
- rescue Exception => e
119
- puts "finish: #{e}"
120
- end
113
+ # this can't throw an exception, so no catch is necessary
114
+ handler.call
121
115
  end
122
116
  end
123
117
 
124
118
  # map signal name to marshal proc
125
119
  MARSHAL_ALL = {
126
- :preeval => MARSHAL_PROGRESS,
127
- :eval => MARSHAL_PROGRESS,
128
- :posteval => MARSHAL_PROGRESS,
129
- :read => MARSHAL_READ,
130
- :seek => MARSHAL_SEEK,
131
- :write => MARSHAL_WRITE,
132
- :finish => MARSHAL_FINISH,
120
+ preeval: MARSHAL_PROGRESS,
121
+ eval: MARSHAL_PROGRESS,
122
+ posteval: MARSHAL_PROGRESS,
123
+ read: MARSHAL_READ,
124
+ seek: MARSHAL_SEEK,
125
+ write: MARSHAL_WRITE,
126
+ finish: MARSHAL_FINISH
133
127
  }
134
128
 
135
129
  attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
136
130
  attach_function :vips_enum_nick, [:GType, :int], :string
137
131
 
138
132
  attach_function :vips_value_set_ref_string,
139
- [GObject::GValue.ptr, :string], :void
133
+ [GObject::GValue.ptr, :string], :void
140
134
  attach_function :vips_value_set_array_double,
141
- [GObject::GValue.ptr, :pointer, :int], :void
135
+ [GObject::GValue.ptr, :pointer, :int], :void
142
136
  attach_function :vips_value_set_array_int,
143
- [GObject::GValue.ptr, :pointer, :int], :void
137
+ [GObject::GValue.ptr, :pointer, :int], :void
144
138
  attach_function :vips_value_set_array_image,
145
- [GObject::GValue.ptr, :int], :void
139
+ [GObject::GValue.ptr, :int], :void
146
140
  callback :free_fn, [:pointer], :void
147
141
  attach_function :vips_value_set_blob,
148
- [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
142
+ [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
149
143
 
150
144
  class SizeStruct < FFI::Struct
151
145
  layout :value, :size_t
@@ -156,15 +150,15 @@ module Vips
156
150
  end
157
151
 
158
152
  attach_function :vips_value_get_ref_string,
159
- [GObject::GValue.ptr, SizeStruct.ptr], :string
153
+ [GObject::GValue.ptr, SizeStruct.ptr], :string
160
154
  attach_function :vips_value_get_array_double,
161
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
155
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
162
156
  attach_function :vips_value_get_array_int,
163
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
157
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
164
158
  attach_function :vips_value_get_array_image,
165
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
159
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
166
160
  attach_function :vips_value_get_blob,
167
- [GObject::GValue.ptr, SizeStruct.ptr], :pointer
161
+ [GObject::GValue.ptr, SizeStruct.ptr], :pointer
168
162
 
169
163
  attach_function :type_find, :vips_type_find, [:string, :string], :GType
170
164
 
@@ -173,7 +167,7 @@ module Vips
173
167
  # debugging ruby-vips.
174
168
  def self.print_all
175
169
  GC.start
176
- Vips::vips_object_print_all
170
+ Vips.vips_object_print_all
177
171
  end
178
172
 
179
173
  # the layout of the VipsObject struct
@@ -182,15 +176,15 @@ module Vips
182
176
  base.class_eval do
183
177
  # don't actually need most of these
184
178
  layout :parent, GObject::GObject::Struct,
185
- :constructed, :int,
186
- :static_object, :int,
187
- :argument_table, :pointer,
188
- :nickname, :string,
189
- :description, :string,
190
- :preclose, :int,
191
- :close, :int,
192
- :postclose, :int,
193
- :local_memory, :size_t
179
+ :constructed, :int,
180
+ :static_object, :int,
181
+ :argument_table, :pointer,
182
+ :nickname, :string,
183
+ :description, :string,
184
+ :preclose, :int,
185
+ :close, :int,
186
+ :postclose, :int,
187
+ :local_memory, :size_t
194
188
  end
195
189
  end
196
190
  end
@@ -210,8 +204,8 @@ module Vips
210
204
  argument_class = Vips::ArgumentClassPtr.new
211
205
  argument_instance = Vips::ArgumentInstancePtr.new
212
206
 
213
- result = Vips::vips_object_get_argument self, name,
214
- ppspec, argument_class, argument_instance
207
+ result = Vips.vips_object_get_argument self, name,
208
+ ppspec, argument_class, argument_instance
215
209
  return nil if result != 0
216
210
 
217
211
  ppspec[:value]
@@ -229,7 +223,7 @@ module Vips
229
223
  def get_typeof name
230
224
  pspec = get_pspec name
231
225
  unless pspec
232
- Vips::vips_error_clear
226
+ Vips.vips_error_clear
233
227
  return 0
234
228
  end
235
229
 
@@ -240,50 +234,49 @@ module Vips
240
234
  gtype = get_typeof_error name
241
235
  gvalue = GObject::GValue.alloc
242
236
  gvalue.init gtype
243
- GObject::g_object_get_property self, name, gvalue
237
+ GObject.g_object_get_property self, name, gvalue
244
238
  result = gvalue.get
245
239
  gvalue.unset
246
240
 
247
- GLib::logger.debug("Vips::Object.get") { "#{name} == #{result}" }
241
+ GLib.logger.debug("Vips::Object.get") { "#{name} == #{result}" }
248
242
 
249
- return result
243
+ result
250
244
  end
251
245
 
252
246
  def set name, value
253
- GLib::logger.debug("Vips::Object.set") { "#{name} = #{value}" }
247
+ GLib.logger.debug("Vips::Object.set") { "#{name} = #{value}" }
254
248
 
255
249
  gtype = get_typeof_error name
256
250
  gvalue = GObject::GValue.alloc
257
251
  gvalue.init gtype
258
252
  gvalue.set value
259
- GObject::g_object_set_property self, name, gvalue
253
+ GObject.g_object_set_property self, name, gvalue
260
254
  gvalue.unset
261
255
  end
262
256
 
263
- def signal_connect name, handler=nil
257
+ def signal_connect name, handler = nil, &block
264
258
  marshal = MARSHAL_ALL[name.to_sym]
265
- raise Vips::Error, "unsupported signal #{name}" if marshal == nil
259
+ raise Vips::Error, "unsupported signal #{name}" if marshal.nil?
266
260
 
267
- if block_given?
268
- # This will grab any block given to us and make it into a proc
269
- prc = Proc.new
261
+ if block
262
+ # our block as a Proc
263
+ prc = block
270
264
  elsif handler
271
- # We assume the hander is a proc (perhaps we should test)
265
+ # We assume the hander is a Proc (perhaps we should test)
272
266
  prc = handler
273
267
  else
274
268
  raise Vips::Error, "must supply either block or handler"
275
269
  end
276
270
 
277
- # The marshal function will make a closure with the right type signature
271
+ # The marshal function will make a closure with the right type signature
278
272
  # for the selected signal
279
- callback = marshal.(prc)
273
+ callback = marshal.call(prc)
280
274
 
281
275
  # we need to make sure this is not GCd while self is alive
282
276
  @references << callback
283
277
 
284
- GObject::g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
278
+ GObject.g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
285
279
  end
286
-
287
280
  end
288
281
 
289
282
  class ObjectClass < FFI::Struct
@@ -322,10 +315,10 @@ module Vips
322
315
 
323
316
  class ArgumentClass < Argument
324
317
  layout :parent, Argument,
325
- :object_class, ObjectClass.ptr,
326
- :flags, :uint,
327
- :priority, :int,
328
- :offset, :ulong_long
318
+ :object_class, ObjectClass.ptr,
319
+ :flags, :uint,
320
+ :priority, :int,
321
+ :offset, :ulong_long
329
322
  end
330
323
 
331
324
  class ArgumentClassPtr < FFI::Struct
@@ -339,10 +332,10 @@ module Vips
339
332
  # just use :pointer, not VipsObject.ptr, to avoid casting gobject
340
333
  # subclasses
341
334
  attach_function :vips_object_get_argument,
342
- [:pointer, :string,
343
- GObject::GParamSpecPtr.ptr,
344
- ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
345
- :int
335
+ [:pointer, :string,
336
+ GObject::GParamSpecPtr.ptr,
337
+ ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
338
+ :int
346
339
 
347
340
  attach_function :vips_object_print_all, [], :void
348
341