discourse_image_optim 0.24.4
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 +7 -0
- data/.appveyor.yml +46 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +110 -0
- data/.travis.yml +42 -0
- data/CHANGELOG.markdown +316 -0
- data/CONTRIBUTING.markdown +11 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +358 -0
- data/Vagrantfile +38 -0
- data/bin/image_optim +28 -0
- data/image_optim.gemspec +34 -0
- data/lib/image_optim.rb +267 -0
- data/lib/image_optim/bin_resolver.rb +142 -0
- data/lib/image_optim/bin_resolver/bin.rb +115 -0
- data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
- data/lib/image_optim/bin_resolver/error.rb +6 -0
- data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
- data/lib/image_optim/cache.rb +72 -0
- data/lib/image_optim/cache_path.rb +16 -0
- data/lib/image_optim/cmd.rb +122 -0
- data/lib/image_optim/config.rb +219 -0
- data/lib/image_optim/configuration_error.rb +3 -0
- data/lib/image_optim/handler.rb +57 -0
- data/lib/image_optim/hash_helpers.rb +45 -0
- data/lib/image_optim/image_meta.rb +20 -0
- data/lib/image_optim/non_negative_integer_range.rb +11 -0
- data/lib/image_optim/optimized_path.rb +25 -0
- data/lib/image_optim/option_definition.rb +38 -0
- data/lib/image_optim/option_helpers.rb +17 -0
- data/lib/image_optim/path.rb +70 -0
- data/lib/image_optim/runner.rb +139 -0
- data/lib/image_optim/runner/glob_helpers.rb +45 -0
- data/lib/image_optim/runner/option_parser.rb +246 -0
- data/lib/image_optim/space.rb +29 -0
- data/lib/image_optim/true_false_nil.rb +16 -0
- data/lib/image_optim/worker.rb +170 -0
- data/lib/image_optim/worker/advpng.rb +37 -0
- data/lib/image_optim/worker/class_methods.rb +107 -0
- data/lib/image_optim/worker/gifsicle.rb +65 -0
- data/lib/image_optim/worker/jhead.rb +47 -0
- data/lib/image_optim/worker/jpegoptim.rb +63 -0
- data/lib/image_optim/worker/jpegrecompress.rb +49 -0
- data/lib/image_optim/worker/jpegtran.rb +48 -0
- data/lib/image_optim/worker/optipng.rb +53 -0
- data/lib/image_optim/worker/pngcrush.rb +56 -0
- data/lib/image_optim/worker/pngout.rb +40 -0
- data/lib/image_optim/worker/pngquant.rb +61 -0
- data/lib/image_optim/worker/svgo.rb +34 -0
- data/script/template/jquery-2.1.3.min.js +4 -0
- data/script/template/sortable-0.6.0.min.js +2 -0
- data/script/template/worker_analysis.erb +254 -0
- data/script/update_worker_options_in_readme +59 -0
- data/script/worker_analysis +589 -0
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
- data/spec/image_optim/bin_resolver_spec.rb +290 -0
- data/spec/image_optim/cache_path_spec.rb +57 -0
- data/spec/image_optim/cache_spec.rb +162 -0
- data/spec/image_optim/cmd_spec.rb +93 -0
- data/spec/image_optim/config_spec.rb +254 -0
- data/spec/image_optim/handler_spec.rb +90 -0
- data/spec/image_optim/hash_helpers_spec.rb +74 -0
- data/spec/image_optim/image_meta_spec.rb +61 -0
- data/spec/image_optim/optimized_path_spec.rb +58 -0
- data/spec/image_optim/option_definition_spec.rb +138 -0
- data/spec/image_optim/option_helpers_spec.rb +25 -0
- data/spec/image_optim/path_spec.rb +103 -0
- data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
- data/spec/image_optim/runner/option_parser_spec.rb +105 -0
- data/spec/image_optim/space_spec.rb +23 -0
- data/spec/image_optim/worker/optipng_spec.rb +102 -0
- data/spec/image_optim/worker/pngquant_spec.rb +67 -0
- data/spec/image_optim/worker_spec.rb +303 -0
- data/spec/image_optim_spec.rb +259 -0
- data/spec/images/broken_jpeg +1 -0
- data/spec/images/comparison.png +0 -0
- data/spec/images/decompressed.jpeg +0 -0
- data/spec/images/icecream.gif +0 -0
- data/spec/images/image.jpg +0 -0
- data/spec/images/invisiblepixels/generate +24 -0
- data/spec/images/invisiblepixels/image.png +0 -0
- data/spec/images/lena.jpg +0 -0
- data/spec/images/orient/0.jpg +0 -0
- data/spec/images/orient/1.jpg +0 -0
- data/spec/images/orient/2.jpg +0 -0
- data/spec/images/orient/3.jpg +0 -0
- data/spec/images/orient/4.jpg +0 -0
- data/spec/images/orient/5.jpg +0 -0
- data/spec/images/orient/6.jpg +0 -0
- data/spec/images/orient/7.jpg +0 -0
- data/spec/images/orient/8.jpg +0 -0
- data/spec/images/orient/generate +23 -0
- data/spec/images/orient/original.jpg +0 -0
- data/spec/images/quant/64.png +0 -0
- data/spec/images/quant/generate +25 -0
- data/spec/images/rails.png +0 -0
- data/spec/images/test.svg +3 -0
- data/spec/images/transparency1.png +0 -0
- data/spec/images/transparency2.png +0 -0
- data/spec/images/vergroessert.jpg +0 -0
- data/spec/spec_helper.rb +93 -0
- metadata +281 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
require 'image_optim/option_helpers'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
class Worker
|
|
6
|
+
# http://advancemame.sourceforge.net/doc-advpng.html
|
|
7
|
+
class Advpng < Worker
|
|
8
|
+
LEVEL_OPTION =
|
|
9
|
+
option(:level, 4, 'Compression level: '\
|
|
10
|
+
'`0` - don\'t compress, '\
|
|
11
|
+
'`1` - fast, '\
|
|
12
|
+
'`2` - normal, '\
|
|
13
|
+
'`3` - extra, '\
|
|
14
|
+
'`4` - extreme') do |v|
|
|
15
|
+
OptionHelpers.limit_with_range(v.to_i, 0..4)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
TIMEOUT_OPTION = timeout_option
|
|
19
|
+
|
|
20
|
+
def run_order
|
|
21
|
+
4
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def optimize(src, dst)
|
|
25
|
+
src.copy(dst)
|
|
26
|
+
args = %W[
|
|
27
|
+
--recompress
|
|
28
|
+
-#{level}
|
|
29
|
+
--quiet
|
|
30
|
+
--
|
|
31
|
+
#{dst}
|
|
32
|
+
]
|
|
33
|
+
execute(:advpng, *args) && optimized?(src, dst)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'image_optim/bin_resolver'
|
|
2
|
+
require 'image_optim/option_definition'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
class Worker
|
|
6
|
+
# Class methods of ImageOptim::Worker
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def self.extended(klass)
|
|
9
|
+
klass.instance_variable_set(:@klasses, [])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# List of available workers
|
|
13
|
+
def klasses
|
|
14
|
+
@klasses.to_enum
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Remember all classes inheriting from this one
|
|
18
|
+
def inherited(base)
|
|
19
|
+
@klasses << base
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Underscored class name symbol
|
|
23
|
+
def bin_sym
|
|
24
|
+
@underscored_name ||=
|
|
25
|
+
name.
|
|
26
|
+
split('::').last. # get last part
|
|
27
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').downcase. # convert AbcDef to abc_def
|
|
28
|
+
to_sym
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def option_definitions
|
|
32
|
+
@option_definitions ||= []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def timeout_option
|
|
36
|
+
option(
|
|
37
|
+
:timeout,
|
|
38
|
+
0,
|
|
39
|
+
'Number of seconds before worker is timed out. Must be greater than' \
|
|
40
|
+
'0 to enable timeout.'
|
|
41
|
+
){ |v| v }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def option(name, default, type, description = nil, &proc)
|
|
45
|
+
attr_reader name
|
|
46
|
+
OptionDefinition.new(name, default, type, description, &proc).
|
|
47
|
+
tap{ |option_definition| option_definitions << option_definition }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Create hash with format mapped to list of workers sorted by run order
|
|
51
|
+
def create_all_by_format(image_optim, &options_proc)
|
|
52
|
+
by_format = {}
|
|
53
|
+
create_all(image_optim, &options_proc).each do |worker|
|
|
54
|
+
worker.image_formats.each do |format|
|
|
55
|
+
by_format[format] ||= []
|
|
56
|
+
by_format[format] << worker
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
by_format
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Create list of workers sorted by run order
|
|
63
|
+
# Workers are initialized with options provided through options_proc
|
|
64
|
+
# Resolve all bins of all workers, if there are errors and
|
|
65
|
+
# skip_missing_workers of image_optim is true - show warnings, otherwise
|
|
66
|
+
# fail with one joint exception
|
|
67
|
+
def create_all(image_optim, &options_proc)
|
|
68
|
+
workers = init_all(image_optim, &options_proc)
|
|
69
|
+
|
|
70
|
+
resolved = []
|
|
71
|
+
errors = BinResolver.collect_errors(workers) do |worker|
|
|
72
|
+
worker.resolve_used_bins!
|
|
73
|
+
resolved << worker
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless errors.empty?
|
|
77
|
+
messages = errors.map(&:to_s).uniq
|
|
78
|
+
if image_optim.skip_missing_workers
|
|
79
|
+
messages.each{ |message| warn message }
|
|
80
|
+
else
|
|
81
|
+
joint_message = ['Bin resolving errors:', *messages].join("\n")
|
|
82
|
+
fail BinResolver::Error, joint_message
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
resolved.sort_by.with_index{ |worker, i| [worker.run_order, i] }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def init_all(image_optim, &options_proc)
|
|
92
|
+
klasses.map do |klass|
|
|
93
|
+
options = options_proc[klass]
|
|
94
|
+
next if options[:disable]
|
|
95
|
+
|
|
96
|
+
[:allow_lossy, :timeout].each do |option_key|
|
|
97
|
+
if !options.key?(option_key) && klass.method_defined?(option_key)
|
|
98
|
+
options[option_key] = image_optim.send(option_key)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
klass.init(image_optim, options)
|
|
103
|
+
end.compact.flatten
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
class Worker
|
|
5
|
+
# http://www.lcdf.org/gifsicle/
|
|
6
|
+
class Gifsicle < Worker
|
|
7
|
+
# If interlace specified initialize one instance
|
|
8
|
+
# Otherwise initialize two, one with interlace off and one with on
|
|
9
|
+
def self.init(image_optim, options = {})
|
|
10
|
+
return super if options.key?(:interlace)
|
|
11
|
+
|
|
12
|
+
[false, true].map do |interlace|
|
|
13
|
+
new(image_optim, options.merge(:interlace => interlace))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
INTERLACE_OPTION =
|
|
18
|
+
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
|
19
|
+
'`true` - interlace on, '\
|
|
20
|
+
'`false` - interlace off, '\
|
|
21
|
+
'`nil` - as is in original image '\
|
|
22
|
+
'(defaults to running two instances, one with interlace off and '\
|
|
23
|
+
'one with on)') do |v|
|
|
24
|
+
TrueFalseNil.convert(v)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
LEVEL_OPTION =
|
|
28
|
+
option(:level, 3, 'Compression level: '\
|
|
29
|
+
'`1` - light and fast, '\
|
|
30
|
+
'`2` - normal, '\
|
|
31
|
+
'`3` - heavy (slower)') do |v|
|
|
32
|
+
OptionHelpers.limit_with_range(v.to_i, 1..3)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
CAREFUL_OPTION =
|
|
36
|
+
option(:careful, false, 'Avoid bugs with some software'){ |v| !!v }
|
|
37
|
+
|
|
38
|
+
TIMEOUT_OPTION = timeout_option
|
|
39
|
+
|
|
40
|
+
def optimize(src, dst)
|
|
41
|
+
args = %W[
|
|
42
|
+
--output=#{dst}
|
|
43
|
+
--no-comments
|
|
44
|
+
--no-names
|
|
45
|
+
--same-delay
|
|
46
|
+
--same-loopcount
|
|
47
|
+
--no-warnings
|
|
48
|
+
--
|
|
49
|
+
#{src}
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
if resolve_bin!(:gifsicle).version >= '1.85'
|
|
53
|
+
args.unshift '--no-extensions', '--no-app-extensions'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless interlace.nil?
|
|
57
|
+
args.unshift interlace ? '--interlace' : '--no-interlace'
|
|
58
|
+
end
|
|
59
|
+
args.unshift '--careful' if careful
|
|
60
|
+
args.unshift "--optimize=#{level}" if level
|
|
61
|
+
execute(:gifsicle, *args) && optimized?(src, dst)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
require 'exifr'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
class Worker
|
|
6
|
+
# http://www.sentex.net/~mwandel/jhead/
|
|
7
|
+
#
|
|
8
|
+
# Jhead internally uses jpegtran which should be on path
|
|
9
|
+
class Jhead < Worker
|
|
10
|
+
TIMEOUT_OPTION = timeout_option
|
|
11
|
+
|
|
12
|
+
ORIENTED = 2..8 # not top-left
|
|
13
|
+
|
|
14
|
+
# Works on jpegs
|
|
15
|
+
def image_formats
|
|
16
|
+
[:jpeg]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Run first, while exif is still present
|
|
20
|
+
def run_order
|
|
21
|
+
-10
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def used_bins
|
|
25
|
+
[:jhead, :jpegtran]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def optimize(src, dst)
|
|
29
|
+
return false unless oriented?(src)
|
|
30
|
+
src.copy(dst)
|
|
31
|
+
args = %W[
|
|
32
|
+
-autorot
|
|
33
|
+
#{dst}
|
|
34
|
+
]
|
|
35
|
+
resolve_bin!(:jpegtran)
|
|
36
|
+
execute(:jhead, *args) && dst.size?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def oriented?(image)
|
|
42
|
+
exif = EXIFR::JPEG.new(image.to_s)
|
|
43
|
+
ORIENTED.include?(exif.orientation.to_i)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
require 'image_optim/option_helpers'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
class Worker
|
|
6
|
+
# http://www.kokkonen.net/tjko/projects.html
|
|
7
|
+
class Jpegoptim < Worker
|
|
8
|
+
ALLOW_LOSSY_OPTION =
|
|
9
|
+
option(:allow_lossy, false, 'Allow limiting maximum quality'){ |v| !!v }
|
|
10
|
+
|
|
11
|
+
STRIP_OPTION =
|
|
12
|
+
option(:strip, :all, Array, 'List of extra markers to strip: '\
|
|
13
|
+
'`:comments`, '\
|
|
14
|
+
'`:exif`, '\
|
|
15
|
+
'`:iptc`, '\
|
|
16
|
+
'`:icc` or '\
|
|
17
|
+
'`:all`') do |v|
|
|
18
|
+
values = Array(v).map(&:to_s)
|
|
19
|
+
known_values = %w[all comments exif iptc icc]
|
|
20
|
+
unknown_values = values - known_values
|
|
21
|
+
unless unknown_values.empty?
|
|
22
|
+
warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}"
|
|
23
|
+
end
|
|
24
|
+
values & known_values
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
MAX_QUALITY_OPTION =
|
|
28
|
+
option(:max_quality, 100, 'Maximum image quality factor '\
|
|
29
|
+
'`0`..`100`, ignored in default/lossless mode') do |v, opt_def|
|
|
30
|
+
if allow_lossy
|
|
31
|
+
OptionHelpers.limit_with_range(v.to_i, 0..100)
|
|
32
|
+
else
|
|
33
|
+
if v != opt_def.default
|
|
34
|
+
warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
|
|
35
|
+
'in lossless mode'
|
|
36
|
+
end
|
|
37
|
+
opt_def.default
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
TIMEOUT_OPTION = timeout_option
|
|
42
|
+
|
|
43
|
+
# Run earlier if max_quality is less than 100
|
|
44
|
+
def run_order
|
|
45
|
+
max_quality < 100 ? -1 : 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def optimize(src, dst)
|
|
49
|
+
src.copy(dst)
|
|
50
|
+
args = %W[
|
|
51
|
+
--quiet
|
|
52
|
+
--
|
|
53
|
+
#{dst}
|
|
54
|
+
]
|
|
55
|
+
strip.each do |strip_marker|
|
|
56
|
+
args.unshift "--strip-#{strip_marker}"
|
|
57
|
+
end
|
|
58
|
+
args.unshift "--max=#{max_quality}" if max_quality < 100
|
|
59
|
+
execute(:jpegoptim, *args) && optimized?(src, dst)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
require 'image_optim/option_helpers'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
class Worker
|
|
6
|
+
# https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress
|
|
7
|
+
class Jpegrecompress < Worker
|
|
8
|
+
ALLOW_LOSSY_OPTION =
|
|
9
|
+
option(:allow_lossy, false, 'Allow worker, it is always lossy'){ |v| !!v }
|
|
10
|
+
|
|
11
|
+
# Initialize only if allow_lossy
|
|
12
|
+
def self.init(image_optim, options = {})
|
|
13
|
+
super if options[:allow_lossy]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
QUALITY_NAMES = [:low, :medium, :high, :veryhigh].freeze
|
|
17
|
+
|
|
18
|
+
quality_names_desc = QUALITY_NAMES.each_with_index.map do |name, i|
|
|
19
|
+
"`#{i}` - #{name}"
|
|
20
|
+
end.join(', ')
|
|
21
|
+
|
|
22
|
+
QUALITY_OPTION =
|
|
23
|
+
option(:quality, 3, "JPEG quality preset: #{quality_names_desc}") do |v|
|
|
24
|
+
OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
TIMEOUT_OPTION = timeout_option
|
|
28
|
+
|
|
29
|
+
def used_bins
|
|
30
|
+
[:'jpeg-recompress']
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Run early as lossy worker
|
|
34
|
+
def run_order
|
|
35
|
+
-5
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def optimize(src, dst)
|
|
39
|
+
args = %W[
|
|
40
|
+
--quality #{QUALITY_NAMES[quality]}
|
|
41
|
+
--no-copy
|
|
42
|
+
#{src}
|
|
43
|
+
#{dst}
|
|
44
|
+
]
|
|
45
|
+
execute(:'jpeg-recompress', *args) && optimized?(src, dst)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
class Worker
|
|
5
|
+
# http://www.ijg.org/
|
|
6
|
+
#
|
|
7
|
+
# Uses jpegtran through jpegrescan if enabled, jpegrescan is vendored with
|
|
8
|
+
# this gem
|
|
9
|
+
class Jpegtran < Worker
|
|
10
|
+
COPY_CHUNKS_OPTION =
|
|
11
|
+
option(:copy_chunks, false, 'Copy all chunks'){ |v| !!v }
|
|
12
|
+
|
|
13
|
+
PROGRESSIVE_OPTION =
|
|
14
|
+
option(:progressive, true, 'Create progressive JPEG file'){ |v| !!v }
|
|
15
|
+
|
|
16
|
+
JPEGRESCAN_OPTION =
|
|
17
|
+
option(:jpegrescan, false, 'Use jpegtran through jpegrescan, '\
|
|
18
|
+
'ignore progressive option'){ |v| !!v }
|
|
19
|
+
|
|
20
|
+
TIMEOUT_OPTION = timeout_option
|
|
21
|
+
|
|
22
|
+
def used_bins
|
|
23
|
+
jpegrescan ? [:jpegtran, :jpegrescan] : [:jpegtran]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def optimize(src, dst)
|
|
27
|
+
if jpegrescan
|
|
28
|
+
args = %W[
|
|
29
|
+
#{src}
|
|
30
|
+
#{dst}
|
|
31
|
+
]
|
|
32
|
+
args.unshift '-s' unless copy_chunks
|
|
33
|
+
resolve_bin!(:jpegtran)
|
|
34
|
+
execute(:jpegrescan, *args) && optimized?(src, dst)
|
|
35
|
+
else
|
|
36
|
+
args = %W[
|
|
37
|
+
-optimize
|
|
38
|
+
-outfile #{dst}
|
|
39
|
+
#{src}
|
|
40
|
+
]
|
|
41
|
+
args.unshift '-copy', (copy_chunks ? 'all' : 'none')
|
|
42
|
+
args.unshift '-progressive' if progressive
|
|
43
|
+
execute(:jpegtran, *args) && optimized?(src, dst)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'image_optim/worker'
|
|
2
|
+
require 'image_optim/option_helpers'
|
|
3
|
+
require 'image_optim/true_false_nil'
|
|
4
|
+
|
|
5
|
+
class ImageOptim
|
|
6
|
+
class Worker
|
|
7
|
+
# http://optipng.sourceforge.net/
|
|
8
|
+
class Optipng < Worker
|
|
9
|
+
LEVEL_OPTION =
|
|
10
|
+
option(:level, 6, 'Optimization level preset: '\
|
|
11
|
+
'`0` is least, '\
|
|
12
|
+
'`7` is best') do |v|
|
|
13
|
+
OptionHelpers.limit_with_range(v.to_i, 0..7)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
INTERLACE_OPTION =
|
|
17
|
+
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
|
18
|
+
'`true` - interlace on, '\
|
|
19
|
+
'`false` - interlace off, '\
|
|
20
|
+
'`nil` - as is in original image') do |v|
|
|
21
|
+
TrueFalseNil.convert(v)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
STRIP_OPTION =
|
|
25
|
+
option(:strip, true, 'Remove all auxiliary chunks'){ |v| !!v }
|
|
26
|
+
|
|
27
|
+
TIMEOUT_OPTION = timeout_option
|
|
28
|
+
|
|
29
|
+
def run_order
|
|
30
|
+
-4
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def optimize(src, dst)
|
|
34
|
+
src.copy(dst)
|
|
35
|
+
args = %W[
|
|
36
|
+
-o #{level}
|
|
37
|
+
-quiet
|
|
38
|
+
--
|
|
39
|
+
#{dst}
|
|
40
|
+
]
|
|
41
|
+
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
|
|
42
|
+
if resolve_bin!(:optipng).version >= '0.7'
|
|
43
|
+
args.unshift '-strip', 'all' if strip
|
|
44
|
+
end
|
|
45
|
+
execute(:optipng, *args) && optimized?(src, dst)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def optimized?(src, dst)
|
|
49
|
+
interlace ? dst.size? : super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|