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.
Files changed (155) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +165 -0
  4. data/README +38 -0
  5. data/Rakefile +38 -0
  6. data/examples/css/css.rb +17 -0
  7. data/examples/css/css_cli.rb +29 -0
  8. data/examples/example_imgs/1.png +0 -0
  9. data/examples/example_imgs/10.png +0 -0
  10. data/examples/example_imgs/11.png +0 -0
  11. data/examples/example_imgs/12.png +0 -0
  12. data/examples/example_imgs/13.png +0 -0
  13. data/examples/example_imgs/14.png +0 -0
  14. data/examples/example_imgs/15.png +0 -0
  15. data/examples/example_imgs/16.png +0 -0
  16. data/examples/example_imgs/17.png +0 -0
  17. data/examples/example_imgs/18.png +0 -0
  18. data/examples/example_imgs/19.png +0 -0
  19. data/examples/example_imgs/2.png +0 -0
  20. data/examples/example_imgs/20.png +0 -0
  21. data/examples/example_imgs/21.png +0 -0
  22. data/examples/example_imgs/22.png +0 -0
  23. data/examples/example_imgs/23.png +0 -0
  24. data/examples/example_imgs/24.png +0 -0
  25. data/examples/example_imgs/25.png +0 -0
  26. data/examples/example_imgs/26.png +0 -0
  27. data/examples/example_imgs/27.png +0 -0
  28. data/examples/example_imgs/28.png +0 -0
  29. data/examples/example_imgs/29.png +0 -0
  30. data/examples/example_imgs/3.png +0 -0
  31. data/examples/example_imgs/30.png +0 -0
  32. data/examples/example_imgs/31.png +0 -0
  33. data/examples/example_imgs/32.png +0 -0
  34. data/examples/example_imgs/33.png +0 -0
  35. data/examples/example_imgs/34.png +0 -0
  36. data/examples/example_imgs/35.png +0 -0
  37. data/examples/example_imgs/36.png +0 -0
  38. data/examples/example_imgs/37.png +0 -0
  39. data/examples/example_imgs/38.png +0 -0
  40. data/examples/example_imgs/39.png +0 -0
  41. data/examples/example_imgs/4.png +0 -0
  42. data/examples/example_imgs/40.png +0 -0
  43. data/examples/example_imgs/41.png +0 -0
  44. data/examples/example_imgs/42.png +0 -0
  45. data/examples/example_imgs/43.png +0 -0
  46. data/examples/example_imgs/44.png +0 -0
  47. data/examples/example_imgs/45.png +0 -0
  48. data/examples/example_imgs/46.png +0 -0
  49. data/examples/example_imgs/47.png +0 -0
  50. data/examples/example_imgs/48.png +0 -0
  51. data/examples/example_imgs/49.png +0 -0
  52. data/examples/example_imgs/5.png +0 -0
  53. data/examples/example_imgs/50.png +0 -0
  54. data/examples/example_imgs/51.png +0 -0
  55. data/examples/example_imgs/52.png +0 -0
  56. data/examples/example_imgs/53.png +0 -0
  57. data/examples/example_imgs/54.png +0 -0
  58. data/examples/example_imgs/55.png +0 -0
  59. data/examples/example_imgs/56.png +0 -0
  60. data/examples/example_imgs/57.png +0 -0
  61. data/examples/example_imgs/58.png +0 -0
  62. data/examples/example_imgs/59.png +0 -0
  63. data/examples/example_imgs/6.png +0 -0
  64. data/examples/example_imgs/60.png +0 -0
  65. data/examples/example_imgs/7.png +0 -0
  66. data/examples/example_imgs/8.png +0 -0
  67. data/examples/example_imgs/9.png +0 -0
  68. data/examples/packing/packing.rb +19 -0
  69. data/lash-sprites.gemspec +21 -0
  70. data/lib/.DS_Store +0 -0
  71. data/lib/lash-sprites/.DS_Store +0 -0
  72. data/lib/lash-sprites/block.rb +34 -0
  73. data/lib/lash-sprites/css.rb +27 -0
  74. data/lib/lash-sprites/graphics_manager/gd2.rb +43 -0
  75. data/lib/lash-sprites/graphics_manager/graphics_manager.rb +22 -0
  76. data/lib/lash-sprites/graphics_manager/rmagick.rb +45 -0
  77. data/lib/lash-sprites/image.rb +46 -0
  78. data/lib/lash-sprites/packer/both_smart.rb +90 -0
  79. data/lib/lash-sprites/packer/both_split.rb +171 -0
  80. data/lib/lash-sprites/packer/even.rb +78 -0
  81. data/lib/lash-sprites/packer/horizontal_smart.rb +72 -0
  82. data/lib/lash-sprites/packer/horizontal_split.rb +156 -0
  83. data/lib/lash-sprites/packer/horizontal_stack.rb +22 -0
  84. data/lib/lash-sprites/packer/ratio.rb +94 -0
  85. data/lib/lash-sprites/packer/vertical_smart.rb +72 -0
  86. data/lib/lash-sprites/packer/vertical_split.rb +164 -0
  87. data/lib/lash-sprites/packer/vertical_stack.rb +23 -0
  88. data/lib/lash-sprites/sprite.rb +272 -0
  89. data/lib/lash-sprites/version.rb +5 -0
  90. data/test/b_graphics.rb +37 -0
  91. data/test/b_packers.rb +53 -0
  92. data/test/imgs/1.png +0 -0
  93. data/test/imgs/10.png +0 -0
  94. data/test/imgs/11.png +0 -0
  95. data/test/imgs/12.png +0 -0
  96. data/test/imgs/13.png +0 -0
  97. data/test/imgs/14.png +0 -0
  98. data/test/imgs/15.png +0 -0
  99. data/test/imgs/16.png +0 -0
  100. data/test/imgs/17.png +0 -0
  101. data/test/imgs/18.png +0 -0
  102. data/test/imgs/19.png +0 -0
  103. data/test/imgs/2.png +0 -0
  104. data/test/imgs/20.png +0 -0
  105. data/test/imgs/21.png +0 -0
  106. data/test/imgs/22.png +0 -0
  107. data/test/imgs/23.png +0 -0
  108. data/test/imgs/24.png +0 -0
  109. data/test/imgs/25.png +0 -0
  110. data/test/imgs/26.png +0 -0
  111. data/test/imgs/27.png +0 -0
  112. data/test/imgs/28.png +0 -0
  113. data/test/imgs/29.png +0 -0
  114. data/test/imgs/3.png +0 -0
  115. data/test/imgs/30.png +0 -0
  116. data/test/imgs/31.png +0 -0
  117. data/test/imgs/32.png +0 -0
  118. data/test/imgs/33.png +0 -0
  119. data/test/imgs/34.png +0 -0
  120. data/test/imgs/35.png +0 -0
  121. data/test/imgs/36.png +0 -0
  122. data/test/imgs/37.png +0 -0
  123. data/test/imgs/38.png +0 -0
  124. data/test/imgs/39.png +0 -0
  125. data/test/imgs/4.png +0 -0
  126. data/test/imgs/40.png +0 -0
  127. data/test/imgs/41.png +0 -0
  128. data/test/imgs/42.png +0 -0
  129. data/test/imgs/43.png +0 -0
  130. data/test/imgs/44.png +0 -0
  131. data/test/imgs/45.png +0 -0
  132. data/test/imgs/46.png +0 -0
  133. data/test/imgs/47.png +0 -0
  134. data/test/imgs/48.png +0 -0
  135. data/test/imgs/49.png +0 -0
  136. data/test/imgs/5.png +0 -0
  137. data/test/imgs/50.png +0 -0
  138. data/test/imgs/51.png +0 -0
  139. data/test/imgs/52.png +0 -0
  140. data/test/imgs/53.png +0 -0
  141. data/test/imgs/54.png +0 -0
  142. data/test/imgs/55.png +0 -0
  143. data/test/imgs/56.png +0 -0
  144. data/test/imgs/57.png +0 -0
  145. data/test/imgs/58.png +0 -0
  146. data/test/imgs/59.png +0 -0
  147. data/test/imgs/6.png +0 -0
  148. data/test/imgs/60.png +0 -0
  149. data/test/imgs/7.png +0 -0
  150. data/test/imgs/8.png +0 -0
  151. data/test/imgs/9.png +0 -0
  152. data/test/t_gd2.rb +28 -0
  153. data/test/t_generic.rb +183 -0
  154. data/test/t_rmagick.rb +29 -0
  155. 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
@@ -0,0 +1,5 @@
1
+ module Lash
2
+ module Sprites
3
+ VERSION = "0.3.1"
4
+ end
5
+ end
@@ -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')