image_optim 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://img.shields.io/gem/v/image_optim.svg)](https://rubygems.org/gems/image_optim)
|
2
|
-
[![Build Status](https://img.shields.io/travis/toy/image_optim/master.svg)](https://travis-ci.org/toy/image_optim)
|
3
|
-
[![Code Climate](https://img.shields.io/codeclimate/github/toy/image_optim.svg)](https://codeclimate.com/github/toy/image_optim)
|
4
|
-
[![Dependency Status](https://img.shields.io/gemnasium/toy/image_optim.svg)](https://gemnasium.com/toy/image_optim)
|
5
|
-
[![Inch CI](http://inch-ci.org/github/toy/image_optim.svg?branch=master)](http://inch-ci.org/github/toy/image_optim)
|
6
|
-
[![Gittip](https://img.shields.io/gittip/toy.svg)](https://www.gittip.com/toy/)
|
1
|
+
[![Gem Version](https://img.shields.io/gem/v/image_optim.svg?style=flat)](https://rubygems.org/gems/image_optim)
|
2
|
+
[![Build Status](https://img.shields.io/travis/toy/image_optim/master.svg?style=flat)](https://travis-ci.org/toy/image_optim)
|
3
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/toy/image_optim.svg?style=flat)](https://codeclimate.com/github/toy/image_optim)
|
4
|
+
[![Dependency Status](https://img.shields.io/gemnasium/toy/image_optim.svg?style=flat)](https://gemnasium.com/toy/image_optim)
|
5
|
+
[![Inch CI](http://inch-ci.org/github/toy/image_optim.svg?branch=master&style=flat)](http://inch-ci.org/github/toy/image_optim)
|
6
|
+
[![Gittip](https://img.shields.io/gittip/toy.svg?style=flat)](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
|