vips 8.8.4 → 8.12.1
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.
- 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
|
|