vips 8.8.4 → 8.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/workflows/development.yml +54 -0
- data/.standard.yml +17 -0
- data/.travis.yml +1 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +50 -0
- data/Gemfile +8 -1
- data/README.md +31 -15
- data/Rakefile +23 -18
- data/TODO +43 -0
- data/example/annotate.rb +6 -6
- data/example/connection.rb +26 -0
- data/example/daltonize8.rb +6 -6
- data/example/draw_lines.rb +30 -0
- data/example/example1.rb +4 -4
- data/example/example2.rb +6 -6
- data/example/example3.rb +5 -5
- data/example/example4.rb +2 -2
- data/example/example5.rb +4 -4
- data/example/inheritance_with_refcount.rb +46 -39
- data/example/progress.rb +30 -0
- data/example/thumb.rb +6 -6
- data/example/trim8.rb +1 -1
- data/example/watermark.rb +2 -2
- data/example/wobble.rb +1 -1
- data/lib/vips/blend_mode.rb +29 -25
- data/lib/vips/connection.rb +46 -0
- data/lib/vips/gobject.rb +27 -12
- data/lib/vips/gvalue.rb +62 -50
- data/lib/vips/image.rb +548 -287
- data/lib/vips/interpolate.rb +3 -2
- data/lib/vips/methods.rb +2877 -2319
- data/lib/vips/mutableimage.rb +173 -0
- data/lib/vips/object.rb +159 -54
- data/lib/vips/operation.rb +286 -117
- data/lib/vips/region.rb +73 -0
- data/lib/vips/source.rb +88 -0
- data/lib/vips/sourcecustom.rb +89 -0
- data/lib/vips/target.rb +86 -0
- data/lib/vips/targetcustom.rb +77 -0
- data/lib/vips/version.rb +1 -1
- data/lib/vips.rb +199 -80
- data/vips.gemspec +3 -3
- metadata +36 -22
@@ -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
|
7
|
+
require "ffi"
|
8
8
|
|
9
9
|
module Vips
|
10
10
|
private
|
@@ -21,40 +21,125 @@ module Vips
|
|
21
21
|
public
|
22
22
|
|
23
23
|
# some handy gtypes
|
24
|
-
IMAGE_TYPE = GObject
|
25
|
-
ARRAY_INT_TYPE = GObject
|
26
|
-
ARRAY_DOUBLE_TYPE = GObject
|
27
|
-
ARRAY_IMAGE_TYPE = GObject
|
28
|
-
REFSTR_TYPE = GObject
|
29
|
-
BLOB_TYPE = GObject
|
30
|
-
|
31
|
-
BAND_FORMAT_TYPE = Vips
|
32
|
-
INTERPRETATION_TYPE = Vips
|
33
|
-
CODING_TYPE = Vips
|
34
|
-
|
35
|
-
if Vips
|
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
|
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
|
+
# this can't throw an exception, so no catch is necessary
|
68
|
+
handler.call(Progress.new(prog))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
MARSHAL_READ = proc do |handler|
|
73
|
+
FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
|
74
|
+
begin
|
75
|
+
result = handler.call(p, len)
|
76
|
+
rescue Exception => e
|
77
|
+
puts "read: #{e}"
|
78
|
+
result = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
MARSHAL_SEEK = proc do |handler|
|
86
|
+
FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
|
87
|
+
begin
|
88
|
+
result = handler.call(off, whence)
|
89
|
+
rescue Exception => e
|
90
|
+
puts "seek: #{e}"
|
91
|
+
result = -1
|
92
|
+
end
|
93
|
+
|
94
|
+
result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
MARSHAL_WRITE = proc do |handler|
|
99
|
+
FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
|
100
|
+
begin
|
101
|
+
result = handler.call(p, len)
|
102
|
+
rescue Exception => e
|
103
|
+
puts "write: #{e}"
|
104
|
+
result = 0
|
105
|
+
end
|
106
|
+
|
107
|
+
result
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
MARSHAL_FINISH = proc do |handler|
|
112
|
+
FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
|
113
|
+
# this can't throw an exception, so no catch is necessary
|
114
|
+
handler.call
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# map signal name to marshal proc
|
119
|
+
MARSHAL_ALL = {
|
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
|
127
|
+
}
|
128
|
+
|
44
129
|
attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
|
45
130
|
attach_function :vips_enum_nick, [:GType, :int], :string
|
46
131
|
|
47
132
|
attach_function :vips_value_set_ref_string,
|
48
|
-
|
133
|
+
[GObject::GValue.ptr, :string], :void
|
49
134
|
attach_function :vips_value_set_array_double,
|
50
|
-
|
135
|
+
[GObject::GValue.ptr, :pointer, :int], :void
|
51
136
|
attach_function :vips_value_set_array_int,
|
52
|
-
|
137
|
+
[GObject::GValue.ptr, :pointer, :int], :void
|
53
138
|
attach_function :vips_value_set_array_image,
|
54
|
-
|
139
|
+
[GObject::GValue.ptr, :int], :void
|
55
140
|
callback :free_fn, [:pointer], :void
|
56
141
|
attach_function :vips_value_set_blob,
|
57
|
-
|
142
|
+
[GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
|
58
143
|
|
59
144
|
class SizeStruct < FFI::Struct
|
60
145
|
layout :value, :size_t
|
@@ -65,15 +150,15 @@ module Vips
|
|
65
150
|
end
|
66
151
|
|
67
152
|
attach_function :vips_value_get_ref_string,
|
68
|
-
|
153
|
+
[GObject::GValue.ptr, SizeStruct.ptr], :string
|
69
154
|
attach_function :vips_value_get_array_double,
|
70
|
-
|
155
|
+
[GObject::GValue.ptr, IntStruct.ptr], :pointer
|
71
156
|
attach_function :vips_value_get_array_int,
|
72
|
-
|
157
|
+
[GObject::GValue.ptr, IntStruct.ptr], :pointer
|
73
158
|
attach_function :vips_value_get_array_image,
|
74
|
-
|
159
|
+
[GObject::GValue.ptr, IntStruct.ptr], :pointer
|
75
160
|
attach_function :vips_value_get_blob,
|
76
|
-
|
161
|
+
[GObject::GValue.ptr, SizeStruct.ptr], :pointer
|
77
162
|
|
78
163
|
attach_function :type_find, :vips_type_find, [:string, :string], :GType
|
79
164
|
|
@@ -82,7 +167,7 @@ module Vips
|
|
82
167
|
# debugging ruby-vips.
|
83
168
|
def self.print_all
|
84
169
|
GC.start
|
85
|
-
Vips
|
170
|
+
Vips.vips_object_print_all
|
86
171
|
end
|
87
172
|
|
88
173
|
# the layout of the VipsObject struct
|
@@ -91,15 +176,15 @@ module Vips
|
|
91
176
|
base.class_eval do
|
92
177
|
# don't actually need most of these
|
93
178
|
layout :parent, GObject::GObject::Struct,
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
103
188
|
end
|
104
189
|
end
|
105
190
|
end
|
@@ -115,15 +200,15 @@ module Vips
|
|
115
200
|
# return a pspec, or nil ... nil wil leave a message in the error log
|
116
201
|
# which you must clear
|
117
202
|
def get_pspec name
|
118
|
-
|
203
|
+
ppspec = GObject::GParamSpecPtr.new
|
119
204
|
argument_class = Vips::ArgumentClassPtr.new
|
120
205
|
argument_instance = Vips::ArgumentInstancePtr.new
|
121
206
|
|
122
|
-
result = Vips
|
123
|
-
|
207
|
+
result = Vips.vips_object_get_argument self, name,
|
208
|
+
ppspec, argument_class, argument_instance
|
124
209
|
return nil if result != 0
|
125
210
|
|
126
|
-
|
211
|
+
ppspec[:value]
|
127
212
|
end
|
128
213
|
|
129
214
|
# return a gtype, raise an error on not found
|
@@ -131,40 +216,60 @@ module Vips
|
|
131
216
|
pspec = get_pspec name
|
132
217
|
raise Vips::Error unless pspec
|
133
218
|
|
134
|
-
pspec[:
|
219
|
+
pspec[:value_type]
|
135
220
|
end
|
136
221
|
|
137
222
|
# return a gtype, 0 on not found
|
138
223
|
def get_typeof name
|
139
224
|
pspec = get_pspec name
|
140
225
|
unless pspec
|
141
|
-
Vips
|
226
|
+
Vips.vips_error_clear
|
142
227
|
return 0
|
143
228
|
end
|
144
229
|
|
145
|
-
pspec[:
|
230
|
+
pspec[:value_type]
|
146
231
|
end
|
147
232
|
|
148
233
|
def get name
|
149
234
|
gtype = get_typeof_error name
|
150
235
|
gvalue = GObject::GValue.alloc
|
151
236
|
gvalue.init gtype
|
152
|
-
GObject
|
237
|
+
GObject.g_object_get_property self, name, gvalue
|
153
238
|
result = gvalue.get
|
239
|
+
gvalue.unset
|
154
240
|
|
155
|
-
GLib
|
241
|
+
GLib.logger.debug("Vips::Object.get") { "#{name} == #{result}" }
|
156
242
|
|
157
|
-
|
243
|
+
result
|
158
244
|
end
|
159
245
|
|
160
246
|
def set name, value
|
161
|
-
GLib
|
247
|
+
GLib.logger.debug("Vips::Object.set") { "#{name} = #{value}" }
|
162
248
|
|
163
249
|
gtype = get_typeof_error name
|
164
250
|
gvalue = GObject::GValue.alloc
|
165
251
|
gvalue.init gtype
|
166
252
|
gvalue.set value
|
167
|
-
GObject
|
253
|
+
GObject.g_object_set_property self, name, gvalue
|
254
|
+
gvalue.unset
|
255
|
+
end
|
256
|
+
|
257
|
+
def signal_connect name, handler = nil, &block
|
258
|
+
marshal = MARSHAL_ALL[name.to_sym]
|
259
|
+
raise Vips::Error, "unsupported signal #{name}" if marshal.nil?
|
260
|
+
|
261
|
+
unless handler ||= block
|
262
|
+
raise Vips::Error, "must supply either block or handler"
|
263
|
+
end
|
264
|
+
|
265
|
+
# The marshal function will make a closure with the right type signature
|
266
|
+
# for the selected signal
|
267
|
+
callback = marshal.(handler)
|
268
|
+
|
269
|
+
# we need to make sure this is not GCd while self is alive
|
270
|
+
@references << callback
|
271
|
+
|
272
|
+
GObject.g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
|
168
273
|
end
|
169
274
|
end
|
170
275
|
|
@@ -204,10 +309,10 @@ module Vips
|
|
204
309
|
|
205
310
|
class ArgumentClass < Argument
|
206
311
|
layout :parent, Argument,
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
312
|
+
:object_class, ObjectClass.ptr,
|
313
|
+
:flags, :uint,
|
314
|
+
:priority, :int,
|
315
|
+
:offset, :ulong_long
|
211
316
|
end
|
212
317
|
|
213
318
|
class ArgumentClassPtr < FFI::Struct
|
@@ -221,10 +326,10 @@ module Vips
|
|
221
326
|
# just use :pointer, not VipsObject.ptr, to avoid casting gobject
|
222
327
|
# subclasses
|
223
328
|
attach_function :vips_object_get_argument,
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
329
|
+
[:pointer, :string,
|
330
|
+
GObject::GParamSpecPtr.ptr,
|
331
|
+
ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
|
332
|
+
:int
|
228
333
|
|
229
334
|
attach_function :vips_object_set_from_string, [:pointer, :string], :int
|
230
335
|
|