the-architect-sprite_generator 0.1.12 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/sprite_generator.rb +147 -85
- data/test/units/test_sprite_generator.rb +30 -1
- metadata +1 -1
data/lib/sprite_generator.rb
CHANGED
@@ -39,29 +39,24 @@ class SpriteGenerator
|
|
39
39
|
def initialize(files_or_paths, output, root, options = {})
|
40
40
|
@files = find_files(files_or_paths)
|
41
41
|
return if @files.nil? || @files.empty?
|
42
|
-
@root
|
43
|
-
@output
|
44
|
-
@delimiter
|
45
|
-
@analyzed
|
46
|
-
@template
|
47
|
-
|
48
|
-
@
|
49
|
-
@
|
50
|
-
@
|
51
|
-
@alignment = options[:alignment] ? Magick.const_get("#{camelize(options[:alignment])}Gravity") : Magick::CenterGravity
|
42
|
+
@root = root || ''
|
43
|
+
@output = output
|
44
|
+
@delimiter = options[:delimiter] || '-'
|
45
|
+
@analyzed = find_files_for_mode(options[:distribution] || :smart)
|
46
|
+
@template = Liquid::Template.parse(options[:template] || '')
|
47
|
+
@sprite_location = options[:sprite_location] || @output
|
48
|
+
@background = options[:background] || '#FFFFFF00'
|
49
|
+
@tile_size = options[:tile]
|
50
|
+
@alignment = options[:alignment] ? Magick.const_get("#{camelize(options[:alignment])}Gravity") : Magick::CenterGravity
|
52
51
|
end
|
53
52
|
|
54
53
|
|
55
54
|
def create
|
56
55
|
raise 'No files found.' if @files.nil? || @files.empty?
|
57
|
-
|
58
|
-
unless @tile_size.nil?
|
59
|
-
size_x, size_y = @tile_size.split('x').first(2).map{|dim| dim.to_i}
|
60
|
-
tile = Magick::Image.new(size_x, size_y){ self.background_color = background }
|
61
|
-
tile.format = "PNG"
|
62
|
-
end
|
56
|
+
build_tile
|
63
57
|
destination = @root.nil? || @root.empty? ? @output : File.join(@root, @output)
|
64
|
-
image, css
|
58
|
+
image, css = build_sprite_and_css
|
59
|
+
background = @background
|
65
60
|
image.write(destination){ self.background_color = background }
|
66
61
|
css
|
67
62
|
end
|
@@ -69,66 +64,99 @@ class SpriteGenerator
|
|
69
64
|
|
70
65
|
protected
|
71
66
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
def build(tile = nil)
|
78
|
-
background = @background
|
79
|
-
images = ImageList.new{ self.background_color = background }
|
80
|
-
context = { 'sprite_location' => @sprite_location, 'tile' => tile }
|
81
|
-
css = []
|
67
|
+
def build_sprite_and_css
|
68
|
+
background = @background
|
69
|
+
@images = ImageList.new{ self.background_color = background }
|
70
|
+
context = { 'sprite_location' => @sprite_location }
|
71
|
+
@css = []
|
82
72
|
|
83
|
-
@analyzed.each do |key
|
84
|
-
|
85
|
-
|
73
|
+
@analyzed.keys.sort.each do |key|
|
74
|
+
value = @analyzed[key]
|
75
|
+
if @tile
|
76
|
+
context['left'] = @images.length > 0 ? @tile.columns : 0
|
86
77
|
else
|
87
|
-
context['left'] = images.length > 0 ? images.append(false).columns : 0
|
78
|
+
context['left'] = @images.length > 0 ? @images.append(false).columns : 0
|
88
79
|
end
|
89
|
-
context['top']
|
80
|
+
context['top'] = 0
|
90
81
|
context['basename'] = key
|
91
|
-
context['overall']
|
82
|
+
context['overall'] = @images.length
|
92
83
|
|
93
|
-
|
94
|
-
|
95
|
-
if tile
|
96
|
-
tiles = ImageList.new{ self.background_color = background }
|
97
|
-
image_list.each do |image|
|
98
|
-
tiles << tile.composite(image, @alignment, Magick::OverCompositeOp)
|
99
|
-
end
|
100
|
-
images.from_blob(tiles.append(true).to_blob){ self.background_color = background }
|
101
|
-
else
|
102
|
-
images.from_blob(image_list.append(true).to_blob){ self.background_color = background }
|
103
|
-
end
|
104
|
-
context['variations'] = image_list.length
|
105
|
-
context['type'] = :list
|
106
|
-
context['images'] = image_list
|
107
|
-
context['filenames'] = value
|
108
|
-
else
|
109
|
-
|
110
|
-
filename = value.flatten.first
|
111
|
-
image = Image.read(filename){ self.background_color = background }
|
112
|
-
|
113
|
-
context['variations'] = 0
|
114
|
-
context['variation_name'] = ''
|
115
|
-
context['variation_number'] = 0
|
116
|
-
context['full_filename'] = filename
|
117
|
-
context['file_basename'] = File.basename(filename, '.*')
|
118
|
-
context['variation_name'] = context['file_basename'].gsub(/^#{context['basename']}#{@delimiter}/, '')
|
119
|
-
|
120
|
-
context['type'] = :image
|
121
|
-
|
122
|
-
if tile
|
123
|
-
images.from_blob(tile.composite(image.first, @alignment, Magick::OverCompositeOp).to_blob){ self.background_color = background }
|
124
|
-
else
|
125
|
-
images.from_blob(image.first.to_blob){ self.background_color = background }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
css << build_css(context)
|
84
|
+
value.size > 1 ? build_image_list(context, value.sort) : build_single_image(context, value.flatten.first)
|
85
|
+
@css << build_css(context)
|
129
86
|
end
|
130
87
|
|
131
|
-
[images.append(false), css.join("\n")]
|
88
|
+
[@images.append(false), @css.join("\n")]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def build_single_image(context, filename)
|
93
|
+
background = @background
|
94
|
+
image = Image.read(filename){ self.background_color = background }.flatten.first
|
95
|
+
|
96
|
+
context.merge!(build_context_for_single_image(image, filename, context['basename']))
|
97
|
+
|
98
|
+
if @tile
|
99
|
+
@images.from_blob(@tile.composite(image, @alignment, Magick::OverCompositeOp).to_blob){ self.background_color = background }
|
100
|
+
else
|
101
|
+
@images.from_blob(image.to_blob){ self.background_color = background }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def build_context_for_single_image(image, filename, basename)
|
107
|
+
file_basename = File.basename(filename, '.*')
|
108
|
+
{
|
109
|
+
'variations' => 0,
|
110
|
+
'variation_name' => '',
|
111
|
+
'variation_number' => 0,
|
112
|
+
'full_filename' => filename,
|
113
|
+
'file_basename' => file_basename,
|
114
|
+
'variation_name' => file_basename.gsub(/^#{basename}#{@delimiter}/, ''),
|
115
|
+
'width' => @tile ? tile.columns : image.columns,
|
116
|
+
'height' => @tile ? tile.rows : image.rows,
|
117
|
+
'type' => :image
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def build_image_list(context, image_filenames)
|
123
|
+
background = @background
|
124
|
+
image_list = ImageList.new(*image_filenames){ self.background_color = background }
|
125
|
+
if @tile
|
126
|
+
tiles = ImageList.new{ self.background_color = background }
|
127
|
+
image_list.each do |image|
|
128
|
+
tiles << @tile.composite(image, @alignment, Magick::OverCompositeOp)
|
129
|
+
end
|
130
|
+
append_to_sprite(tiles)
|
131
|
+
else
|
132
|
+
append_to_sprite(image_list)
|
133
|
+
end
|
134
|
+
context.merge!(build_context_for_image_list(image_list, image_filenames))
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def append_to_sprite(images)
|
139
|
+
background = @background
|
140
|
+
@images.from_blob(images.append(true).to_blob){ self.background_color = background }
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def build_context_for_image_list(image_list, image_filenames)
|
145
|
+
{
|
146
|
+
'variations' => image_list.length,
|
147
|
+
'type' => :list,
|
148
|
+
'images' => image_list,
|
149
|
+
'filenames' => image_filenames
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def build_tile
|
155
|
+
return if @tile_size.nil?
|
156
|
+
background = @background
|
157
|
+
size_x, size_y = @tile_size.split('x').first(2).map{|dim| dim.to_i}
|
158
|
+
@tile = Magick::Image.new(size_x, size_y){ self.background_color = background }
|
159
|
+
@tile.format = "PNG"
|
132
160
|
end
|
133
161
|
|
134
162
|
|
@@ -147,21 +175,19 @@ protected
|
|
147
175
|
|
148
176
|
def build_css_for_list(context)
|
149
177
|
new_context = context.dup
|
150
|
-
|
151
|
-
image_list = context.delete('images')
|
178
|
+
image_list = context.delete('images')
|
152
179
|
new_context['type'] = :image
|
153
180
|
css = image_list.inject([]) do |css, image|
|
154
|
-
new_context['width']
|
155
|
-
new_context['height']
|
181
|
+
new_context['width'] = @tile ? @tile.columns : image.columns
|
182
|
+
new_context['height'] = @tile ? @tile.rows : image.rows
|
156
183
|
new_context['variation_number'] = css.size
|
157
|
-
|
158
|
-
new_context['
|
159
|
-
new_context['
|
160
|
-
new_context['
|
161
|
-
new_context['variation_name'] = new_context['file_basename'].gsub(/^#{new_context['basename']}#{@delimiter}/, '')
|
184
|
+
new_context['full_filename'] = context['filenames'].shift
|
185
|
+
new_context['filename'] = File.basename(new_context['full_filename'])
|
186
|
+
new_context['file_basename'] = File.basename(new_context['full_filename'], '.*')
|
187
|
+
new_context['variation_name'] = new_context['file_basename'].gsub(/^#{new_context['basename']}#{@delimiter}/, '')
|
162
188
|
|
163
189
|
css << build_css(new_context.dup)
|
164
|
-
new_context['top'] += tile ? tile.columns : new_context['height']
|
190
|
+
new_context['top'] += @tile ? @tile.columns : new_context['height']
|
165
191
|
css
|
166
192
|
end.join("\n")
|
167
193
|
end
|
@@ -181,17 +207,53 @@ protected
|
|
181
207
|
end
|
182
208
|
|
183
209
|
|
210
|
+
# put filenames in format for each mode
|
211
|
+
def find_files_for_mode(mode)
|
212
|
+
case mode
|
213
|
+
when :smart
|
214
|
+
smart_distribution
|
215
|
+
when :horizontal
|
216
|
+
horizontal_distribution
|
217
|
+
when :vertical
|
218
|
+
vertical_distribution
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
184
223
|
# gather information about the selected files
|
185
224
|
# check for variations by using a delimiter as an indicator
|
186
|
-
def
|
187
|
-
|
188
|
-
basename
|
189
|
-
without_variation = basename.split(delimiter)[0..-2].join(delimiter)
|
190
|
-
basename
|
225
|
+
def smart_distribution
|
226
|
+
@files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
|
227
|
+
basename = File.basename(file, '.*')
|
228
|
+
without_variation = basename.split(@delimiter)[0..-2].join(@delimiter)
|
229
|
+
basename = without_variation.nil? || without_variation == '' ? basename : without_variation
|
191
230
|
h[basename] << file
|
192
231
|
h
|
193
232
|
end
|
194
233
|
end
|
195
234
|
|
196
235
|
|
236
|
+
def horizontal_distribution
|
237
|
+
@files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
|
238
|
+
basename = File.basename(file)
|
239
|
+
h[basename] << file
|
240
|
+
h
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def vertical_distribution
|
246
|
+
@files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
|
247
|
+
basename = File.basename(file)
|
248
|
+
h['all'] << file
|
249
|
+
h
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# simplyfied version of active_support's camelize version
|
255
|
+
def camelize(lower_case_and_underscored_word)
|
256
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
257
|
+
end
|
258
|
+
|
197
259
|
end
|
@@ -18,13 +18,42 @@ class SpriteGeneratorTest < Test::Unit::TestCase
|
|
18
18
|
Dir.glob('test/output/*').each{|f| File.delete f }
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def test_should_user_horizontal_distribution
|
22
|
+
template = %q{ {{left}} }
|
23
|
+
generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :distribution => :horizontal })
|
24
|
+
css = generator.create
|
25
|
+
assert css.include?('240')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_user_vertical_distribution
|
29
|
+
template = %q{ {{top}} }
|
30
|
+
generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :distribution => :vertical })
|
31
|
+
css = generator.create
|
32
|
+
`open #{@output}`
|
33
|
+
sleep 1
|
34
|
+
assert css.include?('240')
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_should_set_correct_context_filebasename_for_images_without_variations
|
22
38
|
template = %q{ {{file_basename}} }
|
23
39
|
generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
|
24
40
|
css = generator.create
|
25
41
|
assert css.include?('emoticon-evilgrin')
|
26
42
|
end
|
27
43
|
|
44
|
+
def test_should_set_correct_context_width_for_images_without_variations
|
45
|
+
template = %q{ {{width}} }
|
46
|
+
generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
|
47
|
+
css = generator.create
|
48
|
+
assert css.include?('16')
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_should_set_correct_context_top_for_images_without_variations
|
52
|
+
template = %q{ {{top}} }
|
53
|
+
generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
|
54
|
+
css = generator.create
|
55
|
+
assert !css.include?('-16')
|
56
|
+
end
|
28
57
|
|
29
58
|
def test_should_create_correct_context
|
30
59
|
template = %q{
|