active_assets 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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