the-architect-sprite_generator 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,33 @@
1
+ = SpriteGenerator
2
+
3
+ Automatically generate Sprite Images and the corresponding CSS using RMagick and Liquid.
4
+
5
+ == Dependencies
6
+
7
+ * RMagick 2.2.2
8
+ * Liquid 1.7.0
9
+
10
+ == LICENSE:
11
+
12
+ (The MIT License)
13
+
14
+ Copyright (c) 2009 Marcel Scherf
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining
17
+ a copy of this software and associated documentation files (the
18
+ 'Software'), to deal in the Software without restriction, including
19
+ without limitation the rights to use, copy, modify, merge, publish,
20
+ distribute, sublicense, and/or sell copies of the Software, and to
21
+ permit persons to whom the Software is furnished to do so, subject to
22
+ the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be
25
+ included in all copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
28
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
31
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
32
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
33
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,190 @@
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
+ @analyzed = analyze_filenames(@files, @delimiter)
46
+ @template = Liquid::Template.parse(options[:template] || '')
47
+
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
+ background = @background
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
63
+ destination = @root.nil? || @root.empty? ? @output : File.join(@root, @output)
64
+ image, css = build(tile)
65
+ image.write(destination){ self.background_color = background }
66
+ css
67
+ end
68
+
69
+
70
+ protected
71
+
72
+ # simplyfied version of active_supports camelize version
73
+ def camelize(lower_case_and_underscored_word)
74
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
75
+ end
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 = []
82
+
83
+ @analyzed.each do |key, value|
84
+ if tile
85
+ context['left'] = images.length > 0 ? tile.columns : 0
86
+ else
87
+ context['left'] = images.length > 0 ? images.append(false).columns : 0
88
+ end
89
+ context['top'] = 0
90
+ context['basename'] = key
91
+ context['overall'] = images.length
92
+
93
+ if value.size > 1
94
+ image_list = ImageList.new(*value){ self.background_color = background }
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
+ image = Image.read(value.flatten.first){ self.background_color = background }
110
+ context['variations'] = 0
111
+ context['variation_name'] = ''
112
+ context['variation_number'] = 0
113
+ context['type'] = :image
114
+
115
+ if tile
116
+ images.from_blob(tile.composite(image.first, @alignment, Magick::OverCompositeOp).to_blob){ self.background_color = background }
117
+ else
118
+ images.from_blob(image.first.to_blob){ self.background_color = background }
119
+ end
120
+ end
121
+ css << build_css(context)
122
+ end
123
+
124
+ [images.append(false), css.join("\n")]
125
+ end
126
+
127
+
128
+ def build_css(context = {})
129
+ type = context.delete('type')
130
+ case type
131
+ when :list
132
+ css = build_css_for_list(context)
133
+ when :image
134
+ # render template if there is only one image
135
+ css = @template.render(context)
136
+ end
137
+ css
138
+ end
139
+
140
+
141
+ def build_css_for_list(context)
142
+ new_context = context.dup
143
+ tile = new_context.delete('tile')
144
+ image_list = context.delete('images')
145
+ new_context['type'] = :image
146
+ css = image_list.inject([]) do |css, image|
147
+ new_context['width'] = tile ? tile.columns : image.columns
148
+ new_context['height'] = tile ? tile.rows : image.rows
149
+ new_context['variation_number'] = css.size
150
+
151
+ new_context['full_filename'] = context['filenames'].shift
152
+ new_context['filename'] = File.basename(new_context['full_filename'])
153
+ new_context['file_basename'] = File.basename(new_context['full_filename'], '.*')
154
+ new_context['variation_name'] = new_context['file_basename'].gsub(/^#{new_context['basename']}#{@delimiter}/, '')
155
+
156
+ css << build_css(new_context.dup)
157
+ new_context['top'] += tile ? tile.columns : new_context['height']
158
+ css
159
+ end.join("\n")
160
+ end
161
+
162
+
163
+ # gather files that will be used to create the sprite
164
+ def find_files(*args)
165
+ args.inject([]) do |files, arg|
166
+ found_files = Dir.glob(arg)
167
+ if found_files.empty?
168
+ files << arg if File.exists?(arg) rescue raise arg.inspect
169
+ else
170
+ files << found_files.flatten
171
+ end
172
+ files.flatten.compact.uniq
173
+ end
174
+ end
175
+
176
+
177
+ # gather information about the selected files
178
+ # check for variations by using a delimiter as an indicator
179
+ def analyze_filenames(file_names, delimiter = '_')
180
+ file_names.inject(Hash.new{|hash, key| hash[key] = Array.new;}) do |h, file|
181
+ basename = File.basename(file).split('.').first
182
+ without_variation = basename.split(delimiter)[0..-2].join(delimiter)
183
+ basename = without_variation.nil? || without_variation == '' ? basename : without_variation
184
+ h[basename] << file
185
+ h
186
+ end
187
+ end
188
+
189
+
190
+ end
@@ -0,0 +1,29 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../lib/sprite_batch_generator')
3
+
4
+ class SpriteBatchGeneratorTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @config = 'test/config/batch.yml'
8
+ end
9
+
10
+ def teardown
11
+ # delete test output
12
+ Dir.glob('test/output/*').each{|f| File.delete f }
13
+ end
14
+
15
+ def test_should_read_config_file
16
+ @batch = SpriteBatchGenerator.new(@config)
17
+ assert_not_nil @batch
18
+ assert_equal 2, @batch.batches.size
19
+ end
20
+
21
+ def test_should_create_files_from_config
22
+ @batch = SpriteBatchGenerator.new(@config)
23
+ css = @batch.generate
24
+ assert_not_nil css
25
+ output_files = Dir.glob('test/output/*')
26
+ assert 6, output_files.size
27
+ end
28
+
29
+ end
@@ -0,0 +1,122 @@
1
+ require 'rubygems'
2
+ require 'liquid'
3
+ require 'test/unit'
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../lib/sprite_generator')
5
+
6
+ class SpriteGeneratorTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @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'
13
+ end
14
+
15
+
16
+ def teardown
17
+ # delete test output
18
+ Dir.glob('test/output/*').each{|f| File.delete f }
19
+ end
20
+
21
+
22
+ def test_should_create_correct_context
23
+ template = %q{
24
+ basename: {{basename}}
25
+ variation: {{variation}}
26
+ sprite_location: {{sprite_location}}
27
+ left: {{left}}
28
+ top: {{top}}
29
+ width: {{width}}
30
+ height: {{height}}
31
+ filename: {{filename}}
32
+ file_basename: {{file_basename}}
33
+ full_filename: {{full_filename}}
34
+ variations: {{variations}}
35
+ variation_number: {{variation_number}}
36
+ variation_name: {{variation_name}}
37
+ }
38
+ @generator = SpriteGenerator.new(@all_images_path, @output, nil, {:template => template})
39
+ css = @generator.create
40
+ assert css.include?('basename: emoticon-evilgrin')
41
+ assert css.include?('variation_name: evilgrin')
42
+ assert css.include?('file_basename: emoticon-evilgrin')
43
+ end
44
+
45
+ def test_should_use_alignment_option
46
+ assert_nothing_raised do
47
+ SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'west'})
48
+ end
49
+ end
50
+
51
+ def test_should_complain_over_unknown_alignment_option
52
+ assert_raise NameError do
53
+ SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF', :alignment => 'somewhere'})
54
+ end
55
+ end
56
+
57
+ def test_should_center_images_on_tiles
58
+ @generator = SpriteGenerator.new(@all_images_path, @output, nil, {:template => @template, :tile => '100x100', :background => '#FFFFFF00'})
59
+ css = @generator.create
60
+ page = Liquid::Template.parse(File.open('test/templates/test.html').read).render('css' => css)
61
+ assert page.include?("{ background:")
62
+ assert page.include?("width:100px")
63
+ assert page.include?("height:100px")
64
+ assert page.include?("-300px")
65
+ assert page.include?("-800px")
66
+ File.open(@page_path, 'w+'){|f| f.puts page }
67
+ assert File.exists?(@page_path)
68
+ end
69
+
70
+
71
+ def test_should_create_correct_css
72
+ @generator = SpriteGenerator.new(@all_images_path, @output, nil, :template => @template)
73
+ css = @generator.create()
74
+ page = Liquid::Template.parse(File.open('test/templates/test.html').read).render('css' => css)
75
+ assert page.include?("{ background:")
76
+ assert page.include?("width:16px")
77
+ assert page.include?("height:16px")
78
+ assert page.include?("-16px")
79
+ assert page.include?("-96px")
80
+ File.open(@page_path, 'w+'){|f| f.puts page }
81
+ assert File.exists?(@page_path)
82
+ end
83
+
84
+
85
+ def test_should_generate_sprite_file
86
+ @generator = SpriteGenerator.new(@all_images_path, @output, nil, :template => @template)
87
+ css = @generator.create
88
+ assert !(css.nil? || css.empty?)
89
+ assert File.exists?(@output)
90
+ end
91
+
92
+ # bad, testing internal state
93
+ def test_should_find_versions_of_emoticons
94
+ files = Dir.glob(@all_images_path)
95
+ @generator = SpriteGenerator.new(files, @output, nil, {})
96
+ analyzed = @generator.instance_variable_get(:@analyzed)
97
+ assert_equal 9, analyzed['emoticon'].size
98
+ end
99
+
100
+ # bad, using internal state
101
+ def test_should_find_files_for_glob_path
102
+ @generator = SpriteGenerator.new(@all_images_path, @output, nil, {})
103
+ files = @generator.instance_variable_get(:@files)
104
+ assert_equal 16, files.size
105
+ end
106
+
107
+ # bad, using internal state
108
+ def test_should_find_files
109
+ @generator = SpriteGenerator.new(['test/images/emoticon-evilgrin.png', 'test/images/emoticon-grin.png'], @output, nil, {})
110
+ files = @generator.instance_variable_get(:@files)
111
+ assert_equal 2, files.size
112
+ end
113
+
114
+ # bad, using internal state
115
+ def test_should_not_find_anything
116
+ @generator = SpriteGenerator.new('test/blalala/*.hurz', @output, nil, {})
117
+ files = @generator.instance_variable_get(:@files)
118
+ assert_equal 0, files.size
119
+ end
120
+
121
+
122
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: the-architect-sprite_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.11
5
+ platform: ruby
6
+ authors:
7
+ - Marcel Scherf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: liquid
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rmagick
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: marcel.scherf@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.txt
43
+ files:
44
+ - lib/sprite_batch_generator.rb
45
+ - lib/sprite_generator.rb
46
+ - README.txt
47
+ has_rdoc: true
48
+ homepage: http://github.com/the-architect/spritegenerator
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.2.0
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Automatically generate Sprite Images and the corresponding CSS using RMagick and Liquid.
73
+ test_files:
74
+ - test/units/test_sprite_batch_generator.rb
75
+ - test/units/test_sprite_generator.rb