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.
- checksums.yaml +4 -4
- data/.appveyor.yml +9 -2
- data/.rubocop.yml +34 -12
- data/.travis.yml +18 -15
- data/CHANGELOG.markdown +23 -0
- data/CONTRIBUTING.markdown +4 -1
- data/Gemfile +3 -1
- data/LICENSE.txt +1 -1
- data/README.markdown +10 -3
- data/Vagrantfile +2 -0
- data/bin/image_optim +1 -0
- data/image_optim.gemspec +5 -7
- data/lib/image_optim.rb +2 -0
- data/lib/image_optim/bin_resolver.rb +2 -0
- data/lib/image_optim/bin_resolver/bin.rb +4 -0
- data/lib/image_optim/bin_resolver/comparable_condition.rb +3 -0
- data/lib/image_optim/bin_resolver/error.rb +2 -0
- data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
- data/lib/image_optim/cache.rb +2 -0
- data/lib/image_optim/cache_path.rb +21 -2
- data/lib/image_optim/cmd.rb +2 -0
- data/lib/image_optim/config.rb +6 -4
- data/lib/image_optim/configuration_error.rb +2 -0
- data/lib/image_optim/handler.rb +2 -0
- data/lib/image_optim/hash_helpers.rb +2 -0
- data/lib/image_optim/image_meta.rb +2 -0
- data/lib/image_optim/non_negative_integer_range.rb +2 -0
- data/lib/image_optim/optimized_path.rb +3 -1
- data/lib/image_optim/option_definition.rb +2 -0
- data/lib/image_optim/option_helpers.rb +2 -0
- data/lib/image_optim/path.rb +30 -5
- data/lib/image_optim/runner.rb +2 -0
- data/lib/image_optim/runner/glob_helpers.rb +3 -1
- data/lib/image_optim/runner/option_parser.rb +5 -2
- data/lib/image_optim/space.rb +3 -1
- data/lib/image_optim/true_false_nil.rb +2 -0
- data/lib/image_optim/worker.rb +1 -0
- data/lib/image_optim/worker/advpng.rb +2 -0
- data/lib/image_optim/worker/class_methods.rb +4 -0
- data/lib/image_optim/worker/gifsicle.rb +2 -0
- data/lib/image_optim/worker/jhead.rb +3 -1
- data/lib/image_optim/worker/jpegoptim.rb +8 -4
- data/lib/image_optim/worker/jpegrecompress.rb +17 -0
- data/lib/image_optim/worker/jpegtran.rb +2 -0
- data/lib/image_optim/worker/optipng.rb +4 -2
- data/lib/image_optim/worker/pngcrush.rb +4 -2
- data/lib/image_optim/worker/pngout.rb +2 -0
- data/lib/image_optim/worker/pngquant.rb +3 -0
- data/lib/image_optim/worker/svgo.rb +2 -0
- data/script/update_worker_options_in_readme +4 -3
- data/script/worker_analysis +7 -3
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +2 -0
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +2 -0
- data/spec/image_optim/bin_resolver_spec.rb +2 -0
- data/spec/image_optim/cache_path_spec.rb +71 -26
- data/spec/image_optim/cache_spec.rb +5 -1
- data/spec/image_optim/cmd_spec.rb +2 -0
- data/spec/image_optim/config_spec.rb +2 -0
- data/spec/image_optim/handler_spec.rb +2 -0
- data/spec/image_optim/hash_helpers_spec.rb +2 -0
- data/spec/image_optim/image_meta_spec.rb +2 -0
- data/spec/image_optim/optimized_path_spec.rb +2 -0
- data/spec/image_optim/option_definition_spec.rb +2 -0
- data/spec/image_optim/option_helpers_spec.rb +2 -0
- data/spec/image_optim/path_spec.rb +65 -24
- data/spec/image_optim/runner/glob_helpers_spec.rb +2 -0
- data/spec/image_optim/runner/option_parser_spec.rb +2 -0
- data/spec/image_optim/space_spec.rb +13 -11
- data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
- data/spec/image_optim/worker/optipng_spec.rb +2 -0
- data/spec/image_optim/worker/pngquant_spec.rb +2 -0
- data/spec/image_optim/worker_spec.rb +2 -0
- data/spec/image_optim_spec.rb +7 -4
- data/spec/images/invisiblepixels/generate +1 -0
- data/spec/images/quant/generate +3 -2
- data/spec/spec_helper.rb +2 -0
- metadata +14 -13
data/lib/image_optim/cmd.rb
CHANGED
data/lib/image_optim/config.rb
CHANGED
@@ -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'
|
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'
|
data/lib/image_optim/handler.rb
CHANGED
@@ -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
|
-
|
10
|
+
super(path)
|
9
11
|
if original_or_size.is_a?(Integer)
|
10
12
|
@original = path
|
11
13
|
@original_size = original_or_size
|
data/lib/image_optim/path.rb
CHANGED
@@ -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.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
data/lib/image_optim/runner.rb
CHANGED
@@ -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
|
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
|
data/lib/image_optim/space.rb
CHANGED
@@ -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
|
20
|
+
number_string = if degree == 0
|
19
21
|
size.to_s
|
20
22
|
else
|
21
23
|
denominator = BASE**degree
|
data/lib/image_optim/worker.rb
CHANGED
@@ -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
|
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
|
13
|
-
'`:
|
14
|
+
option(:strip, :all, Array, 'List of markers to strip: '\
|
15
|
+
'`:com`, '\
|
14
16
|
'`:exif`, '\
|
15
17
|
'`:iptc`, '\
|
16
|
-
'`:icc
|
18
|
+
'`:icc`, '\
|
19
|
+
'`:xmp`, '\
|
20
|
+
'`:none` or '\
|
17
21
|
'`:all`') do |v|
|
18
22
|
values = Array(v).map(&:to_s)
|
19
|
-
known_values = %w[
|
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
|
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'
|
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'
|
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
|
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,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>-->'
|
10
|
-
END_MARKER = '<!---</worker-options>-->'
|
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
|
21
|
+
line += " *(defaults to #{option_definition.default_description})*"
|
21
22
|
end
|
22
23
|
io.puts line
|
23
24
|
end
|
data/script/worker_analysis
CHANGED
@@ -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'
|
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
|
-
|
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
|
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
|