ruby-vips 2.0.15 → 2.1.2

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 +39 -0
  7. data/Gemfile +3 -1
  8. data/README.md +42 -41
  9. data/Rakefile +13 -21
  10. data/TODO +14 -14
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +6 -6
  13. data/example/connection.rb +26 -0
  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 +35 -36
  22. data/example/progress.rb +30 -0
  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 +191 -79
  29. data/lib/vips/blend_mode.rb +29 -25
  30. data/lib/vips/connection.rb +46 -0
  31. data/lib/vips/gobject.rb +27 -12
  32. data/lib/vips/gvalue.rb +62 -50
  33. data/lib/vips/image.rb +475 -256
  34. data/lib/vips/interpolate.rb +3 -2
  35. data/lib/vips/methods.rb +788 -121
  36. data/lib/vips/mutableimage.rb +173 -0
  37. data/lib/vips/object.rb +171 -54
  38. data/lib/vips/operation.rb +272 -117
  39. data/lib/vips/region.rb +73 -0
  40. data/lib/vips/source.rb +88 -0
  41. data/lib/vips/sourcecustom.rb +89 -0
  42. data/lib/vips/target.rb +86 -0
  43. data/lib/vips/targetcustom.rb +77 -0
  44. data/lib/vips/version.rb +1 -1
  45. data/ruby-vips.gemspec +26 -20
  46. metadata +39 -50
  47. data/.rubocop.yml +0 -22
  48. data/.rubocop_todo.yml +0 -515
  49. data/.travis.yml +0 -62
  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,40 +21,131 @@ 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
41
41
 
42
42
  private
43
43
 
44
+ class Progress < FFI::Struct
45
+ layout :im, :pointer,
46
+ :run, :int,
47
+ :eta, :int,
48
+ :tpels, :int64_t,
49
+ :npels, :int64_t,
50
+ :percent, :int,
51
+ :start, :pointer
52
+ end
53
+
54
+ # Our signal marshalers.
55
+ #
56
+ # These are functions which take the handler as a param and return a
57
+ # closure with the right FFI signature for g_signal_connect for this
58
+ # specific signal.
59
+ #
60
+ # ruby-ffi makes it hard to use the g_signal_connect user data param
61
+ # to pass the function pointer through, unfortunately.
62
+ #
63
+ # We can't throw exceptions across C, so we must catch everything.
64
+
65
+ MARSHAL_PROGRESS = proc do |handler|
66
+ FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do |vi, prog, cb|
67
+ begin
68
+ handler.call(Progress.new(prog))
69
+ rescue Exception => e
70
+ puts "progress: #{e}"
71
+ end
72
+ end
73
+ end
74
+
75
+ MARSHAL_READ = proc do |handler|
76
+ FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
77
+ begin
78
+ result = handler.call(p, len)
79
+ rescue Exception => e
80
+ puts "read: #{e}"
81
+ result = 0
82
+ end
83
+
84
+ result
85
+ end
86
+ end
87
+
88
+ MARSHAL_SEEK = proc do |handler|
89
+ FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
90
+ begin
91
+ result = handler.call(off, whence)
92
+ rescue Exception => e
93
+ puts "seek: #{e}"
94
+ result = -1
95
+ end
96
+
97
+ result
98
+ end
99
+ end
100
+
101
+ MARSHAL_WRITE = proc do |handler|
102
+ FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
103
+ begin
104
+ result = handler.call(p, len)
105
+ rescue Exception => e
106
+ puts "write: #{e}"
107
+ result = 0
108
+ end
109
+
110
+ result
111
+ end
112
+ end
113
+
114
+ MARSHAL_FINISH = proc do |handler|
115
+ FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
116
+ begin
117
+ handler.call
118
+ rescue Exception => e
119
+ puts "finish: #{e}"
120
+ end
121
+ end
122
+ end
123
+
124
+ # map signal name to marshal proc
125
+ 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
133
+ }
134
+
44
135
  attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
45
136
  attach_function :vips_enum_nick, [:GType, :int], :string
46
137
 
47
138
  attach_function :vips_value_set_ref_string,
48
- [GObject::GValue.ptr, :string], :void
139
+ [GObject::GValue.ptr, :string], :void
49
140
  attach_function :vips_value_set_array_double,
50
- [GObject::GValue.ptr, :pointer, :int], :void
141
+ [GObject::GValue.ptr, :pointer, :int], :void
51
142
  attach_function :vips_value_set_array_int,
52
- [GObject::GValue.ptr, :pointer, :int], :void
143
+ [GObject::GValue.ptr, :pointer, :int], :void
53
144
  attach_function :vips_value_set_array_image,
54
- [GObject::GValue.ptr, :int], :void
145
+ [GObject::GValue.ptr, :int], :void
55
146
  callback :free_fn, [:pointer], :void
56
147
  attach_function :vips_value_set_blob,
57
- [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
148
+ [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
58
149
 
59
150
  class SizeStruct < FFI::Struct
60
151
  layout :value, :size_t
@@ -65,15 +156,15 @@ module Vips
65
156
  end
66
157
 
67
158
  attach_function :vips_value_get_ref_string,
68
- [GObject::GValue.ptr, SizeStruct.ptr], :string
159
+ [GObject::GValue.ptr, SizeStruct.ptr], :string
69
160
  attach_function :vips_value_get_array_double,
70
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
161
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
71
162
  attach_function :vips_value_get_array_int,
72
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
163
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
73
164
  attach_function :vips_value_get_array_image,
74
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
165
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
75
166
  attach_function :vips_value_get_blob,
76
- [GObject::GValue.ptr, SizeStruct.ptr], :pointer
167
+ [GObject::GValue.ptr, SizeStruct.ptr], :pointer
77
168
 
78
169
  attach_function :type_find, :vips_type_find, [:string, :string], :GType
79
170
 
@@ -82,7 +173,7 @@ module Vips
82
173
  # debugging ruby-vips.
83
174
  def self.print_all
84
175
  GC.start
85
- Vips::vips_object_print_all
176
+ Vips.vips_object_print_all
86
177
  end
87
178
 
88
179
  # the layout of the VipsObject struct
@@ -91,15 +182,15 @@ module Vips
91
182
  base.class_eval do
92
183
  # don't actually need most of these
93
184
  layout :parent, GObject::GObject::Struct,
94
- :constructed, :int,
95
- :static_object, :int,
96
- :argument_table, :pointer,
97
- :nickname, :string,
98
- :description, :string,
99
- :preclose, :int,
100
- :close, :int,
101
- :postclose, :int,
102
- :local_memory, :size_t
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
103
194
  end
104
195
  end
105
196
  end
@@ -115,15 +206,15 @@ module Vips
115
206
  # return a pspec, or nil ... nil wil leave a message in the error log
116
207
  # which you must clear
117
208
  def get_pspec name
118
- pspec = GObject::GParamSpecPtr.new
209
+ ppspec = GObject::GParamSpecPtr.new
119
210
  argument_class = Vips::ArgumentClassPtr.new
120
211
  argument_instance = Vips::ArgumentInstancePtr.new
121
212
 
122
- result = Vips::vips_object_get_argument self, name,
123
- pspec, argument_class, argument_instance
213
+ result = Vips.vips_object_get_argument self, name,
214
+ ppspec, argument_class, argument_instance
124
215
  return nil if result != 0
125
216
 
126
- pspec
217
+ ppspec[:value]
127
218
  end
128
219
 
129
220
  # return a gtype, raise an error on not found
@@ -131,40 +222,66 @@ module Vips
131
222
  pspec = get_pspec name
132
223
  raise Vips::Error unless pspec
133
224
 
134
- pspec[:value][:value_type]
225
+ pspec[:value_type]
135
226
  end
136
227
 
137
228
  # return a gtype, 0 on not found
138
229
  def get_typeof name
139
230
  pspec = get_pspec name
140
231
  unless pspec
141
- Vips::vips_error_clear
232
+ Vips.vips_error_clear
142
233
  return 0
143
234
  end
144
235
 
145
- pspec[:value][:value_type]
236
+ pspec[:value_type]
146
237
  end
147
238
 
148
239
  def get name
149
240
  gtype = get_typeof_error name
150
241
  gvalue = GObject::GValue.alloc
151
242
  gvalue.init gtype
152
- GObject::g_object_get_property self, name, gvalue
243
+ GObject.g_object_get_property self, name, gvalue
153
244
  result = gvalue.get
245
+ gvalue.unset
154
246
 
155
- GLib::logger.debug("Vips::Object.get") { "#{name} == #{result}" }
247
+ GLib.logger.debug("Vips::Object.get") { "#{name} == #{result}" }
156
248
 
157
- return result
249
+ result
158
250
  end
159
251
 
160
252
  def set name, value
161
- GLib::logger.debug("Vips::Object.set") { "#{name} = #{value}" }
253
+ GLib.logger.debug("Vips::Object.set") { "#{name} = #{value}" }
162
254
 
163
255
  gtype = get_typeof_error name
164
256
  gvalue = GObject::GValue.alloc
165
257
  gvalue.init gtype
166
258
  gvalue.set value
167
- GObject::g_object_set_property self, name, gvalue
259
+ GObject.g_object_set_property self, name, gvalue
260
+ gvalue.unset
261
+ end
262
+
263
+ def signal_connect name, handler = nil, &block
264
+ marshal = MARSHAL_ALL[name.to_sym]
265
+ raise Vips::Error, "unsupported signal #{name}" if marshal.nil?
266
+
267
+ if block
268
+ # our block as a Proc
269
+ prc = block
270
+ elsif handler
271
+ # We assume the hander is a Proc (perhaps we should test)
272
+ prc = handler
273
+ else
274
+ raise Vips::Error, "must supply either block or handler"
275
+ end
276
+
277
+ # The marshal function will make a closure with the right type signature
278
+ # for the selected signal
279
+ callback = marshal.call(prc)
280
+
281
+ # we need to make sure this is not GCd while self is alive
282
+ @references << callback
283
+
284
+ GObject.g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
168
285
  end
169
286
  end
170
287
 
@@ -204,10 +321,10 @@ module Vips
204
321
 
205
322
  class ArgumentClass < Argument
206
323
  layout :parent, Argument,
207
- :object_class, ObjectClass.ptr,
208
- :flags, :uint,
209
- :priority, :int,
210
- :offset, :ulong_long
324
+ :object_class, ObjectClass.ptr,
325
+ :flags, :uint,
326
+ :priority, :int,
327
+ :offset, :ulong_long
211
328
  end
212
329
 
213
330
  class ArgumentClassPtr < FFI::Struct
@@ -221,10 +338,10 @@ module Vips
221
338
  # just use :pointer, not VipsObject.ptr, to avoid casting gobject
222
339
  # subclasses
223
340
  attach_function :vips_object_get_argument,
224
- [:pointer, :string,
225
- GObject::GParamSpecPtr.ptr,
226
- ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
227
- :int
341
+ [:pointer, :string,
342
+ GObject::GParamSpecPtr.ptr,
343
+ ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
344
+ :int
228
345
 
229
346
  attach_function :vips_object_print_all, [], :void
230
347