ruby-vips 2.0.13 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) 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 +44 -0
  7. data/Gemfile +3 -1
  8. data/README.md +45 -47
  9. data/Rakefile +13 -15
  10. data/TODO +19 -10
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +7 -7
  13. data/example/connection.rb +26 -0
  14. data/example/daltonize8.rb +27 -29
  15. data/example/draw_lines.rb +30 -0
  16. data/example/example1.rb +5 -6
  17. data/example/example2.rb +11 -11
  18. data/example/example3.rb +9 -9
  19. data/example/example4.rb +8 -8
  20. data/example/example5.rb +8 -9
  21. data/example/inheritance_with_refcount.rb +203 -221
  22. data/example/progress.rb +30 -0
  23. data/example/thumb.rb +12 -14
  24. data/example/trim8.rb +7 -7
  25. data/example/watermark.rb +15 -36
  26. data/example/wobble.rb +25 -25
  27. data/lib/ruby-vips.rb +1 -1
  28. data/lib/vips.rb +473 -338
  29. data/lib/vips/access.rb +9 -9
  30. data/lib/vips/align.rb +7 -8
  31. data/lib/vips/angle.rb +8 -9
  32. data/lib/vips/angle45.rb +12 -13
  33. data/lib/vips/bandformat.rb +16 -18
  34. data/lib/vips/blend_mode.rb +36 -0
  35. data/lib/vips/coding.rb +11 -12
  36. data/lib/vips/compass_direction.rb +13 -14
  37. data/lib/vips/connection.rb +46 -0
  38. data/lib/vips/direction.rb +7 -8
  39. data/lib/vips/extend.rb +13 -14
  40. data/lib/vips/gobject.rb +111 -100
  41. data/lib/vips/gvalue.rb +243 -237
  42. data/lib/vips/image.rb +1501 -1338
  43. data/lib/vips/interesting.rb +10 -11
  44. data/lib/vips/interpolate.rb +50 -54
  45. data/lib/vips/interpretation.rb +25 -26
  46. data/lib/vips/kernel.rb +18 -19
  47. data/lib/vips/methods.rb +929 -309
  48. data/lib/vips/mutableimage.rb +154 -0
  49. data/lib/vips/object.rb +318 -208
  50. data/lib/vips/operation.rb +467 -320
  51. data/lib/vips/operationboolean.rb +10 -11
  52. data/lib/vips/operationcomplex.rb +8 -9
  53. data/lib/vips/operationcomplex2.rb +6 -7
  54. data/lib/vips/operationcomplexget.rb +7 -8
  55. data/lib/vips/operationmath.rb +14 -15
  56. data/lib/vips/operationmath2.rb +6 -7
  57. data/lib/vips/operationrelational.rb +11 -12
  58. data/lib/vips/operationround.rb +7 -8
  59. data/lib/vips/region.rb +73 -0
  60. data/lib/vips/size.rb +9 -10
  61. data/lib/vips/source.rb +88 -0
  62. data/lib/vips/sourcecustom.rb +89 -0
  63. data/lib/vips/target.rb +86 -0
  64. data/lib/vips/targetcustom.rb +77 -0
  65. data/lib/vips/version.rb +1 -2
  66. data/ruby-vips.gemspec +29 -20
  67. metadata +51 -40
  68. data/.travis.yml +0 -55
  69. data/install-vips.sh +0 -26
data/lib/vips/gvalue.rb CHANGED
@@ -3,279 +3,285 @@
3
3
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
4
4
  # License:: MIT
5
5
 
6
- require 'ffi'
6
+ require "ffi"
7
7
 
8
8
  module GObject
9
-
10
- # Represent a GValue. Example use:
11
- #
12
- # ```ruby
13
- # gvalue = GValue::alloc
14
- # gvalue.init GObject::GDOUBLE_TYPE
15
- # gvalue.set 3.1415
16
- # value = gvalue.get
17
- # ```
18
- #
19
- # Lifetime is managed automatically. It doesn't know about all GType values,
20
- # but it does know the ones that libvips uses.
21
-
22
- class GValue < FFI::ManagedStruct
23
- layout :gtype, :GType,
24
- :data, [:ulong_long, 2]
25
-
26
- # convert an enum value (str/symb/int) into an int ready for libvips
27
- def self.from_nick(gtype, value)
28
- value = value.to_s if value.is_a? Symbol
29
-
30
- if value.is_a? String
31
- value = Vips::vips_enum_from_nick "ruby-vips", gtype, value
32
- if value == -1
33
- raise Vips::Error
34
- end
35
- end
36
-
37
- value
9
+ # Represent a GValue. Example use:
10
+ #
11
+ # ```ruby
12
+ # gvalue = GValue::alloc
13
+ # gvalue.init GObject::GDOUBLE_TYPE
14
+ # gvalue.set 3.1415
15
+ # value = gvalue.get
16
+ # # optional -- drop any ref the gvalue had
17
+ # gvalue.unset
18
+ # ```
19
+ #
20
+ # Lifetime is managed automatically. It doesn't know about all GType values,
21
+ # but it does know the ones that libvips uses.
22
+
23
+ class GValue < FFI::ManagedStruct
24
+ layout :gtype, :GType,
25
+ :data, [:ulong_long, 2]
26
+
27
+ # convert an enum value (str/symb/int) into an int ready for libvips
28
+ def self.from_nick(gtype, value)
29
+ value = value.to_s if value.is_a? Symbol
30
+
31
+ if value.is_a? String
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("_", "-")
35
+ if value == -1
36
+ raise Vips::Error
38
37
  end
38
+ end
39
39
 
40
- # convert an int enum back into a symbol
41
- def self.to_nick(gtype, enum_value)
42
- enum_name = Vips::vips_enum_nick gtype, enum_value
43
- if enum_name == nil
44
- raise Vips::Error
45
- end
40
+ value
41
+ end
46
42
 
47
- enum_name.to_sym
48
- end
43
+ # convert an int enum back into a symbol
44
+ def self.to_nick(gtype, enum_value)
45
+ enum_name = Vips.vips_enum_nick gtype, enum_value
46
+ if enum_name.nil?
47
+ raise Vips::Error
48
+ end
49
49
 
50
- def self.release ptr
51
- # GLib::logger.debug("GObject::GValue::release") {"ptr = #{ptr}"}
52
- ::GObject::g_value_unset ptr
53
- end
50
+ enum_name.to_sym
51
+ end
54
52
 
55
- # Allocate memory for a GValue and return a class wrapper. Memory will
56
- # be freed automatically when it goes out of scope. The GValue is inited
57
- # to 0, use {GValue.init} to set a type.
58
- #
59
- # @return [GValue] a new gvalue set to 0
60
- def self.alloc
61
- # allocate memory
62
- memory = FFI::MemoryPointer.new GValue
63
-
64
- # make this alloc autorelease ... we mustn't release in
65
- # GValue::release, since we are used to wrap GValue pointers
66
- # made by other people
67
- pointer = FFI::Pointer.new GValue, memory
68
-
69
- # ... and wrap in a GValue
70
- return GValue.new pointer
71
- end
53
+ def self.release ptr
54
+ # GLib::logger.debug("GObject::GValue::release") {"ptr = #{ptr}"}
55
+ ::GObject.g_value_unset ptr
56
+ end
72
57
 
73
- # Set the type of thing a gvalue can hold.
74
- #
75
- # @param gtype [GType] the type of thing this GValue can hold.
76
- def init gtype
77
- ::GObject::g_value_init self, gtype
78
- end
58
+ # Allocate memory for a GValue and return a class wrapper. Memory will
59
+ # be freed automatically when it goes out of scope. The GValue is inited
60
+ # to 0, use {GValue.init} to set a type.
61
+ #
62
+ # @return [GValue] a new gvalue set to 0
63
+ def self.alloc
64
+ # allocate memory
65
+ memory = FFI::MemoryPointer.new GValue
66
+
67
+ # make this alloc autorelease ... we mustn't release in
68
+ # GValue::release, since we are used to wrap GValue pointers
69
+ # made by other people
70
+ pointer = FFI::Pointer.new GValue, memory
71
+
72
+ # ... and wrap in a GValue
73
+ GValue.new pointer
74
+ end
79
75
 
80
- # Set the value of a GValue. The value is converted to the type of the
81
- # GValue, if possible.
82
- #
83
- # @param value [Any] The value to set
84
- def set value
85
- # GLib::logger.debug("GObject::GValue.set") {
86
- # "value = #{value.inspect[0..50]}"
87
- # }
76
+ # Set the type of thing a gvalue can hold.
77
+ #
78
+ # @param gtype [GType] the type of thing this GValue can hold.
79
+ def init gtype
80
+ ::GObject.g_value_init self, gtype
81
+ end
88
82
 
89
- gtype = self[:gtype]
90
- fundamental = ::GObject::g_type_fundamental gtype
83
+ # Set the value of a GValue. The value is converted to the type of the
84
+ # GValue, if possible.
85
+ #
86
+ # @param value [Any] The value to set
87
+ def set value
88
+ # GLib::logger.debug("GObject::GValue.set") {
89
+ # "value = #{value.inspect[0..50]}"
90
+ # }
91
91
 
92
- case gtype
93
- when GBOOL_TYPE
94
- ::GObject::g_value_set_boolean self, (value ? 1 : 0)
92
+ gtype = self[:gtype]
93
+ fundamental = ::GObject.g_type_fundamental gtype
95
94
 
96
- when GINT_TYPE
97
- ::GObject::g_value_set_int self, value
95
+ case gtype
96
+ when GBOOL_TYPE
97
+ ::GObject.g_value_set_boolean self, (value ? 1 : 0)
98
98
 
99
- when GUINT64_TYPE
100
- ::GObject::g_value_set_uint64 self, value
99
+ when GINT_TYPE
100
+ ::GObject.g_value_set_int self, value
101
101
 
102
- when GDOUBLE_TYPE
103
- ::GObject::g_value_set_double self, value
102
+ when GUINT64_TYPE
103
+ ::GObject.g_value_set_uint64 self, value
104
104
 
105
- when GSTR_TYPE
106
- ::GObject::g_value_set_string self, value
105
+ when GDOUBLE_TYPE
106
+ ::GObject.g_value_set_double self, value
107
107
 
108
- when Vips::REFSTR_TYPE
109
- ::Vips::vips_value_set_ref_string self, value
108
+ when GSTR_TYPE
109
+ ::GObject.g_value_set_string self, value
110
110
 
111
- when Vips::ARRAY_INT_TYPE
112
- value = [value] unless value.is_a? Array
111
+ when Vips::REFSTR_TYPE
112
+ ::Vips.vips_value_set_ref_string self, value
113
113
 
114
- Vips::vips_value_set_array_int self, nil, value.length
115
- ptr = Vips::vips_value_get_array_int self, nil
116
- ptr.write_array_of_int32 value
114
+ when Vips::ARRAY_INT_TYPE
115
+ value = [value] unless value.is_a? Array
117
116
 
118
- when Vips::ARRAY_DOUBLE_TYPE
119
- value = [value] unless value.is_a? Array
117
+ Vips.vips_value_set_array_int self, nil, value.length
118
+ ptr = Vips.vips_value_get_array_int self, nil
119
+ ptr.write_array_of_int32 value
120
120
 
121
- # this will allocate an array in the gvalue
122
- Vips::vips_value_set_array_double self, nil, value.length
121
+ when Vips::ARRAY_DOUBLE_TYPE
122
+ value = [value] unless value.is_a? Array
123
123
 
124
- # pull the array out and fill it
125
- ptr = Vips::vips_value_get_array_double self, nil
124
+ # this will allocate an array in the gvalue
125
+ Vips.vips_value_set_array_double self, nil, value.length
126
126
 
127
- ptr.write_array_of_double value
127
+ # pull the array out and fill it
128
+ ptr = Vips.vips_value_get_array_double self, nil
128
129
 
129
- when Vips::ARRAY_IMAGE_TYPE
130
- value = [value] unless value.is_a? Array
130
+ ptr.write_array_of_double value
131
131
 
132
- Vips::vips_value_set_array_image self, value.length
133
- ptr = Vips::vips_value_get_array_image self, nil
134
- ptr.write_array_of_pointer value
132
+ when Vips::ARRAY_IMAGE_TYPE
133
+ value = [value] unless value.is_a? Array
135
134
 
136
- # the gvalue needs a ref on each of the images
137
- value.each {|image| ::GObject::g_object_ref image}
135
+ Vips.vips_value_set_array_image self, value.length
136
+ ptr = Vips.vips_value_get_array_image self, nil
137
+ ptr.write_array_of_pointer value
138
138
 
139
- when Vips::BLOB_TYPE
140
- len = value.bytesize
141
- ptr = GLib::g_malloc len
142
- Vips::vips_value_set_blob self, GLib::G_FREE, ptr, len
143
- ptr.write_bytes value
139
+ # the gvalue needs a ref on each of the images
140
+ value.each { |image| ::GObject.g_object_ref image }
144
141
 
145
- else
146
- case fundamental
147
- when GFLAGS_TYPE
148
- ::GObject::g_value_set_flags self, value
142
+ when Vips::BLOB_TYPE
143
+ len = value.bytesize
144
+ ptr = GLib.g_malloc len
145
+ Vips.vips_value_set_blob self, GLib::G_FREE, ptr, len
146
+ ptr.write_bytes value
149
147
 
150
- when GENUM_TYPE
151
- enum_value = GValue.from_nick(self[:gtype], value)
152
- ::GObject::g_value_set_enum self, enum_value
148
+ else
149
+ case fundamental
150
+ when GFLAGS_TYPE
151
+ ::GObject.g_value_set_flags self, value
153
152
 
154
- when GOBJECT_TYPE
155
- ::GObject::g_value_set_object self, value
153
+ when GENUM_TYPE
154
+ enum_value = GValue.from_nick(self[:gtype], value)
155
+ ::GObject.g_value_set_enum self, enum_value
156
156
 
157
- else
158
- raise Vips::Error, "unimplemented gtype for set: " +
159
- "#{::GObject::g_type_name gtype} (#{gtype})"
157
+ when GOBJECT_TYPE
158
+ ::GObject.g_value_set_object self, value
160
159
 
161
- end
162
- end
160
+ else
161
+ raise Vips::Error, "unimplemented gtype for set: " \
162
+ "#{::GObject.g_type_name gtype} (#{gtype})"
163
163
  end
164
+ end
165
+ end
164
166
 
165
- # Get the value of a GValue. The value is converted to a Ruby type in
166
- # the obvious way.
167
- #
168
- # @return [Any] the value held by the GValue
169
- def get
170
- gtype = self[:gtype]
171
- fundamental = ::GObject::g_type_fundamental gtype
172
- result = nil
173
-
174
- case gtype
175
- when GBOOL_TYPE
176
- result = ::GObject::g_value_get_boolean(self) != 0 ? true : false
177
-
178
- when GINT_TYPE
179
- result = ::GObject::g_value_get_int self
180
-
181
- when GUINT64_TYPE
182
- result = ::GObject::g_value_get_uint64 self
183
-
184
- when GDOUBLE_TYPE
185
- result = ::GObject::g_value_get_double self
186
-
187
- when GSTR_TYPE
188
- result = ::GObject::g_value_get_string self
189
-
190
- when Vips::REFSTR_TYPE
191
- len = Vips::SizeStruct.new
192
- result = ::Vips::vips_value_get_ref_string self, len
193
-
194
- when Vips::ARRAY_INT_TYPE
195
- len = Vips::IntStruct.new
196
- array = Vips::vips_value_get_array_int self, len
197
- result = array.get_array_of_int32 0, len[:value]
198
-
199
- when Vips::ARRAY_DOUBLE_TYPE
200
- len = Vips::IntStruct.new
201
- array = Vips::vips_value_get_array_double self, len
202
- result = array.get_array_of_double 0, len[:value]
203
-
204
- when Vips::ARRAY_IMAGE_TYPE
205
- len = Vips::IntStruct.new
206
- array = Vips::vips_value_get_array_image self, len
207
- result = array.get_array_of_pointer 0, len[:value]
208
- result.map! do |pointer|
209
- ::GObject::g_object_ref pointer
210
- Vips::Image.new pointer
211
- end
212
-
213
- when Vips::BLOB_TYPE
214
- len = Vips::SizeStruct.new
215
- array = Vips::vips_value_get_blob self, len
216
- result = array.get_bytes 0, len[:value]
217
-
218
- else
219
- case fundamental
220
- when GFLAGS_TYPE
221
- result = ::GObject::g_value_get_flags self
222
-
223
- when GENUM_TYPE
224
- enum_value = ::GObject::g_value_get_enum(self)
225
- result = GValue.to_nick self[:gtype], enum_value
226
-
227
- when GOBJECT_TYPE
228
- obj = ::GObject::g_value_get_object self
229
- # g_value_get_object() does not add a ref ... we need to add
230
- # one to match the unref in gobject release
231
- ::GObject::g_object_ref obj
232
- result = Vips::Image.new obj
233
-
234
- else
235
- raise Vips::Error, "unimplemented gtype for get: " +
236
- "#{::GObject::g_type_name gtype} (#{gtype})"
237
-
238
- end
239
- end
240
-
241
- # GLib::logger.debug("GObject::GValue.get") {
242
- # "result = #{result.inspect[0..50]}"
243
- # }
244
-
245
- return result
167
+ # Get the value of a GValue. The value is converted to a Ruby type in
168
+ # the obvious way.
169
+ #
170
+ # @return [Any] the value held by the GValue
171
+ def get
172
+ gtype = self[:gtype]
173
+ fundamental = ::GObject.g_type_fundamental gtype
174
+ result = nil
175
+
176
+ case gtype
177
+ when GBOOL_TYPE
178
+ result = ::GObject.g_value_get_boolean(self) != 0
179
+
180
+ when GINT_TYPE
181
+ result = ::GObject.g_value_get_int self
182
+
183
+ when GUINT64_TYPE
184
+ result = ::GObject.g_value_get_uint64 self
185
+
186
+ when GDOUBLE_TYPE
187
+ result = ::GObject.g_value_get_double self
188
+
189
+ when GSTR_TYPE
190
+ result = ::GObject.g_value_get_string self
191
+
192
+ when Vips::REFSTR_TYPE
193
+ len = Vips::SizeStruct.new
194
+ result = ::Vips.vips_value_get_ref_string self, len
195
+
196
+ when Vips::ARRAY_INT_TYPE
197
+ len = Vips::IntStruct.new
198
+ array = Vips.vips_value_get_array_int self, len
199
+ result = array.get_array_of_int32 0, len[:value]
200
+
201
+ when Vips::ARRAY_DOUBLE_TYPE
202
+ len = Vips::IntStruct.new
203
+ array = Vips.vips_value_get_array_double self, len
204
+ result = array.get_array_of_double 0, len[:value]
205
+
206
+ when Vips::ARRAY_IMAGE_TYPE
207
+ len = Vips::IntStruct.new
208
+ array = Vips.vips_value_get_array_image self, len
209
+ result = array.get_array_of_pointer 0, len[:value]
210
+ result.map! do |pointer|
211
+ ::GObject.g_object_ref pointer
212
+ Vips::Image.new pointer
213
+ end
246
214
 
215
+ when Vips::BLOB_TYPE
216
+ len = Vips::SizeStruct.new
217
+ array = Vips.vips_value_get_blob self, len
218
+ result = array.get_bytes 0, len[:value]
219
+
220
+ else
221
+ case fundamental
222
+ when GFLAGS_TYPE
223
+ result = ::GObject.g_value_get_flags self
224
+
225
+ when GENUM_TYPE
226
+ enum_value = ::GObject.g_value_get_enum(self)
227
+ result = GValue.to_nick self[:gtype], enum_value
228
+
229
+ when GOBJECT_TYPE
230
+ obj = ::GObject.g_value_get_object self
231
+ # g_value_get_object() does not add a ref ... we need to add
232
+ # one to match the unref in gobject release
233
+ ::GObject.g_object_ref obj
234
+ result = Vips::Image.new obj
235
+
236
+ else
237
+ raise Vips::Error, "unimplemented gtype for get: " \
238
+ "#{::GObject.g_type_name gtype} (#{gtype})"
247
239
  end
240
+ end
248
241
 
249
- end
242
+ # GLib::logger.debug("GObject::GValue.get") {
243
+ # "result = #{result.inspect[0..50]}"
244
+ # }
250
245
 
251
- attach_function :g_value_init, [GValue.ptr, :GType], :void
252
-
253
- # we must use a plain :pointer here, since we call this from #release, which
254
- # just gives us the unwrapped pointer, not the ruby class
255
- attach_function :g_value_unset, [:pointer], :void
256
-
257
- attach_function :g_value_set_boolean, [GValue.ptr, :int], :void
258
- attach_function :g_value_set_int, [GValue.ptr, :int], :void
259
- attach_function :g_value_set_uint64, [GValue.ptr, :uint64], :void
260
- attach_function :g_value_set_double, [GValue.ptr, :double], :void
261
- attach_function :g_value_set_enum, [GValue.ptr, :int], :void
262
- attach_function :g_value_set_flags, [GValue.ptr, :uint], :void
263
- attach_function :g_value_set_string, [GValue.ptr, :string], :void
264
- attach_function :g_value_set_object, [GValue.ptr, :pointer], :void
265
-
266
- attach_function :g_value_get_boolean, [GValue.ptr], :int
267
- attach_function :g_value_get_int, [GValue.ptr], :int
268
- attach_function :g_value_get_uint64, [GValue.ptr], :uint64
269
- attach_function :g_value_get_double, [GValue.ptr], :double
270
- attach_function :g_value_get_enum, [GValue.ptr], :int
271
- attach_function :g_value_get_flags, [GValue.ptr], :int
272
- attach_function :g_value_get_string, [GValue.ptr], :string
273
- attach_function :g_value_get_object, [GValue.ptr], :pointer
274
-
275
- # use :pointer rather than GObject.ptr to avoid casting later
276
- attach_function :g_object_set_property,
277
- [:pointer, :string, GValue.ptr], :void
278
- attach_function :g_object_get_property,
279
- [:pointer, :string, GValue.ptr], :void
246
+ result
247
+ end
280
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
256
+ end
257
+
258
+ attach_function :g_value_init, [GValue.ptr, :GType], :void
259
+
260
+ # we must use a plain :pointer here, since we call this from #release, which
261
+ # just gives us the unwrapped pointer, not the ruby class
262
+ attach_function :g_value_unset, [:pointer], :void
263
+
264
+ attach_function :g_value_set_boolean, [GValue.ptr, :int], :void
265
+ attach_function :g_value_set_int, [GValue.ptr, :int], :void
266
+ attach_function :g_value_set_uint64, [GValue.ptr, :uint64], :void
267
+ attach_function :g_value_set_double, [GValue.ptr, :double], :void
268
+ attach_function :g_value_set_enum, [GValue.ptr, :int], :void
269
+ attach_function :g_value_set_flags, [GValue.ptr, :uint], :void
270
+ attach_function :g_value_set_string, [GValue.ptr, :string], :void
271
+ attach_function :g_value_set_object, [GValue.ptr, :pointer], :void
272
+
273
+ attach_function :g_value_get_boolean, [GValue.ptr], :int
274
+ attach_function :g_value_get_int, [GValue.ptr], :int
275
+ attach_function :g_value_get_uint64, [GValue.ptr], :uint64
276
+ attach_function :g_value_get_double, [GValue.ptr], :double
277
+ attach_function :g_value_get_enum, [GValue.ptr], :int
278
+ attach_function :g_value_get_flags, [GValue.ptr], :int
279
+ attach_function :g_value_get_string, [GValue.ptr], :string
280
+ attach_function :g_value_get_object, [GValue.ptr], :pointer
281
+
282
+ # use :pointer rather than GObject.ptr to avoid casting later
283
+ attach_function :g_object_set_property,
284
+ [:pointer, :string, GValue.ptr], :void
285
+ attach_function :g_object_get_property,
286
+ [:pointer, :string, GValue.ptr], :void
281
287
  end