ruby-vips8 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +20 -0
- data/README.md +170 -0
- data/Rakefile +45 -0
- data/TODO +11 -0
- data/VERSION +1 -0
- data/example/annotate.rb +17 -0
- data/example/daltonize8.rb +75 -0
- data/example/example1.rb +84 -0
- data/example/example2.rb +31 -0
- data/example/example3.rb +19 -0
- data/example/example4.rb +18 -0
- data/example/example5.rb +31 -0
- data/example/trim8.rb +41 -0
- data/example/watermark.rb +44 -0
- data/example/wobble.rb +36 -0
- data/lib/vips8.rb +153 -0
- data/lib/vips8/access.rb +14 -0
- data/lib/vips8/align.rb +11 -0
- data/lib/vips8/angle.rb +12 -0
- data/lib/vips8/angle45.rb +16 -0
- data/lib/vips8/argument.rb +163 -0
- data/lib/vips8/bandformat.rb +20 -0
- data/lib/vips8/call.rb +302 -0
- data/lib/vips8/coding.rb +14 -0
- data/lib/vips8/demandstyle.rb +35 -0
- data/lib/vips8/direction.rb +11 -0
- data/lib/vips8/error.rb +30 -0
- data/lib/vips8/extend.rb +22 -0
- data/lib/vips8/foreignflags.rb +20 -0
- data/lib/vips8/image.rb +1383 -0
- data/lib/vips8/interpolate.rb +37 -0
- data/lib/vips8/interpretation.rb +28 -0
- data/lib/vips8/methods.rb +1807 -0
- data/lib/vips8/operation.rb +19 -0
- data/ruby-vips8.gemspec +112 -0
- data/spec/image_spec.rb +515 -0
- data/spec/samples/balloon.v +0 -0
- data/spec/samples/ghost.ppm +405 -0
- data/spec/samples/huge.jpg +0 -0
- data/spec/samples/icc.jpg +0 -0
- data/spec/samples/lcd.icc +0 -0
- data/spec/samples/lion.svg +154 -0
- data/spec/samples/sample.csv +7 -0
- data/spec/samples/sample.exr +0 -0
- data/spec/samples/wagon.jpg +0 -0
- data/spec/samples/wagon.v +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/vips_spec.rb +74 -0
- metadata +198 -0
data/lib/vips8/access.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Vips
|
2
|
+
# The type of access an operation has to supply.
|
3
|
+
#
|
4
|
+
# * `:random` means requests can come in any order.
|
5
|
+
#
|
6
|
+
# * `:sequential` means requests will be top-to-bottom, but with some
|
7
|
+
# amount of buffering behind the read point for small non-local
|
8
|
+
# accesses.
|
9
|
+
#
|
10
|
+
# * `:sequential_unbuffered` means requests will be strictly
|
11
|
+
# top-to-bottom with no read-behind. This can save some memory.
|
12
|
+
class Access
|
13
|
+
end
|
14
|
+
end
|
data/lib/vips8/align.rb
ADDED
data/lib/vips8/angle.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# Various fixed 45 degree rotation angles. See {Vips::Image.rot45}.
|
4
|
+
#
|
5
|
+
# * `:d0` no rotate
|
6
|
+
# * `:d45` 45 degrees clockwise
|
7
|
+
# * `:d90` 90 degrees clockwise
|
8
|
+
# * `:d135` 135 degrees clockwise
|
9
|
+
# * `:d180` 180 degrees
|
10
|
+
# * `:d225` 135 degrees anti-clockwise
|
11
|
+
# * `:d270` 90 degrees anti-clockwise
|
12
|
+
# * `:d315` 45 degrees anti-clockwise
|
13
|
+
|
14
|
+
class Angle45
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
|
2
|
+
module Vips
|
3
|
+
|
4
|
+
# This class is used internally to convert Ruby values to arguments to
|
5
|
+
# libvips operations.
|
6
|
+
# @private
|
7
|
+
class Argument
|
8
|
+
attr_reader :op, :name, :flags, :priority, :isset, :prop
|
9
|
+
attr_reader :blurb, :gtype, :type
|
10
|
+
|
11
|
+
# map gobject-introspection's ruby class names to ours
|
12
|
+
@@map_goi_to_vips = {
|
13
|
+
"TrueClass" => "Boolean",
|
14
|
+
"Vips::ArrayDouble" => "Array<Double>",
|
15
|
+
"Vips::ArrayInt" => "Array<Integer>",
|
16
|
+
"Vips::ArrayImage" => "Array<Image>",
|
17
|
+
"Vips::ArrayString" => "Array<String>",
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(op, name)
|
21
|
+
@op = op
|
22
|
+
@name = name.tr '-', '_'
|
23
|
+
@prop = op.gtype.to_class.property name
|
24
|
+
@blurb = @prop.blurb
|
25
|
+
@gtype = prop.value_type
|
26
|
+
@flags = op.get_argument_flags name
|
27
|
+
@priority = op.get_argument_priority @name
|
28
|
+
@isset = op.argument_isset @name
|
29
|
+
|
30
|
+
type = GLib::Type[gtype.name].to_class.name
|
31
|
+
type = @@map_goi_to_vips[type] if @@map_goi_to_vips.include? type
|
32
|
+
@type = type
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.imageize match_image, value
|
38
|
+
return value if match_image == nil
|
39
|
+
return value if value.is_a? Vips::Image
|
40
|
+
|
41
|
+
# 2D array values become tiny 2D images
|
42
|
+
if value.is_a? Array and value[0].is_a? Array
|
43
|
+
return Vips::Image.new_from_array value
|
44
|
+
end
|
45
|
+
|
46
|
+
# if there's nothing to match to, we also make a 2D image
|
47
|
+
if match_image == nil
|
48
|
+
return Vips::Image.new_from_array value
|
49
|
+
end
|
50
|
+
|
51
|
+
# we have a 1D array ... use that as a pixel constant and expand
|
52
|
+
# to match match_image
|
53
|
+
pixel = (Vips::Image.black(1, 1) + value).cast(match_image.format)
|
54
|
+
pixel = pixel.copy :interpretation => match_image.interpretation,
|
55
|
+
:xres => match_image.xres, :yres => match_image.yres
|
56
|
+
pixel.embed(0, 0, match_image.width, match_image.height,
|
57
|
+
:extend => :copy)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
class ArrayImageConst < Vips::ArrayImage
|
62
|
+
def self.new(value)
|
63
|
+
if not value.is_a? Array
|
64
|
+
value = [value]
|
65
|
+
end
|
66
|
+
|
67
|
+
match_image = value.find {|x| x.is_a? Vips::Image}
|
68
|
+
if match_image == nil
|
69
|
+
raise Vips::Error,
|
70
|
+
"Argument must contain at least one image."
|
71
|
+
end
|
72
|
+
|
73
|
+
value = value.map {|x| Argument::imageize match_image, x}
|
74
|
+
|
75
|
+
# we'd like to just
|
76
|
+
# super(value)
|
77
|
+
# to construct, but the gobject-introspection gem does not
|
78
|
+
# support new from object array ... instead, we build in stages
|
79
|
+
array = Vips::ArrayImage.empty
|
80
|
+
value.each {|x| array = array.append(x)}
|
81
|
+
|
82
|
+
return array
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# if this gtype needs an array, try to transform the value into one
|
87
|
+
def self.arrayize(gtype, value)
|
88
|
+
arrayize_map = {
|
89
|
+
GLib::Type["VipsArrayDouble"] => Vips::ArrayDouble,
|
90
|
+
GLib::Type["VipsArrayInt"] => Vips::ArrayInt,
|
91
|
+
GLib::Type["VipsArrayImage"] => ArrayImageConst
|
92
|
+
}
|
93
|
+
|
94
|
+
if arrayize_map.has_key? gtype
|
95
|
+
if not value.is_a? Array
|
96
|
+
value = [value]
|
97
|
+
end
|
98
|
+
|
99
|
+
value = arrayize_map[gtype].new(value)
|
100
|
+
end
|
101
|
+
|
102
|
+
value
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.unwrap value
|
106
|
+
[Vips::Blob, Vips::ArrayDouble, Vips::ArrayImage,
|
107
|
+
Vips::ArrayInt, Vips::RefString].each do |cls|
|
108
|
+
if value.is_a? cls
|
109
|
+
value, length = value.get
|
110
|
+
|
111
|
+
# blobs come from gobject-introspection as arrays ...
|
112
|
+
# repack as strings for convenience
|
113
|
+
if value and cls == Vips::Blob
|
114
|
+
value = value.pack("C*")
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
public
|
125
|
+
|
126
|
+
def set_value(match_image, value)
|
127
|
+
# array-ize
|
128
|
+
value = Argument::arrayize gtype, value
|
129
|
+
|
130
|
+
# blob-ize
|
131
|
+
if gtype.type_is_a? GLib::Type["VipsBlob"]
|
132
|
+
if not value.is_a? Vips::Blob
|
133
|
+
value = Vips::Blob.copy value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# image-ize
|
138
|
+
if gtype.type_is_a? GLib::Type["VipsImage"]
|
139
|
+
if not value.is_a? Vips::Image
|
140
|
+
value = Argument::imageize match_image, value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# MODIFY input images need to be copied before assigning them
|
145
|
+
if (flags & :modify) != 0
|
146
|
+
# don't use .copy(): we want to make a new pipeline with no
|
147
|
+
# reference back to the old stuff ... this way we can free the
|
148
|
+
# previous image earlier
|
149
|
+
new_image = Vips::Image.memory
|
150
|
+
value.write new_image
|
151
|
+
value = new_image
|
152
|
+
end
|
153
|
+
|
154
|
+
op.set_property @name, value
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_value
|
158
|
+
Argument::unwrap @op.get_property(@name)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# The format used for each band element. Each corresponds to a native C type
|
4
|
+
# for the current machine.
|
5
|
+
#
|
6
|
+
# * `:notset` invalid setting
|
7
|
+
# * `:uchar` unsigned char format
|
8
|
+
# * `:char` char format
|
9
|
+
# * `:ushort` unsigned short format
|
10
|
+
# * `:short` short format
|
11
|
+
# * `:uint` unsigned int format
|
12
|
+
# * `:int` int format
|
13
|
+
# * `:float` float format
|
14
|
+
# * `:complex` complex (two floats) format
|
15
|
+
# * `:double` double float format
|
16
|
+
# * `:dpcomplex` double complex (two double) format
|
17
|
+
class BandFormat
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/vips8/call.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# call a vips operation ... this will crash if there's a GC during the call,
|
4
|
+
# I've really no idea why :-(
|
5
|
+
#
|
6
|
+
# Workaround: don't call this directly, use call_base (see below) instead.
|
7
|
+
# This will disable the GC, call this operation, then reenable it.
|
8
|
+
|
9
|
+
private
|
10
|
+
def self.call_base_nogc(name, instance, option_string, supplied_values)
|
11
|
+
log "in Vips::call_base"
|
12
|
+
log "name = #{name}"
|
13
|
+
log "instance = #{instance}"
|
14
|
+
log "option_string = #{option_string}"
|
15
|
+
log "supplied_values are:"
|
16
|
+
supplied_values.each {|x| log " #{x}"}
|
17
|
+
|
18
|
+
if supplied_values.last.is_a? Hash
|
19
|
+
optional_values = supplied_values.last
|
20
|
+
supplied_values.delete_at -1
|
21
|
+
else
|
22
|
+
optional_values = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
op = Vips::Operation.new name
|
27
|
+
rescue
|
28
|
+
raise Vips::Error, "no operator '#{name}'"
|
29
|
+
end
|
30
|
+
|
31
|
+
# set string options first
|
32
|
+
log "setting string options ..."
|
33
|
+
if option_string
|
34
|
+
if op.set_from_string(option_string) != 0
|
35
|
+
raise Error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
all_args = op.get_args
|
40
|
+
|
41
|
+
# the instance, if supplied, must be a vips image ... we use it for
|
42
|
+
# match_image, below
|
43
|
+
if instance and not instance.is_a? Vips::Image
|
44
|
+
raise Vips::Error, "@instance is not a Vips::Image."
|
45
|
+
end
|
46
|
+
|
47
|
+
# if the op needs images but the user supplies constants, we expand
|
48
|
+
# them to match the first input image argument ... find the first
|
49
|
+
# image
|
50
|
+
log "searching for first image argument ..."
|
51
|
+
match_image = instance
|
52
|
+
if match_image == nil
|
53
|
+
match_image = supplied_values.find {|x| x.is_a? Vips::Image}
|
54
|
+
end
|
55
|
+
if match_image == nil
|
56
|
+
match = optional_values.find do |name, value|
|
57
|
+
value.is_a? Vips::Image
|
58
|
+
end
|
59
|
+
# if we found a match, it'll be [name, value]
|
60
|
+
if match
|
61
|
+
match_image = match[1]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# find unassigned required input args
|
66
|
+
log "finding unassigned required input arguments ..."
|
67
|
+
required_input = all_args.select do |arg|
|
68
|
+
not arg.isset and
|
69
|
+
(arg.flags & :input) != 0 and
|
70
|
+
(arg.flags & :required) != 0
|
71
|
+
end
|
72
|
+
|
73
|
+
# do we have a non-nil instance? set the first image arg with this
|
74
|
+
if instance != nil
|
75
|
+
log "setting first image arg with instance ..."
|
76
|
+
x = required_input.find do |arg|
|
77
|
+
gtype = GLib::Type["VipsImage"]
|
78
|
+
vtype = arg.prop.value_type
|
79
|
+
|
80
|
+
vtype.type_is_a? gtype
|
81
|
+
end
|
82
|
+
if x == nil
|
83
|
+
raise Vips::Error,
|
84
|
+
"No #{instance.class} argument to #{name}."
|
85
|
+
end
|
86
|
+
x.set_value match_image, instance
|
87
|
+
required_input.delete x
|
88
|
+
end
|
89
|
+
|
90
|
+
if required_input.length != supplied_values.length
|
91
|
+
raise Vips::Error,
|
92
|
+
"Wrong number of arguments. '#{name}' requires " +
|
93
|
+
"#{required_input.length} arguments, you supplied " +
|
94
|
+
"#{supplied_values.length}."
|
95
|
+
end
|
96
|
+
|
97
|
+
log "setting required input arguments ..."
|
98
|
+
required_input.zip(supplied_values).each do |arg, value|
|
99
|
+
arg.set_value match_image, value
|
100
|
+
end
|
101
|
+
|
102
|
+
# find optional unassigned input args
|
103
|
+
log "finding optional unassigned input arguments ..."
|
104
|
+
optional_input = all_args.select do |arg|
|
105
|
+
not arg.isset and
|
106
|
+
(arg.flags & :input) != 0 and
|
107
|
+
(arg.flags & :required) == 0
|
108
|
+
end
|
109
|
+
|
110
|
+
# make a hash from name to arg
|
111
|
+
optional_input = Hash[
|
112
|
+
optional_input.map(&:name).zip(optional_input)]
|
113
|
+
|
114
|
+
# find optional unassigned output args
|
115
|
+
log "finding optional unassigned output arguments ..."
|
116
|
+
optional_output = all_args.select do |arg|
|
117
|
+
not arg.isset and
|
118
|
+
(arg.flags & :output) != 0 and
|
119
|
+
(arg.flags & :required) == 0
|
120
|
+
end
|
121
|
+
optional_output = Hash[
|
122
|
+
optional_output.map(&:name).zip(optional_output)]
|
123
|
+
|
124
|
+
# set all optional args
|
125
|
+
log "setting optional values ..."
|
126
|
+
optional_values.each do |name, value|
|
127
|
+
# we are passed symbols as keys
|
128
|
+
name = name.to_s
|
129
|
+
if optional_input.has_key? name
|
130
|
+
log "setting #{name} to #{value}"
|
131
|
+
optional_input[name].set_value match_image, value
|
132
|
+
elsif optional_output.has_key? name and value != true
|
133
|
+
raise Vips::Error,
|
134
|
+
"Optional output argument #{name} must be true."
|
135
|
+
elsif not optional_output.has_key? name
|
136
|
+
raise Vips::Error, "No such option '#{name}',"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
log "building ..."
|
141
|
+
|
142
|
+
op2 = Vips::cache_operation_lookup op
|
143
|
+
if op2
|
144
|
+
log "cache hit"
|
145
|
+
op = op2
|
146
|
+
|
147
|
+
all_args = op.get_args
|
148
|
+
|
149
|
+
# find optional output args
|
150
|
+
optional_output = all_args.select do |arg|
|
151
|
+
(arg.flags & :output) != 0 and
|
152
|
+
(arg.flags & :required) == 0
|
153
|
+
end
|
154
|
+
optional_output = Hash[
|
155
|
+
optional_output.map(&:name).zip(optional_output)]
|
156
|
+
else
|
157
|
+
log "cache miss ... building"
|
158
|
+
if not op.build
|
159
|
+
raise Vips::Error
|
160
|
+
end
|
161
|
+
showall
|
162
|
+
|
163
|
+
log "adding to cache ... "
|
164
|
+
Vips::cache_operation_add op
|
165
|
+
end
|
166
|
+
|
167
|
+
log "fetching outputs ..."
|
168
|
+
|
169
|
+
# gather output args
|
170
|
+
out = []
|
171
|
+
|
172
|
+
all_args.each do |arg|
|
173
|
+
# required output
|
174
|
+
if (arg.flags & :output) != 0 and
|
175
|
+
(arg.flags & :required) != 0
|
176
|
+
log "fetching required output #{arg.name}"
|
177
|
+
out << arg.get_value
|
178
|
+
end
|
179
|
+
|
180
|
+
# modified input arg ... this will get the result of the
|
181
|
+
# copy() we did in Argument.set_value above
|
182
|
+
if (arg.flags & :input) != 0 and
|
183
|
+
(arg.flags & :modify) != 0
|
184
|
+
log "fetching modified input arg ..."
|
185
|
+
out << arg.get_value
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
opts = {}
|
190
|
+
optional_values.each do |name, value|
|
191
|
+
# we are passed symbols as keys
|
192
|
+
name = name.to_s
|
193
|
+
if optional_output.has_key? name
|
194
|
+
log "fetching optional output arg ..."
|
195
|
+
opts[name] = optional_output[name].get_value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
out << opts if opts != {}
|
199
|
+
|
200
|
+
if out.length == 1
|
201
|
+
out = out[0]
|
202
|
+
elsif out.length == 0
|
203
|
+
out = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
log "unreffing outputs ..."
|
207
|
+
op.unref_outputs
|
208
|
+
op = nil
|
209
|
+
# showall
|
210
|
+
|
211
|
+
log "success! #{name}.out = #{out}"
|
212
|
+
|
213
|
+
return out
|
214
|
+
end
|
215
|
+
|
216
|
+
# run call_base_nogc, with the GC disabled
|
217
|
+
private
|
218
|
+
def self.call_base(name, instance, option_string, supplied_values)
|
219
|
+
gc_was_enabled = GC.disable
|
220
|
+
begin
|
221
|
+
result = call_base_nogc name, instance,
|
222
|
+
option_string, supplied_values
|
223
|
+
ensure
|
224
|
+
GC.enable if gc_was_enabled
|
225
|
+
end
|
226
|
+
|
227
|
+
return result
|
228
|
+
end
|
229
|
+
|
230
|
+
public
|
231
|
+
|
232
|
+
# This is the public entry point for the vips8 binding. {call} will run
|
233
|
+
# any vips operation, for example:
|
234
|
+
#
|
235
|
+
# ```ruby
|
236
|
+
# out = Vips::call "black", 100, 100, :bands => 12
|
237
|
+
# ```
|
238
|
+
#
|
239
|
+
# will call the C function
|
240
|
+
#
|
241
|
+
# ```C
|
242
|
+
# vips_black( &out, 100, 100, "bands", 12, NULL );
|
243
|
+
# ```
|
244
|
+
#
|
245
|
+
# There are {Image#method_missing} hooks which will run {call} for you
|
246
|
+
# on {Image} for undefined instance or class methods. So you can also
|
247
|
+
# write:
|
248
|
+
#
|
249
|
+
# ```ruby
|
250
|
+
# out = Vips::Image.black 100, 100, :bands => 12
|
251
|
+
# ```
|
252
|
+
#
|
253
|
+
# Or perhaps:
|
254
|
+
#
|
255
|
+
# ```ruby
|
256
|
+
# x = Vips::Image.black 100, 100
|
257
|
+
# y = x.invert
|
258
|
+
# ```
|
259
|
+
#
|
260
|
+
# to run the `vips_invert()` operator.
|
261
|
+
#
|
262
|
+
# There are also a set of operator overloads and some convenience functions,
|
263
|
+
# see {Image}.
|
264
|
+
#
|
265
|
+
# If the operator needs a vector constant, {call} will turn a scalar into a
|
266
|
+
# vector for you. So for `x.linear(a, b)`, which calculates
|
267
|
+
# `x * a + b` where `a` and `b` are vector constants, you can write:
|
268
|
+
#
|
269
|
+
# ```ruby
|
270
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
271
|
+
# y = x.linear(1, 2)
|
272
|
+
# y = x.linear([1], 4)
|
273
|
+
# y = x.linear([1, 2, 3], 4)
|
274
|
+
# ```
|
275
|
+
#
|
276
|
+
# or any other combination. The operator overloads use this facility to
|
277
|
+
# support all the variations on:
|
278
|
+
#
|
279
|
+
# ```ruby
|
280
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
281
|
+
# y = x * 2
|
282
|
+
# y = x + [1,2,3]
|
283
|
+
# y = x % [1]
|
284
|
+
# ```
|
285
|
+
#
|
286
|
+
# Similarly, whereever an image is required, you can use a constant. The
|
287
|
+
# constant will be expanded to an image matching the first input image
|
288
|
+
# argument. For example, you can write:
|
289
|
+
#
|
290
|
+
# ```
|
291
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
292
|
+
# y = x.bandjoin(255)
|
293
|
+
# ```
|
294
|
+
#
|
295
|
+
# to add an extra band to the image where each pixel in the new band has
|
296
|
+
# the constant value 255.
|
297
|
+
|
298
|
+
def self.call(name, *args)
|
299
|
+
Vips::call_base name, nil, "", args
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|