start work on better packing algorithm, start with scan

This commit is contained in:
John Bintz 2011-05-16 11:30:14 -04:00
parent d9ca08f685
commit cc26b98909
9 changed files with 226 additions and 12 deletions

View File

@ -7,7 +7,7 @@ GIT
PATH
remote: .
specs:
compass (0.11.1.d9e4054)
compass (0.11.1.d9ca08f)
chunky_png (~> 1.1)
fssm (>= 0.2.7)
sass (~> 3.1)
@ -50,7 +50,7 @@ GEM
autotest-fsevent (0.2.5)
sys-uname
builder (2.1.2)
chunky_png (1.1.1)
chunky_png (1.2.0)
compass-validator (3.0.0)
css_parser (1.0.1)
cucumber (0.9.4)

View File

@ -0,0 +1,18 @@
module Compass
module SassExtensions
module Sprites
class ImageGroup
attr_reader :images
def initialize(images = [])
@images = images
end
def best_fitting_rows
end
end
end
end
end

View File

@ -1,8 +1,13 @@
require 'forwardable'
module Compass
module SassExtensions
module Sprites
class ImageRow
extend Forwardable
attr_reader :images, :max_width
def_delegators :@images, :last, :delete, :empty?, :length
def initialize(max_width)
@images = []
@ -10,16 +15,13 @@ module Compass
end
def add(image)
unless image.is_a?(Compass::SassExtensions::Sprites::Image)
raise ArgumentError, "Must be a SpriteImage"
end
if (images.inject(0) {|sum, img| sum + img.width} + image.width) > max_width
return false
end
return false if !will_fit?(image)
@images << image
true
end
alias :<< :add
def height
images.map(&:height).max
end
@ -28,6 +30,17 @@ module Compass
images.map(&:width).max
end
def total_width
images.inject(0) {|sum, img| sum + img.width }
end
def efficiency
1 - (total_width.to_f / max_width.to_f)
end
def will_fit?(image)
(total_width + image.width) <= max_width
end
end
end
end

View File

@ -0,0 +1,81 @@
require 'forwardable'
module Compass
module SassExtensions
module Sprites
class RowFitter
extend Forwardable
attr_reader :images, :rows
def_delegators :rows, :[]
def initialize(images)
@images = images
@rows = []
end
def fit!(style = :scan)
send("#{style}_fit")
end
def width
@width ||= @images.collect(&:width).max
end
def efficiency
@rows.inject(0) { |sum, row| sum += row.efficiency } ** @rows.length
end
private
def new_row(image = nil)
row = Compass::SassExtensions::Sprites::ImageRow.new(width)
row.add(image) if image
row
end
def fast_fit
row = new_row
@images.each do |image|
if !row.add(image)
@rows << row
row = new_row(image)
end
end
@rows << row
end
def scan_fit
fast_fit
moved_images = []
begin
removed = false
catch :done do
@rows.each do |row|
(@rows - [ row ]).each do |other_row|
other_row.images.each do |image|
if !moved_images.include?(image)
if row.will_fit?(image)
other_row.delete(image)
row << image
@rows.delete(other_row) if other_row.empty?
removed = true
moved_images << image
throw :done
end
end
end
end
end
end
end while removed
end
end
end
end
end

7
test.watchr Normal file
View File

@ -0,0 +1,7 @@
watch('test/(.*)_test\.rb') { |m| test(m[0]) }
watch('lib/compass/sass_extensions/sprites/image_group.rb') { test('test/units/sprites/image_group_test.rb') }
watch('lib/compass/sass_extensions/sprites/row_fitter.rb') { test('test/units/sprites/row_fitter_test.rb') }
def test(file = nil)
system %{ruby -I"lib:test" #{file}}.tap { |o| puts o }
end

View File

@ -21,7 +21,7 @@ require 'rubygems' if need_gems
require 'compass'
require 'test/unit'
require 'mocha'
%w(command_line diff io rails test_case).each do |helper|
require "helpers/#{helper}"

View File

@ -0,0 +1,9 @@
require 'test_helper'
require 'compass/sass_extensions/sprites/image_group'
class ImageGroupTest < Test::Unit::TestCase
def setup
end
end

View File

@ -49,4 +49,8 @@ class ImageRowTest < Test::Unit::TestCase
assert_equal 40, @image_row.height
end
it "should have an efficiency rating" do
populate_row
assert_equal 1 - (580.0 / 1000.0), @image_row.efficiency
end
end

View File

@ -0,0 +1,82 @@
require 'test_helper'
require 'compass/sass_extensions/sprites/row_fitter'
class RowFitterTest < Test::Unit::TestCase
def setup
end
def row_fitter(images = nil)
@row_fitter ||= Compass::SassExtensions::Sprites::RowFitter.new(images)
end
def teardown
@row_fitter = nil
end
def create_images(dims)
dims.collect { |width, height| stub(:width => width, :height => height) }
end
def basic_dims
[
[ 100, 10 ],
[ 80, 10 ],
[ 50, 10 ],
[ 20, 10 ],
[ 35, 10 ]
]
end
it 'should use the fast placement algorithm' do
images = create_images(basic_dims)
row_fitter(images)
assert_equal 100, row_fitter.width
row_fitter.fit!(:fast)
assert_equal 4, row_fitter.rows.length
assert_equal [ images[0] ], row_fitter[0].images
assert_equal [ images[1] ], row_fitter[1].images
assert_equal [ images[2], images[3] ], row_fitter[2].images
assert_equal [ images[4] ], row_fitter[3].images
end
it 'should use the scan placement algorithm' do
images = create_images(basic_dims)
row_fitter(images)
row_fitter.fit!(:scan)
assert_equal 3, row_fitter.rows.length
assert_equal [ images[0] ], row_fitter[0].images
assert_equal [ images[1], images[3] ], row_fitter[1].images
assert_equal [ images[2], images[4] ], row_fitter[2].images
end
it 'should use the scan placement algorithm and get images from the front' do
images = create_images(
[
[ 100, 10 ],
[ 35, 10 ],
[ 80, 10 ],
[ 50, 10 ],
[ 20, 10 ],
]
)
row_fitter(images)
row_fitter.fit!(:scan)
assert_equal 3, row_fitter.rows.length
assert_equal [ images[0] ], row_fitter[0].images
assert_equal [ images[1], images[3] ], row_fitter[1].images
assert_equal [ images[2], images[4] ], row_fitter[2].images
end
end