image_optim 0.26.3 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
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