the-architect-sprite_generator 0.1.11

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