sprite_generator 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default)
4
+ require 'yaml'
5
+ require 'ostruct'
6
+ require 'RMagick'
7
+ require 'liquid'
8
+
9
+ require File.expand_path('../sprites/generator', __FILE__)
10
+ require File.expand_path('../sprites/batch', __FILE__)
11
+
12
+ module Sprites
13
+ class Config
14
+ class << self
15
+ def root=(root = nil)
16
+ @root = root
17
+ end
18
+
19
+ def root
20
+ @root || File.expand_path('../../', __FILE__)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ module Sprites
2
+ class Batch
3
+ attr_reader :batches
4
+
5
+ def initialize(filename)
6
+ config = YAML.load(File.read(filename))
7
+ @batches = config.inject([]) do |arr, pair|
8
+ if !!defined?(RAILS_ROOT)
9
+ pair.last.merge!(:config_root => RAILS_ROOT)
10
+ elsif pair.last[:root]
11
+ root = File.expand_path(pair.last[:root], file_name)
12
+ pair.last.merge!(:config_root => root)
13
+ end
14
+ arr.push OpenStruct.new(pair.last)
15
+ arr
16
+ end
17
+ end
18
+
19
+ def generate
20
+ @batches.each do |batch|
21
+ generator = Sprites::Generator.new(batch.files, batch.output, batch.config_root, batch.options || {})
22
+ css = generator.create
23
+ # only write output if css_output is specified
24
+ unless css.nil? || css.empty? || batch.css_output.nil?
25
+ output = batch.css_template.nil? ? css : Liquid::Template.parse(File.open(batch.css_template).read).render('css' => css)
26
+ File.open(batch.css_output, 'w+'){|f| f.puts output }
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+
@@ -0,0 +1,244 @@
1
+ module Sprites
2
+ class Generator
3
+ include Magick
4
+
5
+ # parameters:
6
+ # files_or_path: can be Dir.glob-able paths or an array of filenames
7
+ # ie: ['../images/icons/icon1_out.png', '../images/icons/icon1_over.png'] or '../images/icons/icon*.png'
8
+ # output: filename where the generated sprite will be stored
9
+ # ie: '../images/sprites/icons.png'
10
+ # options: set a variety of options
11
+ # - delimiter: characters by which multiple variations of images will be found
12
+ # ie: '_' for 'icon_hover.png'
13
+ # ie: '-' for 'icon-hover.png'
14
+ # if icon is the basename
15
+ # - align:
16
+ # - sprite_location: will be available as variable in the liquid template, if this is not set, the template will use output as sprite_location
17
+ # - tile: if set to ie. '100x100' it will center every image on a 100 by 100 tile
18
+ # - template: Liquid template for each sprite, use this to build the css for your sprites
19
+ # these variables are available:
20
+ # - top: distance to top border of sprite in pixels
21
+ # - left: distance to left border of sprite in pixels
22
+ # - width: width of current image in pixels
23
+ # - height: height of current image in pixels
24
+ # - basename: filename or basename of variations
25
+ # ie: with variations: icon_out.png, icon_over.png => icon
26
+ # ie: without variations: icon.png => icon.png
27
+ # - file_basename: always the name of the current image without extension
28
+ # ie: icon_over
29
+ # - filename: icon_over.png
30
+ # - full_filename: ../images/icons/icon_over.ong
31
+ # - variations: number of variations as number
32
+ # - variation: the current variation as zero based number
33
+ # - sprite_location: path to sprite
34
+ def initialize(files_or_paths, output, root, options = {})
35
+ @files = find_files(files_or_paths)
36
+ return if @files.nil? || @files.empty?
37
+ @root = root || ''
38
+ @output = output
39
+ @delimiter = options[:delimiter] || '-'
40
+ @distribution = (options[:distribution] || :smart).to_sym
41
+ @analyzed = find_files_for_mode
42
+ @template = Liquid::Template.parse(options[:template] || '')
43
+ @sprite_location = options[:sprite_location] || @output
44
+ @background = options[:background] || '#FFFFFF00'
45
+ @tile_size = options[:tile]
46
+ @alignment = options[:alignment] ? Magick.const_get("#{camelize(options[:alignment])}Gravity") : Magick::CenterGravity
47
+ end
48
+
49
+ def create
50
+ raise 'No files found.' if @files.nil? || @files.empty?
51
+ build_tile
52
+ destination = @root.nil? || @root.empty? ? @output : File.join(@root, @output)
53
+ image, css = build_sprite_and_css
54
+ background = @background
55
+ image.write(destination){ self.background_color = background }
56
+ css
57
+ end
58
+
59
+ protected
60
+
61
+ def build_sprite_and_css
62
+ background = @background
63
+ @images = ImageList.new{ self.background_color = background }
64
+ context = { 'sprite_location' => @sprite_location }
65
+ @css = []
66
+
67
+ @analyzed.keys.sort.each do |key|
68
+ value = @analyzed[key]
69
+
70
+ context['top'] = 0
71
+ context['basename'] = key
72
+ context['overall'] = @images.length
73
+ if value.size > 1
74
+ context = build_image_list(context, value.sort)
75
+ else
76
+ context = build_single_image(context, value.flatten.first)
77
+ end
78
+ @css << build_css(context)
79
+ end
80
+ [@images.append(false), @css.join("\n")]
81
+ end
82
+
83
+ def build_single_image(context, filename)
84
+ background = @background
85
+ image = Image.read(filename){ self.background_color = background }.flatten.first
86
+
87
+ context.merge!(build_context_for_single_image(image, filename, context['basename']))
88
+ if @tile
89
+ @images.from_blob(@tile.composite(image, @alignment, Magick::OverCompositeOp).to_blob){ self.background_color = background }
90
+ else
91
+ @images.from_blob(image.to_blob){ self.background_color = background }
92
+ end
93
+ context
94
+ end
95
+
96
+ def build_context_for_single_image(image, filename, basename)
97
+ file_basename = File.basename(filename, '.*')
98
+ {
99
+ 'variations' => 0,
100
+ 'variation_number' => 0,
101
+ 'full_filename' => filename,
102
+ 'file_basename' => file_basename,
103
+ 'variation_name' => file_basename.gsub("#{basename}#{@delimiter}", ''),
104
+ 'width' => @tile ? @tile.columns : image.columns,
105
+ 'height' => @tile ? @tile.rows : image.rows,
106
+ 'type' => :image,
107
+ 'left' => left_value_for_context
108
+ }
109
+ end
110
+
111
+ def left_value_for_context
112
+ @tile ? (@images.length * @tile.columns) : (@images.any? ? @images.append(false).columns : 0)
113
+ end
114
+
115
+ def build_image_list(context, image_filenames)
116
+ background = @background
117
+ image_list = ImageList.new(*image_filenames){ self.background_color = background }
118
+ context.merge!(build_context_for_image_list(image_list, image_filenames))
119
+ if @tile
120
+ tiles = ImageList.new{ self.background_color = background }
121
+ image_list.each do |image|
122
+ tiles << @tile.composite(image, @alignment, Magick::OverCompositeOp)
123
+ end
124
+ append_to_sprite(tiles)
125
+ else
126
+ append_to_sprite(image_list)
127
+ end
128
+ context
129
+ end
130
+
131
+ def append_to_sprite(images)
132
+ background = @background
133
+ @images.from_blob(images.append(true).to_blob){ self.background_color = background }
134
+ end
135
+
136
+ def build_context_for_image_list(image_list, image_filenames)
137
+ {
138
+ 'variations' => image_list.length,
139
+ 'type' => :list,
140
+ 'images' => image_list,
141
+ 'filenames' => image_filenames,
142
+ 'left' => left_value_for_context
143
+ }
144
+ end
145
+
146
+ def build_tile
147
+ return if @tile_size.nil?
148
+ background = @background
149
+ size_x, size_y = @tile_size.split('x').first(2).map{|dim| dim.to_i}
150
+ @tile = Magick::Image.new(size_x, size_y){ self.background_color = background }
151
+ @tile.format = "PNG"
152
+ end
153
+
154
+ def build_css(context = {})
155
+ type = context['type']
156
+ case type
157
+ when :list
158
+ css = build_css_for_list(context)
159
+ when :image
160
+ # render template if there is only one image
161
+ css = @template.render(context)
162
+ end
163
+ css
164
+ end
165
+
166
+ def build_css_for_list(context)
167
+ new_context = context.dup
168
+ image_list = context.delete('images')
169
+ new_context['type'] = :image
170
+ css = image_list.inject([]) do |css, image|
171
+ new_context['width'] = @tile ? @tile.columns : image.columns
172
+ new_context['height'] = @tile ? @tile.rows : image.rows
173
+ new_context['variation_number'] = css.size
174
+ new_context['full_filename'] = context['filenames'].shift
175
+ new_context['filename'] = File.basename(new_context['full_filename'])
176
+ new_context['file_basename'] = File.basename(new_context['full_filename'], '.*')
177
+ new_context['variation_name'] = new_context['file_basename'].gsub(/^#{new_context['basename']}#{@delimiter}/, '')
178
+ css << build_css(new_context.dup)
179
+
180
+ new_context['top'] += @tile ? @tile.rows : new_context['height']
181
+ css
182
+ end.join("\n")
183
+ end
184
+
185
+ # gather files that will be used to create the sprite
186
+ def find_files(*args)
187
+ args.inject([]) do |files, arg|
188
+ found_files = Dir.glob(arg)
189
+ if found_files.empty?
190
+ files << arg if File.exists?(arg) rescue raise arg.inspect
191
+ else
192
+ files << found_files.flatten
193
+ end
194
+ files.flatten.compact.uniq
195
+ end
196
+ end
197
+
198
+ # put filenames in format for each mode
199
+ def find_files_for_mode
200
+ case @distribution
201
+ when :smart
202
+ smart_distribution
203
+ when :horizontal
204
+ horizontal_distribution
205
+ when :vertical
206
+ vertical_distribution
207
+ end
208
+ end
209
+
210
+ # gather information about the selected files
211
+ # check for variations by using a delimiter as an indicator
212
+ def smart_distribution
213
+ @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
214
+ basename = File.basename(file, '.*')
215
+ without_variation = basename.split(@delimiter)[0..-2].join(@delimiter)
216
+ basename = without_variation.nil? || without_variation == '' ? basename : without_variation
217
+ h[basename] << file
218
+ h
219
+ end
220
+ end
221
+
222
+ def horizontal_distribution
223
+ @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
224
+ basename = File.basename(file)
225
+ h[basename] << file
226
+ h
227
+ end
228
+ end
229
+
230
+ def vertical_distribution
231
+ @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
232
+ basename = File.basename(file)
233
+ h['__all__'] << file
234
+ h
235
+ end
236
+ end
237
+
238
+ # simplyfied version of active_support's camelize version
239
+ def camelize(lower_case_and_underscored_word)
240
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
241
+ end
242
+ end
243
+ end
244
+
@@ -0,0 +1,9 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default, :test)
4
+ require File.expand_path('../../lib/sprites', __FILE__)
5
+
6
+ require 'test/unit'
7
+ require 'mocha'
8
+ require 'shoulda'
9
+
@@ -1,10 +1,9 @@
1
- require 'test/unit'
2
- require File.expand_path(File.dirname(__FILE__) + '/../../lib/sprite_batch_generator')
1
+ require File.expand_path('../../test_helper', __FILE__)
3
2
 
4
- class SpriteBatchGeneratorTest < Test::Unit::TestCase
3
+ class BatchTest < Test::Unit::TestCase
5
4
 
6
5
  def setup
7
- @config = 'test/config/batch.yml'
6
+ @config = File.join(Sprites::Config.root, 'test', 'config', 'batch.yml')
8
7
  end
9
8
 
10
9
  def teardown
@@ -12,14 +11,14 @@ class SpriteBatchGeneratorTest < Test::Unit::TestCase
12
11
  Dir.glob('test/output/*').each{|f| File.delete f }
13
12
  end
14
13
 
15
- def test_should_read_config_file
16
- @batch = SpriteBatchGenerator.new(@config)
14
+ should "read config file" do
15
+ @batch = Sprites::Batch.new(@config)
17
16
  assert_not_nil @batch
18
17
  assert_equal 2, @batch.batches.size
19
18
  end
20
19
 
21
- def test_should_create_files_from_config
22
- @batch = SpriteBatchGenerator.new(@config)
20
+ should "create files from config" do
21
+ @batch = Sprites::Batch.new(@config)
23
22
  css = @batch.generate
24
23
  assert_not_nil css
25
24
  output_files = Dir.glob('test/output/*')
@@ -1,24 +1,20 @@
1
- require 'rubygems'
2
- require 'liquid'
3
- require 'test/unit'
4
- require File.expand_path(File.dirname(__FILE__) + '/../../lib/sprite_generator')
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class GeneratorTest < Test::Unit::TestCase
5
4
 
6
- class SpriteGeneratorTest < Test::Unit::TestCase
7
-
8
5
  def setup
9
6
  @template = '.{{basename}}_{{variation}}{ background:transparent url({{sprite_location}}) -{{left}}px -{{top}}px no-repeat; width:{{width}}px; height:{{height}}px; }'
10
- @all_images_path = 'test/images/*.png'
11
- @output = 'test/output/sprite_by_create.png'
12
- @page_path = 'test/output/test.html'
7
+ @all_images_path = File.join(Sprites::Config.root, 'test', 'images', '*.png')
8
+ @output = File.join(Sprites::Config.root, 'test', 'output', 'sprite_by_create.png')
9
+ @page_path = File.join(Sprites::Config.root, 'test', 'output', 'test.html')
13
10
  end
14
11
 
15
-
16
12
  def teardown
17
13
  # delete test output
18
- Dir.glob('test/output/*').each{|f| File.delete f }
14
+ Dir.glob(File.join(Sprites::Config.root, 'test', 'output', '*')).each{|f| File.delete f }
19
15
  end
20
16
 
21
- def test_should_create_correct_sprite_for_tile_with_vertical_distribution
17
+ should "create correct sprite for tile with vertical distribution" do
22
18
  options = {
23
19
  :distribution => 'vertical',
24
20
  :tile => '40x300',
@@ -26,7 +22,7 @@ class SpriteGeneratorTest < Test::Unit::TestCase
26
22
  :template => "a.{{file_basename}} { padding-left:2em; background: transparent url(#{this_method}.png) -{{left}}px -{{top}}px no-repeat; }"
27
23
  }
28
24
 
29
- generator = SpriteGenerator.new(@all_images_path, "test/output/#{this_method}.png", nil, options)
25
+ generator = Sprites::Generator.new(@all_images_path, "test/output/#{this_method}.png", nil, options)
30
26
  css = generator.create
31
27
  assert css.include?('-4500px')
32
28
 
@@ -36,28 +32,7 @@ class SpriteGeneratorTest < Test::Unit::TestCase
36
32
  assert File.exists?(output_file)
37
33
  end
38
34
 
39
-
40
- def test_should_create_correct_sprite_for_tile_with_vertical_distribution
41
- options = {
42
- :distribution => 'vertical',
43
- :tile => '40x300',
44
- :alignment => 'north_west',
45
- :template => "a.{{file_basename}} { padding-left:2em; background: transparent url(#{this_method}.png) -{{left}}px -{{top}}px no-repeat; }"
46
- }
47
-
48
- generator = SpriteGenerator.new(@all_images_path, "test/output/#{this_method}.png", nil, options)
49
- css = generator.create
50
- assert css.include?('-4500px')
51
-
52
- page = Liquid::Template.parse(File.open('test/templates/link.html').read).render('css' => css, 'image' => "#{this_method}.png")
53
- output_file = File.join('test', 'output', "#{this_method}.html")
54
- File.open(output_file, 'w+'){ |f| f.puts page }
55
- assert File.exists?(output_file)
56
- end
57
-
58
-
59
-
60
- def test_should_create_correct_sprite_for_tile_with_horizontal_distribution
35
+ should "create correct sprite for tile with horizontal distribution" do
61
36
  options = {
62
37
  :distribution => 'horizontal',
63
38
  :tile => '400x50',
@@ -65,7 +40,7 @@ class SpriteGeneratorTest < Test::Unit::TestCase
65
40
  :template => "a.{{file_basename}} { padding-left:2em; background: transparent url(#{this_method}.png) -{{left}}px -{{top}}px no-repeat; }"
66
41
  }
67
42
 
68
- generator = SpriteGenerator.new(@all_images_path, "test/output/#{this_method}.png", nil, options)
43
+ generator = Sprites::Generator.new(@all_images_path, "test/output/#{this_method}.png", nil, options)
69
44
  css = generator.create
70
45
  assert css.include?('-6000px')
71
46
 
@@ -76,42 +51,42 @@ class SpriteGeneratorTest < Test::Unit::TestCase
76
51
  end
77
52
 
78
53
 
79
- def test_should_user_horizontal_distribution
54
+ should "use horizontal distribution" do
80
55
  template = %q{ {{left}} }
81
- generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :distribution => 'horizontal' })
56
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, { :template => template, :distribution => 'horizontal' })
82
57
  css = generator.create
83
58
  assert css.include?('240')
84
59
  end
85
60
 
86
- def test_should_user_vertical_distribution
61
+ should "use vertical distribution" do
87
62
  template = %q{ {{top}} }
88
- generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :distribution => 'vertical' })
63
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, { :template => template, :distribution => 'vertical' })
89
64
  css = generator.create
90
65
  assert css.include?('240')
91
66
  end
92
67
 
93
- def test_should_set_correct_context_filebasename_for_images_without_variations
68
+ should "set correct context filebasename for images without variations" do
94
69
  template = %q{ {{file_basename}} }
95
- generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
70
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
96
71
  css = generator.create
97
72
  assert css.include?('emoticon-evilgrin')
98
73
  end
99
74
 
100
- def test_should_set_correct_context_width_for_images_without_variations
75
+ should "set correct context width for images without variations" do
101
76
  template = %q{ {{width}} }
102
- generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
77
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
103
78
  css = generator.create
104
79
  assert css.include?('16')
105
80
  end
106
81
 
107
- def test_should_set_correct_context_top_for_images_without_variations
82
+ should "set correct context top for images without variations" do
108
83
  template = %q{ {{top}} }
109
- generator = SpriteGenerator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
84
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, { :template => template, :delimiter => '_' })
110
85
  css = generator.create
111
86
  assert !css.include?('-16')
112
87
  end
113
88
 
114
- def test_should_create_correct_context
89
+ should "create correct context" do
115
90
  template = %q{
116
91
  basename: {{basename}}
117
92
  variation: {{variation}}
@@ -127,27 +102,27 @@ class SpriteGeneratorTest < Test::Unit::TestCase
127
102
  variation_number: {{variation_number}}
128
103
  variation_name: {{variation_name}}
129
104
  }
130
- generator = SpriteGenerator.new(@all_images_path, @output, nil, {:template => template})
105
+ generator = Sprites::Generator.new(@all_images_path, @output, nil, {:template => template})
131
106
  css = generator.create
132
107
  assert css.include?('basename: emoticon-evilgrin')
133
108
  assert css.include?('variation_name: evilgrin')
134
109
  assert css.include?('file_basename: emoticon-evilgrin')
135
110
  end
136
111
 
137
- def test_should_use_alignment_option
112
+ should "use alignment option" do
138
113
  assert_nothing_raised do
139
- SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'west'})
114
+ Sprites::Generator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'west'})
140
115
  end
141
116
  end
142
117
 
143
- def test_should_complain_over_unknown_alignment_option
118
+ should "complain about unknown alignment option" do
144
119
  assert_raise NameError do
145
- SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'somewhere'})
120
+ Sprites::Generator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'somewhere'})
146
121
  end
147
122
  end
148
123
 
149
- def test_should_center_images_on_tiles
150
- @generator = SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF00'})
124
+ should "center images on tiles" do
125
+ @generator = Sprites::Generator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF00'})
151
126
  css = @generator.create
152
127
  page = Liquid::Template.parse(File.open('test/templates/test.html').read).render('css' => css)
153
128
  assert page.include?("{ background:")
@@ -160,8 +135,8 @@ class SpriteGeneratorTest < Test::Unit::TestCase
160
135
  end
161
136
 
162
137
 
163
- def test_should_create_correct_css
164
- @generator = SpriteGenerator.new(@all_images_path, @output, nil, :template => @template)
138
+ should "create correct css" do
139
+ @generator = Sprites::Generator.new(@all_images_path, @output, nil, :template => @template)
165
140
  css = @generator.create()
166
141
  page = Liquid::Template.parse(File.open('test/templates/test.html').read).render('css' => css)
167
142
  assert page.include?("{ background:")
@@ -174,44 +149,44 @@ class SpriteGeneratorTest < Test::Unit::TestCase
174
149
  end
175
150
 
176
151
 
177
- def test_should_generate_sprite_file
178
- @generator = SpriteGenerator.new(@all_images_path, @output, nil, :template => @template)
152
+ should "generate sprite file" do
153
+ @generator = Sprites::Generator.new(@all_images_path, @output, nil, :template => @template)
179
154
  css = @generator.create
180
155
  assert !(css.nil? || css.empty?)
181
156
  assert File.exists?(@output)
182
157
  end
183
158
 
184
159
  # bad, testing internal state
185
- def test_should_find_versions_of_emoticons
160
+ should "find versions of emoticons" do
186
161
  files = Dir.glob(@all_images_path)
187
- @generator = SpriteGenerator.new(files, @output, nil, {})
162
+ @generator = Sprites::Generator.new(files, @output, nil, {})
188
163
  analyzed = @generator.instance_variable_get(:@analyzed)
189
164
  assert_equal 9, analyzed['emoticon'].size
190
165
  end
191
166
 
192
167
  # bad, using internal state
193
- def test_should_find_files_for_glob_path
194
- @generator = SpriteGenerator.new(@all_images_path, @output, nil, {})
168
+ should "find files for glob path" do
169
+ @generator = Sprites::Generator.new(@all_images_path, @output, nil, {})
195
170
  files = @generator.instance_variable_get(:@files)
196
171
  assert_equal 16, files.size
197
172
  end
198
173
 
199
174
  # bad, using internal state
200
- def test_should_find_files
201
- @generator = SpriteGenerator.new(['test/images/emoticon-evilgrin.png', 'test/images/emoticon-grin.png'], @output, nil, {})
175
+ should "find files" do
176
+ @generator = Sprites::Generator.new(['test/images/emoticon-evilgrin.png', 'test/images/emoticon-grin.png'], @output, nil, {})
202
177
  files = @generator.instance_variable_get(:@files)
203
178
  assert_equal 2, files.size
204
179
  end
205
180
 
206
181
  # bad, using internal state
207
- def test_should_not_find_anything
208
- @generator = SpriteGenerator.new('test/blalala/*.hurz', @output, nil, {})
182
+ should "not find anything" do
183
+ @generator = Sprites::Generator.new('test/blalala/*.hurz', @output, nil, {})
209
184
  files = @generator.instance_variable_get(:@files)
210
185
  assert_equal 0, files.size
211
186
  end
212
187
 
213
188
  protected
214
-
189
+ # output filename based on test name for inspection
215
190
  def this_method
216
191
  caller[0] =~ /`([^']*)'/ and $1
217
192
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sprite_generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Marcel Scherf
@@ -9,30 +14,75 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-12-08 00:00:00 +01:00
17
+ date: 2011-01-18 00:00:00 +01:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: liquid
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
23
29
  version: "0"
24
- version:
30
+ type: :runtime
31
+ prerelease: false
32
+ version_requirements: *id001
25
33
  - !ruby/object:Gem::Dependency
26
34
  name: rmagick
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
27
43
  type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
44
+ prerelease: false
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ none: false
30
50
  requirements:
31
51
  - - ">="
32
52
  - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
33
55
  version: "0"
34
- version:
35
- description:
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: liquid
61
+ requirement: &id004 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ type: :runtime
70
+ prerelease: false
71
+ version_requirements: *id004
72
+ - !ruby/object:Gem::Dependency
73
+ name: rmagick
74
+ requirement: &id005 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: *id005
85
+ description: Automatically generate Sprite Images and the corresponding CSS.
36
86
  email: marcel.scherf@gmail.com
37
87
  executables: []
38
88
 
@@ -41,37 +91,46 @@ extensions: []
41
91
  extra_rdoc_files:
42
92
  - README.txt
43
93
  files:
44
- - lib/sprite_batch_generator.rb
45
- - lib/sprite_generator.rb
94
+ - lib/sprites.rb
95
+ - lib/sprites/batch.rb
96
+ - lib/sprites/generator.rb
46
97
  - README.txt
98
+ - test/test_helper.rb
99
+ - test/units/batch_test.rb
100
+ - test/units/generator_test.rb
47
101
  has_rdoc: true
48
102
  homepage: http://github.com/the-architect/spritegenerator
49
103
  licenses: []
50
104
 
51
105
  post_install_message:
52
- rdoc_options:
53
- - --charset=UTF-8
106
+ rdoc_options: []
107
+
54
108
  require_paths:
55
109
  - lib
56
110
  required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
57
112
  requirements:
58
113
  - - ">="
59
114
  - !ruby/object:Gem::Version
115
+ segments:
116
+ - 0
60
117
  version: "0"
61
- version:
62
118
  required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
63
120
  requirements:
64
121
  - - ">="
65
122
  - !ruby/object:Gem::Version
123
+ segments:
124
+ - 0
66
125
  version: "0"
67
- version:
68
126
  requirements: []
69
127
 
70
128
  rubyforge_project:
71
- rubygems_version: 1.3.5
129
+ rubygems_version: 1.3.7
72
130
  signing_key:
73
131
  specification_version: 3
74
132
  summary: Automatically generate Sprite Images and the corresponding CSS using RMagick and Liquid.
75
133
  test_files:
76
- - test/units/test_sprite_batch_generator.rb
77
- - test/units/test_sprite_generator.rb
134
+ - test/test_helper.rb
135
+ - test/units/batch_test.rb
136
+ - test/units/generator_test.rb
@@ -1,37 +0,0 @@
1
- require 'yaml'
2
- require 'ostruct'
3
- require 'pathname'
4
- require File.expand_path(File.dirname(__FILE__) + '/sprite_generator')
5
-
6
- class SpriteBatchGenerator
7
- attr_reader :batches
8
-
9
- def initialize(filename)
10
- config = YAML.load(File.read(filename))
11
- @batches = config.inject([]) do |arr, pair|
12
- if !!defined?(RAILS_ROOT)
13
- pair.last.merge!(:config_root => RAILS_ROOT)
14
- elsif pair.last[:root]
15
- root = File.expand_path(pair.last[:root], file_name)
16
- pair.last.merge!(:config_root => root)
17
- end
18
- arr.push OpenStruct.new(pair.last)
19
- arr
20
- end
21
- end
22
-
23
-
24
- def generate
25
- @batches.each do |batch|
26
- generator = SpriteGenerator.new(batch.files, batch.output, batch.config_root, batch.options || {})
27
- css = generator.create
28
- # only write output if css_output is specified
29
- unless css.nil? || css.empty? || batch.css_output.nil?
30
- output = batch.css_template.nil? ? css : Liquid::Template.parse(File.open(batch.css_template).read).render('css' => css)
31
- File.open(batch.css_output, 'w+'){|f| f.puts output }
32
- end
33
- end
34
- end
35
-
36
-
37
- end
@@ -1,264 +0,0 @@
1
- require 'rubygems'
2
- require 'RMagick'
3
- require 'liquid'
4
-
5
- class SpriteGenerator
6
- VERSION = '0.1.10'
7
-
8
- include Magick
9
-
10
- # parameters:
11
- # files_or_path: can be Dir.glob-able paths or an array of filenames
12
- # ie: ['../images/icons/icon1_out.png', '../images/icons/icon1_over.png'] or '../images/icons/icon*.png'
13
- # output: filename where the generated sprite will be stored
14
- # ie: '../images/sprites/icons.png'
15
- # options: set a variety of options
16
- # - delimiter: characters by which multiple variations of images will be found
17
- # ie: '_' for 'icon_hover.png'
18
- # ie: '-' for 'icon-hover.png'
19
- # if icon is the basename
20
- # - align:
21
- # - sprite_location: will be available as variable in the liquid template, if this is not set, the template will use output as sprite_location
22
- # - tile: if set to ie. '100x100' it will center every image on a 100 by 100 tile
23
- # - template: Liquid template for each sprite, use this to build the css for your sprites
24
- # these variables are available:
25
- # - top: distance to top border of sprite in pixels
26
- # - left: distance to left border of sprite in pixels
27
- # - width: width of current image in pixels
28
- # - height: height of current image in pixels
29
- # - basename: filename or basename of variations
30
- # ie: with variations: icon_out.png, icon_over.png => icon
31
- # ie: without variations: icon.png => icon.png
32
- # - file_basename: always the name of the current image without extension
33
- # ie: icon_over
34
- # - filename: icon_over.png
35
- # - full_filename: ../images/icons/icon_over.ong
36
- # - variations: number of variations as number
37
- # - variation: the current variation as zero based number
38
- # - sprite_location: path to sprite
39
- def initialize(files_or_paths, output, root, options = {})
40
- @files = find_files(files_or_paths)
41
- return if @files.nil? || @files.empty?
42
- @root = root || ''
43
- @output = output
44
- @delimiter = options[:delimiter] || '-'
45
- @distribution = (options[:distribution] || :smart).to_sym
46
- @analyzed = find_files_for_mode
47
- @template = Liquid::Template.parse(options[:template] || '')
48
- @sprite_location = options[:sprite_location] || @output
49
- @background = options[:background] || '#FFFFFF00'
50
- @tile_size = options[:tile]
51
- @alignment = options[:alignment] ? Magick.const_get("#{camelize(options[:alignment])}Gravity") : Magick::CenterGravity
52
- end
53
-
54
-
55
- def create
56
- raise 'No files found.' if @files.nil? || @files.empty?
57
- build_tile
58
- destination = @root.nil? || @root.empty? ? @output : File.join(@root, @output)
59
- image, css = build_sprite_and_css
60
- background = @background
61
- image.write(destination){ self.background_color = background }
62
- css
63
- end
64
-
65
- protected
66
-
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 = []
72
-
73
- @analyzed.keys.sort.each do |key|
74
- value = @analyzed[key]
75
-
76
- context['top'] = 0
77
- context['basename'] = key
78
- context['overall'] = @images.length
79
- if value.size > 1
80
- context = build_image_list(context, value.sort)
81
- else
82
- context = build_single_image(context, value.flatten.first)
83
- end
84
- @css << build_css(context)
85
- end
86
- [@images.append(false), @css.join("\n")]
87
- end
88
-
89
-
90
- def build_single_image(context, filename)
91
- background = @background
92
- image = Image.read(filename){ self.background_color = background }.flatten.first
93
-
94
- context.merge!(build_context_for_single_image(image, filename, context['basename']))
95
- if @tile
96
- @images.from_blob(@tile.composite(image, @alignment, Magick::OverCompositeOp).to_blob){ self.background_color = background }
97
- else
98
- @images.from_blob(image.to_blob){ self.background_color = background }
99
- end
100
- context
101
- end
102
-
103
-
104
- def build_context_for_single_image(image, filename, basename)
105
- file_basename = File.basename(filename, '.*')
106
- {
107
- 'variations' => 0,
108
- 'variation_number' => 0,
109
- 'full_filename' => filename,
110
- 'file_basename' => file_basename,
111
- 'variation_name' => file_basename.gsub("#{basename}#{@delimiter}", ''),
112
- 'width' => @tile ? @tile.columns : image.columns,
113
- 'height' => @tile ? @tile.rows : image.rows,
114
- 'type' => :image,
115
- 'left' => left_value_for_context
116
- }
117
- end
118
-
119
-
120
- def left_value_for_context
121
- @tile ? (@images.length * @tile.columns) : (@images.any? ? @images.append(false).columns : 0)
122
- end
123
-
124
-
125
- def build_image_list(context, image_filenames)
126
- background = @background
127
- image_list = ImageList.new(*image_filenames){ self.background_color = background }
128
- context.merge!(build_context_for_image_list(image_list, image_filenames))
129
- if @tile
130
- tiles = ImageList.new{ self.background_color = background }
131
- image_list.each do |image|
132
- tiles << @tile.composite(image, @alignment, Magick::OverCompositeOp)
133
- end
134
- append_to_sprite(tiles)
135
- else
136
- append_to_sprite(image_list)
137
- end
138
- context
139
- end
140
-
141
-
142
- def append_to_sprite(images)
143
- background = @background
144
- @images.from_blob(images.append(true).to_blob){ self.background_color = background }
145
- end
146
-
147
-
148
- def build_context_for_image_list(image_list, image_filenames)
149
- {
150
- 'variations' => image_list.length,
151
- 'type' => :list,
152
- 'images' => image_list,
153
- 'filenames' => image_filenames,
154
- 'left' => left_value_for_context
155
- }
156
- end
157
-
158
-
159
- def build_tile
160
- return if @tile_size.nil?
161
- background = @background
162
- size_x, size_y = @tile_size.split('x').first(2).map{|dim| dim.to_i}
163
- @tile = Magick::Image.new(size_x, size_y){ self.background_color = background }
164
- @tile.format = "PNG"
165
- end
166
-
167
-
168
- def build_css(context = {})
169
- type = context['type']
170
- case type
171
- when :list
172
- css = build_css_for_list(context)
173
- when :image
174
- # render template if there is only one image
175
- css = @template.render(context)
176
- end
177
- css
178
- end
179
-
180
-
181
- def build_css_for_list(context)
182
- new_context = context.dup
183
- image_list = context.delete('images')
184
- new_context['type'] = :image
185
- css = image_list.inject([]) do |css, image|
186
- new_context['width'] = @tile ? @tile.columns : image.columns
187
- new_context['height'] = @tile ? @tile.rows : image.rows
188
- new_context['variation_number'] = css.size
189
- new_context['full_filename'] = context['filenames'].shift
190
- new_context['filename'] = File.basename(new_context['full_filename'])
191
- new_context['file_basename'] = File.basename(new_context['full_filename'], '.*')
192
- new_context['variation_name'] = new_context['file_basename'].gsub(/^#{new_context['basename']}#{@delimiter}/, '')
193
- css << build_css(new_context.dup)
194
-
195
- new_context['top'] += @tile ? @tile.rows : new_context['height']
196
- css
197
- end.join("\n")
198
- end
199
-
200
-
201
- # gather files that will be used to create the sprite
202
- def find_files(*args)
203
- args.inject([]) do |files, arg|
204
- found_files = Dir.glob(arg)
205
- if found_files.empty?
206
- files << arg if File.exists?(arg) rescue raise arg.inspect
207
- else
208
- files << found_files.flatten
209
- end
210
- files.flatten.compact.uniq
211
- end
212
- end
213
-
214
-
215
- # put filenames in format for each mode
216
- def find_files_for_mode
217
- case @distribution
218
- when :smart
219
- smart_distribution
220
- when :horizontal
221
- horizontal_distribution
222
- when :vertical
223
- vertical_distribution
224
- end
225
- end
226
-
227
-
228
- # gather information about the selected files
229
- # check for variations by using a delimiter as an indicator
230
- def smart_distribution
231
- @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
232
- basename = File.basename(file, '.*')
233
- without_variation = basename.split(@delimiter)[0..-2].join(@delimiter)
234
- basename = without_variation.nil? || without_variation == '' ? basename : without_variation
235
- h[basename] << file
236
- h
237
- end
238
- end
239
-
240
-
241
- def horizontal_distribution
242
- @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
243
- basename = File.basename(file)
244
- h[basename] << file
245
- h
246
- end
247
- end
248
-
249
-
250
- def vertical_distribution
251
- @files.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
252
- basename = File.basename(file)
253
- h['__all__'] << file
254
- h
255
- end
256
- end
257
-
258
-
259
- # simplyfied version of active_support's camelize version
260
- def camelize(lower_case_and_underscored_word)
261
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
262
- end
263
-
264
- end