image_optim 0.14.0 → 0.15.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.
- checksums.yaml +8 -8
- data/.gitignore +2 -0
- data/.travis.yml +9 -3
- data/CONTRIBUTING.markdown +9 -0
- data/README.markdown +38 -27
- data/bin/image_optim +5 -0
- data/image_optim.gemspec +4 -4
- data/lib/image_optim.rb +20 -17
- data/lib/image_optim/bin_resolver.rb +13 -5
- data/lib/image_optim/bin_resolver/simple_version.rb +7 -0
- data/lib/image_optim/handler.rb +16 -0
- data/lib/image_optim/non_negative_integer_range.rb +11 -0
- data/lib/image_optim/railtie.rb +9 -4
- data/lib/image_optim/runner.rb +1 -2
- data/lib/image_optim/space.rb +1 -1
- data/lib/image_optim/true_false_nil.rb +5 -0
- data/lib/image_optim/worker.rb +8 -0
- data/lib/image_optim/worker/optipng.rb +1 -2
- data/lib/image_optim/worker/pngquant.rb +43 -0
- data/script/update_worker_options_in_readme +1 -1
- data/spec/image_optim/bin_resolver_spec.rb +3 -3
- data/spec/image_optim/handler_spec.rb +33 -0
- data/spec/image_optim/worker_spec.rb +3 -1
- data/spec/image_optim_spec.rb +25 -17
- data/spec/images/orient/generate +2 -0
- data/spec/images/quant/64.png +0 -0
- data/spec/images/quant/generate +25 -0
- metadata +15 -7
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ODA5N2IyMjgwM2FjYTU3OGM4NjE2OTU0NzM5NGRhOTRiNGExNDM5Mw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YTNiZmYwOTk0OWY5NmE0OGM3OTliNzRmNTE5NDIwMGQ4OWM4NmMyMQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YzI4NWY2MmU5Y2Y3YjJjMmExNzQ1NDk4NmE5NjhmOWFjMjVlZmY1NmQyMmM3
|
10
|
+
M2RmZTAzZWI1Mzg2MTQ3ZDQ4YzkyZjM5MGYxYjcwM2NiMTE3Mzg1ZmMwY2Vl
|
11
|
+
MmQwODgxMDkwYmVlOTE2ZjY1MTA2YzY4ODgxMzJmODFiN2FmMmU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NzViMzQ4Yjc0Y2MyYjdlNDBiNjM1N2U5ZmIzYTkzYzZlZjNkNjZlMzMxMDVk
|
14
|
+
NWEwYjk0MzEzMTU3NDdlNmZiZjlmMjMzYWM2ZjRjNGNlNjRmY2I2MTY2MjRm
|
15
|
+
YmFjNmE0ZTc2M2I1MGVlM2RiOTg1OTJmOGFhZjEwYzkxMjNiNTY=
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -17,12 +17,18 @@ script:
|
|
17
17
|
before_install:
|
18
18
|
- sudo apt-get update -qq
|
19
19
|
- sudo apt-get install -qq advancecomp gifsicle jhead jpegoptim libjpeg-progs optipng pngcrush
|
20
|
+
- npm install -g svgo
|
21
|
+
- mkdir ~/bin
|
22
|
+
# pngquant:
|
23
|
+
- git clone git://github.com/pornel/pngquant.git
|
24
|
+
- pushd pngquant && git checkout $(git describe --tags --abbrev=0) && make && popd
|
25
|
+
- mv pngquant/pngquant ~/bin
|
26
|
+
# pngout:
|
20
27
|
- wget http://static.jonof.id.au/dl/kenutils/pngout-20130221-linux.tar.gz
|
21
28
|
- tar -xzf pngout-*-linux.tar.gz
|
22
|
-
- mv pngout-*-linux
|
23
|
-
- npm install -g svgo
|
29
|
+
- mv pngout-*-linux/x86_64/pngout ~/bin
|
24
30
|
env:
|
25
|
-
- PATH
|
31
|
+
- PATH=~/bin:$PATH
|
26
32
|
matrix:
|
27
33
|
allow_failures:
|
28
34
|
- rvm: ruby-head
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
* Create topic/feature branch: `git checkout -b awesome-changes`
|
4
|
+
* Commit…
|
5
|
+
* Run tests: `bundle exec rspec`
|
6
|
+
* Check code style: `bundle exec rubocop`
|
7
|
+
* Push your branch: `git push origin awesome-changes`
|
8
|
+
* Create pull request
|
9
|
+
* Check if [travis is happy](https://travis-ci.org/toy/image_optim/pull_requests)
|
data/README.markdown
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
[](https://rubygems.org/gems/image_optim)
|
2
|
-
[](https://travis-ci.org/toy/image_optim)
|
3
|
-
[](https://codeclimate.com/github/toy/image_optim)
|
4
|
-
[](https://gemnasium.com/toy/image_optim)
|
5
|
-
[](http://inch-ci.org/github/toy/image_optim)
|
6
|
-
[](https://www.gittip.com/toy/)
|
1
|
+
[](https://rubygems.org/gems/image_optim)
|
2
|
+
[](https://travis-ci.org/toy/image_optim)
|
3
|
+
[](https://codeclimate.com/github/toy/image_optim)
|
4
|
+
[](https://gemnasium.com/toy/image_optim)
|
5
|
+
[](http://inch-ci.org/github/toy/image_optim)
|
6
|
+
[](https://www.gittip.com/toy/)
|
7
7
|
|
8
8
|
# image_optim
|
9
9
|
|
@@ -18,6 +18,7 @@ Optimize (lossless compress) images (jpeg, png, gif, svg) using external utiliti
|
|
18
18
|
* [optipng](http://optipng.sourceforge.net/)
|
19
19
|
* [pngcrush](http://pmt.sourceforge.net/pngcrush/)
|
20
20
|
* [pngout](http://www.advsys.net/ken/util/pngout.htm)
|
21
|
+
* [pngquant](http://pngquant.org/)
|
21
22
|
* [svgo](https://github.com/svg/svgo)
|
22
23
|
|
23
24
|
Based on [ImageOptim.app](http://imageoptim.com/).
|
@@ -81,13 +82,15 @@ Besides permanently setting environment variables in `~/.profile`, `~/.bash_prof
|
|
81
82
|
### Linux - Debian/Ubuntu
|
82
83
|
|
83
84
|
```bash
|
84
|
-
sudo apt-get install -y advancecomp gifsicle jhead jpegoptim libjpeg-progs optipng pngcrush
|
85
|
+
sudo apt-get install -y advancecomp gifsicle jhead jpegoptim libjpeg-progs optipng pngcrush pngquant
|
85
86
|
```
|
86
87
|
|
88
|
+
If you get an old version of `pngquant`, please check how to install up-to-date version or compile from source at [http://pngquant.org/](http://pngquant.org/).
|
89
|
+
|
87
90
|
### Linux - RHEL/Fedora/Centos
|
88
91
|
|
89
92
|
```bash
|
90
|
-
sudo yum install -y advancecomp gifsicle jhead libjpeg optipng
|
93
|
+
sudo yum install -y advancecomp gifsicle jhead libjpeg optipng pngquant
|
91
94
|
```
|
92
95
|
|
93
96
|
You may also need to install `libjpeg-turbo-utils` instead of `libjpeg`:
|
@@ -127,13 +130,13 @@ make && cp -f pngcrush /usr/local/bin
|
|
127
130
|
### OS X: Macports
|
128
131
|
|
129
132
|
```bash
|
130
|
-
sudo port install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush
|
133
|
+
sudo port install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush pngquant
|
131
134
|
```
|
132
135
|
|
133
136
|
### OS X: Brew
|
134
137
|
|
135
138
|
```bash
|
136
|
-
brew install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush
|
139
|
+
brew install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush pngquant
|
137
140
|
```
|
138
141
|
|
139
142
|
### pngout installation (optional)
|
@@ -238,24 +241,14 @@ optipng:
|
|
238
241
|
Worker can be disabled by passing `false` instead of options hash.
|
239
242
|
|
240
243
|
<!---<worker-options>-->
|
241
|
-
<!-- worker options
|
242
|
-
|
243
|
-
### :pngcrush =>
|
244
|
-
* `:chunks` — List of chunks to remove or `:alla` - all except tRNS/transparency or `:allb` - all except tRNS and gAMA/gamma *(defaults to `:alla`)*
|
245
|
-
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to `false`)*
|
246
|
-
* `:brute` — Brute force try all methods, very time-consuming and generally not worthwhile *(defaults to `false`)*
|
247
|
-
|
248
|
-
### :pngout =>
|
249
|
-
* `:copy_chunks` — Copy optional chunks *(defaults to `false`)*
|
250
|
-
* `:strategy` — Strategy: `0` - xtreme, `1` - intense, `2` - longest Match, `3` - huffman Only, `4` - uncompressed *(defaults to `0`)*
|
251
|
-
|
252
|
-
### :optipng =>
|
253
|
-
* `:level` — Optimization level preset: `0` is least, `7` is best *(defaults to `6`)*
|
254
|
-
* `:interlace` — Interlace, `true` - interlace on, `false` - interlace off, `nil` - as is in original image *(defaults to `false`)*
|
244
|
+
<!-- markdown for worker options is generated by `script/update_worker_options_in_readme` -->
|
255
245
|
|
256
246
|
### :advpng =>
|
257
247
|
* `:level` — Compression level: `0` - don't compress, `1` - fast, `2` - normal, `3` - extra, `4` - extreme *(defaults to `4`)*
|
258
248
|
|
249
|
+
### :gifsicle =>
|
250
|
+
* `:interlace` — Turn interlacing on *(defaults to `false`)*
|
251
|
+
|
259
252
|
### :jhead =>
|
260
253
|
Worker has no options
|
261
254
|
|
@@ -268,14 +261,32 @@ Worker has no options
|
|
268
261
|
* `:progressive` — Create progressive JPEG file *(defaults to `true`)*
|
269
262
|
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to `false`)*
|
270
263
|
|
271
|
-
### :
|
272
|
-
* `:
|
264
|
+
### :optipng =>
|
265
|
+
* `:level` — Optimization level preset: `0` is least, `7` is best *(defaults to `6`)*
|
266
|
+
* `:interlace` — Interlace, `true` - interlace on, `false` - interlace off, `nil` - as is in original image *(defaults to `false`)*
|
267
|
+
|
268
|
+
### :pngcrush =>
|
269
|
+
* `:chunks` — List of chunks to remove or `:alla` - all except tRNS/transparency or `:allb` - all except tRNS and gAMA/gamma *(defaults to `:alla`)*
|
270
|
+
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to `false`)*
|
271
|
+
* `:brute` — Brute force try all methods, very time-consuming and generally not worthwhile *(defaults to `false`)*
|
272
|
+
|
273
|
+
### :pngout =>
|
274
|
+
* `:copy_chunks` — Copy optional chunks *(defaults to `false`)*
|
275
|
+
* `:strategy` — Strategy: `0` - xtreme, `1` - intense, `2` - longest Match, `3` - huffman Only, `4` - uncompressed *(defaults to `0`)*
|
276
|
+
|
277
|
+
### :pngquant =>
|
278
|
+
* `:quality` — min..max - don't save below min, use less colors below max (both in range `0..100`; in yaml - `!ruby/range 0..100`) *(defaults to `100..100`)*
|
279
|
+
* `:speed` — speed/quality trade-off: `1` - slow, `3` - default, `11` - fast & rough *(defaults to `3`)*
|
273
280
|
|
274
281
|
### :svgo =>
|
275
282
|
Worker has no options
|
276
283
|
|
277
284
|
<!---</worker-options>-->
|
278
285
|
|
286
|
+
## Contributing
|
287
|
+
|
288
|
+
If you would like to contribute - that is great and you are very welcome. Please check few notes about [CONTRIBUTING](CONTRIBUTING.markdown).
|
289
|
+
|
279
290
|
## Copyright
|
280
291
|
|
281
|
-
Copyright (c) 2012-2014 Ivan Kuchin. See LICENSE.txt for details.
|
292
|
+
Copyright (c) 2012-2014 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
|
data/bin/image_optim
CHANGED
@@ -2,11 +2,14 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
4
|
require 'image_optim/runner'
|
5
|
+
require 'image_optim/true_false_nil'
|
6
|
+
require 'image_optim/non_negative_integer_range'
|
5
7
|
|
6
8
|
options = {}
|
7
9
|
|
8
10
|
option_parser = OptionParser.new do |op|
|
9
11
|
ImageOptim::TrueFalseNil.add_to_option_parser(op)
|
12
|
+
ImageOptim::NonNegativeIntegerRange.add_to_option_parser(op)
|
10
13
|
|
11
14
|
op.banner = <<-TEXT.gsub(/^\s*\|/, '')
|
12
15
|
|#{ImageOptim.full_version}
|
@@ -65,6 +68,8 @@ option_parser = OptionParser.new do |op|
|
|
65
68
|
[Integer, 'N']
|
66
69
|
when Array >= type
|
67
70
|
[Array, 'a,b,c']
|
71
|
+
when ImageOptim::NonNegativeIntegerRange == type
|
72
|
+
[type, 'M-N']
|
68
73
|
else
|
69
74
|
fail "Unknown type #{type}"
|
70
75
|
end
|
data/image_optim.gemspec
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_optim'
|
5
|
-
s.version = '0.
|
6
|
-
s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif, svg) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout, svgo)}
|
5
|
+
s.version = '0.15.0'
|
6
|
+
s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif, svg) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout, pngquant, svgo)}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
9
9
|
s.license = 'MIT'
|
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_dependency 'fspath', '~> 2.1.0'
|
19
19
|
s.add_dependency 'image_size', '~> 1.3.0'
|
20
20
|
s.add_dependency 'exifr', '~> 1.1.3'
|
21
|
-
s.add_dependency 'progress', '~> 3.0.
|
22
|
-
s.add_dependency 'in_threads', '~> 1.2.
|
21
|
+
s.add_dependency 'progress', '~> 3.0.1'
|
22
|
+
s.add_dependency 'in_threads', '~> 1.2.2'
|
23
23
|
s.add_development_dependency 'rspec', '~> 3.0'
|
24
24
|
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.2')
|
25
25
|
s.add_development_dependency 'rubocop', '~> 0.24.1'
|
data/lib/image_optim.rb
CHANGED
@@ -68,15 +68,15 @@ class ImageOptim
|
|
68
68
|
def optimize_image(original)
|
69
69
|
original = ImagePath.convert(original)
|
70
70
|
return unless (workers = workers_for_image(original))
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
result = Handler.for(original) do |handler|
|
72
|
+
workers.each do |worker|
|
73
|
+
handler.process do |src, dst|
|
74
|
+
worker.optimize(src, dst)
|
75
|
+
end
|
75
76
|
end
|
76
77
|
end
|
77
|
-
|
78
|
-
|
79
|
-
ImagePath::Optimized.new(handler.result, original)
|
78
|
+
return unless result
|
79
|
+
ImagePath::Optimized.new(result, original)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Optimize one file in place, return original as OptimizedImagePath or nil if
|
@@ -106,7 +106,7 @@ class ImageOptim
|
|
106
106
|
# Optimize multiple images
|
107
107
|
# if block given yields path and result for each image and returns array of
|
108
108
|
# yield results
|
109
|
-
# else return array of
|
109
|
+
# else return array of path and result pairs
|
110
110
|
def optimize_images(paths, &block)
|
111
111
|
run_method_for(paths, :optimize_image, &block)
|
112
112
|
end
|
@@ -114,7 +114,7 @@ class ImageOptim
|
|
114
114
|
# Optimize multiple images in place
|
115
115
|
# if block given yields path and result for each image and returns array of
|
116
116
|
# yield results
|
117
|
-
# else return array of
|
117
|
+
# else return array of path and result pairs
|
118
118
|
def optimize_images!(paths, &block)
|
119
119
|
run_method_for(paths, :optimize_image!, &block)
|
120
120
|
end
|
@@ -122,7 +122,7 @@ class ImageOptim
|
|
122
122
|
# Optimize multiple image datas
|
123
123
|
# if block given yields original and result for each image data and returns
|
124
124
|
# array of yield results
|
125
|
-
# else return array of
|
125
|
+
# else return array of path and result pairs
|
126
126
|
def optimize_images_data(datas, &block)
|
127
127
|
run_method_for(datas, :optimize_image_data, &block)
|
128
128
|
end
|
@@ -178,14 +178,17 @@ private
|
|
178
178
|
by_format.each{ |_format, workers| workers.sort! }
|
179
179
|
end
|
180
180
|
|
181
|
-
# Run method for each
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
# Run method for each item in list
|
182
|
+
# if block given yields item and result for item and returns array of yield
|
183
|
+
# results
|
184
|
+
# else return array of item and result pairs
|
185
|
+
def run_method_for(list, method_name, &block)
|
186
|
+
apply_threading(list).map do |item|
|
187
|
+
result = send(method_name, item)
|
185
188
|
if block
|
186
|
-
block.call(
|
189
|
+
block.call(item, result)
|
187
190
|
else
|
188
|
-
result
|
191
|
+
[item, result]
|
189
192
|
end
|
190
193
|
end
|
191
194
|
end
|
@@ -201,7 +204,7 @@ private
|
|
201
204
|
end
|
202
205
|
|
203
206
|
%w[
|
204
|
-
pngcrush pngout optipng
|
207
|
+
pngcrush pngout advpng optipng pngquant
|
205
208
|
jhead jpegoptim jpegtran
|
206
209
|
gifsicle
|
207
210
|
svgo
|
@@ -4,15 +4,16 @@ require 'image_optim/bin_resolver/simple_version'
|
|
4
4
|
require 'image_optim/bin_resolver/comparable_condition'
|
5
5
|
|
6
6
|
class ImageOptim
|
7
|
-
class BinNotFoundError < StandardError; end
|
8
|
-
class BadBinVersion < StandardError; end
|
9
|
-
|
10
7
|
# Handles resolving binaries and checking versions
|
11
8
|
#
|
12
9
|
# If there is an environment variable XXX_BIN when resolbing xxx, then a
|
13
10
|
# symlink to binary will be created in a temporary directory which will be
|
14
11
|
# added to PATH
|
15
12
|
class BinResolver
|
13
|
+
class Error < StandardError; end
|
14
|
+
class BinNotFound < Error; end
|
15
|
+
class BadBinVersion < Error; end
|
16
|
+
|
16
17
|
# Holds name and version of an executable
|
17
18
|
class Bin
|
18
19
|
attr_reader :name, :version
|
@@ -47,7 +48,7 @@ class ImageOptim
|
|
47
48
|
if @bins[name]
|
48
49
|
check!(@bins[name])
|
49
50
|
else
|
50
|
-
fail
|
51
|
+
fail BinNotFound, "`#{name}` not found"
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -84,7 +85,7 @@ class ImageOptim
|
|
84
85
|
|
85
86
|
def version(name)
|
86
87
|
case name.to_sym
|
87
|
-
when :advpng, :gifsicle, :jpegoptim, :optipng
|
88
|
+
when :advpng, :gifsicle, :jpegoptim, :optipng, :pngquant
|
88
89
|
capture_output("#{name} --version")[/\d+(\.\d+){1,}/]
|
89
90
|
when :svgo
|
90
91
|
capture_output("#{name} --version 2>&1")[/\d+(\.\d+){1,}/]
|
@@ -114,6 +115,13 @@ class ImageOptim
|
|
114
115
|
when c = is < '1.17'
|
115
116
|
warn "Note that `#{bin}` (#{c}) does not use zopfli"
|
116
117
|
end
|
118
|
+
when :pngquant
|
119
|
+
case bin.version
|
120
|
+
when c = is < '2.0'
|
121
|
+
fail BadBinVersion, "`#{bin}` (#{c}) is not supported"
|
122
|
+
when c = is < '2.1'
|
123
|
+
warn "Note that `#{bin}` (#{c}) may be lossy even with quality `100-`"
|
124
|
+
end
|
117
125
|
end
|
118
126
|
end
|
119
127
|
|
@@ -5,16 +5,23 @@ class ImageOptim
|
|
5
5
|
class SimpleVersion
|
6
6
|
include Comparable
|
7
7
|
|
8
|
+
# Numbers extracted from version string
|
8
9
|
attr_reader :parts
|
10
|
+
|
11
|
+
# Initialize with a string or an object convertible to string
|
12
|
+
#
|
13
|
+
# SimpleVersion.new('2.0.1') <=> SimpleVersion.new(2)
|
9
14
|
def initialize(str)
|
10
15
|
@str = String(str)
|
11
16
|
@parts = @str.split('.').map(&:to_i).reverse.drop_while(&:zero?).reverse
|
12
17
|
end
|
13
18
|
|
19
|
+
# Returns original version string
|
14
20
|
def to_s
|
15
21
|
@str
|
16
22
|
end
|
17
23
|
|
24
|
+
# Compare version parts of self with other
|
18
25
|
def <=>(other)
|
19
26
|
other = self.class.new(other) unless other.is_a?(self.class)
|
20
27
|
parts <=> other.parts
|
data/lib/image_optim/handler.rb
CHANGED
@@ -16,6 +16,22 @@ class ImageOptim
|
|
16
16
|
@result = nil
|
17
17
|
end
|
18
18
|
|
19
|
+
# with no associated block, works as new. Otherwise creates instance and
|
20
|
+
# passes it to block, runs cleanup and returns result of handler
|
21
|
+
def self.for(original)
|
22
|
+
handler = new(original)
|
23
|
+
if block_given?
|
24
|
+
begin
|
25
|
+
yield handler
|
26
|
+
handler.result
|
27
|
+
ensure
|
28
|
+
handler.cleanup
|
29
|
+
end
|
30
|
+
else
|
31
|
+
handler
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
19
35
|
# Yields two paths, one to latest successful result or original, second to
|
20
36
|
# temp path
|
21
37
|
def process
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ImageOptim
|
2
|
+
# Denote range of non negative integers for worker option
|
3
|
+
class NonNegativeIntegerRange
|
4
|
+
# Add handling of range of non negative integers in OptionParser instance
|
5
|
+
def self.add_to_option_parser(option_parser)
|
6
|
+
option_parser.accept(self, /(\d+)(?:-|\.\.)(\d+)/) do |_, m, n|
|
7
|
+
m.to_i..n.to_i
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/image_optim/railtie.rb
CHANGED
@@ -4,11 +4,17 @@ class ImageOptim
|
|
4
4
|
# Adds image_optim as preprocessor for gif, jpeg, png and svg images
|
5
5
|
class Railtie < Rails::Railtie
|
6
6
|
initializer 'image_optim.initializer' do |app|
|
7
|
+
register_preprocessor(app) if register_preprocessor?(app)
|
8
|
+
end
|
9
|
+
|
10
|
+
def register_preprocessor?(app)
|
11
|
+
return if app.config.assets.compress == false
|
12
|
+
return if app.config.assets.image_optim == false
|
7
13
|
|
8
|
-
|
9
|
-
|
10
|
-
break unless app.assets
|
14
|
+
app.assets
|
15
|
+
end
|
11
16
|
|
17
|
+
def register_preprocessor(app)
|
12
18
|
options = if app.config.assets.image_optim == true
|
13
19
|
{}
|
14
20
|
else
|
@@ -25,7 +31,6 @@ class ImageOptim
|
|
25
31
|
app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
|
26
32
|
app.assets.register_preprocessor 'image/png', :image_optim, &processor
|
27
33
|
app.assets.register_preprocessor 'image/svg+xml', :image_optim, &processor
|
28
|
-
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
data/lib/image_optim/runner.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'image_optim'
|
2
2
|
require 'image_optim/hash_helpers'
|
3
|
-
require 'image_optim/true_false_nil'
|
4
3
|
require 'image_optim/space'
|
5
4
|
require 'progress'
|
6
5
|
require 'optparse'
|
@@ -56,7 +55,7 @@ class ImageOptim
|
|
56
55
|
unless @to_optimize.empty?
|
57
56
|
results = Results.new
|
58
57
|
|
59
|
-
optimize_images
|
58
|
+
optimize_images!.each do |original, optimized|
|
60
59
|
results.add(original, optimized)
|
61
60
|
end
|
62
61
|
|
data/lib/image_optim/space.rb
CHANGED
@@ -13,7 +13,7 @@ class ImageOptim
|
|
13
13
|
when 0, nil
|
14
14
|
EMPTY_SPACE
|
15
15
|
else
|
16
|
-
log_denominator = Math.log(size) / Math.log(BASE)
|
16
|
+
log_denominator = Math.log(size.abs) / Math.log(BASE)
|
17
17
|
degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
|
18
18
|
number_string = if degree == 0
|
19
19
|
size.to_s
|
@@ -7,5 +7,10 @@ class ImageOptim
|
|
7
7
|
completing = OptionParser.top.atype[TrueClass][0].merge('nil' => nil)
|
8
8
|
option_parser.accept(self, completing){ |_arg, val| val }
|
9
9
|
end
|
10
|
+
|
11
|
+
# Convert everything truthy to `true`, leave `false` and `nil` as is
|
12
|
+
def self.convert(v)
|
13
|
+
v && true
|
14
|
+
end
|
10
15
|
end
|
11
16
|
end
|
data/lib/image_optim/worker.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
require 'image_optim/configuration_error'
|
3
4
|
require 'image_optim/option_definition'
|
4
5
|
require 'image_optim/option_helpers'
|
5
6
|
require 'shellwords'
|
@@ -40,6 +41,9 @@ class ImageOptim
|
|
40
41
|
|
41
42
|
# Configure (raises on extra options)
|
42
43
|
def initialize(image_optim, options = {})
|
44
|
+
unless image_optim.is_a?(ImageOptim)
|
45
|
+
fail ArgumentError, 'first parameter should be an ImageOptim instance'
|
46
|
+
end
|
43
47
|
@image_optim = image_optim
|
44
48
|
self.class.option_definitions.each do |option_definition|
|
45
49
|
value = if options.key?(option_definition.name)
|
@@ -98,6 +102,10 @@ class ImageOptim
|
|
98
102
|
# Forward bin resolving to image_optim
|
99
103
|
def resolve_bin!(bin)
|
100
104
|
@image_optim.resolve_bin!(bin)
|
105
|
+
rescue BinResolver::Error => e
|
106
|
+
name = self.class.bin_sym
|
107
|
+
raise e, "#{name} worker: #{e.message}; please provide proper binary or "\
|
108
|
+
"disable this worker (`:#{name} => false`)", e.backtrace
|
101
109
|
end
|
102
110
|
|
103
111
|
# Run command setting priority and hiding output
|
@@ -18,8 +18,7 @@ class ImageOptim
|
|
18
18
|
'`true` - interlace on, '\
|
19
19
|
'`false` - interlace off, '\
|
20
20
|
'`nil` - as is in original image') do |v|
|
21
|
-
|
22
|
-
v && true
|
21
|
+
TrueFalseNil.convert(v)
|
23
22
|
end
|
24
23
|
|
25
24
|
def optimize(src, dst)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
require 'image_optim/option_helpers'
|
3
|
+
require 'image_optim/non_negative_integer_range'
|
4
|
+
|
5
|
+
class ImageOptim
|
6
|
+
class Worker
|
7
|
+
# http://pngquant.org/
|
8
|
+
class Pngquant < Worker
|
9
|
+
QUALITY_OPTION =
|
10
|
+
option(:quality, 100..100, NonNegativeIntegerRange, 'min..max - don\'t '\
|
11
|
+
'save below min, use less colors below max (both in range `0..100`; '\
|
12
|
+
'in yaml - `!ruby/range 0..100`)') do |v|
|
13
|
+
min = OptionHelpers.limit_with_range(v.begin, 0..100)
|
14
|
+
min..OptionHelpers.limit_with_range(v.end, min..100)
|
15
|
+
end
|
16
|
+
|
17
|
+
SPEED_OPTION =
|
18
|
+
option(:speed, 3, 'speed/quality trade-off: '\
|
19
|
+
'`1` - slow, '\
|
20
|
+
'`3` - default, '\
|
21
|
+
'`11` - fast & rough') do |v|
|
22
|
+
OptionHelpers.limit_with_range(v.to_i, 1..11)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Always run first
|
26
|
+
def run_order
|
27
|
+
-5
|
28
|
+
end
|
29
|
+
|
30
|
+
def optimize(src, dst)
|
31
|
+
args = %W[
|
32
|
+
--quality=#{quality.begin}-#{quality.end}
|
33
|
+
--speed=#{speed}
|
34
|
+
--output=#{dst}
|
35
|
+
--force
|
36
|
+
--
|
37
|
+
#{src}
|
38
|
+
]
|
39
|
+
execute(:pngquant, *args) && optimized?(src, dst)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -71,7 +71,7 @@ describe ImageOptim::BinResolver do
|
|
71
71
|
5.times do
|
72
72
|
expect do
|
73
73
|
resolver.resolve!(:should_not_exist)
|
74
|
-
end.to raise_error ImageOptim::
|
74
|
+
end.to raise_error ImageOptim::BinResolver::BinNotFound
|
75
75
|
end
|
76
76
|
expect(resolver.env_path).to eq([
|
77
77
|
ENV['PATH'],
|
@@ -103,7 +103,7 @@ describe ImageOptim::BinResolver do
|
|
103
103
|
5.times do
|
104
104
|
expect do
|
105
105
|
resolver.resolve!(:should_not_exist)
|
106
|
-
end.to raise_error ImageOptim::
|
106
|
+
end.to raise_error ImageOptim::BinResolver::BinNotFound
|
107
107
|
end
|
108
108
|
expect(resolver.env_path).to eq([
|
109
109
|
tmpdir,
|
@@ -139,7 +139,7 @@ describe ImageOptim::BinResolver do
|
|
139
139
|
5.times do
|
140
140
|
expect do
|
141
141
|
resolver.resolve!(:pngcrush)
|
142
|
-
end.to raise_error ImageOptim::BadBinVersion
|
142
|
+
end.to raise_error ImageOptim::BinResolver::BadBinVersion
|
143
143
|
end
|
144
144
|
expect(resolver.env_path).to eq([
|
145
145
|
ENV['PATH'],
|
@@ -56,4 +56,37 @@ describe ImageOptim::Handler do
|
|
56
56
|
handler.cleanup
|
57
57
|
handler.cleanup
|
58
58
|
end
|
59
|
+
|
60
|
+
describe :open do
|
61
|
+
it 'should yield instance, run cleanup and return result' do
|
62
|
+
original = double
|
63
|
+
handler = double
|
64
|
+
result = double
|
65
|
+
|
66
|
+
expect(ImageOptim::Handler).to receive(:new).
|
67
|
+
with(original).and_return(handler)
|
68
|
+
expect(handler).to receive(:process)
|
69
|
+
expect(handler).to receive(:cleanup)
|
70
|
+
expect(handler).to receive(:result).and_return(result)
|
71
|
+
|
72
|
+
expect(ImageOptim::Handler.for(original) do |h|
|
73
|
+
h.process
|
74
|
+
end).to eq(result)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should cleanup if exception is raised' do
|
78
|
+
original = double
|
79
|
+
handler = double
|
80
|
+
|
81
|
+
expect(ImageOptim::Handler).to receive(:new).
|
82
|
+
with(original).and_return(handler)
|
83
|
+
expect(handler).to receive(:cleanup)
|
84
|
+
|
85
|
+
expect do
|
86
|
+
ImageOptim::Handler.for(original) do
|
87
|
+
fail 'hello'
|
88
|
+
end
|
89
|
+
end.to raise_error 'hello'
|
90
|
+
end
|
91
|
+
end
|
59
92
|
end
|
@@ -9,8 +9,10 @@ describe ImageOptim::Worker do
|
|
9
9
|
it 'should raise NotImplementedError unless overriden' do
|
10
10
|
class Abc < ImageOptim::Worker; end
|
11
11
|
|
12
|
+
image_optim = ImageOptim.new
|
13
|
+
|
12
14
|
expect do
|
13
|
-
Abc.new({}).optimize(double, double)
|
15
|
+
Abc.new(image_optim, {}).optimize(double, double)
|
14
16
|
end.to raise_error NotImplementedError
|
15
17
|
end
|
16
18
|
end
|
data/spec/image_optim_spec.rb
CHANGED
@@ -47,9 +47,10 @@ describe ImageOptim do
|
|
47
47
|
|
48
48
|
describe 'workers' do
|
49
49
|
it 'should be ordered by run_order' do
|
50
|
+
image_optim = ImageOptim.new
|
50
51
|
original_klasses = ImageOptim::Worker.klasses
|
51
52
|
formats = original_klasses.map do |klass|
|
52
|
-
klass.new({}).image_formats
|
53
|
+
klass.new(image_optim, {}).image_formats
|
53
54
|
end.flatten.uniq
|
54
55
|
|
55
56
|
[
|
@@ -62,7 +63,7 @@ describe ImageOptim do
|
|
62
63
|
image_optim = ImageOptim.new
|
63
64
|
|
64
65
|
formats.each do |format|
|
65
|
-
path = ImagePath.new("test.#{format}")
|
66
|
+
path = ImageOptim::ImagePath.new("test.#{format}")
|
66
67
|
expect(path).to receive(:format).and_return(format)
|
67
68
|
|
68
69
|
workers = image_optim.workers_for_image(path)
|
@@ -73,12 +74,14 @@ describe ImageOptim do
|
|
73
74
|
end
|
74
75
|
|
75
76
|
describe 'worker' do
|
77
|
+
image_optim = ImageOptim.new
|
78
|
+
|
76
79
|
base_options = Hash[ImageOptim::Worker.klasses.map do |klass|
|
77
80
|
[klass.bin_sym, false]
|
78
81
|
end]
|
79
82
|
|
80
83
|
real_workers = ImageOptim::Worker.klasses.reject do |klass|
|
81
|
-
klass.new({}).image_formats.empty?
|
84
|
+
klass.new(image_optim, {}).image_formats.empty?
|
82
85
|
end
|
83
86
|
|
84
87
|
real_workers.each do |worker_klass|
|
@@ -171,32 +174,37 @@ describe ImageOptim do
|
|
171
174
|
describe 'bunch' do
|
172
175
|
it 'should optimize' do
|
173
176
|
copies = TEST_IMAGES.map(&:temp_copy)
|
174
|
-
|
175
|
-
zipped = TEST_IMAGES.zip(copies,
|
176
|
-
zipped.each do |original, copy,
|
177
|
-
expect(
|
178
|
-
expect(
|
179
|
-
expect(
|
177
|
+
results = ImageOptim.optimize_images(copies)
|
178
|
+
zipped = TEST_IMAGES.zip(copies, results)
|
179
|
+
zipped.each do |original, copy, result|
|
180
|
+
expect(result[0]).to eq(copy)
|
181
|
+
expect(result[1]).to be_a(ImageOptim::ImagePath::Optimized)
|
182
|
+
expect(result[1].size).to be_in_range(1...original.size)
|
180
183
|
expect(copy.read).to eq(original.read)
|
181
184
|
end
|
182
185
|
end
|
183
186
|
|
184
187
|
it 'should optimize in place' do
|
185
188
|
copies = TEST_IMAGES.map(&:temp_copy)
|
186
|
-
ImageOptim.optimize_images!(copies)
|
187
|
-
TEST_IMAGES.zip(copies
|
189
|
+
results = ImageOptim.optimize_images!(copies)
|
190
|
+
zipped = TEST_IMAGES.zip(copies, results)
|
191
|
+
zipped.each do |original, copy, result|
|
192
|
+
expect(result[0]).to eq(copy)
|
193
|
+
expect(result[1]).to be_a(ImageOptim::ImagePath::Optimized)
|
188
194
|
expect(copy.size).to be_in_range(1...original.size)
|
189
|
-
expect(copy.read).not_to eq(original.read)
|
190
195
|
end
|
191
196
|
end
|
192
197
|
|
193
198
|
it 'should optimize datas' do
|
194
|
-
|
195
|
-
TEST_IMAGES.zip(
|
196
|
-
|
199
|
+
results = ImageOptim.optimize_images_data(TEST_IMAGES.map(&:read))
|
200
|
+
zipped = TEST_IMAGES.zip(results)
|
201
|
+
zipped.each do |original, result|
|
202
|
+
expect(result[0]).to eq(original.read)
|
203
|
+
expect(result[1]).to be_a(String)
|
204
|
+
expect(result[1].size).to be_in_range(1...original.size)
|
197
205
|
|
198
206
|
expected_path = ImageOptim.optimize_image(original.temp_copy)
|
199
|
-
expect(
|
207
|
+
expect(result[1]).to eq(expected_path.open('rb', &:read))
|
200
208
|
end
|
201
209
|
end
|
202
210
|
end
|
@@ -259,7 +267,7 @@ describe ImageOptim do
|
|
259
267
|
expect(image_optim).to receive(method).with(src).and_return(dst)
|
260
268
|
dst
|
261
269
|
end
|
262
|
-
expect(image_optim.send(list_method, srcs)).to eq(dsts)
|
270
|
+
expect(image_optim.send(list_method, srcs)).to eq(srcs.zip(dsts))
|
263
271
|
end
|
264
272
|
end
|
265
273
|
|
data/spec/images/orient/generate
CHANGED
Binary file
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
Dir.chdir(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
palettes = [64]
|
8
|
+
side = 256
|
9
|
+
|
10
|
+
palettes.each do |palette|
|
11
|
+
IO.popen(%W[
|
12
|
+
convert
|
13
|
+
-depth 8
|
14
|
+
-size #{side}x#{side}
|
15
|
+
-strip
|
16
|
+
rgb:-
|
17
|
+
PNG24:#{palette}.png
|
18
|
+
].shelljoin, 'w') do |f|
|
19
|
+
(side * side).times do |i|
|
20
|
+
color = i * palette / (side * side) * 0x10000 / palette
|
21
|
+
f << [color / 0x100, color % 0x100, 0].pack('C*')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
system "identify -format 'Wrote %f with %k unique colors\n' #{palette}.png"
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_optim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Kuchin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fspath
|
@@ -58,28 +58,28 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 3.0.
|
61
|
+
version: 3.0.1
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 3.0.
|
68
|
+
version: 3.0.1
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: in_threads
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ~>
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.2.
|
75
|
+
version: 1.2.2
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.2.
|
82
|
+
version: 1.2.2
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rspec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- .gitignore
|
119
119
|
- .rubocop.yml
|
120
120
|
- .travis.yml
|
121
|
+
- CONTRIBUTING.markdown
|
121
122
|
- Gemfile
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.markdown
|
@@ -133,6 +134,7 @@ files:
|
|
133
134
|
- lib/image_optim/hash_helpers.rb
|
134
135
|
- lib/image_optim/image_meta.rb
|
135
136
|
- lib/image_optim/image_path.rb
|
137
|
+
- lib/image_optim/non_negative_integer_range.rb
|
136
138
|
- lib/image_optim/option_definition.rb
|
137
139
|
- lib/image_optim/option_helpers.rb
|
138
140
|
- lib/image_optim/railtie.rb
|
@@ -148,6 +150,7 @@ files:
|
|
148
150
|
- lib/image_optim/worker/optipng.rb
|
149
151
|
- lib/image_optim/worker/pngcrush.rb
|
150
152
|
- lib/image_optim/worker/pngout.rb
|
153
|
+
- lib/image_optim/worker/pngquant.rb
|
151
154
|
- lib/image_optim/worker/svgo.rb
|
152
155
|
- script/update_worker_options_in_readme
|
153
156
|
- spec/image_optim/bin_resolver/comparable_condition_spec.rb
|
@@ -176,6 +179,8 @@ files:
|
|
176
179
|
- spec/images/orient/8.jpg
|
177
180
|
- spec/images/orient/generate
|
178
181
|
- spec/images/orient/original.jpg
|
182
|
+
- spec/images/quant/64.png
|
183
|
+
- spec/images/quant/generate
|
179
184
|
- spec/images/test.svg
|
180
185
|
- spec/images/transparency1.png
|
181
186
|
- spec/images/transparency2.png
|
@@ -205,7 +210,8 @@ rubygems_version: 2.0.3
|
|
205
210
|
signing_key:
|
206
211
|
specification_version: 4
|
207
212
|
summary: Optimize (lossless compress) images (jpeg, png, gif, svg) using external
|
208
|
-
utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout,
|
213
|
+
utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout, pngquant,
|
214
|
+
svgo)
|
209
215
|
test_files:
|
210
216
|
- spec/image_optim/bin_resolver/comparable_condition_spec.rb
|
211
217
|
- spec/image_optim/bin_resolver/simple_version_spec.rb
|
@@ -233,6 +239,8 @@ test_files:
|
|
233
239
|
- spec/images/orient/8.jpg
|
234
240
|
- spec/images/orient/generate
|
235
241
|
- spec/images/orient/original.jpg
|
242
|
+
- spec/images/quant/64.png
|
243
|
+
- spec/images/quant/generate
|
236
244
|
- spec/images/test.svg
|
237
245
|
- spec/images/transparency1.png
|
238
246
|
- spec/images/transparency2.png
|