image_optim 0.18.0 → 0.19.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 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]