sobakasu-image_science 1.1.3

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.
data/History.txt ADDED
@@ -0,0 +1,39 @@
1
+ == 1.1.3 2010-02-24
2
+
3
+ * fixed call to FreeImage_Rotate() (used in newer versions of FreeImage)
4
+
5
+ == 1.1.2 / 2010-02-12
6
+
7
+ * added bin/image_science script.
8
+ * added buffer() method to get image data as a string.
9
+ * crop(), with_crop(), resize(), thumbnail(), cropped_thumbnail() can now all be called without providing a block (modifies the image in-place).
10
+ * resize() now accepts optional filter (see FreeImage::ImageFilters).
11
+ * image_type() can now be called as an instance method.
12
+ * added bmp, tiff, xpm files to test suite.
13
+ * added support for ImageScience.new (takes filename or data)
14
+ * added flip_horizontal, flip_vertical, rotate methods
15
+ * added support for load/save flags to new() and buffer().
16
+ * added set_pixel_color method
17
+ * added fit_within method
18
+
19
+ == 1.1.1 / 2010-02-06
20
+
21
+ * merged changes from scambra/master (depth, colorspace, image_type, colortype, file_type methods)
22
+ * reinstated lib/image_science.rb for methods implemented in ruby, c extension
23
+ is now named image_science_ext
24
+ * added histogram, invert, adjust_contrast, adjust_brightness, adjust_gamma
25
+ * fixed up rdoc
26
+ * defines various FreeImage constants
27
+ * generate ext/image_science_ext.c from ext/image_science_ext.c.in
28
+
29
+ == 1.1.0 / 2010-02-05
30
+
31
+ * converted to use extconf (removed ruby-inline)
32
+ * converted tests to rspec
33
+ * added tests for different file types (gif, png, jpg)
34
+
35
+ == 1.0.0 / 2010-01-14
36
+
37
+ * Fork of seattlerb/image_science
38
+ * added get_pixel_color method
39
+ * build using LDFLAGS from rbconfig.rb
data/History.txt.orig ADDED
@@ -0,0 +1,77 @@
1
+ === 1.2.1 / 2009-08-14
2
+
3
+ * 2 minor enhancements:
4
+
5
+ * Added luis' patches to make it build properly on windows.
6
+ * with_image now raises on missing/bad files.
7
+
8
+ == 1.2.0 / 2009-06-23
9
+
10
+ * 7 minor enhancements:
11
+
12
+ * Moved quick_thumb to bin/image_science_thumb and properly added.
13
+ * Added -s (square) flag to bin/image_science_thumb
14
+ * Added autorotating on image load. (choonkeat)
15
+ * Added ruby_inline to clean globs
16
+ * Added with_image_from_memory. (sumbach)
17
+ * Switched to minitest.
18
+ * Updated rakefile for now hoe capabilities.
19
+
20
+ * 3 bug fixes:
21
+
22
+ * Check and convert to 24 BPP if save type is jpg. Caused by 32bpp png to jpg.
23
+ * Fixed 1.9isms
24
+ * Fixed BMP support. Tweaked whitespace.
25
+
26
+ == 1.1.3 / 2007-05-30
27
+
28
+ * 2 minor enhancements:
29
+
30
+ * Added quick_thumb as an example to look at.
31
+ * Error handler doesn't raise by default. Raises if $DEBUG==true.
32
+
33
+ == 1.1.2 / 2007-04-18
34
+
35
+ * 2 bug fixes:
36
+
37
+ * reports bad height/width values for resize
38
+ * explicitly removes ICC color profiles from PNGs (bug in freeimage).
39
+
40
+ == 1.1.1 / 2007-03-08
41
+
42
+ * 5 minor enhancements:
43
+
44
+ * Added error handler that raises with information about what went wrong.
45
+ * thumbnail is now pure ruby, everything now uses resize.
46
+ * Produces cleaner JPEG files, with a small cost to file size/speed.
47
+ * resize now uses Catmull-Rom spline filter for better quality.
48
+ * resize copies existing ICC Profile to thumbnail, producing better color.
49
+ * ICC Profile NOT copied for PNG as it seems to be buggy.
50
+
51
+ * 1 bug fix:
52
+
53
+ * Fixed rdoc
54
+
55
+ == 1.1.0 / 2007-01-05
56
+
57
+ * 3 major enhancements:
58
+
59
+ * Added resize(width, height)
60
+ * Added save(path)
61
+ * All thumbnail and resize methods yield instead of saving directly.
62
+
63
+ * 1 minor enhancement:
64
+
65
+ * Will now try to use FreeImage from ports if /opt/local exists.
66
+
67
+ * 2 bug fixes:
68
+
69
+ * Fixed the linker issue on PPC.
70
+ * Rakefile will now clean the image files created by bench.rb
71
+
72
+ == 1.0.0 / 2006-12-01
73
+
74
+ * 1 major enhancement
75
+
76
+ * Birthday!
77
+
data/Manifest.txt ADDED
@@ -0,0 +1,20 @@
1
+ Manifest.txt
2
+ History.txt
3
+ History.txt.orig
4
+ Rakefile
5
+ README.txt
6
+ bench.rb
7
+ ext/extconf.rb
8
+ ext/image_science_ext.c.in
9
+ bin/image_science
10
+ bin/image_science_thumb
11
+ lib/image_science.rb
12
+ spec/spec_helper.rb
13
+ spec/fixtures/pix2.png
14
+ spec/fixtures/pix2.gif
15
+ spec/fixtures/pix2.jpg
16
+ spec/fixtures/pix.jpg
17
+ spec/fixtures/pix.png
18
+ spec/fixtures/pix.gif
19
+ spec/spec.opts
20
+ spec/image_science_spec.rb
data/README.txt ADDED
@@ -0,0 +1,73 @@
1
+ = ImageScience
2
+
3
+ * http://seattlerb.rubyforge.org/ImageScience.html
4
+ * http://rubyforge.org/projects/seattlerb
5
+
6
+ == DESCRIPTION:
7
+
8
+ ImageScience is a clean and happy Ruby library that generates
9
+ thumbnails -- and kicks the living crap out of RMagick. Oh, and it
10
+ doesn't leak memory like a sieve. :)
11
+
12
+ For more information including build steps, see http://seattlerb.rubyforge.org/
13
+
14
+ == FORK
15
+
16
+ This is a fork of seattlerb/image_science.
17
+ Converted to use extconf instead of ruby-inline.
18
+ Added many new features.
19
+ See History.txt for the full list of changes since the fork.
20
+
21
+ == FEATURES/PROBLEMS:
22
+
23
+ * Glorious graphics manipulation magi... errr, SCIENCE! in less than 200 LoC!
24
+ * Supports square and proportional thumbnails, as well as arbitrary resizes.
25
+ * Pretty much any graphics format you could want. No really.
26
+
27
+ == SYNOPSYS:
28
+
29
+ ImageScience.with_image(file) do |img|
30
+ img.cropped_thumbnail(100) do |thumb|
31
+ thumb.save "#{file}_cropped.png"
32
+ end
33
+
34
+ img.thumbnail(100) do |thumb|
35
+ thumb.save "#{file}_thumb.png"
36
+ end
37
+ end
38
+
39
+ == REQUIREMENTS:
40
+
41
+ * FreeImage
42
+ * ImageScience
43
+
44
+ == INSTALL:
45
+
46
+ * Download and install FreeImage. See notes at url above.
47
+ * sudo gem install -y image_science
48
+ * see http://seattlerb.rubyforge.org/ImageScience.html for more info.
49
+
50
+ == LICENSE:
51
+
52
+ (The MIT License)
53
+
54
+ Copyright (c) 2006-2009 Ryan Davis, Seattle.rb
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ 'Software'), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ gem 'hoe'
3
+ require 'hoe'
4
+
5
+ Hoe.spec 'sobakasu-image_science' do
6
+ developer('Ryan Davis', 'ryand-ruby@zenspider.com')
7
+ developer('Andrew Williams', 'sobakasu@gmail.com')
8
+ clean_globs << 'blah*png' << 'images/*_thumb.*'
9
+ spec_extras[:extensions] = "ext/extconf.rb"
10
+ extra_rdoc_files << 'bin/image_science'
11
+ end
12
+
13
+ Dir['tasks/**/*.rake'].each { |t| load t }
data/bench.rb ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'benchmark'
5
+ require 'image_science'
6
+
7
+ max = (ARGV.shift || 100).to_i
8
+ ext = ARGV.shift || "png"
9
+
10
+ file = "blah_big.#{ext}"
11
+
12
+ if RUBY_PLATFORM =~ /darwin/ then
13
+ # how fucking cool is this???
14
+ puts "taking screenshot for thumbnailing benchmarks"
15
+ system "screencapture -SC #{file}"
16
+ else
17
+ abort "You need to plonk down #{file} or buy a mac"
18
+ end unless test ?f, "#{file}"
19
+
20
+ ImageScience.with_image(file.sub(/#{ext}$/, 'png')) do |img|
21
+ img.save(file)
22
+ end if ext != "png"
23
+
24
+ puts "# of iterations = #{max}"
25
+ Benchmark::bm(20) do |x|
26
+ x.report("null_time") {
27
+ for i in 0..max do
28
+ # do nothing
29
+ end
30
+ }
31
+
32
+ x.report("cropped") {
33
+ for i in 0..max do
34
+ ImageScience.with_image(file) do |img|
35
+ img.cropped_thumbnail(100) do |thumb|
36
+ thumb.save("blah_cropped.#{ext}")
37
+ end
38
+ end
39
+ end
40
+ }
41
+
42
+ x.report("proportional") {
43
+ for i in 0..max do
44
+ ImageScience.with_image(file) do |img|
45
+ img.thumbnail(100) do |thumb|
46
+ thumb.save("blah_thumb.#{ext}")
47
+ end
48
+ end
49
+ end
50
+ }
51
+
52
+ x.report("resize") {
53
+ for i in 0..max do
54
+ ImageScience.with_image(file) do |img|
55
+ img.resize(200, 200) do |resize|
56
+ resize.save("blah_resize.#{ext}")
57
+ end
58
+ end
59
+ end
60
+ }
61
+ end
62
+
63
+ # File.unlink(*Dir["blah*#{ext}"])
data/bin/image_science ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # = Usage
4
+ #
5
+ # image_science [OPTIONS] [command] [param ...] [file]
6
+ #
7
+ # == Options
8
+ #
9
+ # -h, --help:
10
+ #
11
+ # show help
12
+ #
13
+ # -i, --input
14
+ #
15
+ # specify input file.
16
+ # by default input is read from stdin.
17
+ # if stdin is attached to a tty, the last argument is used as a filename.
18
+ # e.g. image_science info pic.jpg
19
+ #
20
+ # -o, --output
21
+ #
22
+ # specify output file.
23
+ # by default output is written to stdout, or a file named 'output'
24
+ # if stdout is attached to a tty.
25
+ #
26
+ # -d, --debug:
27
+ #
28
+ # debug mode
29
+ #
30
+ # == Commands
31
+ #
32
+ # The following commands are recognised
33
+ #
34
+ # * info
35
+ # * histogram
36
+ # * get_version
37
+ # * resize <width> <height>
38
+ # * crop <left> <top> <right> <bottom>
39
+ # * get_pixel_color <x> <y>
40
+ # * set_pixel_color <x> <y> <red> <green> <blue> [<alpha>]
41
+ # * adjust_gamma <value>
42
+ # * adjust_brightness <value>
43
+ # * adjust_contrast <value>
44
+ # * invert
45
+ # * thumbnail <size>
46
+ # * cropped_thumbnail <size>
47
+ # * flip_vertical
48
+ # * flip_horizontal
49
+ # * rotate <angle> [<x_shift> <y_shift> <x_origin> <y_origin> <use_mask>]
50
+ #
51
+ # Refer to the ImageScience documentation for allowed value ranges.
52
+ #
53
+ # = Examples
54
+ #
55
+ # # convert an image from one format to another
56
+ # > image_science -i pix.jpg -o pix.gif
57
+ #
58
+ # # create a thumbnail of size 100 pixels from pix.jpg, writes to 'output':
59
+ # > image_science thumbnail 100 pix.jpg
60
+ #
61
+ # # as above, but create a png named pix_thumb.png:
62
+ # > image_science thumbnail 100 pix.jpg -o pix_thumb.png
63
+ #
64
+ # # resize to 200x100, increase brightness by 30%, save as png:
65
+ # > image_science resize 200 100 pix.jpg | image_science adjust_brightness 30 -o pix_new.png
66
+ #
67
+ # # display effects of gamma adjustments on pixel color (writes to 'output'):
68
+ # > image_science get_pixel_color 0 0 pix.jpg | image_science adjust_gamma 0.5 | image_science get_pixel_color 0 0
69
+
70
+ require 'rubygems'
71
+ require File.dirname(__FILE__) + '/../lib/image_science'
72
+ require 'getoptlong'
73
+
74
+ ARG_SPEC = [ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
75
+ [ '--test', '-n', GetoptLong::NO_ARGUMENT ],
76
+ [ '--input', '-i', GetoptLong::REQUIRED_ARGUMENT ],
77
+ [ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ],
78
+ [ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
79
+ ]
80
+
81
+ private
82
+
83
+ # parse command line arguments
84
+ def command_line_options
85
+ opts = GetoptLong.new(*ARG_SPEC)
86
+ options = {}
87
+ opts.each do |opt,arg|
88
+ opt[0,2] = ''
89
+ opt = opt.to_sym
90
+ case opt
91
+ when :help
92
+ puts usage("[param ...]")
93
+ exit 0
94
+ else
95
+ options[opt] = arg ? arg : true
96
+ end
97
+ end
98
+ options
99
+ end
100
+
101
+ def process_data(opts)
102
+ if input = opts[:input]
103
+ method = :with_image
104
+ else
105
+ method = :with_image_from_memory
106
+ input = STDIN.binmode.read
107
+ end
108
+
109
+ output = opts[:output] || ($stdout.tty? ? "output" : nil)
110
+
111
+ ImageScience.send(method, input) do |i|
112
+ yield i if block_given?
113
+ if @changed || !$stdout.tty? || opts[:output]
114
+ if output
115
+ i.save(output)
116
+ else
117
+ $stdout.sync = true
118
+ print i.data
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def usage(bonus_args = nil)
125
+ cmd = @command || "[command]"
126
+ usage = ["usage: image_science [OPTIONS]", cmd, bonus_args, "[image_file]"].
127
+ compact.join(" ")
128
+
129
+ if !@command
130
+ re = Regexp.new('== Commands(.*?)= Examples', Regexp::MULTILINE)
131
+ commands = $1.gsub(/#/, '') if File.read(__FILE__).match(re)
132
+ usage += commands.sub(/[\n\s]+\z/, '') + "\n\n"
133
+ end
134
+
135
+ usage
136
+ end
137
+
138
+ def expect_params(required, optional = [])
139
+ required = [required] unless required.kind_of?(Array)
140
+ pl = @params.length
141
+
142
+ if (pl < required.length) || (pl > required.length + optional.length) ||
143
+ @params.any? { |i| i.nil? }
144
+ usage_str = required.join(" ")
145
+ usage_str += " [%s]" % optional.join(" ") unless optional.empty?
146
+ raise usage(usage_str)
147
+ end
148
+ (@params.length == 1) ? @params[0] : @params
149
+ end
150
+
151
+ begin
152
+ opts = command_line_options
153
+ @command, *params = ARGV
154
+ opts[:input] = params.pop if !opts[:input] && $stdin.tty?
155
+ @params = params.collect { |i| i ? i.to_f : nil }
156
+
157
+ unless @command || (opts[:input] || opts[:output])
158
+ puts usage("[param ...]")
159
+ exit 1
160
+ end
161
+
162
+ # Note: using warn for string output so that image data can be chained
163
+ # through on the command line.
164
+ case @command
165
+ when nil # no command, just use -i and -o
166
+ process_data(opts)
167
+ when 'info'
168
+ process_data(opts) do |i|
169
+ warn <<EOM
170
+ image type: #{i.image_type}
171
+ dimensions: #{i.width} x #{i.height}
172
+ colorspace: #{i.colorspace}
173
+ depth: #{i.depth}
174
+ EOM
175
+ end
176
+ when 'histogram'
177
+ process_data(opts) { |i| warn i.histogram.inspect }
178
+ when 'get_version'
179
+ warn "FreeImage #{ImageScience.get_version}"
180
+ when 'resize'
181
+ w, h = expect_params(%W{<width> <height>})
182
+ process_data(opts) { |i| i.send(@command, w, h); @changed = true }
183
+ when 'get_pixel_color'
184
+ x, y = expect_params(%W{<x> <y>})
185
+ process_data(opts) { |i| warn i.get_pixel_color(x, y).inspect }
186
+ when 'set_pixel_color'
187
+ x, y, *rgb = expect_params(%W{<x> <y> <red> <green> <blue>}, %W{<alpha>})
188
+ process_data(opts) { |i| i.set_pixel_color(x, y, rgb); @changed = true }
189
+ when /^adjust_/
190
+ value = expect_params("<value>")
191
+ process_data(opts) { |i| i.send(@command, value); @changed = true }
192
+ when 'invert', 'flip_vertical', 'flip_horizontal'
193
+ process_data(opts) { |i| i.send(@command); @changed = true }
194
+ when 'thumbnail', 'cropped_thumbnail'
195
+ value = expect_params("<size>")
196
+ process_data(opts) { |i| i.send(@command, value); @changed = true }
197
+ when 'crop'
198
+ l, r, t, b = expect_params(%W{<left> <top> <right> <bottom>})
199
+ process_data(opts) { |i| i.crop(l, r, t, b); @changed = true }
200
+ when 'rotate'
201
+ a, *args = expect_params("<angle>", %W{<x_shift> <y_shift> <x_origin>
202
+ <y_origin> <use_mask>})
203
+ process_data(opts) do |i|
204
+ args.empty? ? i.rotate(a) : i.rotate(a, *args)
205
+ @changed = true
206
+ end
207
+ else
208
+ raise "unrecognised command '#{@command}'"
209
+ end
210
+
211
+ rescue SystemExit
212
+ rescue Interrupt
213
+ warn ""
214
+ rescue Exception => e
215
+ warn "ERROR: #{e.message}"
216
+ if opts && opts[:debug]
217
+ bt = e.backtrace
218
+ bt = bt.join("\n") if bt.kind_of?(Array)
219
+ warn bt
220
+ end
221
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $s ||= false
4
+
5
+ abort "#{File.basename $0} max_length files..." unless ARGV.size > 1
6
+
7
+ require 'rubygems'
8
+ require 'image_science'
9
+
10
+ max_length = ARGV.shift.to_i
11
+
12
+ msg = $s ? :cropped_thumbnail : :thumbnail
13
+
14
+ ARGV.each do |file|
15
+ begin
16
+ result = ImageScience.with_image file do |img|
17
+ begin
18
+ img.send(msg, max_length) do |thumb|
19
+ # add _thumb and switch from gif to png. Really. gif just sucks.
20
+ out = file.sub(/(\.[^\.]+)$/, '_thumb\1').sub(/gif$/, 'png')
21
+ thumb.save(out)
22
+ end
23
+ rescue => e
24
+ warn "Exception thumbnailing #{file}: #{e}"
25
+ end
26
+ end
27
+ p file => result
28
+ rescue => e
29
+ warn "Exception opening #{file}: #{e}"
30
+ end
31
+ end
data/ext/extconf.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'mkmf'
2
+
3
+ # expand comments in image_science_ext.c.in and generate image_science_ext.c.
4
+ # creates constant definitions (rb_define_const)
5
+ def expand_constants
6
+
7
+ File.open("conftest.c", "w") { |f| f.puts "#include <FreeImage.h>" }
8
+ cpp = cpp_command('')
9
+ system "#{cpp} > confout"
10
+
11
+ constants = {}
12
+ headers = []
13
+
14
+ config = File.readlines("confout")
15
+ config.each do |include|
16
+ next unless include.match(/"(.*?FreeImage.h)"/)
17
+ filename = include.split('"')[1]
18
+ headers << filename
19
+ end
20
+
21
+ headers.uniq!
22
+
23
+ # add typedef constants
24
+ config.each do |define|
25
+ next unless define.match(/^\s*(\w+)\s*=\s*\d/) # typedef
26
+ name = $1
27
+ next unless name.match(/^(FIT|FICC|FIC|FIF|FILTER)_/)
28
+ constants[$1] ||= []
29
+ constants[$1] << [name]
30
+ end
31
+
32
+ raw_headers = headers.collect { |i| File.read(i) }.join
33
+
34
+ # add #defined constants (load/save flags)
35
+ constants['FLAG'] ||= []
36
+ flag_defines = raw_headers.scan(/(flag constants\s*---.*?)^\/\//m)
37
+ flag_defines.each do |flag_data|
38
+ flag_data[0].split(/\n/).each do |define|
39
+ next unless define.match(/^\#define\s*(\w+)\s(.*?(\/\/.+)$)?/) # #define
40
+ constants['FLAG'] << [$1, $3]
41
+ end
42
+ end
43
+
44
+ File.unlink("confout")
45
+
46
+ constants.keys.each { |i| constants[i].uniq! }
47
+
48
+ File.open("image_science_ext.c", "w") do |newf|
49
+ File.foreach("image_science_ext.c.in") do |l|
50
+ if l.match(/\/\* expand FreeImage constants\s+(\w+)\s+(\w+)\s*\*\//)
51
+ klass_name = $1
52
+ const_type = $2
53
+ const_list = constants[const_type]
54
+ unless const_list
55
+ puts "warning: no constants found matching #{const_type}"
56
+ next
57
+ end
58
+ const_list.each do |c, comment|
59
+ if comment
60
+ comment.sub!('//', '')
61
+ comment.sub!(/[\r\n]+$/, '')
62
+ comment.gsub!(/:/, '-') # colons break rdoc..
63
+ comment.strip!
64
+ newf.puts %Q{ /* #{comment} */}
65
+ end
66
+ newf.puts %Q{ rb_define_const(#{klass_name}, "#{c}", INT2FIX(#{c}));}
67
+ end
68
+ else
69
+ newf.puts l
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ dir_config('freeimage')
77
+
78
+ ok = have_header('FreeImage.h') &&
79
+ have_library('stdc++') && # sometimes required on OSX
80
+ have_library('freeimage', 'FreeImage_Load')
81
+
82
+ if(ok)
83
+ have_func('FreeImage_Rotate')
84
+ have_func('FreeImage_RotateClassic')
85
+ expand_constants
86
+ create_makefile("image_science_ext")
87
+ end