polymer 1.0.0.beta.3
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/Gemfile +8 -0
- data/History.md +126 -0
- data/LICENSE +28 -0
- data/README.md +229 -0
- data/Rakefile +186 -0
- data/bin/polymer +10 -0
- data/lib/polymer/cache.rb +106 -0
- data/lib/polymer/cli.rb +340 -0
- data/lib/polymer/core_ext.rb +78 -0
- data/lib/polymer/css_generator.rb +32 -0
- data/lib/polymer/deviant_finder.rb +76 -0
- data/lib/polymer/dsl.rb +283 -0
- data/lib/polymer/man/polymer-bond.1 +60 -0
- data/lib/polymer/man/polymer-bond.1.txt +66 -0
- data/lib/polymer/man/polymer-init.1 +33 -0
- data/lib/polymer/man/polymer-init.1.txt +42 -0
- data/lib/polymer/man/polymer-optimise.1 +23 -0
- data/lib/polymer/man/polymer-optimise.1.txt +25 -0
- data/lib/polymer/man/polymer-position.1 +39 -0
- data/lib/polymer/man/polymer-position.1.txt +42 -0
- data/lib/polymer/man/polymer.1 +50 -0
- data/lib/polymer/man/polymer.1.txt +60 -0
- data/lib/polymer/man/polymer.5 +130 -0
- data/lib/polymer/man/polymer.5.txt +145 -0
- data/lib/polymer/optimisation.rb +130 -0
- data/lib/polymer/project.rb +164 -0
- data/lib/polymer/sass_generator.rb +38 -0
- data/lib/polymer/source.rb +55 -0
- data/lib/polymer/sprite.rb +130 -0
- data/lib/polymer/templates/polymer.tt +28 -0
- data/lib/polymer/templates/sass_mixins.erb +29 -0
- data/lib/polymer/templates/sources/one/book.png +0 -0
- data/lib/polymer/templates/sources/one/box-label.png +0 -0
- data/lib/polymer/templates/sources/one/calculator.png +0 -0
- data/lib/polymer/templates/sources/one/calendar-month.png +0 -0
- data/lib/polymer/templates/sources/one/camera.png +0 -0
- data/lib/polymer/templates/sources/one/eraser.png +0 -0
- data/lib/polymer/templates/sources/two/inbox-image.png +0 -0
- data/lib/polymer/templates/sources/two/magnet.png +0 -0
- data/lib/polymer/templates/sources/two/newspaper.png +0 -0
- data/lib/polymer/templates/sources/two/television.png +0 -0
- data/lib/polymer/templates/sources/two/wand-hat.png +0 -0
- data/lib/polymer/templates/sources/two/wooden-box-label.png +0 -0
- data/lib/polymer/version.rb +4 -0
- data/lib/polymer.rb +49 -0
- data/polymer.gemspec +94 -0
- metadata +206 -0
data/lib/polymer/cli.rb
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module Polymer
|
5
|
+
class CLI < Thor
|
6
|
+
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
class_option 'no-color', :type => :boolean, :default => false,
|
10
|
+
:desc => 'Disable colours in output', :aliases => '--no-colour'
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
self.shell = Thor::Shell::Basic.new if options['no-color']
|
15
|
+
end
|
16
|
+
|
17
|
+
# --- bond ---------------------------------------------------------------
|
18
|
+
|
19
|
+
desc 'bond [SPRITES]',
|
20
|
+
'Creates the sprites specified by your .polymer or polymer.rb file'
|
21
|
+
|
22
|
+
long_desc <<-DESC
|
23
|
+
The bond task reads your project configuration and creates your shiny
|
24
|
+
new sprites. If enabled, CSS and/or SCSS will also be written so as to
|
25
|
+
make working with your sprites a little easier.
|
26
|
+
|
27
|
+
You may specify exactly which sprites you want generated, otherwise
|
28
|
+
Polymer will generate all sprites defined in your config file. Any
|
29
|
+
sprite which has not changed since you last ran this command will not be
|
30
|
+
re-generated unless you pass the --force option.
|
31
|
+
DESC
|
32
|
+
|
33
|
+
method_option :force, :type => :boolean, :default => false,
|
34
|
+
:desc => 'Re-generates sprites whose sources have not changed'
|
35
|
+
|
36
|
+
method_option :fast, :type => :boolean, :default => false,
|
37
|
+
:desc => "Skip optimisation of images after they are generated"
|
38
|
+
|
39
|
+
def bond(*sprites)
|
40
|
+
project = find_project!
|
41
|
+
|
42
|
+
# Determine which sprites we'll be working on.
|
43
|
+
sprites = project.sprites.select do |sprite|
|
44
|
+
if sprites.empty? or sprites.include?(sprite.name)
|
45
|
+
# The user specified no sprites, or this sprite was requested.
|
46
|
+
if project.cache.stale?(sprite) or options[:force]
|
47
|
+
# Digest is different, user is forcing update or sprite file
|
48
|
+
# has been deleted.
|
49
|
+
project.cache.set(sprite)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# There's nothing to generate.
|
55
|
+
return if sprites.empty?
|
56
|
+
|
57
|
+
# Get on with it.
|
58
|
+
sprites.each do |sprite|
|
59
|
+
next unless sprite.save
|
60
|
+
|
61
|
+
say_status('generated', sprite.name, :green)
|
62
|
+
|
63
|
+
unless options[:fast]
|
64
|
+
say " optimising #{sprite.name} ... "
|
65
|
+
before = sprite.save_path.size
|
66
|
+
|
67
|
+
reduction = Polymer::Optimisation.optimise_file(sprite.save_path)
|
68
|
+
|
69
|
+
if reduction > 0
|
70
|
+
saved = '- saved %.2fkb (%.1f' %
|
71
|
+
[reduction.to_f / 1024, (reduction.to_f / before) * 100]
|
72
|
+
say_status "\r\e[0K optimised", "#{sprite.name} #{saved}%)", :green
|
73
|
+
else
|
74
|
+
print "\r\e[0K"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Stylesheets.
|
80
|
+
if SassGenerator.generate(project)
|
81
|
+
say_status('written', 'Sass mixin', :green)
|
82
|
+
end
|
83
|
+
|
84
|
+
#process Processors::CSS, project
|
85
|
+
|
86
|
+
# Find sprites with deviant-width sources.
|
87
|
+
sprites.each do |sprite|
|
88
|
+
if deviants = DeviantFinder.find_deviants(sprite)
|
89
|
+
say DeviantFinder.format_ui_message(sprite, deviants)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Clean up the cache, removing sprites which no longer exist.
|
94
|
+
project.cache.remove_all_except(project.sprites)
|
95
|
+
|
96
|
+
# Finish by writing the new cache.
|
97
|
+
project.cache.write
|
98
|
+
|
99
|
+
rescue Polymer::MissingSource, Polymer::TargetNotWritable => e
|
100
|
+
say e.message.compress_lines, :red
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
|
104
|
+
# --- help ---------------------------------------------------------------
|
105
|
+
|
106
|
+
# Provides customised help information using the man pages.
|
107
|
+
# Nod-of-the-hat to Bundler.
|
108
|
+
def help(command = nil)
|
109
|
+
page_map = {
|
110
|
+
# Main manual page.
|
111
|
+
nil => 'polymer.1',
|
112
|
+
'polymer' => 'polymer.1',
|
113
|
+
|
114
|
+
# Sub-commands.
|
115
|
+
'init' => 'polymer-init.1',
|
116
|
+
'bond' => 'polymer-bond.1',
|
117
|
+
'optimise' => 'polymer-optimise.1',
|
118
|
+
'optimize' => 'polymer-optimise.1',
|
119
|
+
'position' => 'polymer-position.1',
|
120
|
+
|
121
|
+
# Configuration format.
|
122
|
+
'polymer(5)' => 'polymer.5',
|
123
|
+
'polymer.5' => 'polymer.5',
|
124
|
+
'.polymer' => 'polymer.5',
|
125
|
+
'polymer.rb' => 'polymer.5',
|
126
|
+
'config' => 'polymer.5'
|
127
|
+
}
|
128
|
+
|
129
|
+
if page_map.has_key?(command)
|
130
|
+
root = File.expand_path('../man', __FILE__)
|
131
|
+
|
132
|
+
if groff_available?
|
133
|
+
groff = 'groff -Wall -mtty-char -mandoc -Tascii'
|
134
|
+
pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
|
135
|
+
|
136
|
+
Kernel.exec "#{groff} #{root}/#{page_map[command]} | #{pager}"
|
137
|
+
else
|
138
|
+
puts File.read("#{root}/#{page_map[command]}.txt")
|
139
|
+
end
|
140
|
+
else
|
141
|
+
super
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# --- init ---------------------------------------------------------------
|
146
|
+
|
147
|
+
desc 'init', 'Creates a new Polymer project in the current directory'
|
148
|
+
|
149
|
+
long_desc <<-DESC
|
150
|
+
In order to use Polymer, a .polymer configuration file must be created.
|
151
|
+
The init task creates a sample configuration, and also adds a couple of
|
152
|
+
example source images to demonstrate how to use Polymer to create your
|
153
|
+
own sprite images.
|
154
|
+
DESC
|
155
|
+
|
156
|
+
method_option :sprites, :type => :string, :default => 'public/images',
|
157
|
+
:desc => 'Default location to which generated sprites are saved'
|
158
|
+
|
159
|
+
method_option :sources, :type => :string, :default => '<sprites>/sprites',
|
160
|
+
:desc => 'Default location of source images'
|
161
|
+
|
162
|
+
method_option 'no-examples', :type => :boolean, :default => false,
|
163
|
+
:desc => "Disables copying of example source files"
|
164
|
+
|
165
|
+
method_option :windows, :type => :boolean, :default => false,
|
166
|
+
:desc => 'Create polymer.rb instead of .polymer for easier editing on ' \
|
167
|
+
'Windows systems.'
|
168
|
+
|
169
|
+
def init
|
170
|
+
if File.exists?('.polymer')
|
171
|
+
say 'A .polymer file already exists in this directory.', :red
|
172
|
+
exit 1
|
173
|
+
end
|
174
|
+
|
175
|
+
project_dir = Pathname.new(Dir.pwd)
|
176
|
+
|
177
|
+
config = {
|
178
|
+
:sprites => options[:sprites],
|
179
|
+
:sources => options[:sources].gsub(/<sprites>/, options[:sprites]),
|
180
|
+
:windows => options[:windows]
|
181
|
+
}
|
182
|
+
|
183
|
+
filename = options[:windows] ? 'polymer.rb' : '.polymer'
|
184
|
+
polymerfile = project_dir + filename
|
185
|
+
|
186
|
+
template 'polymer.tt', polymerfile, config
|
187
|
+
|
188
|
+
# Clean up the template.
|
189
|
+
contents = polymerfile.read.gsub(/\n{3,}/, "\n\n")
|
190
|
+
polymerfile.open('w') { |file| file.puts contents }
|
191
|
+
|
192
|
+
unless options['no-examples']
|
193
|
+
directory 'sources', project_dir + config[:sources]
|
194
|
+
end
|
195
|
+
|
196
|
+
say_status '', '-------------------------'
|
197
|
+
say_status '', 'Your project was created!'
|
198
|
+
end
|
199
|
+
|
200
|
+
# --- optimise -----------------------------------------------------------
|
201
|
+
|
202
|
+
desc 'optimise PATHS', 'Optimises PNG images at the given PATHS'
|
203
|
+
|
204
|
+
long_desc <<-DESC
|
205
|
+
Given a path to an image (or multiple images), runs Polymer's optimisers
|
206
|
+
on the image. Requires that the paths be images to PNG files. Image
|
207
|
+
paths are relative to the current working directory.
|
208
|
+
DESC
|
209
|
+
|
210
|
+
map 'optimize' => :optimise
|
211
|
+
|
212
|
+
def optimise(*paths)
|
213
|
+
dir = Pathname.new(Dir.pwd)
|
214
|
+
paths = paths.map { |path| dir + path }
|
215
|
+
|
216
|
+
paths.each do |path|
|
217
|
+
fpath = path.relative_path_from(dir).to_s
|
218
|
+
|
219
|
+
# Ensure the file is a PNG.
|
220
|
+
unless path.to_s =~ /\.png/
|
221
|
+
say_status 'skipped', "#{fpath} - not a PNG", :yellow
|
222
|
+
next
|
223
|
+
end
|
224
|
+
|
225
|
+
before = path.size
|
226
|
+
say " optimising #{fpath} "
|
227
|
+
reduction = Polymer::Optimisation.optimise_file(path)
|
228
|
+
|
229
|
+
if reduction > 0
|
230
|
+
saved = '- saved %.2fkb (%.1f' %
|
231
|
+
[reduction.to_f / 1024, (reduction.to_f / before) * 100]
|
232
|
+
say_status "\r\e[0K optimised", "#{fpath} #{saved}%)", :green
|
233
|
+
else
|
234
|
+
say_status "\r\e[0K optimised", "#{fpath} - no savings", :green
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# --- position -----------------------------------------------------------
|
240
|
+
|
241
|
+
desc 'position SOURCE', 'Shows the position of a source within a sprite'
|
242
|
+
|
243
|
+
long_desc <<-DESC
|
244
|
+
The position task shows you the position of a source image within a
|
245
|
+
sprite and also shows the appropriate CSS statement for the source
|
246
|
+
should you wish to create your own CSS files.
|
247
|
+
|
248
|
+
You may supply the name of a source image; if a source image with the
|
249
|
+
same name exists in multiple sprites, the positions of each of them will
|
250
|
+
be shown to you. If you want a particular source, you may instead
|
251
|
+
provide a "sprite/source" pair.
|
252
|
+
DESC
|
253
|
+
|
254
|
+
def position(source)
|
255
|
+
project = find_project!
|
256
|
+
|
257
|
+
if source.index('/')
|
258
|
+
# Full sprite/source pair given.
|
259
|
+
sprite, source = source.split('/', 2)
|
260
|
+
|
261
|
+
if project.sprite(sprite)
|
262
|
+
sprites = [project.sprite(sprite)]
|
263
|
+
else
|
264
|
+
say "No such sprite: #{sprite}", :red
|
265
|
+
exit 1
|
266
|
+
end
|
267
|
+
else
|
268
|
+
# Only a source name was given.
|
269
|
+
sprites = project.sprites
|
270
|
+
end
|
271
|
+
|
272
|
+
# Remove sprites which don't have a matching source.
|
273
|
+
sprites.reject! { |sprite| not sprite.source(source) }
|
274
|
+
say("No such source: #{source}") && exit(1) if sprites.empty?
|
275
|
+
|
276
|
+
say ""
|
277
|
+
|
278
|
+
sprites.each do |sprite|
|
279
|
+
say "#{sprite.name}/#{source}: #{sprite.position_of(source)}px", :green
|
280
|
+
say " #{Polymer::CSSGenerator.background_statement(sprite, source)}"
|
281
|
+
say " - or -"
|
282
|
+
say " #{Polymer::CSSGenerator.position_statement(sprite, source)}"
|
283
|
+
say ""
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# --- version ------------------------------------------------------------
|
288
|
+
|
289
|
+
desc 'version', "Shows the version of Polymer you're using"
|
290
|
+
map '--version' => :version
|
291
|
+
|
292
|
+
def version
|
293
|
+
say "Polymer #{Polymer::VERSION}"
|
294
|
+
end
|
295
|
+
|
296
|
+
private # ----------------------------------------------------------------
|
297
|
+
|
298
|
+
# Returns the Project for the current directory. Exits with a message if
|
299
|
+
# no project could be found.
|
300
|
+
#
|
301
|
+
# @return [Polymer::Project]
|
302
|
+
#
|
303
|
+
def find_project!
|
304
|
+
Polymer::DSL.load Polymer::Project.find_config(Dir.pwd)
|
305
|
+
rescue Polymer::MissingProject
|
306
|
+
say <<-ERROR.compress_lines, :red
|
307
|
+
Couldn't find a Polymer project in the current directory, or any of
|
308
|
+
the parent directories. Run "polymer init" if you want to create a new
|
309
|
+
project here.
|
310
|
+
ERROR
|
311
|
+
exit 1
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns if the current machine has groff available.
|
315
|
+
#
|
316
|
+
# @return [Boolean]
|
317
|
+
#
|
318
|
+
def groff_available?
|
319
|
+
require 'rbconfig'
|
320
|
+
|
321
|
+
if RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
|
322
|
+
`which groff 2>NUL`
|
323
|
+
else
|
324
|
+
`which groff 2>/dev/null`
|
325
|
+
end
|
326
|
+
|
327
|
+
$? == 0
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.source_root
|
331
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
332
|
+
end
|
333
|
+
|
334
|
+
# Temporary -- until the next Thor release.
|
335
|
+
def self.banner(task, namespace = nil, subcommand = false)
|
336
|
+
super.gsub(/^.*polymer/, 'polymer')
|
337
|
+
end
|
338
|
+
|
339
|
+
end # CLI
|
340
|
+
end # Polymer
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Some simple string extensions to make things easier.
|
2
|
+
class String
|
3
|
+
|
4
|
+
# Replace sequences of whitespace (including newlines) with either
|
5
|
+
# a single space or remove them entirely (according to param _spaced_)
|
6
|
+
#
|
7
|
+
# <<QUERY.compress_lines
|
8
|
+
# SELECT name
|
9
|
+
# FROM users
|
10
|
+
# QUERY => "SELECT name FROM users"
|
11
|
+
#
|
12
|
+
# @return [String] Receiver with whitespace (including newlines) replaced
|
13
|
+
#
|
14
|
+
def compress_lines
|
15
|
+
split($/).map { |line| line.strip }.join(' ')
|
16
|
+
end
|
17
|
+
|
18
|
+
# Removes leading whitespace from each line, such as might be added when
|
19
|
+
# using a HEREDOC string.
|
20
|
+
#
|
21
|
+
# @return [String] Receiver with leading whitespace removed.
|
22
|
+
#
|
23
|
+
def unindent
|
24
|
+
(other = dup) and other.unindent! and other
|
25
|
+
end
|
26
|
+
|
27
|
+
# Bang version of #unindent.
|
28
|
+
#
|
29
|
+
# @return [String] Receiver with leading whitespace removed.
|
30
|
+
#
|
31
|
+
def unindent!
|
32
|
+
gsub!(/^[ \t]{#{minimum_leading_whitespace}}/, '')
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Checks each line and determines the minimum amount of leading whitespace.
|
38
|
+
#
|
39
|
+
# @return [Integer] The number of leading whitespace characters.
|
40
|
+
#
|
41
|
+
def minimum_leading_whitespace
|
42
|
+
whitespace = split("\n", -1).inject(0) do |indent, line|
|
43
|
+
if line.strip.empty?
|
44
|
+
indent # Ignore completely blank lines.
|
45
|
+
elsif line =~ /^(\s+)/
|
46
|
+
(1.0 / $1.length) > indent ? 1.0 / $1.length : indent
|
47
|
+
else
|
48
|
+
1.0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
whitespace == 1.0 ? 0 : (1.0 / whitespace).to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
# String#compress_lines is extracted from the extlib gem
|
58
|
+
# ------------------------------------------------------
|
59
|
+
#
|
60
|
+
# Copyright (c) 2009 Dan Kubb
|
61
|
+
#
|
62
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
63
|
+
# of this software and associated documentation files (the "Software"), to
|
64
|
+
# deal in the Software without restriction, including without limitation the
|
65
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
66
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
67
|
+
# furnished to do so, subject to the following conditions:
|
68
|
+
#
|
69
|
+
# The above copyright notice and this permission notice shall be included in
|
70
|
+
# all copies or substantial portions of the Software.
|
71
|
+
#
|
72
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
73
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
74
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
75
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
76
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
77
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
78
|
+
# IN THE SOFTWARE.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Polymer
|
2
|
+
class CSSGenerator
|
3
|
+
|
4
|
+
# --- Class Methods ------------------------------------------------------
|
5
|
+
|
6
|
+
# Returns a string which may be used as the background statement for the
|
7
|
+
# given sprite and source pair.
|
8
|
+
#
|
9
|
+
# @param [Polymer::Sprite] sprite
|
10
|
+
# @param [Polymer::Source] source
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
def self.background_statement(sprite, source)
|
15
|
+
"background: url(#{sprite.url}) 0 " \
|
16
|
+
"#{-sprite.position_of(source)}px no-repeat;"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a string which may be used as the background-position statement
|
20
|
+
# for the given sprite and source pair.
|
21
|
+
#
|
22
|
+
# @param [Polymer::Sprite] sprite
|
23
|
+
# @param [Polymer::Source] source
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
def self.position_statement(sprite, source)
|
28
|
+
"background-position: 0 #{-sprite.position_of(source)}px;"
|
29
|
+
end
|
30
|
+
|
31
|
+
end # CSSGenerator
|
32
|
+
end # Polymer
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Polymer
|
2
|
+
# Given a Sprite, DeviantFinder checks to see if the sprite has any sources
|
3
|
+
# which are significantly wider than the average.
|
4
|
+
class DeviantFinder
|
5
|
+
|
6
|
+
# Given a Sprite, checks to see if the sprite has any sources which are
|
7
|
+
# significantly wider than the average. Images with a small standard
|
8
|
+
# deviation in source width or fewer than 3 sources will be skipped.
|
9
|
+
#
|
10
|
+
# @param [Polymer::Sprite] sprite
|
11
|
+
# The sprite whose source widths are to be checked.
|
12
|
+
#
|
13
|
+
# @return [Array<String>]
|
14
|
+
# Returns an array of source images whose width is greater than the
|
15
|
+
# standard deviation.
|
16
|
+
# @return [nil]
|
17
|
+
# Returns nil if all of the source images are an approriate width.
|
18
|
+
#
|
19
|
+
def self.find_deviants(sprite)
|
20
|
+
# Need more than two sources to find deviants.
|
21
|
+
return false if sprite.sources.size < 2
|
22
|
+
|
23
|
+
mean, std_dev = standard_deviation(sprite.sources.map do |source|
|
24
|
+
source.image.columns
|
25
|
+
end)
|
26
|
+
|
27
|
+
return false if std_dev < 100 # Skip images with a < 100px deviation.
|
28
|
+
|
29
|
+
deviants = sprite.sources.select do |source|
|
30
|
+
width = source.image.columns
|
31
|
+
width > mean + std_dev || width < mean - std_dev
|
32
|
+
end
|
33
|
+
|
34
|
+
deviants.any? and deviants
|
35
|
+
end
|
36
|
+
|
37
|
+
# Print a warning if the sprite contains wide sources.
|
38
|
+
def self.format_ui_message(sprite, deviants)
|
39
|
+
if deviants
|
40
|
+
<<-MESSAGE.compress_lines
|
41
|
+
Your "#{sprite.name}" sprite contains one or more source images
|
42
|
+
which deviate significantly from the average source width. You might
|
43
|
+
want to consider removing these sources from the sprite in order to
|
44
|
+
reduce the sprite filesize.
|
45
|
+
|
46
|
+
Wide sources: #{deviants.map(&:name).join(', ')}
|
47
|
+
MESSAGE
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private # ----------------------------------------------------------------
|
52
|
+
|
53
|
+
# Knuth.
|
54
|
+
#
|
55
|
+
# @param [Array<Integer>] data
|
56
|
+
# An array containing the widths of each source image.
|
57
|
+
#
|
58
|
+
# @return [Array<Integer, Integer>]
|
59
|
+
# Returns a two-element array whose first element is the mean width of
|
60
|
+
# the source images; the second element is the standard deviation.
|
61
|
+
#
|
62
|
+
def self.standard_deviation(data)
|
63
|
+
n, mean, m2 = 0, 0, 0
|
64
|
+
|
65
|
+
data.each do |x|
|
66
|
+
n = n + 1
|
67
|
+
delta = x - mean
|
68
|
+
mean = mean + delta / n
|
69
|
+
m2 = m2 + delta * (x - mean)
|
70
|
+
end
|
71
|
+
|
72
|
+
[ mean, Math.sqrt(m2 / (n - 1)) ]
|
73
|
+
end
|
74
|
+
|
75
|
+
end # DeviantFinder
|
76
|
+
end # Polymer
|