active_assets 0.2.3 → 0.2.4

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.
@@ -1,124 +0,0 @@
1
- require 'action_controller'
2
- require 'action_view'
3
- require 'rack/mount'
4
- require 'action_view'
5
- require 'rmagick'
6
- require 'fileutils'
7
-
8
- module ActiveAssets
9
- module ActiveSprites
10
- class RmagickRunner
11
- class AssetContext < ActionView::Base
12
- end
13
-
14
- include Magick
15
-
16
- DEFAULT_SPRITE = Image.new(0,0).freeze
17
-
18
- def initialize(sprites)
19
- @sprites = if ENV['SPRITE']
20
- sprites.select do |name, sprite|
21
- ENV['SPRITE'].split(',').map(&:strip).any? do |sp|
22
- # were going to be very forgiving
23
- name == sp ||
24
- name == sp.to_sym ||
25
- name == ::Rack::Mount::Utils.normalize_path(sp)
26
- end
27
- end.map(&:last)
28
- else
29
- sprites.values
30
- end
31
- end
32
-
33
- def generate!(railtie = Rails.application, debug = ENV['DEBUG'])
34
- p "Engine Class Name: #{railtie.class.name}" if debug
35
-
36
- context = setup_context(railtie)
37
-
38
- @sprites.each do |sprite|
39
- next if sprite.sprite_pieces.empty?
40
- sprite_path = sanitize_asset_path(context.image_path(sprite.path))
41
- p "Sprite Path: #{sprite_path}" if debug
42
- sprite_stylesheet_path = sanitize_asset_path(context.stylesheet_path(sprite.stylesheet_path))
43
- p "Sprite Stylesheet Path: #{sprite_stylesheet_path}" if debug
44
-
45
- orientation = sprite.orientation.to_s
46
- sprite_pieces = sprite.sprite_pieces
47
-
48
- begin
49
- sprite_piece_paths = sprite_pieces.map do |sp|
50
- File.join(railtie.config.paths.public.to_a.first, sanitize_asset_path(context.image_path(sp.path)))
51
- end
52
- image_list = ImageList.new(*sprite_piece_paths)
53
-
54
- offset = 0
55
-
56
- image_list.each_with_index do |image, i|
57
- sprite_pieces[i].details = SpritePiece::Details.new(
58
- sprite.url.present? ? sprite.url : sprite_path,
59
- orientation == Sprite::Orientation::VERTICAL ? 0 : offset,
60
- orientation == Sprite::Orientation::VERTICAL ? offset : 0,
61
- image.columns,
62
- image.rows
63
- )
64
- offset += orientation == Sprite::Orientation::VERTICAL ? image.rows : image.columns
65
- end
66
-
67
- @sprite = image_list.montage do
68
- self.tile = orientation == Sprite::Orientation::VERTICAL ? "1x#{sprite_pieces.size}" : "#{sprite_pieces.size}x1"
69
- self.geometry = "+0+0"
70
- self.background_color = 'transparent'
71
- self.matte_color = sprite.matte_color || '#bdbdbd'
72
- end
73
-
74
- @sprite.strip!
75
-
76
- stylesheet = SpriteStylesheet.new(sprite_pieces)
77
- stylesheet.write File.join(railtie.config.paths.public.to_a.first, sprite_stylesheet_path)
78
- write File.join(railtie.config.paths.public.to_a.first, sprite_path), sprite.quality
79
- ensure
80
- finish
81
- end
82
- end
83
- end
84
-
85
- private
86
- def write(path, quality = nil)
87
- FileUtils.mkdir_p(File.dirname(path))
88
- @sprite.write("#{File.extname(path)[1..-1]}:#{path}") do
89
- self.quality = quality || 75
90
- end
91
- end
92
-
93
- def finish
94
- @sprite.destroy! unless @sprite == DEFAULT_SPRITE
95
- @sprite = DEFAULT_SPRITE
96
- end
97
-
98
- def sanitize_asset_path(path)
99
- path.split('?').first
100
- end
101
-
102
- def setup_context(railtie)
103
- unless railtie.config.respond_to?(:action_controller)
104
- railtie.config.action_controller = ActiveSupport::OrderedOptions.new
105
-
106
- paths = railtie.config.paths
107
- options = railtie.config.action_controller
108
-
109
- options.assets_dir ||= paths.public.to_a.first
110
- options.javascripts_dir ||= paths.public.javascripts.to_a.first
111
- options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
112
-
113
- ActiveSupport.on_load(:action_controller) do
114
- options.each { |k,v| send("#{k}=", v) }
115
- end
116
- end
117
-
118
- controller = ActionController::Base.new
119
- AssetContext.new(railtie.config.action_controller, {}, controller)
120
- end
121
-
122
- end
123
- end
124
- end
@@ -1,623 +0,0 @@
1
- ###########################################################################
2
- # Represents an RGB[http://en.wikipedia.org/wiki/Rgb] colour.
3
- class RGBColour
4
- # Red, green and blue values must fall in the range 0..255.
5
- def initialize(red, green, blue)
6
- ok = [red, green, blue].inject(true) {|ok,c| ok &= c.between?(0,255)}
7
- unless ok
8
- raise ArgumentError, "invalid RGB parameters: #{[red, green, blue].inspect}"
9
- end
10
- @red, @green, @blue = red, green, blue
11
- end
12
- attr_reader :red, :green, :blue
13
- alias_method :r, :red
14
- alias_method :g, :green
15
- alias_method :b, :blue
16
-
17
- # the difference between two colours
18
- def -(a_colour)
19
- (@red - a_colour.red).abs +
20
- (@green - a_colour.green).abs +
21
- (@blue - a_colour.blue).abs
22
- end
23
-
24
- # Return the list of [red, green, blue] values.
25
- # RGBColour.new(100,150,200).values # => [100, 150, 200]
26
- # call-seq:
27
- # values -> array
28
- #
29
- def values
30
- [@red, @green, @blue]
31
- end
32
-
33
- # Equality test: two RGBColour objects are equal if they have the same
34
- # red, green and blue values.
35
- # call-seq:
36
- # ==(a_colour) -> true or false
37
- #
38
- def ==(a_colour)
39
- values == a_colour.values
40
- end
41
-
42
- # Comparison test: compares two RGBColour objects based on their #luminosity value
43
- # call-seq:
44
- # <=>(a_colour) -> -1, 0, +1
45
- #
46
- def <=>(a_colour)
47
- self.luminosity <=> a_colour.luminosity
48
- end
49
-
50
- # Calculate a integer luminosity value, in the range 0..255
51
- # RGBColour.new(100,150,200).luminosity # => 142
52
- # call-seq:
53
- # luminosity -> int
54
- #
55
- def luminosity
56
- Integer(0.2126*@red + 0.7152*@green + 0.0722*@blue)
57
- end
58
-
59
- # Return a new RGBColour value where all the red, green, blue values are the
60
- # #luminosity value.
61
- # RGBColour.new(100,150,200).to_grayscale.values # => [142, 142, 142]
62
- # call-seq:
63
- # to_grayscale -> a_colour
64
- #
65
- def to_grayscale
66
- l = luminosity
67
- self.class.new(l, l, l)
68
- end
69
-
70
- # Return a new RGBColour object given an iteration value for the Pixmap.mandelbrot
71
- # method.
72
- def self.mandel_colour(i)
73
- self.new( 16*(i % 15), 32*(i % 7), 8*(i % 31) )
74
- end
75
-
76
- RED = RGBColour.new(255,0,0)
77
- GREEN = RGBColour.new(0,255,0)
78
- BLUE = RGBColour.new(0,0,255)
79
- YELLOW= RGBColour.new(255,255,0)
80
- BLACK = RGBColour.new(0,0,0)
81
- WHITE = RGBColour.new(255,255,255)
82
- end
83
-
84
- ###########################################################################
85
- # A Pixel represents an (x,y) point in a Pixmap.
86
- Pixel = Struct.new(:x, :y)
87
-
88
- ###########################################################################
89
- class Pixmap
90
- def initialize(width, height)
91
- @width = width
92
- @height = height
93
- @data = fill(RGBColour::WHITE)
94
- end
95
- attr_reader :width, :height
96
-
97
- def fill(colour)
98
- @data = Array.new(@width) {Array.new(@height, colour)}
99
- end
100
-
101
- def -(a_pixmap)
102
- if @width != a_pixmap.width or @height != a_pixmap.height
103
- raise ArgumentError, "can't compare images with different sizes"
104
- end
105
- sum = 0
106
- each_pixel {|x,y| sum += self[x,y] - a_pixmap[x,y]}
107
- Float(sum) / (@width * @height * 255 * 3)
108
- end
109
-
110
- def validate_pixel(x,y)
111
- unless x.between?(0, @width-1) and y.between?(0, @height-1)
112
- raise ArgumentError, "requested pixel (#{x}, #{y}) is outside dimensions of this bitmap"
113
- end
114
- end
115
-
116
- ###############################################
117
- def [](x,y)
118
- validate_pixel(x,y)
119
- @data[x][y]
120
- end
121
- alias_method :get_pixel, :[]
122
-
123
- def []=(x,y,colour)
124
- validate_pixel(x,y)
125
- @data[x][y] = colour
126
- end
127
- alias_method :set_pixel, :[]=
128
-
129
- def each_pixel
130
- if block_given?
131
- @height.times {|y| @width.times {|x| yield x,y}}
132
- else
133
- to_enum(:each_pixel)
134
- end
135
- end
136
-
137
- ###############################################
138
- # write to file/stream
139
- PIXMAP_FORMATS = ["P3", "P6"] # implemented output formats
140
- PIXMAP_BINARY_FORMATS = ["P6"] # implemented output formats which are binary
141
-
142
- def write_ppm(ios, format="P6")
143
- if not PIXMAP_FORMATS.include?(format)
144
- raise NotImplementedError, "pixmap format #{format} has not been implemented"
145
- end
146
- ios.puts format, "#{@width} #{@height}", "255"
147
- ios.binmode if PIXMAP_BINARY_FORMATS.include?(format)
148
- @height.times do |y|
149
- @width.times do |x|
150
- case format
151
- when "P3" then ios.print @data[x][y].values.join(" "),"\n"
152
- when "P6" then ios.print @data[x][y].values.pack('C3')
153
- end
154
- end
155
- end
156
- end
157
-
158
- def save(filename, opts={:format=>"P6"})
159
- File.open(filename, 'w') do |f|
160
- write_ppm(f, opts[:format])
161
- end
162
- end
163
- alias_method :write, :save
164
-
165
- def print(opts={:format=>"P6"})
166
- write_ppm($stdout, opts[:format])
167
- end
168
-
169
- def save_as_jpeg(filename, quality=75)
170
- # using the ImageMagick convert tool
171
- begin
172
- pipe = IO.popen("convert ppm:- -quality #{quality} jpg:#{filename}", 'w')
173
- write_ppm(pipe)
174
- rescue SystemCallError => e
175
- warn "problem writing data to 'convert' utility -- does it exist in your $PATH?"
176
- ensure
177
- pipe.close rescue false
178
- end
179
- end
180
-
181
- ###############################################
182
- # read from file/pipe
183
- def self.read_ppm(ios)
184
- format = ios.gets.chomp
185
- width, height = ios.gets.chomp.split.map {|n| n.to_i }
186
- max_colour = ios.gets.chomp
187
-
188
- if (not PIXMAP_FORMATS.include?(format)) or
189
- width < 1 or height < 1 or
190
- max_colour != '255'
191
- then
192
- ios.close
193
- raise StandardError, "file '#{filename}' does not start with the expected header"
194
- end
195
- ios.binmode if PIXMAP_BINARY_FORMATS.include?(format)
196
-
197
- bitmap = self.new(width, height)
198
- height.times do |y|
199
- width.times do |x|
200
- # read 3 bytes
201
- red, green, blue = case format
202
- when 'P3' then ios.gets.chomp.split
203
- when 'P6' then ios.read(3).unpack('C3')
204
- end
205
- bitmap[x,y] = RGBColour.new(red, green, blue)
206
- end
207
- end
208
- ios.close
209
- bitmap
210
- end
211
-
212
- def self.open(filename)
213
- read_ppm(File.open(filename, 'r'))
214
- end
215
-
216
- def self.open_from_jpeg(filename)
217
- unless File.readable?(filename)
218
- raise ArgumentError, "#{filename} does not exists or is not readable."
219
- end
220
- begin
221
- pipe = IO.popen("convert jpg:#{filename} ppm:-", 'r')
222
- read_ppm(pipe)
223
- rescue SystemCallError => e
224
- warn "problem reading data from 'convert' utility -- does it exist in your $PATH?"
225
- ensure
226
- pipe.close rescue false
227
- end
228
- end
229
-
230
- ###############################################
231
- # conversion methods
232
- def to_grayscale
233
- gray = self.class.new(@width, @height)
234
- @width.times do |x|
235
- @height.times do |y|
236
- gray[x,y] = self[x,y].to_grayscale
237
- end
238
- end
239
- gray
240
- end
241
-
242
- ###############################################
243
- def draw_line(p1, p2, colour)
244
- validate_pixel(p1.x, p2.y)
245
- validate_pixel(p2.x, p2.y)
246
-
247
- x1, y1 = p1.x, p1.y
248
- x2, y2 = p2.x, p2.y
249
-
250
- steep = (y2 - y1).abs > (x2 - x1).abs
251
- if steep
252
- x1, y1 = y1, x1
253
- x2, y2 = y2, x2
254
- end
255
- if x1 > x2
256
- x1, x2 = x2, x1
257
- y1, y2 = y2, y1
258
- end
259
-
260
- deltax = x2 - x1
261
- deltay = (y2 - y1).abs
262
- error = deltax / 2
263
- ystep = y1 < y2 ? 1 : -1
264
-
265
- y = y1
266
- x1.upto(x2) do |x|
267
- pixel = steep ? [y,x] : [x,y]
268
- self[*pixel] = colour
269
- error -= deltay
270
- if error < 0
271
- y += ystep
272
- error += deltax
273
- end
274
- end
275
- end
276
-
277
- ###############################################
278
- def draw_line_antialised(p1, p2, colour)
279
- x1, y1 = p1.x, p1.y
280
- x2, y2 = p2.x, p2.y
281
-
282
- steep = (y2 - y1).abs > (x2 - x1).abs
283
- if steep
284
- x1, y1 = y1, x1
285
- x2, y2 = y2, x2
286
- end
287
- if x1 > x2
288
- x1, x2 = x2, x1
289
- y1, y2 = y2, y1
290
- end
291
- deltax = x2 - x1
292
- deltay = (y2 - y1).abs
293
- gradient = 1.0 * deltay / deltax
294
-
295
- # handle the first endpoint
296
- xend = x1.round
297
- yend = y1 + gradient * (xend - x1)
298
- xgap = (x1 + 0.5).rfpart
299
- xpxl1 = xend
300
- ypxl1 = yend.truncate
301
- put_colour(xpxl1, ypxl1, colour, steep, yend.rfpart * xgap)
302
- put_colour(xpxl1, ypxl1 + 1, colour, steep, yend.fpart * xgap)
303
- itery = yend + gradient
304
-
305
- # handle the second endpoint
306
- xend = x2.round
307
- yend = y2 + gradient * (xend - x2)
308
- xgap = (x2 + 0.5).rfpart
309
- xpxl2 = xend
310
- ypxl2 = yend.truncate
311
- put_colour(xpxl2, ypxl2, colour, steep, yend.rfpart * xgap)
312
- put_colour(xpxl2, ypxl2 + 1, colour, steep, yend.fpart * xgap)
313
-
314
- # in between
315
- (xpxl1 + 1).upto(xpxl2 - 1).each do |x|
316
- put_colour(x, itery.truncate, colour, steep, itery.rfpart)
317
- put_colour(x, itery.truncate + 1, colour, steep, itery.fpart)
318
- itery = itery + gradient
319
- end
320
- end
321
-
322
- def put_colour(x, y, colour, steep, c)
323
- x, y = y, x if steep
324
- self[x, y] = anti_alias(colour, self[x, y], c)
325
- end
326
-
327
- def anti_alias(new, old, ratio)
328
- blended = new.values.zip(old.values).map {|n, o| (n*ratio + o*(1.0 - ratio)).round}
329
- RGBColour.new(*blended)
330
- end
331
-
332
- ###############################################
333
- def draw_circle(pixel, radius, colour)
334
- validate_pixel(pixel.x, pixel.y)
335
-
336
- self[pixel.x, pixel.y + radius] = colour
337
- self[pixel.x, pixel.y - radius] = colour
338
- self[pixel.x + radius, pixel.y] = colour
339
- self[pixel.x - radius, pixel.y] = colour
340
-
341
- f = 1 - radius
342
- ddF_x = 1
343
- ddF_y = -2 * radius
344
- x = 0
345
- y = radius
346
- while x < y
347
- if f >= 0
348
- y -= 1
349
- ddF_y += 2
350
- f += ddF_y
351
- end
352
- x += 1
353
- ddF_x += 2
354
- f += ddF_x
355
- self[pixel.x + x, pixel.y + y] = colour
356
- self[pixel.x + x, pixel.y - y] = colour
357
- self[pixel.x - x, pixel.y + y] = colour
358
- self[pixel.x - x, pixel.y - y] = colour
359
- self[pixel.x + y, pixel.y + x] = colour
360
- self[pixel.x + y, pixel.y - x] = colour
361
- self[pixel.x - y, pixel.y + x] = colour
362
- self[pixel.x - y, pixel.y - x] = colour
363
- end
364
- end
365
-
366
- ###############################################
367
- def flood_fill(pixel, new_colour)
368
- current_colour = self[pixel.x, pixel.y]
369
- queue = RasterQueue.new
370
- queue.enqueue(pixel)
371
- until queue.empty?
372
- p = queue.dequeue
373
- if self[p.x, p.y] == current_colour
374
- west = find_border(p, current_colour, :west)
375
- east = find_border(p, current_colour, :east)
376
- draw_line(west, east, new_colour)
377
- q = west
378
- while q.x <= east.x
379
- [:north, :south].each do |direction|
380
- n = neighbour(q, direction)
381
- queue.enqueue(n) if self[n.x, n.y] == current_colour
382
- end
383
- q = neighbour(q, :east)
384
- end
385
- end
386
- end
387
- end
388
-
389
- def neighbour(pixel, direction)
390
- case direction
391
- when :north then Pixel[pixel.x, pixel.y - 1]
392
- when :south then Pixel[pixel.x, pixel.y + 1]
393
- when :east then Pixel[pixel.x + 1, pixel.y]
394
- when :west then Pixel[pixel.x - 1, pixel.y]
395
- end
396
- end
397
-
398
- def find_border(pixel, colour, direction)
399
- nextp = neighbour(pixel, direction)
400
- while self[nextp.x, nextp.y] == colour
401
- pixel = nextp
402
- nextp = neighbour(pixel, direction)
403
- end
404
- pixel
405
- end
406
-
407
- ###############################################
408
- def median_filter(radius=3)
409
- if radius.even?
410
- radius += 1
411
- end
412
- filtered = self.class.new(@width, @height)
413
-
414
-
415
- $stdout.puts "processing #{@height} rows"
416
- pb = ProgressBar.new(@height) if $DEBUG
417
-
418
- @height.times do |y|
419
- @width.times do |x|
420
- window = []
421
- (x - radius).upto(x + radius).each do |win_x|
422
- (y - radius).upto(y + radius).each do |win_y|
423
- win_x = 0 if win_x < 0
424
- win_y = 0 if win_y < 0
425
- win_x = @width-1 if win_x >= @width
426
- win_y = @height-1 if win_y >= @height
427
- window << self[win_x, win_y]
428
- end
429
- end
430
- # median
431
- filtered[x, y] = window.sort[window.length / 2]
432
- end
433
- pb.update(y) if $DEBUG
434
- end
435
-
436
- pb.close if $DEBUG
437
-
438
- filtered
439
- end
440
-
441
- ###############################################
442
- def histogram
443
- histogram = Hash.new(0)
444
- @height.times do |y|
445
- @width.times do |x|
446
- histogram[self[x,y].luminosity] += 1
447
- end
448
- end
449
- histogram
450
- end
451
-
452
- def to_blackandwhite
453
- hist = histogram
454
-
455
- # find the median luminosity
456
- median = nil
457
- sum = 0
458
- hist.keys.sort.each do |lum|
459
- sum += hist[lum]
460
- if sum > @height * @width / 2
461
- median = lum
462
- break
463
- end
464
- end
465
-
466
- # create the black and white image
467
- bw = self.class.new(@width, @height)
468
- @height.times do |y|
469
- @width.times do |x|
470
- bw[x,y] = self[x,y].luminosity < median ? RGBColour::BLACK : RGBColour::WHITE
471
- end
472
- end
473
- bw
474
- end
475
-
476
- def save_as_blackandwhite(filename)
477
- to_blackandwhite.save(filename)
478
- end
479
-
480
- ###############################################
481
- def draw_bezier_curve(points, colour)
482
- # ensure the points are increasing along the x-axis
483
- points = points.sort_by {|p| [p.x, p.y]}
484
- xmin = points[0].x
485
- xmax = points[-1].x
486
- increment = 2
487
- prev = points[0]
488
- ((xmin + increment) .. xmax).step(increment) do |x|
489
- t = 1.0 * (x - xmin) / (xmax - xmin)
490
- p = Pixel[x, bezier(t, points).round]
491
- draw_line(prev, p, colour)
492
- prev = p
493
- end
494
- end
495
-
496
- # the generalized n-degree Bezier summation
497
- def bezier(t, points)
498
- n = points.length - 1
499
- points.each_with_index.inject(0.0) do |sum, (point, i)|
500
- sum += n.choose(i) * (1-t)**(n - i) * t**i * point.y
501
- end
502
- end
503
-
504
- ###############################################
505
- def self.mandelbrot(width, height)
506
- mandel = Pixmap.new(width,height)
507
- pb = ProgressBar.new(width) if $DEBUG
508
- width.times do |x|
509
- height.times do |y|
510
- x_ish = Float(x - width*11/15) / (width/3)
511
- y_ish = Float(y - height/2) / (height*3/10)
512
- mandel[x,y] = RGBColour.mandel_colour(mandel_iters(x_ish, y_ish))
513
- end
514
- pb.update(x) if $DEBUG
515
- end
516
- pb.close if $DEBUG
517
- mandel
518
- end
519
-
520
- def self.mandel_iters(cx,cy)
521
- x = y = 0.0
522
- count = 0
523
- while Math.hypot(x,y) < 2 and count < 255
524
- x, y = (x**2 - y**2 + cx), (2*x*y + cy)
525
- count += 1
526
- end
527
- count
528
- end
529
-
530
- ###############################################
531
- # Apply a convolution kernel to a whole image
532
- def convolute(kernel)
533
- newimg = Pixmap.new(@width, @height)
534
- pb = ProgressBar.new(@width) if $DEBUG
535
- @width.times do |x|
536
- @height.times do |y|
537
- apply_kernel(x, y, kernel, newimg)
538
- end
539
- pb.update(x) if $DEBUG
540
- end
541
- pb.close if $DEBUG
542
- newimg
543
- end
544
-
545
- # Applies a convolution kernel to produce a single pixel in the destination
546
- def apply_kernel(x, y, kernel, newimg)
547
- x0 = [0, x-1].max
548
- y0 = [0, y-1].max
549
- x1 = x
550
- y1 = y
551
- x2 = [@width-1, x+1].min
552
- y2 = [@height-1, y+1].min
553
-
554
- r = g = b = 0.0
555
- [x0, x1, x2].zip(kernel).each do |xx, kcol|
556
- [y0, y1, y2].zip(kcol).each do |yy, k|
557
- r += k * self[xx,yy].r
558
- g += k * self[xx,yy].g
559
- b += k * self[xx,yy].b
560
- end
561
- end
562
- newimg[x,y] = RGBColour.new(luma(r), luma(g), luma(b))
563
- end
564
-
565
- # Function for clamping values to those that we can use with colors
566
- def luma(value)
567
- if value < 0
568
- 0
569
- elsif value > 255
570
- 255
571
- else
572
- value
573
- end
574
- end
575
- end
576
-
577
-
578
- ###########################################################################
579
- # Utilities
580
- class ProgressBar
581
- def initialize(max)
582
- $stdout.sync = true
583
- @progress_max = max
584
- @progress_pos = 0
585
- @progress_view = 68
586
- $stdout.print "[#{'-'*@progress_view}]\r["
587
- end
588
-
589
- def update(n)
590
- new_pos = n * @progress_view/@progress_max
591
- if new_pos > @progress_pos
592
- @progress_pos = new_pos
593
- $stdout.print '='
594
- end
595
- end
596
-
597
- def close
598
- $stdout.puts '=]'
599
- end
600
- end
601
-
602
- class RasterQueue < Array
603
- alias_method :enqueue, :push
604
- alias_method :dequeue, :shift
605
- end
606
-
607
- class Numeric
608
- def fpart
609
- self - self.truncate
610
- end
611
- def rfpart
612
- 1.0 - self.fpart
613
- end
614
- end
615
-
616
- class Integer
617
- def choose(k)
618
- self.factorial / (k.factorial * (self - k).factorial)
619
- end
620
- def factorial
621
- (2 .. self).reduce(1, :*)
622
- end
623
- end