image_optim 0.26.3 → 0.28.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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.appveyor.yml +9 -2
  3. data/.rubocop.yml +34 -12
  4. data/.travis.yml +18 -15
  5. data/CHANGELOG.markdown +23 -0
  6. data/CONTRIBUTING.markdown +4 -1
  7. data/Gemfile +3 -1
  8. data/LICENSE.txt +1 -1
  9. data/README.markdown +10 -3
  10. data/Vagrantfile +2 -0
  11. data/bin/image_optim +1 -0
  12. data/image_optim.gemspec +5 -7
  13. data/lib/image_optim.rb +2 -0
  14. data/lib/image_optim/bin_resolver.rb +2 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +4 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +3 -0
  17. data/lib/image_optim/bin_resolver/error.rb +2 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
  19. data/lib/image_optim/cache.rb +2 -0
  20. data/lib/image_optim/cache_path.rb +21 -2
  21. data/lib/image_optim/cmd.rb +2 -0
  22. data/lib/image_optim/config.rb +6 -4
  23. data/lib/image_optim/configuration_error.rb +2 -0
  24. data/lib/image_optim/handler.rb +2 -0
  25. data/lib/image_optim/hash_helpers.rb +2 -0
  26. data/lib/image_optim/image_meta.rb +2 -0
  27. data/lib/image_optim/non_negative_integer_range.rb +2 -0
  28. data/lib/image_optim/optimized_path.rb +3 -1
  29. data/lib/image_optim/option_definition.rb +2 -0
  30. data/lib/image_optim/option_helpers.rb +2 -0
  31. data/lib/image_optim/path.rb +30 -5
  32. data/lib/image_optim/runner.rb +2 -0
  33. data/lib/image_optim/runner/glob_helpers.rb +3 -1
  34. data/lib/image_optim/runner/option_parser.rb +5 -2
  35. data/lib/image_optim/space.rb +3 -1
  36. data/lib/image_optim/true_false_nil.rb +2 -0
  37. data/lib/image_optim/worker.rb +1 -0
  38. data/lib/image_optim/worker/advpng.rb +2 -0
  39. data/lib/image_optim/worker/class_methods.rb +4 -0
  40. data/lib/image_optim/worker/gifsicle.rb +2 -0
  41. data/lib/image_optim/worker/jhead.rb +3 -1
  42. data/lib/image_optim/worker/jpegoptim.rb +8 -4
  43. data/lib/image_optim/worker/jpegrecompress.rb +17 -0
  44. data/lib/image_optim/worker/jpegtran.rb +2 -0
  45. data/lib/image_optim/worker/optipng.rb +4 -2
  46. data/lib/image_optim/worker/pngcrush.rb +4 -2
  47. data/lib/image_optim/worker/pngout.rb +2 -0
  48. data/lib/image_optim/worker/pngquant.rb +3 -0
  49. data/lib/image_optim/worker/svgo.rb +2 -0
  50. data/script/update_worker_options_in_readme +4 -3
  51. data/script/worker_analysis +7 -3
  52. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +2 -0
  53. data/spec/image_optim/bin_resolver/simple_version_spec.rb +2 -0
  54. data/spec/image_optim/bin_resolver_spec.rb +2 -0
  55. data/spec/image_optim/cache_path_spec.rb +71 -26
  56. data/spec/image_optim/cache_spec.rb +5 -1
  57. data/spec/image_optim/cmd_spec.rb +2 -0
  58. data/spec/image_optim/config_spec.rb +2 -0
  59. data/spec/image_optim/handler_spec.rb +2 -0
  60. data/spec/image_optim/hash_helpers_spec.rb +2 -0
  61. data/spec/image_optim/image_meta_spec.rb +2 -0
  62. data/spec/image_optim/optimized_path_spec.rb +2 -0
  63. data/spec/image_optim/option_definition_spec.rb +2 -0
  64. data/spec/image_optim/option_helpers_spec.rb +2 -0
  65. data/spec/image_optim/path_spec.rb +65 -24
  66. data/spec/image_optim/runner/glob_helpers_spec.rb +2 -0
  67. data/spec/image_optim/runner/option_parser_spec.rb +2 -0
  68. data/spec/image_optim/space_spec.rb +13 -11
  69. data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
  70. data/spec/image_optim/worker/optipng_spec.rb +2 -0
  71. data/spec/image_optim/worker/pngquant_spec.rb +2 -0
  72. data/spec/image_optim/worker_spec.rb +2 -0
  73. data/spec/image_optim_spec.rb +7 -4
  74. data/spec/images/invisiblepixels/generate +1 -0
  75. data/spec/images/quant/generate +3 -2
  76. data/spec/spec_helper.rb +2 -0
  77. metadata +14 -13
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'English'
2
4
 
3
5
  class ImageOptim
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/option_helpers'
2
4
  require 'image_optim/configuration_error'
3
5
  require 'image_optim/hash_helpers'
@@ -18,7 +20,7 @@ class ImageOptim
18
20
  end
19
21
 
20
22
  # Local config path at `./.image_optim.yml`
21
- LOCAL_PATH = './.image_optim.yml'.freeze
23
+ LOCAL_PATH = './.image_optim.yml'
22
24
 
23
25
  class << self
24
26
  # Read options at path: expand path (warn on failure), return {} if file
@@ -195,10 +197,10 @@ class ImageOptim
195
197
  when /darwin9/
196
198
  Cmd.capture 'hwprefs cpu_count'
197
199
  when /darwin/
198
- if (Cmd.capture 'which hwprefs') != ''
199
- Cmd.capture 'hwprefs thread_count'
200
- else
200
+ if (Cmd.capture 'which hwprefs') == ''
201
201
  Cmd.capture 'sysctl -n hw.ncpu'
202
+ else
203
+ Cmd.capture 'hwprefs thread_count'
202
204
  end
203
205
  when /linux/
204
206
  Cmd.capture 'grep -c processor /proc/cpuinfo'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  class ConfigurationError < StandardError; end
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/path'
2
4
 
3
5
  class ImageOptim
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Helper methods to manipulate Hash, mainly used in config
3
5
  module HashHelpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_size'
2
4
 
3
5
  class ImageOptim
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Denote range of non negative integers for worker option
3
5
  class NonNegativeIntegerRange
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/path'
2
4
 
3
5
  class ImageOptim
@@ -5,7 +7,7 @@ class ImageOptim
5
7
  class OptimizedPath < DelegateClass(Path)
6
8
  def initialize(path, original_or_size = nil)
7
9
  path = Path.convert(path)
8
- __setobj__(path)
10
+ super(path)
9
11
  if original_or_size.is_a?(Integer)
10
12
  @original = path
11
13
  @original_size = original_or_size
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Hold information about an option
3
5
  class OptionDefinition
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Helper methods for options
3
5
  module OptionHelpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fspath'
2
4
  require 'image_optim/image_meta'
3
5
 
@@ -48,11 +50,16 @@ class ImageOptim
48
50
 
49
51
  # Atomic replace dst with self
50
52
  def replace(dst)
51
- dst = self.class.new(dst)
52
- dst.temp_path(dst.dirname) do |temp|
53
- move(temp)
54
- dst.copy_metadata(temp)
55
- temp.rename(dst.to_s)
53
+ dst = self.class.convert(dst)
54
+ if same_dev?(dst.dirname)
55
+ dst.copy_metadata(self)
56
+ begin
57
+ rename(dst.to_s)
58
+ rescue Errno::EXDEV
59
+ replace_using_tmp_file(dst)
60
+ end
61
+ else
62
+ replace_using_tmp_file(dst)
56
63
  end
57
64
  end
58
65
 
@@ -66,5 +73,23 @@ class ImageOptim
66
73
  def self.convert(path)
67
74
  path.is_a?(self) ? path : new(path)
68
75
  end
76
+
77
+ protected
78
+
79
+ def same_dev?(other)
80
+ stat.dev == other.stat.dev
81
+ end
82
+
83
+ def replace_using_tmp_file(dst)
84
+ dst.temp_path_with_tmp_ext(dst.dirname) do |temp|
85
+ move(temp)
86
+ dst.copy_metadata(temp)
87
+ temp.rename(dst.to_s)
88
+ end
89
+ end
90
+
91
+ def temp_path_with_tmp_ext(*args, &block)
92
+ self.class.temp_file_path([basename.to_s, '.tmp'], *args, &block)
93
+ end
69
94
  end
70
95
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim'
2
4
  require 'image_optim/hash_helpers'
3
5
  require 'image_optim/runner/glob_helpers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  class Runner
3
5
  # Helper methods for glob
@@ -21,7 +23,7 @@ class ImageOptim
21
23
  .* # what is left
22
24
  )
23
25
  \z
24
- /x
26
+ /x.freeze
25
27
 
26
28
  # Expand curly braces in glob as fnmatch in ruby before 2.0 doesn't
27
29
  # support them
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'image_optim'
4
5
  require 'image_optim/true_false_nil'
@@ -42,7 +43,7 @@ class ImageOptim
42
43
  # don't try to wrap if there is too little space for description
43
44
  return text if wrapped_width < 20
44
45
 
45
- wrapped = ''
46
+ wrapped = ''.dup
46
47
  text.split("\n").each do |line|
47
48
  if line.length <= columns
48
49
  wrapped << line << "\n"
@@ -186,7 +187,7 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
186
187
  ImageOptim::Worker.klasses.each_with_index do |klass, i|
187
188
  next if klass.option_definitions.empty?
188
189
 
189
- op.separator nil unless i.zero?
190
+ op.separator nil unless i == 0
190
191
 
191
192
  bin = klass.bin_sym
192
193
  klass.option_definitions.each do |option_definition|
@@ -201,6 +202,8 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
201
202
  [Integer, 'N']
202
203
  when Array >= type
203
204
  [Array, 'a,b,c']
205
+ when String >= type
206
+ [String, 'S']
204
207
  when ImageOptim::NonNegativeIntegerRange == type
205
208
  [type, 'M-N']
206
209
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Present size in readable form as fixed length string
3
5
  module Space
@@ -15,7 +17,7 @@ class ImageOptim
15
17
  else
16
18
  log_denominator = Math.log(size.abs) / Math.log(BASE)
17
19
  degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
18
- number_string = if degree.zero?
20
+ number_string = if degree == 0
19
21
  size.to_s
20
22
  else
21
23
  denominator = BASE**degree
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageOptim
2
4
  # Denote ternary value (`true`/`false`/`nil`) for worker option
3
5
  class TrueFalseNil
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'image_optim/cmd'
4
5
  require 'image_optim/configuration_error'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/bin_resolver'
2
4
  require 'image_optim/option_definition'
3
5
 
@@ -16,6 +18,7 @@ class ImageOptim
16
18
 
17
19
  # Remember all classes inheriting from this one
18
20
  def inherited(base)
21
+ super
19
22
  @klasses << base
20
23
  end
21
24
 
@@ -34,6 +37,7 @@ class ImageOptim
34
37
 
35
38
  def option(name, default, type, description = nil, &proc)
36
39
  attr_reader name
40
+
37
41
  OptionDefinition.new(name, default, type, description, &proc).
38
42
  tap{ |option_definition| option_definitions << option_definition }
39
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
 
3
5
  class ImageOptim
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'exifr/jpeg'
3
5
 
@@ -7,7 +9,7 @@ class ImageOptim
7
9
  #
8
10
  # Jhead internally uses jpegtran which should be on path
9
11
  class Jhead < Worker
10
- ORIENTED = 2..8 # not top-left
12
+ ORIENTED = (2..8).freeze # not top-left
11
13
 
12
14
  # Works on jpegs
13
15
  def image_formats
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
 
@@ -9,14 +11,16 @@ class ImageOptim
9
11
  option(:allow_lossy, false, 'Allow limiting maximum quality'){ |v| !!v }
10
12
 
11
13
  STRIP_OPTION =
12
- option(:strip, :all, Array, 'List of extra markers to strip: '\
13
- '`:comments`, '\
14
+ option(:strip, :all, Array, 'List of markers to strip: '\
15
+ '`:com`, '\
14
16
  '`:exif`, '\
15
17
  '`:iptc`, '\
16
- '`:icc` or '\
18
+ '`:icc`, '\
19
+ '`:xmp`, '\
20
+ '`:none` or '\
17
21
  '`:all`') do |v|
18
22
  values = Array(v).map(&:to_s)
19
- known_values = %w[all comments exif iptc icc]
23
+ known_values = %w[com exif iptc icc xmp none all]
20
24
  unknown_values = values - known_values
21
25
  unless unknown_values.empty?
22
26
  warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
 
@@ -24,6 +26,20 @@ class ImageOptim
24
26
  OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
25
27
  end
26
28
 
29
+ METHOD_OPTION =
30
+ option(:method, 'ssim', 'Comparison Metric: '\
31
+ '`mpe` - Mean pixel error, '\
32
+ '`ssim` - Structural similarity, '\
33
+ '`ms-ssim` - Multi-scale structural similarity (slow!), '\
34
+ '`smallfry` - Linear-weighted BBCQ-like (may be patented)') do |v, opt_def|
35
+ if %w[mpe ssim ms-ssim smallfry].include? v
36
+ v
37
+ else
38
+ warn "Unknown method for jpegrecompress: #{v}"
39
+ opt_def.default
40
+ end
41
+ end
42
+
27
43
  def used_bins
28
44
  [:'jpeg-recompress']
29
45
  end
@@ -36,6 +52,7 @@ class ImageOptim
36
52
  def optimize(src, dst)
37
53
  args = %W[
38
54
  --quality #{QUALITY_NAMES[quality]}
55
+ --method #{method}
39
56
  --no-copy
40
57
  #{src}
41
58
  #{dst}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
 
3
5
  class ImageOptim
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
  require 'image_optim/true_false_nil'
@@ -37,8 +39,8 @@ class ImageOptim
37
39
  #{dst}
38
40
  ]
39
41
  args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
40
- if resolve_bin!(:optipng).version >= '0.7'
41
- args.unshift '-strip', 'all' if strip
42
+ if strip && resolve_bin!(:optipng).version >= '0.7'
43
+ args.unshift '-strip', 'all'
42
44
  end
43
45
  execute(:optipng, *args) && optimized?(src, dst)
44
46
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
 
3
5
  class ImageOptim
@@ -37,8 +39,8 @@ class ImageOptim
37
39
  end
38
40
  flags.push '-fix' if fix
39
41
  flags.push '-brute' if brute
40
- if resolve_bin!(:pngcrush).version >= '1.7.38'
41
- flags.push '-blacken' if blacken
42
+ if blacken && resolve_bin!(:pngcrush).version >= '1.7.38'
43
+ flags.push '-blacken'
42
44
  end
43
45
 
44
46
  args = flags + %W[
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
  require 'image_optim/option_helpers'
3
5
  require 'image_optim/non_negative_integer_range'
@@ -53,6 +55,7 @@ class ImageOptim
53
55
  --quality=#{quality.begin}-#{quality.end}
54
56
  --speed=#{speed}
55
57
  --output=#{dst}
58
+ --skip-if-larger
56
59
  --force
57
60
  #{max_colors}
58
61
  --
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/worker'
2
4
 
3
5
  class ImageOptim
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: UTF-8
3
+ # frozen_string_literal: true
3
4
 
4
5
  require 'bundler/setup'
5
6
 
6
7
  require 'image_optim'
7
8
 
8
9
  README_FILE = File.expand_path('../../README.markdown', __FILE__)
9
- BEGIN_MARKER = '<!---<worker-options>-->'.freeze
10
- END_MARKER = '<!---</worker-options>-->'.freeze
10
+ BEGIN_MARKER = '<!---<worker-options>-->'
11
+ END_MARKER = '<!---</worker-options>-->'
11
12
 
12
13
  def write_worker_options(io, klass)
13
14
  io.puts "### #{klass.bin_sym}:"
@@ -17,7 +18,7 @@ def write_worker_options(io, klass)
17
18
  klass.option_definitions.each do |option_definition|
18
19
  line = "* `:#{option_definition.name}` — #{option_definition.description}"
19
20
  unless line['(defaults']
20
- line << " *(defaults to #{option_definition.default_description})*"
21
+ line += " *(defaults to #{option_definition.default_description})*"
21
22
  end
22
23
  io.puts line
23
24
  end
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: UTF-8
3
+ # frozen_string_literal: true
3
4
 
4
5
  require 'bundler/setup'
5
6
 
@@ -12,7 +13,7 @@ require 'digest'
12
13
  require 'erb'
13
14
  require 'ostruct'
14
15
 
15
- DIR = 'tmp'.freeze
16
+ DIR = 'tmp'
16
17
  Pathname(DIR).mkpath
17
18
 
18
19
  Array.class_eval do
@@ -136,13 +137,14 @@ class Analyser
136
137
  # Delegate to worker with short id
137
138
  class WorkerVariant < DelegateClass(ImageOptim::Worker)
138
139
  attr_reader :name, :id, :cons_id, :required
140
+
139
141
  def initialize(klass, image_optim, options)
140
142
  @required = options.delete(:required)
141
143
  @run_order = options.delete(:run_order)
142
144
  allow_consecutive_on = Array(options.delete(:allow_consecutive_on))
143
145
  @image_optim = image_optim
144
146
  @name = klass.bin_sym.to_s + options_string(options)
145
- __setobj__(klass.new(image_optim, options))
147
+ super(klass.new(image_optim, options))
146
148
  @id = klass.bin_sym.to_s + options_string(self.options)
147
149
  @cons_id = [klass, allow_consecutive_on.map{ |key| [key, send(key)] }]
148
150
  end
@@ -451,6 +453,7 @@ class Analyser
451
453
  attr_reader :name
452
454
  attr_reader :success_count
453
455
  attr_reader :time, :avg_time
456
+
454
457
  def initialize(name, steps)
455
458
  @name = name
456
459
  @success_count = steps.count(&:success)
@@ -459,11 +462,12 @@ class Analyser
459
462
  end
460
463
 
461
464
  def unused?
462
- success_count.zero?
465
+ success_count == 0
463
466
  end
464
467
  end
465
468
 
466
469
  attr_reader :name, :results, :ids2names
470
+
467
471
  def initialize(name, results, ids2names)
468
472
  @name = name.to_s
469
473
  @results = results