active_assets 0.2.0.rc4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +55 -0
- data/.gitignore +13 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +2 -4
- data/Rakefile +18 -0
- data/active_assets.gemspec +24 -0
- data/lib/active_assets/active_sprites/runner.rb +8 -3
- data/lib/active_assets/active_sprites/sprite.rb +9 -4
- data/lib/tasks/active_sprites/sprites.rake +2 -2
- data/script/console +1 -0
- data/test/active_assets/active_sprites/runner_test.rb +54 -7
- data/test/active_assets/active_sprites/sprite_test.rb +16 -0
- data/test/fixtures/rails_root/config/application.rb +1 -2
- data/test/helper.rb +8 -36
- data/test/raster_graphics.rb +623 -0
- data/test/raster_graphics_test.rb +82 -0
- metadata +32 -64
- data/test/fixtures/sinatra_root/active_assets_test_app.rb +0 -12
- data/test/fixtures/sinatra_root/views/layout.erb +0 -7
- data/test/fixtures/sinatra_root/views/sprite.erb +0 -3
data/.autotest
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'autotest/restart'
|
2
|
+
require 'autotest/bundler'
|
3
|
+
|
4
|
+
Autotest.add_hook :initialize do |at|
|
5
|
+
at.testlib = 'test/unit test/autocolor'
|
6
|
+
|
7
|
+
# Remove the old test unit mappings
|
8
|
+
at.clear_mappings
|
9
|
+
|
10
|
+
# Don't track other dirs, this just burns cpu.
|
11
|
+
(File.read('.gitignore').split("\n") + Dir['test/fixtures/**/*']).each do |ignore|
|
12
|
+
next if ignore.nil? or ignore.empty?
|
13
|
+
at.add_exception ignore
|
14
|
+
end
|
15
|
+
|
16
|
+
# Test::Unit is normally test_, so autotest doesn't have this mapping. Tests
|
17
|
+
# want to match themselves, that is, if there's no changes, run them all.
|
18
|
+
at.add_mapping(%r%^test/.*_test\.rb$%) { |f, _| f }
|
19
|
+
|
20
|
+
# Allow for matches of lib files to test files in a flat way
|
21
|
+
at.add_mapping(%r%^lib/(.*)\.rb$%) do |f, md|
|
22
|
+
at.files_matching( %r%^test/#{md[1]}_test\.rb$%)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Make sure that we run all tests if the helper changes:
|
26
|
+
at.add_mapping(%r%^test/helper\.rb$%) do |f, _|
|
27
|
+
at.files_matching %r%.*_test\.rb%
|
28
|
+
end
|
29
|
+
|
30
|
+
# If bundle did something, run all tests again
|
31
|
+
at.add_mapping(%r%^Gemfile\.lock$%) do |f, _|
|
32
|
+
at.files_matching %r%.*_test\.rb%
|
33
|
+
end
|
34
|
+
|
35
|
+
# If update support, run all tests
|
36
|
+
at.add_mapping(%r%^test/support/.*\.rb$%) do |f, _|
|
37
|
+
at.files_matching %r%.*_test\.rb%
|
38
|
+
end
|
39
|
+
|
40
|
+
def at.path_to_classname(path)
|
41
|
+
file = File.basename(path, '.rb')
|
42
|
+
|
43
|
+
file.gsub!('test_', '')
|
44
|
+
file.gsub!('_test', '')
|
45
|
+
file.capitalize + 'Test'
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# If the Gemfile gets updated, run bundle install
|
51
|
+
Autotest.add_hook :updated do |at, *args|
|
52
|
+
if args.flatten.grep(%r%^Gemfile$|^.*\.gemspec$%).any?
|
53
|
+
system 'bundle'
|
54
|
+
end
|
55
|
+
end
|
data/.gitignore
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
.DS_Store
|
2
|
+
.rvmrc
|
3
|
+
pkg/*
|
4
|
+
*.gem
|
5
|
+
.bundle
|
6
|
+
Gemfile.lock
|
7
|
+
doc
|
8
|
+
|
9
|
+
test/fixtures/rails_root/log
|
10
|
+
test/fixtures/rails_root/public/stylesheets/cache
|
11
|
+
test/fixtures/rails_root/public/javascripts/cache
|
12
|
+
test/fixtures/rails_root/public/stylesheets/sprites
|
13
|
+
test/fixtures/rails_root/public/images/sprites
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-2011 Sam Woodard - http://github.com/shwoodard
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ A Railtie that provides a full asset management system, including support for de
|
|
8
8
|
Gemfile
|
9
9
|
-------
|
10
10
|
|
11
|
-
gem 'active_assets'
|
11
|
+
gem 'active_assets'
|
12
12
|
|
13
13
|
In your rails app
|
14
14
|
-----------------
|
@@ -99,7 +99,7 @@ ActiveSprites allows you to generate sprites within your Rails apps with `rake s
|
|
99
99
|
Rails.application.sprites do
|
100
100
|
sprite 'sprites/world_flags.png' => 'sprites/world_flags.css'
|
101
101
|
_"sprite_images/world_flags/Argentina.gif" => ".flags.argentina"
|
102
|
-
_"
|
102
|
+
_"sprite_images/world_flags/Australia.gif" => ".flags.australia"
|
103
103
|
...
|
104
104
|
end
|
105
105
|
end
|
@@ -163,5 +163,3 @@ It is possible to add all of the world flags! Haha, see the following example,
|
|
163
163
|
`_` and `sp` are aliases for `sprite_piece`
|
164
164
|
|
165
165
|
Also, you will notice that I gave a symbol for the sprite instead of a mapping. This will assume that you wish to store your sprite at `path/to/your/public/images/sprites/world_flags.png` and you wish to store your stylesheet at `path/to/your/public/stylesheets/sprites/world_flags.css`.
|
166
|
-
|
167
|
-
Copyright © Sam Woodard 2011. Release under the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'bundler'
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.pattern = 'test/**/*_test.rb'
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :environment do
|
13
|
+
load 'test/fixtures/rails_root/config/environment.rb'
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir['lib/tasks/**/*.rake'].each {|f| load f}
|
17
|
+
|
18
|
+
task :default => :test
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = "active_assets"
|
4
|
+
s.version = '0.2.0'
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.authors = ["Sam Woodard"]
|
7
|
+
s.email = ["sam@wildfireapp.com"]
|
8
|
+
s.homepage = "http://github.com/shwoodard/active_assets"
|
9
|
+
s.summary = %q{A Railtie that provides a full asset management system, including support for development and deployment.}
|
10
|
+
s.description = %q{A Railtie that provides a full asset management system, including support for development and deployment. It is comprised of two libraries, ActiveSprites and ActiveExpansions. ActiveSprites generates sprites and their corresponding stylesheet from dsl definition. ActiveExpansions manages javascript and css, including concatenation support for deployment, using Rails expansions plus a dsl.}
|
11
|
+
|
12
|
+
s.rubyforge_project = "activeassets"
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency 'rmagick'
|
20
|
+
s.add_development_dependency 'css_parser', '~>1.1.5'
|
21
|
+
s.add_development_dependency "rails", "~>3.0.3"
|
22
|
+
s.add_development_dependency "test-unit", "> 2.0"
|
23
|
+
s.add_development_dependency "ZenTest", "~>4.4.2"
|
24
|
+
end
|
@@ -66,11 +66,14 @@ module ActiveAssets
|
|
66
66
|
self.tile = orientation == Sprite::Orientation::VERTICAL ? "1x#{sprite_pieces.size}" : "#{sprite_pieces.size}x1"
|
67
67
|
self.geometry = "+0+0"
|
68
68
|
self.background_color = 'transparent'
|
69
|
+
self.matte_color = sprite.matte_color || '#bdbdbd'
|
69
70
|
end
|
70
71
|
|
72
|
+
@sprite.strip!
|
73
|
+
|
71
74
|
stylesheet = SpriteStylesheet.new(sprite_path, sprite_pieces)
|
72
75
|
stylesheet.write File.join(Rails.application.config.paths.public.to_a.first, sprite_stylesheet_path)
|
73
|
-
write File.join(Rails.application.config.paths.public.to_a.first, sprite_path)
|
76
|
+
write File.join(Rails.application.config.paths.public.to_a.first, sprite_path), sprite.quality
|
74
77
|
ensure
|
75
78
|
finish
|
76
79
|
end
|
@@ -82,9 +85,11 @@ module ActiveAssets
|
|
82
85
|
@context
|
83
86
|
end
|
84
87
|
|
85
|
-
def write(path)
|
88
|
+
def write(path, quality = nil)
|
86
89
|
FileUtils.mkdir_p(File.dirname(path))
|
87
|
-
@sprite.write("
|
90
|
+
@sprite.write("#{File.extname(path)[1..-1]}:#{path}") do
|
91
|
+
self.quality = quality || 75
|
92
|
+
end
|
88
93
|
end
|
89
94
|
|
90
95
|
def finish
|
@@ -21,7 +21,7 @@ module ActiveAssets
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
attr_reader :path, :stylesheet_path, :name, :orientation
|
24
|
+
attr_reader :path, :stylesheet_path, :name, :orientation, :quality, :matte_color
|
25
25
|
|
26
26
|
def initialize
|
27
27
|
# Ordered Hash?
|
@@ -48,7 +48,8 @@ module ActiveAssets
|
|
48
48
|
@path ||= sprite_path
|
49
49
|
@name = options.delete(:as) || sprite_path
|
50
50
|
@stylesheet_path = stylesheet_path
|
51
|
-
@orientation = options
|
51
|
+
@orientation = options.delete(:orientation) || :vertical
|
52
|
+
options.each {|k,v| send("#{k}=",v)}
|
52
53
|
valid!
|
53
54
|
instance_eval(&blk) if block_given?
|
54
55
|
self
|
@@ -81,8 +82,12 @@ module ActiveAssets
|
|
81
82
|
|
82
83
|
private
|
83
84
|
|
84
|
-
def
|
85
|
-
@
|
85
|
+
def matte_color=(val)
|
86
|
+
@matte_color = val
|
87
|
+
end
|
88
|
+
|
89
|
+
def quality=(val)
|
90
|
+
@quality = val
|
86
91
|
end
|
87
92
|
end
|
88
93
|
end
|
data/script/console
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
irb -I./test/fixtures/rails_root -rrubygems -rbundler/setup -rconfig/environment
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
2
|
require 'fileutils'
|
3
|
+
require 'rmagick'
|
4
|
+
require 'css_parser'
|
3
5
|
|
4
6
|
class RunnerTest < Test::Unit::TestCase
|
5
|
-
|
7
|
+
include Magick
|
6
8
|
def setup
|
7
9
|
initialize_application_or_load_sprites!
|
8
10
|
|
@@ -20,15 +22,60 @@ class RunnerTest < Test::Unit::TestCase
|
|
20
22
|
tear_down_assets
|
21
23
|
end
|
22
24
|
|
25
|
+
Selector = Struct.new(:selector, :x, :y, :width, :height)
|
23
26
|
|
24
27
|
def test_generate
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
sprite = Rails.application.sprites['sprites/4.png']
|
29
|
+
orientation = sprite.orientation
|
30
|
+
sprite_pieces = sprite.sprite_pieces
|
31
|
+
sprite_image = Image.read(Rails.root.join('public/images', sprite.path)).first
|
32
|
+
|
33
|
+
stylesheet_path = Rails.root.join('public/stylesheets', sprite.stylesheet_path)
|
34
|
+
parser = CssParser::Parser.new
|
35
|
+
parser.load_file!(File.basename(stylesheet_path), File.dirname(stylesheet_path), :screen)
|
36
|
+
|
37
|
+
sprite_pieces_with_selector_data = []
|
28
38
|
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
parser.each_selector do |selectors, declarations, specificity|
|
40
|
+
sprite_piece = sprite_pieces.find {|sp| sp.css_selector == selectors }
|
41
|
+
width = declarations[%r{width:\s*(\d+)(?:px)?;}, 1].to_i
|
42
|
+
height = declarations[%r{height:\s*(\d+)(?:px)?;}, 1].to_i
|
43
|
+
background = declarations[%r{background:\s*([^;]+)}, 1]
|
44
|
+
x = background[%r{\s-?(\d+)(?:px)?\s}, 1].to_i
|
45
|
+
y = background[%r{\s-?(\d+)(?:px)?$}, 1].to_i
|
46
|
+
sprite_pieces_with_selector_data << [sprite_piece, Selector.new(selectors, x, y, width, height)]
|
47
|
+
end
|
48
|
+
|
49
|
+
sprite_pieces_with_selector_data.each do |sp, selector_data|
|
50
|
+
begin
|
51
|
+
sprite_piece_path = Rails.root.join('public/images', sp.path)
|
52
|
+
sprite_piece_image = Image.read(sprite_piece_path).first
|
53
|
+
curr_sprite_image = sprite_image.crop(
|
54
|
+
selector_data.x,
|
55
|
+
selector_data.y,
|
56
|
+
selector_data.width,
|
57
|
+
selector_data.height
|
58
|
+
)
|
59
|
+
curr_sprite_image_file = Tempfile.new("curr_sprite_img.ppm")
|
60
|
+
curr_sprite_image.write "ppm:#{curr_sprite_image_file.path}"
|
61
|
+
curr_sprite_piece_image_bmp = Tempfile.new("curr_sprite_piece_image_bmp.ppm")
|
62
|
+
sprite_piece_image.write "ppm:#{curr_sprite_piece_image_bmp.path}"
|
63
|
+
pd = percent_difference(curr_sprite_image_file.path, curr_sprite_piece_image_bmp.path)
|
64
|
+
assert pd <= 0.25
|
65
|
+
ensure
|
66
|
+
sprite_piece_image.destroy! if sprite_piece_image
|
67
|
+
curr_sprite_image.destroy! if curr_sprite_image
|
68
|
+
curr_sprite_image_file.close if curr_sprite_image_file
|
69
|
+
curr_sprite_piece_image_bmp.close if curr_sprite_piece_image_bmp
|
70
|
+
curr_sprite_piece_image_bmp = nil
|
71
|
+
curr_sprite_image_file = nil
|
72
|
+
curr_sprite_image = nil
|
73
|
+
sprite_piece_image = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
sprite_image.destroy! if sprite_image
|
78
|
+
sprite_image = nil
|
32
79
|
end
|
33
80
|
|
34
81
|
def test_sprite_exists
|
@@ -50,4 +50,20 @@ class SpriteTest < Test::Unit::TestCase
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
def test_quality
|
55
|
+
Rails.application.sprites do
|
56
|
+
sprite :bas, :quality => 50
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_equal 50, Rails.application.sprites[:bas].quality
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_matte_color
|
63
|
+
Rails.application.sprites do
|
64
|
+
sprite :foobas, :matte_color => '#FFFFFF'
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_equal '#FFFFFF', Rails.application.sprites[:foobas].matte_color
|
68
|
+
end
|
53
69
|
end
|
data/test/helper.rb
CHANGED
@@ -2,9 +2,6 @@ ENV['RAILS_ENV'] ||= 'test'
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'test/unit'
|
5
|
-
require 'capybara'
|
6
|
-
require 'capybara/dsl'
|
7
|
-
require 'culerity'
|
8
5
|
|
9
6
|
require 'rails/all'
|
10
7
|
require 'active_assets'
|
@@ -12,47 +9,22 @@ require 'active_assets'
|
|
12
9
|
require 'socket'
|
13
10
|
require 'timeout'
|
14
11
|
|
12
|
+
require 'raster_graphics'
|
13
|
+
|
15
14
|
TEST_RAILS_ROOT = File.expand_path('../fixtures/rails_root', __FILE__)
|
16
|
-
TEST_SINATRA_ROOT = File.expand_path('../fixtures/sinatra_root', __FILE__)
|
17
15
|
|
18
16
|
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each {|f| load f }
|
19
17
|
|
20
18
|
load File.join(TEST_RAILS_ROOT, 'config/application.rb')
|
21
|
-
load File.join(TEST_SINATRA_ROOT, 'active_assets_test_app.rb')
|
22
|
-
|
23
|
-
Capybara.configure do |capybara|
|
24
|
-
capybara.app = ActiveAssetsTestApp
|
25
|
-
capybara.default_driver = :culerity
|
26
|
-
capybara.default_selector = :css
|
27
|
-
end
|
28
|
-
|
29
|
-
def is_port_open?(ip, port)
|
30
|
-
begin
|
31
|
-
Timeout::timeout(1) do
|
32
|
-
begin
|
33
|
-
s = TCPSocket.new(ip, port)
|
34
|
-
s.close
|
35
|
-
return true
|
36
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
37
|
-
return false
|
38
|
-
end
|
39
|
-
end
|
40
|
-
rescue Timeout::Error
|
41
|
-
end
|
42
|
-
|
43
|
-
return false
|
44
|
-
end
|
45
|
-
|
46
|
-
if is_port_open?('127.0.0.1', '2113')
|
47
|
-
Culerity.jruby_invocation = "#{File.expand_path('../../vendor/bin/ng', __FILE__)} org.jruby.Main"
|
48
|
-
else
|
49
|
-
Culerity.jruby_invocation = "java -Xms32m -Xmx1024m -jar #{File.expand_path('../../vendor/jruby-complete-1.5.6.jar', __FILE__)}"
|
50
|
-
end
|
51
|
-
|
52
19
|
|
53
20
|
class Test::Unit::TestCase
|
54
21
|
include RailsHelper
|
55
|
-
|
22
|
+
|
23
|
+
include(Module.new do
|
24
|
+
def percent_difference(image_1_path, image_2_path)
|
25
|
+
Pixmap.open(image_1_path) - Pixmap.open(image_2_path)
|
26
|
+
end
|
27
|
+
end)
|
56
28
|
|
57
29
|
def sprites
|
58
30
|
Rails.application.sprites
|
@@ -0,0 +1,623 @@
|
|
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
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Pixmap
|
5
|
+
def ==(a_bitmap)
|
6
|
+
return false if @width != a_bitmap.width or @height != a_bitmap.height
|
7
|
+
@width.times do |x|
|
8
|
+
@height.times do |y|
|
9
|
+
return false if not self[x,y] == (a_bitmap[x,y])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RGBColourTest < Test::Unit::TestCase
|
17
|
+
def test_init
|
18
|
+
color = RGBColour.new(0,100,200)
|
19
|
+
assert_equal(100, color.g)
|
20
|
+
end
|
21
|
+
def test_constants
|
22
|
+
assert_equal([255,0,0], [RGBColour::RED.r,RGBColour::RED.g,RGBColour::RED.b])
|
23
|
+
assert_equal([0,255,0], [RGBColour::GREEN.r,RGBColour::GREEN.g,RGBColour::GREEN.b])
|
24
|
+
assert_equal([0,0,255], [RGBColour::BLUE.r,RGBColour::BLUE.g,RGBColour::BLUE.b])
|
25
|
+
end
|
26
|
+
def test_error
|
27
|
+
color = RGBColour.new(0,100,200)
|
28
|
+
assert_raise(ArgumentError) {RGBColour.new(0,0,256)}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PixmapTest < Test::Unit::TestCase
|
33
|
+
def setup
|
34
|
+
@w = 20
|
35
|
+
@h = 30
|
36
|
+
@bitmap = Pixmap.new(@w,@h)
|
37
|
+
end
|
38
|
+
|
39
|
+
def teardown
|
40
|
+
Dir[File.expand_path('../fixtures/*.ppm', __FILE__)].each do |f|
|
41
|
+
FileUtils.rm(f)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_init
|
46
|
+
assert_equal(@w, @bitmap.width)
|
47
|
+
assert_equal(@h, @bitmap.height)
|
48
|
+
assert_equal(RGBColour::WHITE, @bitmap.get_pixel(10,10))
|
49
|
+
end
|
50
|
+
def test_fill
|
51
|
+
@bitmap.fill(RGBColour::RED)
|
52
|
+
assert_equal(255,@bitmap[10,10].red)
|
53
|
+
assert_equal(0,@bitmap[10,10].green)
|
54
|
+
assert_equal(0,@bitmap[10,10].blue)
|
55
|
+
end
|
56
|
+
def test_get_pixel
|
57
|
+
assert_equal(@bitmap[5,6], @bitmap.get_pixel(5,6))
|
58
|
+
assert_raise(ArgumentError) {@bitmap[100,100]}
|
59
|
+
end
|
60
|
+
def test_grayscale
|
61
|
+
@bitmap.fill(RGBColour::BLUE)
|
62
|
+
@bitmap.height.times {|y| [9,10,11].each {|x| @bitmap[x,y]=RGBColour::GREEN}}
|
63
|
+
@bitmap.width.times {|x| [14,15,16].each {|y| @bitmap[x,y]=RGBColour::GREEN}}
|
64
|
+
@bitmap.save(File.expand_path('../fixtures/testcross.ppm', __FILE__))
|
65
|
+
Pixmap.open(File.expand_path('../fixtures/testcross.ppm', __FILE__)).to_grayscale.save(File.expand_path('../fixtures/testgray.ppm', __FILE__))
|
66
|
+
end
|
67
|
+
def test_save
|
68
|
+
@bitmap.fill(RGBColour::BLUE)
|
69
|
+
filename = File.expand_path('../fixtures/test.ppm', __FILE__)
|
70
|
+
@bitmap.save(filename)
|
71
|
+
expected_size = 3 + (@w.to_s.length + 1 + @h.to_s.length + 1) + 4 + (@w * @h * 3)
|
72
|
+
assert_equal(expected_size, File.size(filename))
|
73
|
+
end
|
74
|
+
def test_open
|
75
|
+
@bitmap.fill(RGBColour::RED)
|
76
|
+
@bitmap.set_pixel(10,15, RGBColour::WHITE)
|
77
|
+
filename = File.expand_path('../fixtures/test.ppm', __FILE__)
|
78
|
+
@bitmap.save(filename)
|
79
|
+
new = Pixmap.open(filename)
|
80
|
+
assert(@bitmap == new)
|
81
|
+
end
|
82
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_assets
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
9
|
- 0
|
10
|
-
|
11
|
-
- 4
|
12
|
-
version: 0.2.0.rc4
|
10
|
+
version: 0.2.0
|
13
11
|
platform: ruby
|
14
12
|
authors:
|
15
13
|
- Sam Woodard
|
@@ -17,7 +15,7 @@ autorequire:
|
|
17
15
|
bindir: bin
|
18
16
|
cert_chain: []
|
19
17
|
|
20
|
-
date: 2011-01-
|
18
|
+
date: 2011-01-25 00:00:00 -08:00
|
21
19
|
default_executable:
|
22
20
|
dependencies:
|
23
21
|
- !ruby/object:Gem::Dependency
|
@@ -41,34 +39,18 @@ dependencies:
|
|
41
39
|
requirements:
|
42
40
|
- - ~>
|
43
41
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
42
|
+
hash: 25
|
45
43
|
segments:
|
46
44
|
- 1
|
47
45
|
- 1
|
48
|
-
-
|
49
|
-
version: 1.1.
|
46
|
+
- 5
|
47
|
+
version: 1.1.5
|
50
48
|
type: :development
|
51
|
-
name:
|
49
|
+
name: css_parser
|
52
50
|
requirement: *id002
|
53
51
|
- !ruby/object:Gem::Dependency
|
54
52
|
prerelease: false
|
55
53
|
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
|
-
requirements:
|
58
|
-
- - ~>
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
hash: 17
|
61
|
-
segments:
|
62
|
-
- 1
|
63
|
-
- 2
|
64
|
-
- 7
|
65
|
-
version: 1.2.7
|
66
|
-
type: :development
|
67
|
-
name: thin
|
68
|
-
requirement: *id003
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
prerelease: false
|
71
|
-
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
54
|
none: false
|
73
55
|
requirements:
|
74
56
|
- - ~>
|
@@ -81,10 +63,10 @@ dependencies:
|
|
81
63
|
version: 3.0.3
|
82
64
|
type: :development
|
83
65
|
name: rails
|
84
|
-
requirement: *
|
66
|
+
requirement: *id003
|
85
67
|
- !ruby/object:Gem::Dependency
|
86
68
|
prerelease: false
|
87
|
-
version_requirements: &
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
88
70
|
none: false
|
89
71
|
requirements:
|
90
72
|
- - ">"
|
@@ -96,27 +78,10 @@ dependencies:
|
|
96
78
|
version: "2.0"
|
97
79
|
type: :development
|
98
80
|
name: test-unit
|
99
|
-
requirement: *
|
100
|
-
- !ruby/object:Gem::Dependency
|
101
|
-
prerelease: false
|
102
|
-
version_requirements: &id006 !ruby/object:Gem::Requirement
|
103
|
-
none: false
|
104
|
-
requirements:
|
105
|
-
- - ~>
|
106
|
-
- !ruby/object:Gem::Version
|
107
|
-
hash: 105
|
108
|
-
segments:
|
109
|
-
- 0
|
110
|
-
- 4
|
111
|
-
- 1
|
112
|
-
- 1
|
113
|
-
version: 0.4.1.1
|
114
|
-
type: :development
|
115
|
-
name: capybara
|
116
|
-
requirement: *id006
|
81
|
+
requirement: *id004
|
117
82
|
- !ruby/object:Gem::Dependency
|
118
83
|
prerelease: false
|
119
|
-
version_requirements: &
|
84
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
120
85
|
none: false
|
121
86
|
requirements:
|
122
87
|
- - ~>
|
@@ -129,7 +94,7 @@ dependencies:
|
|
129
94
|
version: 4.4.2
|
130
95
|
type: :development
|
131
96
|
name: ZenTest
|
132
|
-
requirement: *
|
97
|
+
requirement: *id005
|
133
98
|
description: A Railtie that provides a full asset management system, including support for development and deployment. It is comprised of two libraries, ActiveSprites and ActiveExpansions. ActiveSprites generates sprites and their corresponding stylesheet from dsl definition. ActiveExpansions manages javascript and css, including concatenation support for deployment, using Rails expansions plus a dsl.
|
134
99
|
email:
|
135
100
|
- sam@wildfireapp.com
|
@@ -140,6 +105,15 @@ extensions: []
|
|
140
105
|
extra_rdoc_files: []
|
141
106
|
|
142
107
|
files:
|
108
|
+
- .autotest
|
109
|
+
- .gitignore
|
110
|
+
- Gemfile
|
111
|
+
- MIT-LICENSE
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- active_assets.gemspec
|
115
|
+
- lib/active_assets.rb
|
116
|
+
- lib/active_assets/active_expansions.rb
|
143
117
|
- lib/active_assets/active_expansions/asset.rb
|
144
118
|
- lib/active_assets/active_expansions/asset_scope.rb
|
145
119
|
- lib/active_assets/active_expansions/assets.rb
|
@@ -150,22 +124,20 @@ files:
|
|
150
124
|
- lib/active_assets/active_expansions/railtie.rb
|
151
125
|
- lib/active_assets/active_expansions/stylesheets.rb
|
152
126
|
- lib/active_assets/active_expansions/type_inferrable.rb
|
153
|
-
- lib/active_assets/
|
127
|
+
- lib/active_assets/active_sprites.rb
|
154
128
|
- lib/active_assets/active_sprites/railtie.rb
|
155
129
|
- lib/active_assets/active_sprites/runner.rb
|
156
130
|
- lib/active_assets/active_sprites/sprite.rb
|
157
131
|
- lib/active_assets/active_sprites/sprite_piece.rb
|
158
132
|
- lib/active_assets/active_sprites/sprite_stylesheet.rb
|
159
133
|
- lib/active_assets/active_sprites/sprites.rb
|
160
|
-
- lib/active_assets/active_sprites.rb
|
161
134
|
- lib/active_assets/railtie.rb
|
162
|
-
- lib/active_assets.rb
|
163
135
|
- lib/rails/active_assets.rb
|
164
136
|
- lib/rails/active_expansions.rb
|
165
137
|
- lib/rails/active_sprites.rb
|
166
138
|
- lib/tasks/active_expansions/cache.rake
|
167
139
|
- lib/tasks/active_sprites/sprites.rake
|
168
|
-
-
|
140
|
+
- script/console
|
169
141
|
- test/active_assets/active_expansions/asset_test.rb
|
170
142
|
- test/active_assets/active_expansions/expansions_test.rb
|
171
143
|
- test/active_assets/active_sprites/runner_test.rb
|
@@ -233,10 +205,9 @@ files:
|
|
233
205
|
- test/fixtures/rails_root/public/javascripts/bas/bar.js
|
234
206
|
- test/fixtures/rails_root/public/stylesheets/bar/bas.css
|
235
207
|
- test/fixtures/rails_root/public/stylesheets/bas/bar.css
|
236
|
-
- test/fixtures/sinatra_root/active_assets_test_app.rb
|
237
|
-
- test/fixtures/sinatra_root/views/layout.erb
|
238
|
-
- test/fixtures/sinatra_root/views/sprite.erb
|
239
208
|
- test/helper.rb
|
209
|
+
- test/raster_graphics.rb
|
210
|
+
- test/raster_graphics_test.rb
|
240
211
|
- test/support/rails_helper.rb
|
241
212
|
has_rdoc: true
|
242
213
|
homepage: http://github.com/shwoodard/active_assets
|
@@ -259,14 +230,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
259
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
260
231
|
none: false
|
261
232
|
requirements:
|
262
|
-
- - "
|
233
|
+
- - ">="
|
263
234
|
- !ruby/object:Gem::Version
|
264
|
-
hash:
|
235
|
+
hash: 3
|
265
236
|
segments:
|
266
|
-
-
|
267
|
-
|
268
|
-
- 1
|
269
|
-
version: 1.3.1
|
237
|
+
- 0
|
238
|
+
version: "0"
|
270
239
|
requirements: []
|
271
240
|
|
272
241
|
rubyforge_project: activeassets
|
@@ -342,8 +311,7 @@ test_files:
|
|
342
311
|
- test/fixtures/rails_root/public/javascripts/bas/bar.js
|
343
312
|
- test/fixtures/rails_root/public/stylesheets/bar/bas.css
|
344
313
|
- test/fixtures/rails_root/public/stylesheets/bas/bar.css
|
345
|
-
- test/fixtures/sinatra_root/active_assets_test_app.rb
|
346
|
-
- test/fixtures/sinatra_root/views/layout.erb
|
347
|
-
- test/fixtures/sinatra_root/views/sprite.erb
|
348
314
|
- test/helper.rb
|
315
|
+
- test/raster_graphics.rb
|
316
|
+
- test/raster_graphics_test.rb
|
349
317
|
- test/support/rails_helper.rb
|