lash-sprites 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/LICENSE +165 -0
- data/README +38 -0
- data/Rakefile +38 -0
- data/examples/css/css.rb +17 -0
- data/examples/css/css_cli.rb +29 -0
- data/examples/example_imgs/1.png +0 -0
- data/examples/example_imgs/10.png +0 -0
- data/examples/example_imgs/11.png +0 -0
- data/examples/example_imgs/12.png +0 -0
- data/examples/example_imgs/13.png +0 -0
- data/examples/example_imgs/14.png +0 -0
- data/examples/example_imgs/15.png +0 -0
- data/examples/example_imgs/16.png +0 -0
- data/examples/example_imgs/17.png +0 -0
- data/examples/example_imgs/18.png +0 -0
- data/examples/example_imgs/19.png +0 -0
- data/examples/example_imgs/2.png +0 -0
- data/examples/example_imgs/20.png +0 -0
- data/examples/example_imgs/21.png +0 -0
- data/examples/example_imgs/22.png +0 -0
- data/examples/example_imgs/23.png +0 -0
- data/examples/example_imgs/24.png +0 -0
- data/examples/example_imgs/25.png +0 -0
- data/examples/example_imgs/26.png +0 -0
- data/examples/example_imgs/27.png +0 -0
- data/examples/example_imgs/28.png +0 -0
- data/examples/example_imgs/29.png +0 -0
- data/examples/example_imgs/3.png +0 -0
- data/examples/example_imgs/30.png +0 -0
- data/examples/example_imgs/31.png +0 -0
- data/examples/example_imgs/32.png +0 -0
- data/examples/example_imgs/33.png +0 -0
- data/examples/example_imgs/34.png +0 -0
- data/examples/example_imgs/35.png +0 -0
- data/examples/example_imgs/36.png +0 -0
- data/examples/example_imgs/37.png +0 -0
- data/examples/example_imgs/38.png +0 -0
- data/examples/example_imgs/39.png +0 -0
- data/examples/example_imgs/4.png +0 -0
- data/examples/example_imgs/40.png +0 -0
- data/examples/example_imgs/41.png +0 -0
- data/examples/example_imgs/42.png +0 -0
- data/examples/example_imgs/43.png +0 -0
- data/examples/example_imgs/44.png +0 -0
- data/examples/example_imgs/45.png +0 -0
- data/examples/example_imgs/46.png +0 -0
- data/examples/example_imgs/47.png +0 -0
- data/examples/example_imgs/48.png +0 -0
- data/examples/example_imgs/49.png +0 -0
- data/examples/example_imgs/5.png +0 -0
- data/examples/example_imgs/50.png +0 -0
- data/examples/example_imgs/51.png +0 -0
- data/examples/example_imgs/52.png +0 -0
- data/examples/example_imgs/53.png +0 -0
- data/examples/example_imgs/54.png +0 -0
- data/examples/example_imgs/55.png +0 -0
- data/examples/example_imgs/56.png +0 -0
- data/examples/example_imgs/57.png +0 -0
- data/examples/example_imgs/58.png +0 -0
- data/examples/example_imgs/59.png +0 -0
- data/examples/example_imgs/6.png +0 -0
- data/examples/example_imgs/60.png +0 -0
- data/examples/example_imgs/7.png +0 -0
- data/examples/example_imgs/8.png +0 -0
- data/examples/example_imgs/9.png +0 -0
- data/examples/packing/packing.rb +19 -0
- data/lash-sprites.gemspec +21 -0
- data/lib/.DS_Store +0 -0
- data/lib/lash-sprites/.DS_Store +0 -0
- data/lib/lash-sprites/block.rb +34 -0
- data/lib/lash-sprites/css.rb +27 -0
- data/lib/lash-sprites/graphics_manager/gd2.rb +43 -0
- data/lib/lash-sprites/graphics_manager/graphics_manager.rb +22 -0
- data/lib/lash-sprites/graphics_manager/rmagick.rb +45 -0
- data/lib/lash-sprites/image.rb +46 -0
- data/lib/lash-sprites/packer/both_smart.rb +90 -0
- data/lib/lash-sprites/packer/both_split.rb +171 -0
- data/lib/lash-sprites/packer/even.rb +78 -0
- data/lib/lash-sprites/packer/horizontal_smart.rb +72 -0
- data/lib/lash-sprites/packer/horizontal_split.rb +156 -0
- data/lib/lash-sprites/packer/horizontal_stack.rb +22 -0
- data/lib/lash-sprites/packer/ratio.rb +94 -0
- data/lib/lash-sprites/packer/vertical_smart.rb +72 -0
- data/lib/lash-sprites/packer/vertical_split.rb +164 -0
- data/lib/lash-sprites/packer/vertical_stack.rb +23 -0
- data/lib/lash-sprites/sprite.rb +272 -0
- data/lib/lash-sprites/version.rb +5 -0
- data/test/b_graphics.rb +37 -0
- data/test/b_packers.rb +53 -0
- data/test/imgs/1.png +0 -0
- data/test/imgs/10.png +0 -0
- data/test/imgs/11.png +0 -0
- data/test/imgs/12.png +0 -0
- data/test/imgs/13.png +0 -0
- data/test/imgs/14.png +0 -0
- data/test/imgs/15.png +0 -0
- data/test/imgs/16.png +0 -0
- data/test/imgs/17.png +0 -0
- data/test/imgs/18.png +0 -0
- data/test/imgs/19.png +0 -0
- data/test/imgs/2.png +0 -0
- data/test/imgs/20.png +0 -0
- data/test/imgs/21.png +0 -0
- data/test/imgs/22.png +0 -0
- data/test/imgs/23.png +0 -0
- data/test/imgs/24.png +0 -0
- data/test/imgs/25.png +0 -0
- data/test/imgs/26.png +0 -0
- data/test/imgs/27.png +0 -0
- data/test/imgs/28.png +0 -0
- data/test/imgs/29.png +0 -0
- data/test/imgs/3.png +0 -0
- data/test/imgs/30.png +0 -0
- data/test/imgs/31.png +0 -0
- data/test/imgs/32.png +0 -0
- data/test/imgs/33.png +0 -0
- data/test/imgs/34.png +0 -0
- data/test/imgs/35.png +0 -0
- data/test/imgs/36.png +0 -0
- data/test/imgs/37.png +0 -0
- data/test/imgs/38.png +0 -0
- data/test/imgs/39.png +0 -0
- data/test/imgs/4.png +0 -0
- data/test/imgs/40.png +0 -0
- data/test/imgs/41.png +0 -0
- data/test/imgs/42.png +0 -0
- data/test/imgs/43.png +0 -0
- data/test/imgs/44.png +0 -0
- data/test/imgs/45.png +0 -0
- data/test/imgs/46.png +0 -0
- data/test/imgs/47.png +0 -0
- data/test/imgs/48.png +0 -0
- data/test/imgs/49.png +0 -0
- data/test/imgs/5.png +0 -0
- data/test/imgs/50.png +0 -0
- data/test/imgs/51.png +0 -0
- data/test/imgs/52.png +0 -0
- data/test/imgs/53.png +0 -0
- data/test/imgs/54.png +0 -0
- data/test/imgs/55.png +0 -0
- data/test/imgs/56.png +0 -0
- data/test/imgs/57.png +0 -0
- data/test/imgs/58.png +0 -0
- data/test/imgs/59.png +0 -0
- data/test/imgs/6.png +0 -0
- data/test/imgs/60.png +0 -0
- data/test/imgs/7.png +0 -0
- data/test/imgs/8.png +0 -0
- data/test/imgs/9.png +0 -0
- data/test/t_gd2.rb +28 -0
- data/test/t_generic.rb +183 -0
- data/test/t_rmagick.rb +29 -0
- metadata +269 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'ruby_sprites/image'
|
2
|
+
require 'ruby_sprites/block'
|
3
|
+
|
4
|
+
module RubySprites
|
5
|
+
module Packer
|
6
|
+
module Ratio
|
7
|
+
|
8
|
+
def self.pack(images, options = {})
|
9
|
+
width = 0
|
10
|
+
height = 0
|
11
|
+
|
12
|
+
images.sort! do |a, b|
|
13
|
+
ra = 1.0 * a.width / a.height
|
14
|
+
rb = 1.0 * b.width / b.height
|
15
|
+
ra = 1.0 / ra if ra < 1
|
16
|
+
rb = 1.0 / rb if rb < 1
|
17
|
+
ra <=> rb
|
18
|
+
end
|
19
|
+
|
20
|
+
blocks = []
|
21
|
+
|
22
|
+
images.each do |img|
|
23
|
+
next unless img.exists?
|
24
|
+
smallest = nil
|
25
|
+
exact = nil
|
26
|
+
blocks.each do |block|
|
27
|
+
next unless block.fits?(img)
|
28
|
+
|
29
|
+
exact = block if (img.width == block.width || img.height == block.height) && (exact.nil? || block.area < exact.area)
|
30
|
+
smallest = block if smallest.nil? || block.area < smallest.area
|
31
|
+
|
32
|
+
break if smallest.area == img.area
|
33
|
+
end
|
34
|
+
|
35
|
+
if smallest
|
36
|
+
if exact
|
37
|
+
img.x = exact.x
|
38
|
+
img.y = exact.y
|
39
|
+
blocks.concat(blocks.delete(exact).split(img))
|
40
|
+
else
|
41
|
+
img.x = smallest.x
|
42
|
+
img.y = smallest.y
|
43
|
+
blocks.concat(blocks.delete(smallest).split(img))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
if width == 0 && height == 0
|
47
|
+
b = Block.new(0, 0, img.width, img.height)
|
48
|
+
width = img.width
|
49
|
+
height = img.height
|
50
|
+
blocks.concat(b.split(img))
|
51
|
+
else
|
52
|
+
if img.height > height
|
53
|
+
new_area_right = width * (img.height - height)
|
54
|
+
else
|
55
|
+
new_area_right = img.width * (height - img.height)
|
56
|
+
end
|
57
|
+
|
58
|
+
if img.width > width
|
59
|
+
new_area_below = height * (img.width - width)
|
60
|
+
else
|
61
|
+
new_area_below = img.height * (width - img.width)
|
62
|
+
end
|
63
|
+
|
64
|
+
if new_area_below > new_area_right
|
65
|
+
if img.height > height
|
66
|
+
blocks.push(Block.new(0, height, width, img.height - height))
|
67
|
+
height = img.height
|
68
|
+
elsif img.height < height
|
69
|
+
blocks.push(Block.new(width, img.height, img.width, height - img.height))
|
70
|
+
end
|
71
|
+
img.x = width
|
72
|
+
img.y = 0
|
73
|
+
width += img.width
|
74
|
+
else
|
75
|
+
if img.width > width
|
76
|
+
blocks.push(Block.new(width, 0, img.width - width, height))
|
77
|
+
width = img.width
|
78
|
+
elsif img.width < width
|
79
|
+
blocks.push(Block.new(img.width, height, width - img.width, img.height))
|
80
|
+
end
|
81
|
+
img.x = 0
|
82
|
+
img.y = height
|
83
|
+
height += img.height
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return {:width => width, :height => height}
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'ruby_sprites/image'
|
2
|
+
require 'ruby_sprites/block'
|
3
|
+
|
4
|
+
module RubySprites
|
5
|
+
module Packer
|
6
|
+
module VerticalSmart
|
7
|
+
|
8
|
+
def self.pack(images, options = {})
|
9
|
+
width = 0
|
10
|
+
height = 0
|
11
|
+
|
12
|
+
images.sort! do |a, b|
|
13
|
+
if a.width == b.width
|
14
|
+
b.height <=> a.height
|
15
|
+
else
|
16
|
+
b.width <=> a.width
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
blocks = []
|
21
|
+
|
22
|
+
images.each do |img|
|
23
|
+
next unless img.exists?
|
24
|
+
smallest = nil
|
25
|
+
exact = nil
|
26
|
+
blocks.each do |block|
|
27
|
+
next unless block.fits?(img)
|
28
|
+
|
29
|
+
exact = block if (img.width == block.width || img.height == block.height) && (exact.nil? || block.area < exact.area)
|
30
|
+
smallest = block if smallest.nil? || block.area < smallest.area
|
31
|
+
|
32
|
+
break if smallest.area == img.area
|
33
|
+
end
|
34
|
+
|
35
|
+
if smallest
|
36
|
+
if exact
|
37
|
+
blocks.concat(split_block(blocks.delete(exact),img))
|
38
|
+
else
|
39
|
+
blocks.concat(split_block(blocks.delete(smallest),img))
|
40
|
+
end
|
41
|
+
else
|
42
|
+
if width == 0 && height == 0
|
43
|
+
b = Block.new(0, 0, img.width, img.height)
|
44
|
+
width = img.width
|
45
|
+
height = img.height
|
46
|
+
else
|
47
|
+
b = Block.new(0, height, width, img.height)
|
48
|
+
height += img.height
|
49
|
+
end
|
50
|
+
blocks.concat(split_block(b,img))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return {:width => width, :height => height}
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.split_block(block, img)
|
58
|
+
blocks = []
|
59
|
+
img.x = block.x
|
60
|
+
img.y = block.y
|
61
|
+
if (block.width - img.width) * img.height > (block.height - img.height) * img.width
|
62
|
+
blocks.push Block.new(block.x + img.width, block.y, block.width - img.width, block.height) if block.width != img.width
|
63
|
+
blocks.push Block.new(block.x, block.y + img.height, img.width, block.height - img.height) if block.height != img.height
|
64
|
+
else
|
65
|
+
blocks.push Block.new(block.x + img.width, block.y, block.width - img.width, img.height) if block.width != img.width
|
66
|
+
blocks.push Block.new(block.x, block.y + img.height, block.width, block.height - img.height) if block.height != img.height
|
67
|
+
end
|
68
|
+
return blocks
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'ruby_sprites/image'
|
2
|
+
require 'ruby_sprites/block'
|
3
|
+
|
4
|
+
module RubySprites
|
5
|
+
module Packer
|
6
|
+
module VerticalSplit
|
7
|
+
|
8
|
+
def self.pack(images, options = {})
|
9
|
+
width = 0
|
10
|
+
height = 0
|
11
|
+
|
12
|
+
images = images.dup
|
13
|
+
|
14
|
+
images.sort! do |a, b|
|
15
|
+
a.width <=> b.width
|
16
|
+
end
|
17
|
+
|
18
|
+
# Grab the widest image to use as a static width
|
19
|
+
i = images.pop
|
20
|
+
width = i.width
|
21
|
+
height = i.height
|
22
|
+
i.x = 0
|
23
|
+
i.y = 0
|
24
|
+
|
25
|
+
images.sort! do |a, b|
|
26
|
+
a.area <=> b.area
|
27
|
+
end
|
28
|
+
|
29
|
+
block_heap = Heap.new {|a,b| b.area <=> a.area}
|
30
|
+
|
31
|
+
while !images.empty?
|
32
|
+
# This actually runs each loop, but we have the if here just in case
|
33
|
+
if block_heap.empty?
|
34
|
+
# There aren't any blocks to put anything in, so lets make some
|
35
|
+
img = images.pop
|
36
|
+
if width == 0 && height == 0
|
37
|
+
img.x = 0
|
38
|
+
img.y = 0
|
39
|
+
width = img.width
|
40
|
+
height = img.height
|
41
|
+
else
|
42
|
+
if img.width > width
|
43
|
+
block_heap.insert(Block.new(width, 0, img.width - width, height))
|
44
|
+
width = img.width
|
45
|
+
elsif img.width < width
|
46
|
+
block_heap.insert(Block.new(img.width, height, width - img.width, img.height))
|
47
|
+
end
|
48
|
+
img.x = 0
|
49
|
+
img.y = height
|
50
|
+
height += img.height
|
51
|
+
end
|
52
|
+
else
|
53
|
+
while !block_heap.empty? && !images.empty?
|
54
|
+
# We are looping through the blocks we have from smallest to biggest, then images smallest to biggest.
|
55
|
+
# We find the largest image that can fit in the current block, then put it there. If no images fit,
|
56
|
+
# the block is thrown out.
|
57
|
+
block = block_heap.remove
|
58
|
+
cur_img = nil
|
59
|
+
cur_exact = nil
|
60
|
+
images.each_index do |i|
|
61
|
+
cur_img = i if block.fits?(images[i])
|
62
|
+
cur_exact = i if block.fits?(images[i]) && (block.width == images[i].width || block.height == images[i].height)
|
63
|
+
end
|
64
|
+
if !cur_exact.nil?
|
65
|
+
# We prefer when one of the dimensions matches
|
66
|
+
img = images.delete_at(cur_exact)
|
67
|
+
img.x = block.x
|
68
|
+
img.y = block.y
|
69
|
+
split_block(block, img).each do |b|
|
70
|
+
block_heap.insert(b)
|
71
|
+
end
|
72
|
+
elsif !cur_img.nil?
|
73
|
+
# Best we can find
|
74
|
+
img = images.delete_at(cur_img)
|
75
|
+
img.x = block.x
|
76
|
+
img.y = block.y
|
77
|
+
split_block(block, img).each do |b|
|
78
|
+
block_heap.insert(b)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# Nothing will fit in this block, we are throwing it out
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
return {:width => width, :height => height}
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.split_block(block, img)
|
91
|
+
blocks = []
|
92
|
+
img.x = block.x
|
93
|
+
img.y = block.y
|
94
|
+
if (block.width - img.width) * img.height > (block.height - img.height) * img.width
|
95
|
+
blocks.push Block.new(block.x + img.width, block.y, block.width - img.width, block.height) if block.width != img.width
|
96
|
+
blocks.push Block.new(block.x, block.y + img.height, img.width, block.height - img.height) if block.height != img.height
|
97
|
+
else
|
98
|
+
blocks.push Block.new(block.x + img.width, block.y, block.width - img.width, img.height) if block.width != img.width
|
99
|
+
blocks.push Block.new(block.x, block.y + img.height, block.width, block.height - img.height) if block.height != img.height
|
100
|
+
end
|
101
|
+
return blocks
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
class Heap
|
107
|
+
def initialize(&comparer)
|
108
|
+
@comparer = comparer
|
109
|
+
@heap = [0]
|
110
|
+
end
|
111
|
+
|
112
|
+
def insert(el)
|
113
|
+
pos = @heap.length
|
114
|
+
@heap.push(el)
|
115
|
+
new_pos = pos
|
116
|
+
while new_pos != 1 && @comparer.call(el, @heap[new_pos / 2]) < 0
|
117
|
+
new_pos /= 2
|
118
|
+
end
|
119
|
+
|
120
|
+
while(pos > new_pos)
|
121
|
+
@heap[pos] = @heap[pos / 2]
|
122
|
+
pos /= 2
|
123
|
+
end
|
124
|
+
@heap[pos] = el
|
125
|
+
end
|
126
|
+
|
127
|
+
def remove
|
128
|
+
return nil if @heap.length == 1
|
129
|
+
el = @heap[1]
|
130
|
+
shifter = @heap.pop
|
131
|
+
if @heap.length != 1
|
132
|
+
pos = 1
|
133
|
+
while(!@heap[pos * 2].nil?)
|
134
|
+
lesser_pos = pos * 2
|
135
|
+
lesser_pos += 1 if !@heap[pos * 2 + 1].nil? && @comparer.call(@heap[pos * 2 + 1], @heap[pos * 2]) < 0
|
136
|
+
if(@comparer.call(@heap[lesser_pos], shifter) < 0)
|
137
|
+
@heap[pos] = @heap[lesser_pos]
|
138
|
+
pos = lesser_pos
|
139
|
+
else
|
140
|
+
break
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
@heap[pos] = shifter
|
145
|
+
end
|
146
|
+
return el
|
147
|
+
end
|
148
|
+
|
149
|
+
def peek
|
150
|
+
return nil if @heap.length == 1
|
151
|
+
return @heap[1]
|
152
|
+
end
|
153
|
+
|
154
|
+
def empty?
|
155
|
+
return @heap.length == 1
|
156
|
+
end
|
157
|
+
|
158
|
+
def inspect
|
159
|
+
return @heap.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'ruby_sprites/image'
|
2
|
+
|
3
|
+
|
4
|
+
module RubySprites
|
5
|
+
module Packer
|
6
|
+
module VerticalStack
|
7
|
+
|
8
|
+
def self.pack(images, options = {})
|
9
|
+
width = 0
|
10
|
+
height = 0
|
11
|
+
images.each do |img|
|
12
|
+
next unless img.exists?
|
13
|
+
img.x = 0
|
14
|
+
img.y = height
|
15
|
+
width = img.width if img.width > width
|
16
|
+
height += img.height
|
17
|
+
end
|
18
|
+
return {:width => width, :height => height}
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
# Author:: Cullen Walsh
|
2
|
+
# Copyright:: Copyright (c) 2009 Cullen Walsh
|
3
|
+
# License:: Lesser General Public License v3
|
4
|
+
|
5
|
+
require 'ruby_sprites/image'
|
6
|
+
require 'ruby_sprites/block'
|
7
|
+
|
8
|
+
module RubySprites
|
9
|
+
|
10
|
+
# This class is main image sprite creator. It allows for reading existing
|
11
|
+
# sprites, adding images to sprites, and repacking sprites on updates
|
12
|
+
class Sprite
|
13
|
+
|
14
|
+
# A hash of the default options a sprite uses. These should be
|
15
|
+
# sufficient for most usage.
|
16
|
+
@@DEFAULT_OPTIONS = {
|
17
|
+
:graphics_manager => nil, # The image engine to use, may be :rmagick or :gd2
|
18
|
+
:pack_type => 'vertical_split', # Which algorithm should be used to pack images
|
19
|
+
:force_update => false, # Should the sprite image be forced to update, even if it appears up to date?
|
20
|
+
:write_files => true, # Decides whether the script should actually write the sprite and image files, used mainly for testing.
|
21
|
+
:repeat => false # Whether the sprite repeats horizontally or vertically
|
22
|
+
}
|
23
|
+
|
24
|
+
# Lets one programatically override the default option values if you are
|
25
|
+
# generating multiple sprites
|
26
|
+
def self.set_default(key, value)
|
27
|
+
@@DEFAULT_OPTIONS[key.to_sym] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :filename, :file_root, :image_file, :mtime, :width, :height, :options, :images
|
31
|
+
|
32
|
+
# Creates a sprite object. Takes an file_root, absolute or relative, a
|
33
|
+
# sprite filename relative to the file root, and an options hash.
|
34
|
+
def initialize(filename, file_root, options = {})
|
35
|
+
@options = @@DEFAULT_OPTIONS.merge(options)
|
36
|
+
|
37
|
+
@file_root = File.expand_path(file_root)
|
38
|
+
@file_root += '/' unless @file_root[-1, 1] == '/'
|
39
|
+
@filename = filename
|
40
|
+
@image_file = File.expand_path(@file_root + @filename)
|
41
|
+
@sprite_file = "#{@image_file}.sprite"
|
42
|
+
|
43
|
+
@width = 0;
|
44
|
+
@height = 0
|
45
|
+
@blocks = []
|
46
|
+
@image_queue = []
|
47
|
+
@images = {}
|
48
|
+
|
49
|
+
if File.exists?(@image_file) && File.exists?(@sprite_file)
|
50
|
+
@mtime = File.mtime(@image_file)
|
51
|
+
read_file
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_option(key, val)
|
56
|
+
@options[key.to_sym] = val
|
57
|
+
end
|
58
|
+
|
59
|
+
# Destroys the sprite, deleting its related files and freeing up memory
|
60
|
+
def destroy!
|
61
|
+
File.unlink @image_file if File.exists?(@image_file)
|
62
|
+
File.unlink @sprite_file if File.exists?(@sprite_file)
|
63
|
+
initialize(@filename, @file_root, @options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determines if the image in the relative path exists within the sprite
|
67
|
+
# and has been updated since hte sprite was generated.
|
68
|
+
def image_current?(imagepath)
|
69
|
+
img = @images[imagepath]
|
70
|
+
return !img.nil? && img.exists? && !@mtime.nil? && img.mtime <= @mtime
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the x position, y position, width, and height of the image if
|
74
|
+
# it exists in the sprite.
|
75
|
+
def image_info(imagepath)
|
76
|
+
return nil if @images[imagepath].nil?
|
77
|
+
return {:x => @images[imagepath].x,
|
78
|
+
:y => @images[imagepath].y,
|
79
|
+
:width => @images[imagepath].width,
|
80
|
+
:height => @images[imagepath].height,
|
81
|
+
:mtime => @images[imagepath].mtime,
|
82
|
+
:path => @images[imagepath].path}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds the image in the relative path to the sprite.
|
86
|
+
def add_image(img_path)
|
87
|
+
@image_queue.push Image.new(img_path, self, 0, 0) if @images[img_path].nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Adds the images in the array of relative paths to the sprite.
|
91
|
+
def add_images(img_paths)
|
92
|
+
img_paths.each do |path|
|
93
|
+
add_image(path)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Updates the sprite files if it detects changes to the sprite or the
|
98
|
+
# force option is set.
|
99
|
+
def update
|
100
|
+
update = @options[:force_update] || !@image_queue.empty? || @mtime.nil?
|
101
|
+
if update
|
102
|
+
@images.each do |id, img|
|
103
|
+
if @mtime.nil? || img.mtime.nil? || img.mtime >= @mtime
|
104
|
+
update = true
|
105
|
+
break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
if update
|
110
|
+
pack
|
111
|
+
if @options[:write_files]
|
112
|
+
write_image unless @height == 0 || @width == 0
|
113
|
+
write_sprite_file
|
114
|
+
end
|
115
|
+
@mtime = Time.now
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@@managers = nil
|
120
|
+
|
121
|
+
def self.graphics_managers
|
122
|
+
|
123
|
+
if @@managers.nil?
|
124
|
+
@@managers = {}
|
125
|
+
|
126
|
+
dir = File.dirname(__FILE__)
|
127
|
+
|
128
|
+
Dir.foreach("#{dir}/graphics_manager") do |file|
|
129
|
+
next unless file.match(/\.rb$/)
|
130
|
+
begin
|
131
|
+
require("#{dir}/graphics_manager/#{file}")
|
132
|
+
class_name = file.gsub('.rb', '').capitalize.gsub(/_([a-z]+)/) {|x| $1.capitalize}
|
133
|
+
@@managers[file.gsub('.rb', '').to_sym] = GraphicsManager.const_get(class_name) if GraphicsManager.const_get(class_name).availible?
|
134
|
+
rescue Exception => a
|
135
|
+
puts a
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
return @@managers
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns a Graphics manager based on the sprite options that will
|
144
|
+
# be used for this sprite.
|
145
|
+
def graphics_manager
|
146
|
+
if @graphics_manager.nil?
|
147
|
+
Sprite.graphics_managers
|
148
|
+
if @options[:graphics_manager].nil?
|
149
|
+
@graphics_manager = @@managers.values[0].new(self)
|
150
|
+
elsif @@managers[@options[:graphics_manager].to_sym].nil?
|
151
|
+
throw "Invalid Image Manager"
|
152
|
+
else
|
153
|
+
@graphics_manager = @@managers[@options[:graphics_manager].to_sym].new(self)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return @graphics_manager
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
# Writes the sprite image
|
162
|
+
def write_image
|
163
|
+
graphics_manager.combine(@images, @width, @height)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Writes the sprite data file
|
167
|
+
def write_sprite_file
|
168
|
+
lines = []
|
169
|
+
lines.push "#{@width} x #{@height}"
|
170
|
+
lines.push "Repeat #{@options[:repeat]}" if @options[:repeat]
|
171
|
+
@blocks.each do |block|
|
172
|
+
lines.push "B #{block.x} #{block.y} #{block.width} #{block.height}"
|
173
|
+
end
|
174
|
+
@images.each do |img_path, img|
|
175
|
+
lines.push "I #{img_path} #{img.x} #{img.y} #{img.width} #{img.height}"
|
176
|
+
end
|
177
|
+
fp = File.open(@sprite_file, 'w')
|
178
|
+
fp.write(lines.join("\n"))
|
179
|
+
fp.close
|
180
|
+
end
|
181
|
+
|
182
|
+
# Reads a sprite file and populates the images for this sprite
|
183
|
+
def read_file
|
184
|
+
return unless File.exists? @sprite_file
|
185
|
+
lines = File.readlines(@sprite_file)
|
186
|
+
return if lines.empty?
|
187
|
+
dims = lines.delete_at(0).chomp.split(' ')
|
188
|
+
@width = dims[0].to_i
|
189
|
+
@height = dims[2].to_i
|
190
|
+
|
191
|
+
# This is to handle repeating sprites
|
192
|
+
repeat = false
|
193
|
+
repeat = lines.delete_at(0).match(/Repeat (\w+)/)[1].to_sym if lines[0] =~ /Repeat \w+/
|
194
|
+
# We want to make sure the repeating type matches
|
195
|
+
return unless repeat == @options[:repeat]
|
196
|
+
@options[:repeat] = repeat
|
197
|
+
|
198
|
+
lines.each do |line|
|
199
|
+
line_parts = line.chomp.split(' ')
|
200
|
+
if line_parts[0] == 'B'
|
201
|
+
@blocks.push Block.new(line_parts[1].to_i, line_parts[2].to_i, line_parts[3].to_i, line_parts[4].to_i)
|
202
|
+
elsif line_parts[0] == 'I'
|
203
|
+
img = Image.new(line_parts[1], self, line_parts[2].to_i, line_parts[3].to_i, line_parts[4].to_i, line_parts[5].to_i)
|
204
|
+
@images[img.path] = img
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Positions the images in the sprite
|
210
|
+
def pack
|
211
|
+
@width = 0
|
212
|
+
@height = 0
|
213
|
+
|
214
|
+
@image_queue.each do |img|
|
215
|
+
@images[img.path] = img
|
216
|
+
end
|
217
|
+
@image_queue = []
|
218
|
+
|
219
|
+
return if @images.empty?
|
220
|
+
|
221
|
+
class_name = @options[:pack_type].to_s.capitalize.gsub(/_([a-z]+)/) {|x| $1.capitalize}
|
222
|
+
|
223
|
+
if @options[:repeat]
|
224
|
+
if @options[:repeat] == :vertical
|
225
|
+
require "ruby_sprites/packer/horizontal_stack"
|
226
|
+
dims = Packer::HorizontalStack.pack(@images.values)
|
227
|
+
dims[:height] = 1
|
228
|
+
elsif @options[:repeat] == :horizontal
|
229
|
+
require "ruby_sprites/packer/vertical_stack"
|
230
|
+
dims = Packer::VerticalStack.pack(@images.values)
|
231
|
+
dims[:width] = 1
|
232
|
+
else
|
233
|
+
throw Exception.new('Invalid repeat type')
|
234
|
+
end
|
235
|
+
else
|
236
|
+
begin
|
237
|
+
dims = Packer.const_get(class_name.to_sym).pack(@images.values)
|
238
|
+
rescue NameError
|
239
|
+
require "ruby_sprites/packer/#{@options[:pack_type].to_s}"
|
240
|
+
dims = Packer.const_get(class_name.to_sym).pack(@images.values)
|
241
|
+
rescue LoadError
|
242
|
+
throw Exception.new('pack_type is invalid')
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
@width = dims[:width]
|
247
|
+
@height = dims[:height]
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
# Splits a block in multiple parts if needed to accommodate an image
|
252
|
+
def split_block(img, block)
|
253
|
+
if block.nil?
|
254
|
+
if img.width > @width
|
255
|
+
@blocks.push Block.new(@width, 0, img.width - @width, @height) if @height > 0
|
256
|
+
@width = img.width
|
257
|
+
end
|
258
|
+
|
259
|
+
img.x = 0
|
260
|
+
img.y = @height
|
261
|
+
@images[img.path] = img
|
262
|
+
@blocks.concat Block.new(0, @height, @width, img.height).split(img)
|
263
|
+
@height += img.height
|
264
|
+
else
|
265
|
+
img.x = block.x
|
266
|
+
img.y = block.y
|
267
|
+
@images[img.path] = img
|
268
|
+
@blocks.concat @blocks.delete(block).split(img)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
data/test/b_graphics.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
test_dir = File.dirname(__FILE__)
|
4
|
+
|
5
|
+
$:.unshift File.join(test_dir, '../lib')
|
6
|
+
|
7
|
+
require 'ruby_sprites/sprite'
|
8
|
+
require 'benchmark'
|
9
|
+
|
10
|
+
n = 10
|
11
|
+
puts "#{n} Iterations"
|
12
|
+
|
13
|
+
Benchmark.bmbm(10) do |r|
|
14
|
+
RubySprites::Sprite.graphics_managers.each do |key, val|
|
15
|
+
r.report("#{val.const_get(:DESCRIPTION)}:") {
|
16
|
+
for i in 1..n
|
17
|
+
sprite = RubySprites::Sprite.new('sprite.png', test_dir, {:graphics_manager => key, :force_update => true})
|
18
|
+
(1..60).each do |x|
|
19
|
+
sprite.add_image("imgs/#{x}.png")
|
20
|
+
end
|
21
|
+
sprite.update
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
r.report("No force:") {
|
26
|
+
for i in 1..n;
|
27
|
+
sprite = RubySprites::Sprite.new('sprite.png', test_dir, {:force_update => false})
|
28
|
+
(1..60).each do |x|
|
29
|
+
sprite.add_image("imgs/#{x}.png")
|
30
|
+
end
|
31
|
+
sprite.update
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
File.unlink(File.join(test_dir, 'sprite.png')) if File.exists? File.join(test_dir, 'sprite.png')
|
37
|
+
File.unlink(File.join(test_dir, 'sprite.png.sprite')) if File.exists? File.join(test_dir, 'sprite.png.sprite')
|