sprite_generator 0.2.5 → 0.3.0

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.
@@ -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