image_optim 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZDYwZDFmNGU4YjcwY2NmYmY1OTA5MjRmOTQ1NzhkNjM1MjhjYmUwZQ==
4
+ ZDA2ZTJlMWRhYTk0MmEwNmI1MzI1M2I3NGMxNWFjYjIwYWU4ZTI4Yg==
5
5
  data.tar.gz: !binary |-
6
- ZTNhOTFlMjYxMGU4MjczMThiNDhiZDlkMDI2MGY4ZGM5OTlkOGFhOQ==
6
+ ZTA5NWU3OGU1ZTFkMDBhMzBlZGUyZmZkNTI5MGNlOTE3NDgxMDYyNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NTkyZDBhMTUxYTAxOTBlNjBjYWIxZWUyZGQwYTJlNDExNmJlMWMxNzc1NmE5
10
- MmFlYzE2NjBjNGFkZWI4MzU1NzQwOTA3M2M0MDk3MjIyMTc1ODYyMzA3OWE0
11
- NTMwNDEwMWM5ODI1MDlmZDk0NzdiNzBmYWQ2YmVhN2VkYjM4OTg=
9
+ YTcxM2U5OGQwMTU1NzFmNzA4NGIyNzE2NWZmYjQ4ZDFmZDQ0ZjVlZWVhZGMw
10
+ OGIwMjFlZDYxMWI2NmJlY2FlNjQ5ZTE3NTE2OTRmOGQ3ODdjMmFlMTBlNGQ2
11
+ ODgzYjY3Yzc1ODViN2ZlN2I5NTYyYThkNDY5ODQzN2QxN2FhZDM=
12
12
  data.tar.gz: !binary |-
13
- NjkwMzgyYWNkNmYwYjhkMmJiYzY2NzNkY2QwMmZhNDFhZWFmYjhhMzk5ZmM2
14
- YmNjOTQ2MWY5ZDY3ZGEwYzJmNGZiY2I4OTViNzNjYmNkZmE1YmZhNmFkMjM4
15
- MzAwZWFlYzBlNTM2OWVjNjRiZjg1MmNlMTJkMTIxMjEzOWNlNDE=
13
+ Zjg1ZmJkYjQ3MmM0MDRmM2Y2ZWRhNGQwNGJhY2NhZTQ1Njg4YmQ0OWE2N2Zh
14
+ MTExZTM5MzkyNzZjM2Y4Y2I5ZDZiNWIyNmU0MDQyYzM2NGRkZTJmZDZhZjEx
15
+ MWJlNGUyYTRjODc4YWE1YzNmZThlYzFiYmM0YzAwODFmMjcxOTE=
data/.travis.yml CHANGED
@@ -5,29 +5,26 @@ rvm:
5
5
  - 1.9.3
6
6
  - 2.0.0
7
7
  - 2.1.0
8
- - ruby-head
9
8
  - jruby-18mode
10
9
  - jruby-19mode
11
- - jruby-head
12
10
  - ree
13
11
  script:
14
- - bundle exec image_optim --info
15
- - bundle exec rspec
16
- - '! bundle show rubocop || bundle exec rubocop' # run rubocop only if it is bundled
12
+ if [ -z "$RUBOCOP" ]; then
13
+ bundle exec image_optim --info
14
+ && bundle exec rspec
15
+ ; else
16
+ bundle exec rubocop
17
+ ; fi
17
18
  before_install:
18
- - mkdir ~/bin
19
-
20
- - echo 'Installing svgo:'
21
- - npm install -g svgo
22
-
23
- - echo 'Installing pngout:'
24
- - PNGOUT_VERSION=20130221
25
- - curl -L "http://static.jonof.id.au/dl/kenutils/pngout-$PNGOUT_VERSION-linux.tar.gz" | tar -xz
26
- - mv pngout-$PNGOUT_VERSION-linux/x86_64/pngout ~/bin
19
+ if [ -z "$RUBOCOP" ]; then
20
+ mkdir ~/bin
21
+ && npm install -g svgo
22
+ && curl -L "http://static.jonof.id.au/dl/kenutils/pngout-20130221-linux.tar.gz" | tar -xz -C ~/bin --strip-components 2 --wildcards '*/x86_64/pngout'
23
+ ; fi
27
24
  env:
28
25
  - PATH=~/bin:$PATH
29
26
  matrix:
30
27
  fast_finish: true
31
- allow_failures:
32
- - rvm: ruby-head
33
- - rvm: jruby-head
28
+ include:
29
+ - env: RUBOCOP=true
30
+ rvm: default
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v0.19.0 (2014-11-08)
6
+
7
+ * Added lossy worker `jpegrecompress` (uses [`jpeg-recompress`](https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress)), disabled unless `:allow_lossy` is true [#65](https://github.com/toy/image_optim/issues/65) [@wjordan](https://github.com/wjordan) [@toy](https://github.com/toy)
8
+ * `:allow_lossy` option to allow lossy workers and optimizations [@toy](https://github.com/toy)
9
+ * Don't warn multiple times about problematic binary [#69](https://github.com/toy/image_optim/issues/69) [@toy](https://github.com/toy)
10
+ * Run gisicle two times (with interlace off then with on) if interlace is not set explicitly [#70](https://github.com/toy/image_optim/issues/70) [@toy](https://github.com/toy)
11
+ * Remove app and other extensions from gif images [@toy](https://github.com/toy)
12
+ * Change behaviour of gifsicle interlace option to deinterlace for `false`, pass `nil` to leave as is [@toy](https://github.com/toy)
13
+ * Worker can change its initialization by overriding `init` and can initialize multiple instances [#70](https://github.com/toy/image_optim/issues/70) [@toy](https://github.com/toy)
14
+
5
15
  ## v0.18.0 (2014-11-01)
6
16
 
7
17
  * Add interface to `image_optim_pack` [@toy](https://github.com/toy)
data/README.markdown CHANGED
@@ -6,13 +6,14 @@
6
6
 
7
7
  # image_optim
8
8
 
9
- Optimize (lossless compress) images (jpeg, png, gif, svg) using external utilities:
9
+ Optimize (lossless compress, optionally lossy) images (jpeg, png, gif, svg) using external utilities:
10
10
 
11
11
  * [advpng](http://advancemame.sourceforge.net/doc-advpng.html) from [AdvanceCOMP](http://advancemame.sourceforge.net/comp-readme.html)
12
12
  (will use [zopfli](https://code.google.com/p/zopfli/) on default/maximum level 4)
13
13
  * [gifsicle](http://www.lcdf.org/gifsicle/)
14
14
  * [jhead](http://www.sentex.net/~mwandel/jhead/)
15
15
  * [jpegoptim](http://www.kokkonen.net/tjko/projects.html)
16
+ * [jpeg-recompress](https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress)
16
17
  * jpegtran from [Independent JPEG Group's JPEG library](http://www.ijg.org/)
17
18
  * [optipng](http://optipng.sourceforge.net/)
18
19
  * [pngcrush](http://pmt.sourceforge.net/pngcrush/)
@@ -173,6 +174,11 @@ _Note: pngout is free to use even in commercial soft, but you can not redistribu
173
174
  npm install -g svgo
174
175
  ```
175
176
 
177
+ ### jpeg-recompress installation (optional)
178
+
179
+ Download and install the `jpeg-recompress` binary from the [JPEG-Archive Releases](https://github.com/danielgtaylor/jpeg-archive/releases) page,
180
+ or follow the instructions to [build from source](https://github.com/danielgtaylor/jpeg-archive#building).
181
+
176
182
  ## Usage
177
183
 
178
184
  ### From shell
@@ -263,6 +269,7 @@ optipng:
263
269
  * `:verbose` — Verbose output *(defaults to `false`)*
264
270
  * `:pack` — Require image\_optim\_pack or disable it, by default image\_optim\_pack will be used if available, will turn on `:skip-missing-workers` unless explicitly disabled *(defaults to `nil`)*
265
271
  * `:skip_missing_workers` — Skip workers with missing or problematic binaries *(defaults to `false`)*
272
+ * `:allow_lossy` — Allow lossy workers and optimizations *(defaults to `false`)*
266
273
 
267
274
  Worker can be disabled by passing `false` instead of options hash.
268
275
 
@@ -273,7 +280,7 @@ Worker can be disabled by passing `false` instead of options hash.
273
280
  * `:level` — Compression level: `0` - don't compress, `1` - fast, `2` - normal, `3` - extra, `4` - extreme *(defaults to `4`)*
274
281
 
275
282
  ### :gifsicle =>
276
- * `:interlace` — Turn interlacing on *(defaults to `false`)*
283
+ * `:interlace` — Interlace: `true` - interlace on, `false` - interlace off, `nil` - as is in original image (defaults to running two instances, one with interlace off and one with on)
277
284
  * `:level` — Compression level: `1` - light and fast, `2` - normal, `3` - heavy (slower) *(defaults to `3`)*
278
285
  * `:careful` — Avoid bugs with some software *(defaults to `false`)*
279
286
 
@@ -284,6 +291,9 @@ Worker has no options
284
291
  * `:strip` — List of extra markers to strip: `:comments`, `:exif`, `:iptc`, `:icc` or `:all` *(defaults to `:all`)*
285
292
  * `:max_quality` — Maximum image quality factor `0`..`100` *(defaults to `100`)*
286
293
 
294
+ ### :jpegrecompress =>
295
+ * `:quality` — JPEG quality preset: `0` - low, `1` - medium, `2` - high, `3` - veryhigh *(defaults to `3`)*
296
+
287
297
  ### :jpegtran =>
288
298
  * `:copy_chunks` — Copy all chunks *(defaults to `false`)*
289
299
  * `:progressive` — Create progressive JPEG file *(defaults to `true`)*
@@ -291,7 +301,7 @@ Worker has no options
291
301
 
292
302
  ### :optipng =>
293
303
  * `:level` — Optimization level preset: `0` is least, `7` is best *(defaults to `6`)*
294
- * `:interlace` — Interlace, `true` - interlace on, `false` - interlace off, `nil` - as is in original image *(defaults to `false`)*
304
+ * `:interlace` — Interlace: `true` - interlace on, `false` - interlace off, `nil` - as is in original image *(defaults to `false`)*
295
305
 
296
306
  ### :pngcrush =>
297
307
  * `:chunks` — List of chunks to remove or `:alla` - all except tRNS/transparency or `:allb` - all except tRNS and gAMA/gamma *(defaults to `:alla`)*
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.18.0'
6
- s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif, svg) using external utilities (advpng, gifsicle, jhead, jpegoptim, jpegrescan, jpegtran, optipng, pngcrush, pngout, pngquant, svgo)}
5
+ s.version = '0.19.0'
6
+ s.summary = %q{Optimize (lossless compress, optionally lossy) images (jpeg, png, gif, svg) using external utilities (advpng, gifsicle, jhead, jpeg-recompress, jpegoptim, jpegrescan, 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'
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency 'progress', '~> 3.0', '>= 3.0.1'
22
22
  s.add_dependency 'in_threads', '~> 1.3'
23
23
 
24
- s.add_development_dependency 'image_optim_pack', '~> 0.1'
24
+ s.add_development_dependency 'image_optim_pack', '~> 0.2'
25
25
  s.add_development_dependency 'rspec', '~> 3.0'
26
26
  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
27
27
  s.add_development_dependency 'rubocop', '~> 0.26.0'
data/lib/image_optim.rb CHANGED
@@ -3,10 +3,20 @@ require 'image_optim/config'
3
3
  require 'image_optim/handler'
4
4
  require 'image_optim/image_meta'
5
5
  require 'image_optim/image_path'
6
+ require 'image_optim/railtie' if defined?(Rails)
6
7
  require 'image_optim/worker'
7
8
  require 'in_threads'
8
9
  require 'shellwords'
9
10
 
11
+ %w[
12
+ pngcrush pngout advpng optipng pngquant
13
+ jhead jpegoptim jpegrecompress jpegtran
14
+ gifsicle
15
+ svgo
16
+ ].each do |worker|
17
+ require "image_optim/worker/#{worker}"
18
+ end
19
+
10
20
  # Main interface
11
21
  class ImageOptim
12
22
  # Nice level
@@ -24,6 +34,9 @@ class ImageOptim
24
34
  # Skip workers with missing or problematic binaries
25
35
  attr_reader :skip_missing_workers
26
36
 
37
+ # Allow lossy workers and optimizations
38
+ attr_reader :allow_lossy
39
+
27
40
  # Initialize workers, specify options using worker underscored name:
28
41
  #
29
42
  # pass false to disable worker
@@ -50,6 +63,7 @@ class ImageOptim
50
63
  @verbose = config.verbose
51
64
  @pack = config.pack
52
65
  @skip_missing_workers = config.skip_missing_workers
66
+ @allow_lossy = config.allow_lossy
53
67
 
54
68
  if verbose
55
69
  $stderr << "config:\n"
@@ -60,11 +74,12 @@ class ImageOptim
60
74
  $stderr << "threads: #{threads}\n"
61
75
  $stderr << "pack: #{pack}\n"
62
76
  $stderr << "skip_missing_workers: #{skip_missing_workers}\n"
77
+ $stderr << "allow_lossy: #{allow_lossy}\n"
63
78
  end
64
79
 
65
80
  @bin_resolver = BinResolver.new(self)
66
81
 
67
- @workers_by_format = create_workers_by_format do |klass|
82
+ @workers_by_format = Worker.create_all_by_format(self) do |klass|
68
83
  config.for_worker(klass)
69
84
  end
70
85
 
@@ -192,25 +207,6 @@ private
192
207
  end
193
208
  end
194
209
 
195
- # Create hash with format mapped to list of workers sorted by run order
196
- def create_workers_by_format(&options_proc)
197
- by_format = {}
198
- workers = Worker.create_all(self, &options_proc)
199
- if skip_missing_workers
200
- workers = Worker.reject_missing(workers)
201
- else
202
- Worker.resolve_all!(workers)
203
- end
204
- sorted = workers.sort_by.with_index{ |worker, i| [worker.run_order, i] }
205
- sorted.each do |worker|
206
- worker.image_formats.each do |format|
207
- by_format[format] ||= []
208
- by_format[format] << worker
209
- end
210
- end
211
- by_format
212
- end
213
-
214
210
  # Run method for each item in list
215
211
  # if block given yields item and result for item and returns array of yield
216
212
  # results
@@ -235,14 +231,3 @@ private
235
231
  end
236
232
  end
237
233
  end
238
-
239
- %w[
240
- pngcrush pngout advpng optipng pngquant
241
- jhead jpegoptim jpegtran
242
- gifsicle
243
- svgo
244
- ].each do |worker|
245
- require "image_optim/worker/#{worker}"
246
- end
247
-
248
- require 'image_optim/railtie' if defined?(Rails)
@@ -40,10 +40,12 @@ class ImageOptim
40
40
  end
41
41
 
42
42
  @bins[name] = bin
43
+
44
+ bin.check! if bin
43
45
  end
44
46
 
45
47
  if @bins[name]
46
- @bins[name].check!
48
+ @bins[name].check_fail!
47
49
  else
48
50
  fail BinNotFound, "`#{name}` not found"
49
51
  end
@@ -8,6 +8,7 @@ class ImageOptim
8
8
  class BinResolver
9
9
  # Holds bin name and path, gets version
10
10
  class Bin
11
+ class UnknownVersion < Error; end
11
12
  class BadVersion < Error; end
12
13
 
13
14
  attr_reader :name, :path, :version
@@ -21,31 +22,39 @@ class ImageOptim
21
22
  "#{name} #{version || '?'} at #{path}"
22
23
  end
23
24
 
24
- # Fail or warn if version is known to misbehave depending on severity
25
- def check!
26
- unless version
27
- fail BadVersion, "didn't get version of #{name} at #{path}"
25
+ is = ComparableCondition.is
26
+
27
+ FAIL_CHECKS = [
28
+ [:pngcrush, is.between?('1.7.60', '1.7.65'), 'is known to produce '\
29
+ 'broken pngs'],
30
+ [:pngquant, is < '2.0', 'is not supported'],
31
+ ]
32
+
33
+ WARN_CHECKS = [
34
+ [:advpng, is < '1.17', 'does not use zopfli'],
35
+ [:pngquant, is < '2.1', 'may be lossy even with quality `100-`'],
36
+ [:gifsicle, is < '1.85', 'does not support removing extension blocks'],
37
+ ]
38
+
39
+ # Fail if version will not work properly
40
+ def check_fail!
41
+ fail UnknownVersion, "didn't get version of #{self}" unless version
42
+
43
+ FAIL_CHECKS.each do |bin_name, matcher, message|
44
+ next unless bin_name == name
45
+ next unless matcher.match(version)
46
+ fail BadVersion, "#{self} (#{matcher}) #{message}"
28
47
  end
48
+ end
29
49
 
30
- is = ComparableCondition.is
31
- case name
32
- when :pngcrush
33
- case version
34
- when c = is.between?('1.7.60', '1.7.65')
35
- fail BadVersion, "#{self} (#{c}) is known to produce broken pngs"
36
- end
37
- when :advpng
38
- case version
39
- when c = is < '1.17'
40
- warn "WARN: #{self} (#{c}) does not use zopfli"
41
- end
42
- when :pngquant
43
- case version
44
- when c = is < '2.0'
45
- fail BadVersion, "#{self} (#{c}) is not supported"
46
- when c = is < '2.1'
47
- warn "WARN: #{self} (#{c}) may be lossy even with quality `100-`"
48
- end
50
+ # Run check_fail!, otherwise warn if version is known to misbehave
51
+ def check!
52
+ check_fail!
53
+
54
+ WARN_CHECKS.each do |bin_name, matcher, message|
55
+ next unless bin_name == name
56
+ next unless matcher.match(version)
57
+ warn "WARN: #{self} (#{matcher}) #{message}"
49
58
  end
50
59
  end
51
60
 
@@ -64,7 +73,7 @@ class ImageOptim
64
73
  capture("#{escaped_path} --version 2> /dev/null")[/\d+(\.\d+){1,}/]
65
74
  when :svgo
66
75
  capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+){1,}/]
67
- when :jhead
76
+ when :jhead, :'jpeg-recompress'
68
77
  capture("#{escaped_path} -V 2> /dev/null")[/\d+(\.\d+){1,}/]
69
78
  when :jpegtran
70
79
  capture("#{escaped_path} -v - 2>&1")[/version (\d+\S*)/, 1]
@@ -142,6 +142,11 @@ class ImageOptim
142
142
  end
143
143
  end
144
144
 
145
+ # Allow lossy workers and optimizations, converted to boolean
146
+ def allow_lossy
147
+ !!get!(:allow_lossy)
148
+ end
149
+
145
150
  # Options for worker class by its `bin_sym`:
146
151
  # * `Hash` passed as is
147
152
  # * `{}` for `true` or `nil`
@@ -157,6 +157,13 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
157
157
  op.separator nil
158
158
  op.separator ' Worker options:'
159
159
 
160
+ op.on('--allow-lossy', 'Allow lossy workers and '\
161
+ 'optimizations') do |allow_lossy|
162
+ options[:allow_lossy] = allow_lossy
163
+ end
164
+
165
+ op.separator nil
166
+
160
167
  ImageOptim::Worker.klasses.each_with_index do |klass, i|
161
168
  next if klass.option_definitions.empty?
162
169
  op.separator nil unless i.zero?
@@ -180,12 +187,12 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
180
187
  fail "Unknown type #{type}"
181
188
  end
182
189
 
183
- description_lines = %W[
184
- #{option_definition.description.gsub(' - ', ' - ')}
185
- (defaults to #{default})
186
- ].join(' ')
190
+ description = option_definition.description.gsub(' - ', ' - ')
191
+ unless description['(defaults']
192
+ description << " (defaults to #{default})"
193
+ end
187
194
 
188
- op.on("--#{bin}-#{name} #{marking}", type, *description_lines) do |value|
195
+ op.on("--#{bin}-#{name} #{marking}", type, description) do |value|
189
196
  options[bin] = {} unless options[bin].is_a?(Hash)
190
197
  options[bin][option_definition.name.to_sym] = value
191
198
  end
@@ -1,84 +1,32 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'image_optim/bin_resolver/error'
4
- require 'image_optim/configuration_error'
5
- require 'image_optim/option_definition'
6
- require 'image_optim/option_helpers'
7
3
  require 'image_optim/cmd'
4
+ require 'image_optim/configuration_error'
5
+ require 'image_optim/worker/class_methods'
8
6
  require 'shellwords'
9
7
  require 'English'
10
8
 
11
9
  class ImageOptim
12
10
  # Base class for all workers
13
11
  class Worker
14
- @klasses = []
12
+ extend ClassMethods
15
13
 
16
14
  class << self
17
- # List of available workers
18
- def klasses
19
- @klasses.to_enum
20
- end
21
-
22
- # Remember all classes inheriting from this one
23
- def inherited(base)
24
- @klasses << base
25
- end
26
-
27
- # Underscored class name symbol
28
- def bin_sym
29
- @underscored_name ||= name.
30
- split('::').last. # get last part
31
- gsub(/([a-z])([A-Z])/, '\1_\2').downcase. # convert AbcDef to abc_def
32
- to_sym
33
- end
34
-
35
- def option_definitions
36
- @option_definitions ||= []
37
- end
38
-
39
- def option(name, default, type, description = nil, &proc)
40
- attr_reader name
41
- option_definitions <<
42
- OptionDefinition.new(name, default, type, description, &proc)
43
- end
44
-
45
- # Initialize all workers using options from calling options_proc with
46
- # klass
47
- def create_all(image_optim, &options_proc)
48
- Worker.klasses.map do |klass|
49
- next unless (options = options_proc[klass])
50
- klass.new(image_optim, options)
51
- end.compact
52
- end
53
-
54
- # Resolve all bins of all workers failing with one joint exception
55
- def resolve_all!(workers)
56
- errors = BinResolver.collect_errors(workers) do |worker|
57
- worker.resolve_used_bins!
58
- end
59
- return if errors.empty?
60
- fail BinResolver::Error, ['Bin resolving errors:', *errors].join("\n")
61
- end
62
-
63
- # Resolve all bins of all workers showing warning for missing ones and
64
- # returning others
65
- def reject_missing(workers)
66
- resolved = []
67
- errors = BinResolver.collect_errors(workers) do |worker|
68
- worker.resolve_used_bins!
69
- resolved << worker
70
- end
71
- errors.each{ |error| warn error }
72
- resolved
73
- end
15
+ # Default init for worker is new
16
+ # Check example of override in gifsicle worker
17
+ alias_method :init, :new
74
18
  end
75
19
 
20
+ # Allow lossy optimizations
21
+ attr_reader :allow_lossy
22
+
76
23
  # Configure (raises on extra options)
77
24
  def initialize(image_optim, options = {})
78
25
  unless image_optim.is_a?(ImageOptim)
79
26
  fail ArgumentError, 'first parameter should be an ImageOptim instance'
80
27
  end
81
28
  @image_optim = image_optim
29
+ @allow_lossy = !!options.delete(:allow_lossy)
82
30
  self.class.option_definitions.each do |option_definition|
83
31
  value = if options.key?(option_definition.name)
84
32
  options[option_definition.name]