image_optim 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +0 -2
- data/README.markdown +34 -1
- data/bin/image_optim +21 -113
- data/image_optim.gemspec +2 -2
- data/lib/image_optim/bin_not_found_error.rb +3 -0
- data/lib/image_optim/bin_resolver.rb +50 -0
- data/lib/image_optim/config.rb +137 -0
- data/lib/image_optim/configuration_error.rb +3 -0
- data/lib/image_optim/handler.rb +27 -0
- data/lib/image_optim/hash_helpers.rb +35 -0
- data/lib/image_optim/image_path.rb +27 -2
- data/lib/image_optim/option_helpers.rb +1 -20
- data/lib/image_optim/railtie.rb +19 -0
- data/lib/image_optim/runner.rb +107 -0
- data/lib/image_optim/true_false_nil.rb +3 -0
- data/lib/image_optim/worker/advpng.rb +1 -0
- data/lib/image_optim/worker/jpegoptim.rb +1 -0
- data/lib/image_optim/worker/optipng.rb +2 -0
- data/lib/image_optim/worker/pngout.rb +1 -0
- data/lib/image_optim/worker.rb +25 -6
- data/lib/image_optim.rb +60 -126
- data/script/update_worker_options_in_readme +36 -0
- data/spec/image_optim/bin_resolver_spec.rb +92 -0
- data/spec/image_optim/config_spec.rb +153 -0
- data/spec/image_optim/handler_spec.rb +44 -0
- data/spec/image_optim/hash_helpers_spec.rb +74 -0
- data/spec/image_optim/image_path_spec.rb +39 -0
- data/spec/image_optim_spec.rb +26 -94
- metadata +24 -6
- data/TODO +0 -12
- data/script/options_for_readme +0 -14
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzI5M2M3MWM1OWNiMDM5MGFiMmFkNDYwYzBiZjY5NWFiNDc0NTkyNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YWI3NDcwN2U3ZmIzZGI4NjYxZmJkOTVhYmQ5OGMzNTYyNGFlODI3Ng==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjVkYjUwYTEzMzQ2ZWZjNjg3ZWU1ZDJlMGRmY2FjOTc3NzU4YThlYTVmMmE2
|
10
|
+
YWEwODAzMGVkN2QyZDc4MjUyZGQ4ZDAyMTgzMDA3ZjVjNDQ3ODc5NTBlMzI1
|
11
|
+
MDU4ZDRlZTAzNjc2M2IwNWMxZGNjZjY0OWQ0MDQ2NGNhMThlYTE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjlhYjk1NDIyM2FlMzE4NjZlM2QxOTJjYzk0ZjJlYTI1YTYwMGUxODA3Yzdj
|
14
|
+
NzdjODY2NDBjZDIwODA2YTIzNThjZWJjMTg2ZGRlYjllYjg3NGIxNzU0Y2Fk
|
15
|
+
NWYwNDYwODkwN2FjNmI2ODU5NWNlNjdmMjY5NjdkZjk3ZTY3NDM=
|
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -13,7 +13,10 @@ Optimize (lossless compress) images (jpeg, png, gif) using external utilities:
|
|
13
13
|
|
14
14
|
Based on [ImageOptim.app](http://imageoptim.com/).
|
15
15
|
|
16
|
+
[![Gem Version](https://badge.fury.io/rb/image_optim.png)](http://badge.fury.io/rb/image_optim)
|
16
17
|
[![Build Status](https://travis-ci.org/toy/image_optim.png?branch=master)](https://travis-ci.org/toy/image_optim)
|
18
|
+
[![Code Climate](https://codeclimate.com/github/toy/image_optim.png)](https://codeclimate.com/github/toy/image_optim)
|
19
|
+
[![Dependency Status](https://gemnasium.com/toy/image_optim.png)](https://gemnasium.com/toy/image_optim)
|
17
20
|
|
18
21
|
## Gem installation
|
19
22
|
|
@@ -123,10 +126,38 @@ Multiple images:
|
|
123
126
|
|
124
127
|
image_optim.optimize_images!(Dir['*.*'])
|
125
128
|
|
129
|
+
### From rails
|
130
|
+
|
131
|
+
`ImageOptim::Railtie` will automatically initialize processing of assets if `config.assets.compress` is true.
|
132
|
+
|
133
|
+
As image optimization can be time consuming you may prefer to optimize original asset files.
|
134
|
+
|
135
|
+
Automatic assets processing can be turned off by setting `config.assets.image_optim = false`.
|
136
|
+
|
137
|
+
## Configuration
|
138
|
+
|
139
|
+
Configuration in YAML format will be read and prepanded to options from two paths:
|
140
|
+
|
141
|
+
* `$XDG_CONFIG_HOME/image_optim.yml` (by default `~/.config/image_optim.yml`)
|
142
|
+
* `.image_optim.yml` in current working directory
|
143
|
+
|
144
|
+
Example configuration:
|
145
|
+
|
146
|
+
nice: 20
|
147
|
+
pngout: false # disable
|
148
|
+
optipng:
|
149
|
+
level: 5
|
150
|
+
|
126
151
|
## Options
|
127
152
|
|
153
|
+
* `:nice` — Nice level *(defaults to 10)*
|
154
|
+
* `:threads` — Number of threads or disable *(defaults to number of processors)*
|
155
|
+
* `:verbose` — Verbose output *(defaults to false)*
|
156
|
+
|
128
157
|
Worker can be disabled by passing false instead of options hash.
|
129
158
|
|
159
|
+
<!---<worker-options>-->
|
160
|
+
|
130
161
|
### pngcrush
|
131
162
|
* `:chunks` — List of chunks to remove or 'alla' - all except tRNS/transparency or 'allb' - all except tRNS and gAMA/gamma *(defaults to alla)*
|
132
163
|
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to false)*
|
@@ -150,11 +181,13 @@ Worker can be disabled by passing false instead of options hash.
|
|
150
181
|
### jpegtran
|
151
182
|
* `:copy_chunks` — Copy all chunks *(defaults to false)*
|
152
183
|
* `:progressive` — Create progressive JPEG file *(defaults to true)*
|
153
|
-
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to
|
184
|
+
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to false)*
|
154
185
|
|
155
186
|
### gifsicle
|
156
187
|
* `:interlace` — Turn interlacing on *(defaults to false)*
|
157
188
|
|
189
|
+
<!---</worker-options>-->
|
190
|
+
|
158
191
|
## Copyright
|
159
192
|
|
160
193
|
Copyright (c) 2012-2013 Ivan Kuchin. See LICENSE.txt for details.
|
data/bin/image_optim
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
|
-
require 'image_optim'
|
5
|
-
require 'progress'
|
6
|
-
require 'optparse'
|
7
|
-
require 'find'
|
4
|
+
require 'image_optim/runner'
|
8
5
|
|
6
|
+
args = ARGV.dup
|
9
7
|
options = {}
|
10
8
|
|
11
|
-
OptionParser.accept(ImageOptim::TrueFalseNil, OptionParser.top.atype[TrueClass][0].merge('nil' => nil)){ |arg, val| val }
|
12
|
-
|
13
9
|
option_parser = OptionParser.new do |op|
|
14
|
-
op.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
op.accept(ImageOptim::TrueFalseNil, OptionParser.top.atype[TrueClass][0].merge('nil' => nil)){ |arg, val| val }
|
11
|
+
|
12
|
+
op.banner = <<-TEXT.gsub(/^\s*\|/, '')
|
13
|
+
|#{op.program_name} v#{ImageOptim.version}
|
14
|
+
|
|
15
|
+
|Usege:
|
16
|
+
| #{op.program_name} [options] image_path …
|
17
|
+
|
|
18
|
+
|Configuration will be read and prepanded to options from two paths:
|
19
|
+
| #{ImageOptim::Config::GLOBAL_CONFIG_PATH}
|
20
|
+
| #{ImageOptim::Config::LOCAL_CONFIG_PATH} (in current working directory)
|
21
|
+
|
|
20
22
|
TEXT
|
21
23
|
|
22
24
|
op.on('-r', '-R', '--recursive', 'Recurively scan directories for images') do |recursive|
|
@@ -84,7 +86,7 @@ Usege:
|
|
84
86
|
end
|
85
87
|
|
86
88
|
op.on_tail('-h', '--help', 'Show full help') do
|
87
|
-
puts
|
89
|
+
puts op.help
|
88
90
|
exit
|
89
91
|
end
|
90
92
|
|
@@ -95,108 +97,14 @@ Usege:
|
|
95
97
|
end
|
96
98
|
|
97
99
|
begin
|
98
|
-
option_parser.parse!
|
100
|
+
option_parser.parse!(args)
|
101
|
+
ImageOptim::Runner.run!(args, options) or exit 1
|
99
102
|
rescue OptionParser::ParseError => e
|
100
103
|
abort "#{e.to_s}\n\n#{option_parser.help}"
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
prefix = ' ' * level
|
106
|
-
options.each do |key, value|
|
107
|
-
if value.is_a?(Hash)
|
108
|
-
puts "#{prefix}#{key}:"
|
109
|
-
print_options(value, level + 1)
|
110
|
-
else
|
111
|
-
puts "#{prefix}#{key}: #{value.inspect}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
puts 'Options:'
|
117
|
-
print_options(options)
|
118
|
-
end
|
119
|
-
|
120
|
-
if ARGV.empty?
|
121
|
-
abort "specify image paths to optimize\n\n#{option_parser.help}"
|
122
|
-
else
|
123
|
-
recursive = options.delete(:recursive)
|
124
|
-
image_optim = begin
|
125
|
-
ImageOptim.new(options)
|
126
|
-
rescue ImageOptim::ConfigurationError => e
|
104
|
+
rescue => e
|
105
|
+
if options[:verbose]
|
106
|
+
abort "#{e.to_s}\n#{e.backtrace.join("\n")}"
|
107
|
+
else
|
127
108
|
abort e.to_s
|
128
109
|
end
|
129
|
-
|
130
|
-
paths = []
|
131
|
-
ARGV.each do |arg|
|
132
|
-
if File.file?(arg)
|
133
|
-
if image_optim.optimizable?(arg)
|
134
|
-
paths << arg
|
135
|
-
else
|
136
|
-
warn "#{arg} is not an image or there is no optimizer for it"
|
137
|
-
end
|
138
|
-
else
|
139
|
-
if recursive
|
140
|
-
Find.find(arg) do |path|
|
141
|
-
paths << path if File.file?(path) && image_optim.optimizable?(path)
|
142
|
-
end
|
143
|
-
else
|
144
|
-
warn "#{arg} is not a file"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
module Space
|
150
|
-
SIZE_SYMBOLS = %w[B K M G T P E Z Y].freeze
|
151
|
-
PRECISION = 1
|
152
|
-
LENGTH = 4 + PRECISION + 1
|
153
|
-
COEF = 1 / Math.log(10)
|
154
|
-
|
155
|
-
EMPTY_SPACE = ' ' * LENGTH
|
156
|
-
NOT_COUNTED_SPACE = '!' * LENGTH
|
157
|
-
|
158
|
-
class << self
|
159
|
-
attr_writer :base10
|
160
|
-
def denominator
|
161
|
-
@denominator ||= @base10 ? 1000.0 : 1024.0
|
162
|
-
end
|
163
|
-
|
164
|
-
def space(size, options = {})
|
165
|
-
case size
|
166
|
-
when false
|
167
|
-
NOT_COUNTED_SPACE.bold.red
|
168
|
-
when 0, nil
|
169
|
-
EMPTY_SPACE
|
170
|
-
else
|
171
|
-
number, degree = size, 0
|
172
|
-
while number.abs >= 1000 && degree < SIZE_SYMBOLS.length - 1
|
173
|
-
number /= denominator
|
174
|
-
degree += 1
|
175
|
-
end
|
176
|
-
|
177
|
-
"#{degree == 0 ? number.to_s : "%.#{PRECISION}f" % number}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def size_percent(src_size, dst_size)
|
184
|
-
'%5.2f%% %s' % [100 - 100.0 * dst_size / src_size, Space.space(src_size - dst_size)]
|
185
|
-
end
|
186
|
-
|
187
|
-
paths = paths.with_progress('optimizing')
|
188
|
-
results = image_optim.optimize_images(paths) do |src, dst|
|
189
|
-
if dst
|
190
|
-
src_size, dst_size = src.size, dst.size
|
191
|
-
percent = size_percent(src_size, dst_size)
|
192
|
-
dst.replace(src)
|
193
|
-
["#{percent} #{src}", src_size, dst_size]
|
194
|
-
else
|
195
|
-
["------ #{Space::EMPTY_SPACE} #{src}", src.size, src.size]
|
196
|
-
end
|
197
|
-
end
|
198
|
-
lines, src_sizes, dst_sizes = results.transpose
|
199
|
-
if lines
|
200
|
-
$stdout.puts lines, "Total: #{size_percent(src_sizes.inject(:+), dst_sizes.inject(:+))}\n"
|
201
|
-
end
|
202
110
|
end
|
data/image_optim.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_optim'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.10.0'
|
6
6
|
s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
16
|
s.require_paths = %w[lib]
|
17
17
|
|
18
|
-
s.add_dependency 'fspath', '~> 2.0
|
18
|
+
s.add_dependency 'fspath', '~> 2.1.0'
|
19
19
|
s.add_dependency 'image_size', '~> 1.1.2'
|
20
20
|
s.add_dependency 'exifr', '~> 1.1.3'
|
21
21
|
s.add_dependency 'progress', '~> 3.0.0'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'image_optim/bin_not_found_error'
|
2
|
+
require 'thread'
|
3
|
+
require 'fspath'
|
4
|
+
|
5
|
+
class ImageOptim
|
6
|
+
class BinResolver
|
7
|
+
attr_reader :dir
|
8
|
+
def initialize
|
9
|
+
@bins = {}
|
10
|
+
@lock = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve!(bin)
|
14
|
+
bin = bin.to_sym
|
15
|
+
unless @bins.include?(bin)
|
16
|
+
@lock.synchronize do
|
17
|
+
@bins[bin] = resolve?(bin) unless @bins.include?(bin)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@bins[bin] or raise BinNotFoundError, "`#{bin}` not found"
|
21
|
+
end
|
22
|
+
|
23
|
+
VENDOR_PATH = File.expand_path('../../../vendor', __FILE__)
|
24
|
+
|
25
|
+
def env_path
|
26
|
+
"#{dir}:#{ENV['PATH']}:#{VENDOR_PATH}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def resolve?(bin)
|
32
|
+
if path = ENV["#{bin}_bin".upcase]
|
33
|
+
unless @dir
|
34
|
+
@dir = FSPath.temp_dir
|
35
|
+
at_exit{ FileUtils.remove_entry_secure @dir }
|
36
|
+
end
|
37
|
+
symlink = @dir / bin
|
38
|
+
symlink.make_symlink(File.expand_path(path))
|
39
|
+
|
40
|
+
accessible?(symlink)
|
41
|
+
else
|
42
|
+
accessible?(bin)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def accessible?(bin)
|
47
|
+
`env PATH=#{env_path.shellescape} which #{bin.to_s.shellescape}` != ''
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'image_optim/option_helpers'
|
2
|
+
require 'image_optim/configuration_error'
|
3
|
+
require 'image_optim/hash_helpers'
|
4
|
+
require 'image_optim/worker'
|
5
|
+
require 'set'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class ImageOptim
|
9
|
+
class Config
|
10
|
+
include OptionHelpers
|
11
|
+
|
12
|
+
GLOBAL_CONFIG_PATH = File.join(File.expand_path(ENV['XDG_CONFIG_HOME'] || '~/.config'), 'image_optim.yml')
|
13
|
+
LOCAL_CONFIG_PATH = '.image_optim.yml'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def global
|
17
|
+
File.file?(GLOBAL_CONFIG_PATH) ? read(GLOBAL_CONFIG_PATH) : {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def local
|
21
|
+
File.file?(LOCAL_CONFIG_PATH) ? read(LOCAL_CONFIG_PATH) : {}
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def read(path)
|
27
|
+
config = YAML.load_file(path)
|
28
|
+
unless config.is_a?(Hash)
|
29
|
+
raise "excpected hash, got #{config.inspect}"
|
30
|
+
end
|
31
|
+
HashHelpers.deep_symbolise_keys(config)
|
32
|
+
rescue => e
|
33
|
+
warn "exception when reading #{path}: #{e}"
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(options)
|
39
|
+
@options = [
|
40
|
+
Config.global,
|
41
|
+
Config.local,
|
42
|
+
HashHelpers.deep_symbolise_keys(options),
|
43
|
+
].inject do |memo, hash|
|
44
|
+
HashHelpers.deep_merge(memo, hash)
|
45
|
+
end
|
46
|
+
@used = Set.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def get!(key)
|
50
|
+
key = key.to_sym
|
51
|
+
@used << key
|
52
|
+
@options[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_no_unused_options!
|
56
|
+
unknown_options = @options.reject{ |key, value| @used.include?(key) }
|
57
|
+
unless unknown_options.empty?
|
58
|
+
raise ConfigurationError, "unknown options #{unknown_options.inspect} for #{self}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def nice
|
63
|
+
nice = get!(:nice)
|
64
|
+
|
65
|
+
case nice
|
66
|
+
when true, nil
|
67
|
+
10
|
68
|
+
when false
|
69
|
+
0
|
70
|
+
else
|
71
|
+
nice.to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def threads
|
76
|
+
threads = get!(:threads)
|
77
|
+
|
78
|
+
threads = case threads
|
79
|
+
when true, nil
|
80
|
+
processor_count
|
81
|
+
when false
|
82
|
+
1
|
83
|
+
else
|
84
|
+
threads.to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
OptionHelpers.limit_with_range(threads, 1..16)
|
88
|
+
end
|
89
|
+
|
90
|
+
def verbose
|
91
|
+
!!get!(:verbose)
|
92
|
+
end
|
93
|
+
|
94
|
+
def for_worker(klass)
|
95
|
+
worker_options = get!(klass.bin_sym)
|
96
|
+
|
97
|
+
case worker_options
|
98
|
+
when Hash
|
99
|
+
worker_options
|
100
|
+
when true, nil
|
101
|
+
{}
|
102
|
+
when false
|
103
|
+
false
|
104
|
+
else
|
105
|
+
raise ConfigurationError, "Got #{worker_options.inspect} for #{klass.name} options"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
YAML.dump(HashHelpers.deep_stringify_keys(@options)).sub(/\A---\n/, '')
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
|
116
|
+
def processor_count
|
117
|
+
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
118
|
+
when /darwin9/
|
119
|
+
`hwprefs cpu_count`
|
120
|
+
when /darwin/
|
121
|
+
(`which hwprefs` != '') ? `hwprefs thread_count` : `sysctl -n hw.ncpu`
|
122
|
+
when /linux/
|
123
|
+
`grep -c processor /proc/cpuinfo`
|
124
|
+
when /freebsd/
|
125
|
+
`sysctl -n hw.ncpu`
|
126
|
+
when /mswin|mingw/
|
127
|
+
require 'win32ole'
|
128
|
+
wmi = WIN32OLE.connect('winmgmts://')
|
129
|
+
cpu = wmi.ExecQuery('select NumberOfLogicalProcessors from Win32_Processor')
|
130
|
+
cpu.to_enum.first.NumberOfLogicalProcessors
|
131
|
+
else
|
132
|
+
warn "Unknown architecture (#{host_os}) assuming one processor."
|
133
|
+
1
|
134
|
+
end.to_i
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'image_optim/image_path'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Handler
|
5
|
+
attr_reader :result
|
6
|
+
def initialize(original)
|
7
|
+
raise ArgumentError, 'original should respond to temp_path' unless original.respond_to?(:temp_path)
|
8
|
+
|
9
|
+
@original = original
|
10
|
+
@result = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
@src ||= @original
|
15
|
+
@dst ||= @original.temp_path
|
16
|
+
|
17
|
+
if yield @src, @dst
|
18
|
+
@result = @dst
|
19
|
+
if @src == @original
|
20
|
+
@src, @dst = @dst, nil
|
21
|
+
else
|
22
|
+
@src, @dst = @dst, @src
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ImageOptim
|
2
|
+
module HashHelpers
|
3
|
+
class << self
|
4
|
+
def deep_transform_keys(hash, &block)
|
5
|
+
new_hash = {}
|
6
|
+
hash.each do |k, v|
|
7
|
+
new_hash[block.call(k)] = if v.is_a?(Hash)
|
8
|
+
deep_transform_keys(v, &block)
|
9
|
+
else
|
10
|
+
v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
new_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def deep_stringify_keys(hash)
|
17
|
+
deep_transform_keys(hash, &:to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deep_symbolise_keys(hash)
|
21
|
+
deep_transform_keys(hash, &:to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
def deep_merge(a, b)
|
25
|
+
a.merge(b) do |k, v_a, v_b|
|
26
|
+
if v_a.is_a?(Hash) && v_b.is_a?(Hash)
|
27
|
+
deep_merge(v_a, v_b)
|
28
|
+
else
|
29
|
+
v_b
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,10 +1,30 @@
|
|
1
1
|
require 'fspath'
|
2
2
|
require 'image_size'
|
3
3
|
|
4
|
-
require 'image_optim'
|
5
|
-
|
6
4
|
class ImageOptim
|
7
5
|
class ImagePath < FSPath
|
6
|
+
class Optimized < DelegateClass(self)
|
7
|
+
def initialize(path, original_or_size = nil)
|
8
|
+
path = ImagePath.convert(path)
|
9
|
+
__setobj__(path)
|
10
|
+
if original_or_size
|
11
|
+
if original_or_size.is_a?(Integer)
|
12
|
+
@original = path
|
13
|
+
@original_size = original_or_size
|
14
|
+
else
|
15
|
+
@original = ImagePath.convert(original_or_size)
|
16
|
+
@original_size = @original.size
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Original path, use original_size to get its size as original can be overwritten
|
22
|
+
attr_reader :original
|
23
|
+
|
24
|
+
# Stored size of original
|
25
|
+
attr_reader :original_size
|
26
|
+
end
|
27
|
+
|
8
28
|
# Get temp path for this file with same extension
|
9
29
|
def temp_path(*args, &block)
|
10
30
|
ext = extname
|
@@ -30,5 +50,10 @@ class ImageOptim
|
|
30
50
|
def format
|
31
51
|
open{ |f| ImageSize.new(f) }.format
|
32
52
|
end
|
53
|
+
|
54
|
+
# Returns path if it is already an instance of this class otherwise new instance
|
55
|
+
def self.convert(path)
|
56
|
+
path.is_a?(self) ? path : new(path)
|
57
|
+
end
|
33
58
|
end
|
34
59
|
end
|
@@ -1,19 +1,7 @@
|
|
1
|
-
require 'image_optim'
|
1
|
+
require 'image_optim/configuration_error'
|
2
2
|
|
3
3
|
class ImageOptim
|
4
4
|
module OptionHelpers
|
5
|
-
# Remove option from hash and run through block or return default
|
6
|
-
def get_option!(options, name, default)
|
7
|
-
value = default
|
8
|
-
if options.has_key?(name)
|
9
|
-
value = options.delete(name)
|
10
|
-
end
|
11
|
-
if block_given?
|
12
|
-
value = yield(value)
|
13
|
-
end
|
14
|
-
instance_variable_set("@#{name}", value)
|
15
|
-
end
|
16
|
-
|
17
5
|
# Ensure number is in range
|
18
6
|
def self.limit_with_range(number, range)
|
19
7
|
if range.include?(number)
|
@@ -26,12 +14,5 @@ class ImageOptim
|
|
26
14
|
range.last
|
27
15
|
end
|
28
16
|
end
|
29
|
-
|
30
|
-
# Raise unless all options are deleted
|
31
|
-
def assert_options_empty!(options)
|
32
|
-
unless options.empty?
|
33
|
-
raise ConfigurationError, "unknown options #{options.inspect} for #{self}"
|
34
|
-
end
|
35
|
-
end
|
36
17
|
end
|
37
18
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'image_optim'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'image_optim.initializer' do |app|
|
6
|
+
if app.config.assets.compress && app.config.assets.image_optim != false
|
7
|
+
image_optim = ImageOptim.new
|
8
|
+
|
9
|
+
processor = proc do |context, data|
|
10
|
+
image_optim.optimize_image_data(data) || data
|
11
|
+
end
|
12
|
+
|
13
|
+
app.assets.register_preprocessor 'image/gif', :image_optim, &processor
|
14
|
+
app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
|
15
|
+
app.assets.register_preprocessor 'image/png', :image_optim, &processor
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|