image_optim 0.7.3 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.markdown +33 -1
- data/bin/image_optim +59 -5
- data/image_optim.gemspec +1 -1
- data/lib/image_optim.rb +14 -4
- data/lib/image_optim/option_definition.rb +14 -0
- data/lib/image_optim/option_helpers.rb +1 -1
- data/lib/image_optim/worker.rb +17 -7
- data/lib/image_optim/worker/advpng.rb +3 -8
- data/lib/image_optim/worker/gifsicle.rb +1 -8
- data/lib/image_optim/worker/jpegoptim.rb +8 -17
- data/lib/image_optim/worker/jpegtran.rb +15 -15
- data/lib/image_optim/worker/optipng.rb +4 -11
- data/lib/image_optim/worker/pngcrush.rb +4 -14
- data/lib/image_optim/worker/pngout.rb +4 -11
- data/script/options_for_readme +14 -0
- data/vendor/jpegrescan +143 -0
- metadata +81 -104
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTcxMThlMmE2OGE1YWYxZjFkODZjY2EyZjYxYWM0MDk0YTZmNGJhMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDAwNmMyYjc3M2MzN2UzNjZmYmUzMWQwYzg1NTA2NDhmZjU2YTVmNQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YmYwYmQ1YWRhYjljNDAwYmQ3OTk3MzFhYTg5YzM2MmMyMDAxOWY1OGNlMmNl
|
10
|
+
NTg2OWI5YWNlNmIyMDQ2NmZmOGZmMzFjYjJmNTJmYjI1M2QxNzk5NjhhMTg5
|
11
|
+
Zjc1MzFhY2FjZjg5YTlhOGY0YWFmOTUxNjY0YzNkODM4OTU1YTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjJmMWJiZjEzOThiOGQwMzkyNTQxNzRiOGUyZDNkMGY5ZGU5MWJhYzViN2Iy
|
14
|
+
Yjc5N2U1ZWUyNDNlNTgzZmQ0MTU0YjEyYmQ4MDNlMDU0OTJlZjY1NDkyMDg5
|
15
|
+
ZTFjYjZlYjlmZTdiMjkxMmRiMGYwMGMwYjM4MDc0MWIwNGRiODk=
|
data/README.markdown
CHANGED
@@ -82,7 +82,7 @@ _Note: pngout is free to use even in commercial soft, but you can not redistribu
|
|
82
82
|
|
83
83
|
### From ruby
|
84
84
|
|
85
|
-
Initilize optimizer
|
85
|
+
Initilize optimizer:
|
86
86
|
|
87
87
|
image_optim = ImageOptim.new
|
88
88
|
|
@@ -108,6 +108,38 @@ Multiple images:
|
|
108
108
|
|
109
109
|
image_optim.optimize_images!(Dir['*.*'])
|
110
110
|
|
111
|
+
## Options
|
112
|
+
|
113
|
+
Worker can be disabled by passing false instead of options hash.
|
114
|
+
|
115
|
+
### pngcrush
|
116
|
+
* `:chunks` — List of chunks to remove or 'alla' - all except tRNS/transparency or 'allb' - all except tRNS and gAMA/gamma *(defaults to alla)*
|
117
|
+
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to false)*
|
118
|
+
* `:brute` — Brute force try all methods, very time-consuming and generally not worthwhile *(defaults to false)*
|
119
|
+
|
120
|
+
### pngout
|
121
|
+
* `:copy_chunks` — Copy optional chunks *(defaults to false)*
|
122
|
+
* `:strategy` — Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed *(defaults to 0)*
|
123
|
+
|
124
|
+
### optipng
|
125
|
+
* `:level` — Optimization level preset 0 is least, 7 is best *(defaults to 6)*
|
126
|
+
* `:interlace` — Interlace, true - interlace on, false - interlace off, nil - as is in original image *(defaults to false)*
|
127
|
+
|
128
|
+
### advpng
|
129
|
+
* `:level` — Compression level: 0 - don't compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme *(defaults to 4)*
|
130
|
+
|
131
|
+
### jpegoptim
|
132
|
+
* `:strip` — List of extra markers to strip: comments, exif, iptc, icc or all *(defaults to all)*
|
133
|
+
* `:max_quality` — Maximum image quality factor 0..100 *(defaults to 100)*
|
134
|
+
|
135
|
+
### jpegtran
|
136
|
+
* `:copy_chunks` — Copy all chunks *(defaults to false)*
|
137
|
+
* `:progressive` — Create progressive JPEG file *(defaults to true)*
|
138
|
+
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to true)*
|
139
|
+
|
140
|
+
### gifsicle
|
141
|
+
* `:interlace` — Turn interlacing on *(defaults to false)*
|
142
|
+
|
111
143
|
## Copyright
|
112
144
|
|
113
145
|
Copyright (c) 2012-2013 Ivan Kuchin. See LICENSE.txt for details.
|
data/bin/image_optim
CHANGED
@@ -8,6 +8,8 @@ require 'find'
|
|
8
8
|
|
9
9
|
options = {}
|
10
10
|
|
11
|
+
OptionParser.accept(ImageOptim::TrueFalseNil, OptionParser.top.atype[TrueClass][0].merge('nil' => nil)){ |arg, val| val }
|
12
|
+
|
11
13
|
option_parser = OptionParser.new do |op|
|
12
14
|
op.banner = <<-TEXT
|
13
15
|
#{op.program_name} v#{ImageOptim.version}
|
@@ -23,26 +25,61 @@ Usege:
|
|
23
25
|
|
24
26
|
op.separator nil
|
25
27
|
|
26
|
-
op.on('--[no-]threads
|
28
|
+
op.on('--[no-]threads N', Integer, 'Number of threads or disable (defaults to number of processors)') do |threads|
|
27
29
|
options[:threads] = threads
|
28
30
|
end
|
29
31
|
|
30
|
-
op.on('--[no-]nice
|
32
|
+
op.on('--[no-]nice N', Integer, 'Nice level (defaults to 10)') do |nice|
|
31
33
|
options[:nice] = nice
|
32
34
|
end
|
33
35
|
|
34
36
|
op.separator nil
|
37
|
+
op.separator ' Disabling workers:'
|
35
38
|
|
36
39
|
ImageOptim::Worker.klasses.each do |klass|
|
37
|
-
bin = klass.
|
40
|
+
bin = klass.bin_sym
|
38
41
|
op.on("--no-#{bin}", "disable #{bin} worker") do |enable|
|
39
42
|
options[bin] = enable
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
46
|
op.separator nil
|
47
|
+
op.separator ' Worker options:'
|
48
|
+
|
49
|
+
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
50
|
+
op.separator nil unless i.zero?
|
51
|
+
|
52
|
+
bin = klass.bin_sym
|
53
|
+
klass.option_definitions.each do |option_definition|
|
54
|
+
name = option_definition.name.to_s.gsub('_', '-')
|
55
|
+
default = option_definition.default
|
56
|
+
type = option_definition.type
|
57
|
+
|
58
|
+
type, marking = case
|
59
|
+
when [TrueClass, FalseClass, ImageOptim::TrueFalseNil].include?(type)
|
60
|
+
[type, 'B']
|
61
|
+
when Integer >= type
|
62
|
+
[Integer, 'N']
|
63
|
+
when Array >= type
|
64
|
+
[Array, 'a,b,c']
|
65
|
+
else
|
66
|
+
raise "Unknown type #{type}"
|
67
|
+
end
|
44
68
|
|
45
|
-
|
69
|
+
description = "#{option_definition.description.gsub(' - ', ' - ')} (defaults to #{default})"
|
70
|
+
description = description.scan(/(.*?.{1,60})(?:\s|\z)/).flatten.join("\n ").split("\n")
|
71
|
+
|
72
|
+
op.on("--#{bin}-#{name} #{marking}", type, *description) do |value|
|
73
|
+
options[bin] = {} unless options[bin].is_a?(Hash)
|
74
|
+
options[bin][option_definition.name.to_sym] = value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
op.separator nil
|
80
|
+
op.separator ' Common options:'
|
81
|
+
|
82
|
+
op.on('-v', '--verbose', 'Verbose output') do |verbose|
|
46
83
|
options[:verbose] = verbose
|
47
84
|
end
|
48
85
|
|
@@ -63,6 +100,23 @@ rescue OptionParser::ParseError => e
|
|
63
100
|
abort "#{e.to_s}\n\n#{option_parser.help}"
|
64
101
|
end
|
65
102
|
|
103
|
+
if options[:verbose]
|
104
|
+
def print_options(options, level = 1)
|
105
|
+
prefix = ' ' * level
|
106
|
+
options.each do |key, value|
|
107
|
+
if value.is_a?(Hash)
|
108
|
+
puts "#{prefix}#{key}:"
|
109
|
+
print_options(value, level + 1)
|
110
|
+
else
|
111
|
+
puts "#{prefix}#{key}: #{value.inspect}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
puts 'Options:'
|
117
|
+
print_options(options)
|
118
|
+
end
|
119
|
+
|
66
120
|
if ARGV.empty?
|
67
121
|
abort "specify image paths to optimize\n\n#{option_parser.help}"
|
68
122
|
else
|
@@ -70,7 +124,7 @@ else
|
|
70
124
|
image_optim = begin
|
71
125
|
ImageOptim.new(options)
|
72
126
|
rescue ImageOptim::ConfigurationError => e
|
73
|
-
abort e
|
127
|
+
abort e.to_s
|
74
128
|
end
|
75
129
|
|
76
130
|
paths = []
|
data/image_optim.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_optim'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.8.0'
|
6
6
|
s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
data/lib/image_optim.rb
CHANGED
@@ -3,12 +3,15 @@ require 'shellwords'
|
|
3
3
|
|
4
4
|
require 'image_optim/image_path'
|
5
5
|
require 'image_optim/option_helpers'
|
6
|
+
require 'image_optim/option_definition'
|
6
7
|
require 'image_optim/worker'
|
7
8
|
|
8
9
|
class ImageOptim
|
9
10
|
class ConfigurationError < StandardError; end
|
10
11
|
class BinNotFoundError < StandardError; end
|
11
12
|
|
13
|
+
class TrueFalseNil; end
|
14
|
+
|
12
15
|
include OptionHelpers
|
13
16
|
|
14
17
|
# Nice level
|
@@ -62,13 +65,13 @@ class ImageOptim
|
|
62
65
|
else
|
63
66
|
threads.to_i
|
64
67
|
end
|
65
|
-
@threads = limit_with_range(threads, 1..16)
|
68
|
+
@threads = OptionHelpers.limit_with_range(threads, 1..16)
|
66
69
|
|
67
70
|
@verbose = !!options.delete(:verbose)
|
68
71
|
|
69
72
|
@workers_by_format = {}
|
70
73
|
Worker.klasses.each do |klass|
|
71
|
-
case worker_options = options.delete(klass.
|
74
|
+
case worker_options = options.delete(klass.bin_sym)
|
72
75
|
when Hash
|
73
76
|
when true, nil
|
74
77
|
worker_options = {}
|
@@ -150,7 +153,7 @@ class ImageOptim
|
|
150
153
|
|
151
154
|
# Version of image_optim gem spec loaded
|
152
155
|
def self.version
|
153
|
-
Gem.loaded_specs['image_optim'].version.to_s rescue
|
156
|
+
Gem.loaded_specs['image_optim'].version.to_s rescue 'DEV'
|
154
157
|
end
|
155
158
|
|
156
159
|
# Are there workers for file at path?
|
@@ -184,6 +187,13 @@ class ImageOptim
|
|
184
187
|
@resolved_bins[bin] or raise BinNotFoundError, "`#{bin}` not found"
|
185
188
|
end
|
186
189
|
|
190
|
+
VENDOR_PATH = File.expand_path('../../vendor', __FILE__)
|
191
|
+
|
192
|
+
# Join resolve_dir, default path and vendor path for PATH environment variable
|
193
|
+
def env_path
|
194
|
+
"#{resolve_dir}:#{ENV['PATH']}:#{VENDOR_PATH}"
|
195
|
+
end
|
196
|
+
|
187
197
|
private
|
188
198
|
|
189
199
|
# Run method for each path and yield each path and result if block given
|
@@ -210,7 +220,7 @@ private
|
|
210
220
|
|
211
221
|
# Check if bin can be accessed
|
212
222
|
def bin_accessible?(bin)
|
213
|
-
`which #{bin.to_s.shellescape}` != ''
|
223
|
+
`env PATH=#{env_path.shellescape} which #{bin.to_s.shellescape}` != ''
|
214
224
|
end
|
215
225
|
|
216
226
|
# http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class OptionDefinition
|
2
|
+
|
3
|
+
attr_reader :name, :default, :type, :description, :proc
|
4
|
+
|
5
|
+
def initialize(name, default, type, description, &proc)
|
6
|
+
if type.is_a?(String)
|
7
|
+
type, description = default.class, type
|
8
|
+
end
|
9
|
+
|
10
|
+
@name = name.to_sym
|
11
|
+
@description = description.to_s
|
12
|
+
@default, @type, @proc = default, type, proc
|
13
|
+
end
|
14
|
+
end
|
data/lib/image_optim/worker.rb
CHANGED
@@ -7,7 +7,7 @@ require 'image_optim'
|
|
7
7
|
class ImageOptim
|
8
8
|
class Worker
|
9
9
|
class << self
|
10
|
-
# List of
|
10
|
+
# List of available workers
|
11
11
|
def klasses
|
12
12
|
@klasses ||= []
|
13
13
|
end
|
@@ -17,9 +17,18 @@ class ImageOptim
|
|
17
17
|
klasses << base
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
21
|
-
def
|
22
|
-
@underscored_name ||= name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
20
|
+
# Underscored class name symbol
|
21
|
+
def bin_sym
|
22
|
+
@underscored_name ||= name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
def option_definitions
|
26
|
+
@option_definitions ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def option(name, default, type, description = nil, &proc)
|
30
|
+
attr_reader name
|
31
|
+
option_definitions << OptionDefinition.new(name, default, type, description, &proc)
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
@@ -28,7 +37,9 @@ class ImageOptim
|
|
28
37
|
# Configure (raises on extra options)
|
29
38
|
def initialize(image_optim, options = {})
|
30
39
|
@image_optim = image_optim
|
31
|
-
|
40
|
+
self.class.option_definitions.each do |option_definition|
|
41
|
+
get_option!(options, option_definition.name, option_definition.default, &option_definition.proc)
|
42
|
+
end
|
32
43
|
assert_options_empty!(options)
|
33
44
|
end
|
34
45
|
|
@@ -60,10 +71,9 @@ class ImageOptim
|
|
60
71
|
resolve_bin!(bin)
|
61
72
|
|
62
73
|
command = [bin, *arguments].map(&:to_s).shelljoin
|
63
|
-
env_path = "#{@image_optim.resolve_dir}:#{ENV['PATH']}"
|
64
74
|
start = Time.now
|
65
75
|
|
66
|
-
system "env PATH=#{env_path.shellescape} nice -n #{@image_optim.nice} #{command} > /dev/null 2>&1"
|
76
|
+
system "env PATH=#{@image_optim.env_path.shellescape} nice -n #{@image_optim.nice} #{command} > /dev/null 2>&1"
|
67
77
|
|
68
78
|
raise SignalException.new($?.termsig) if $?.signaled?
|
69
79
|
|
@@ -3,20 +3,15 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Advpng < Worker
|
6
|
-
|
7
|
-
|
6
|
+
option(:level, 4, 'Compression level: 0 - don\'t compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme') do |v|
|
7
|
+
OptionHelpers.limit_with_range(v.to_i, 0..4)
|
8
|
+
end
|
8
9
|
|
9
10
|
def optimize(src, dst)
|
10
11
|
src.copy(dst)
|
11
12
|
args = %W[-#{level} -z -q -- #{dst}]
|
12
13
|
execute(:advpng, *args) && optimized?(src, dst)
|
13
14
|
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def parse_options(options)
|
18
|
-
get_option!(options, :level, 4){ |v| limit_with_range(v.to_i, 0..4) }
|
19
|
-
end
|
20
15
|
end
|
21
16
|
end
|
22
17
|
end
|
@@ -3,20 +3,13 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Gifsicle < Worker
|
6
|
-
|
7
|
-
attr_reader :interlace
|
6
|
+
option(:interlace, false, 'Turn interlacing on'){ |v| !!v }
|
8
7
|
|
9
8
|
def optimize(src, dst)
|
10
9
|
args = %W[-o #{dst} -O3 --no-comments --no-names --same-delay --same-loopcount --no-warnings -- #{src}]
|
11
10
|
args.unshift('-i') if interlace
|
12
11
|
execute(:gifsicle, *args) && optimized?(src, dst)
|
13
12
|
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def parse_options(options)
|
18
|
-
get_option!(options, :interlace, false){ |v| !!v }
|
19
|
-
end
|
20
13
|
end
|
21
14
|
end
|
22
15
|
end
|
@@ -3,11 +3,15 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Jpegoptim < Worker
|
6
|
-
|
7
|
-
|
6
|
+
option(:strip, :all, Array, 'List of extra markers to strip: comments, exif, iptc, icc or all') do |v|
|
7
|
+
values = Array(v).map(&:to_s)
|
8
|
+
known_values = %w[all comments exif iptc icc]
|
9
|
+
unknown_values = values - known_values
|
10
|
+
warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}" unless unknown_values.empty?
|
11
|
+
values & unknown_values
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
attr_reader :max_quality
|
14
|
+
option(:max_quality, 100, 'Maximum image quality factor 0..100'){ |v| OptionHelpers.limit_with_range(v.to_i, 0..100) }
|
11
15
|
|
12
16
|
# Run first if max_quality < 100
|
13
17
|
def run_order
|
@@ -23,19 +27,6 @@ class ImageOptim
|
|
23
27
|
args.unshift "-m#{max_quality}" if max_quality < 100
|
24
28
|
execute(:jpegoptim, *args) && optimized?(src, dst)
|
25
29
|
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def parse_options(options)
|
30
|
-
get_option!(options, :strip, :all) do |v|
|
31
|
-
markers = Array(v).map(&:to_s)
|
32
|
-
possible_markers = %w[all comments exif iptc icc]
|
33
|
-
unknown_markers = markers - possible_markers
|
34
|
-
warn "Unknown markers for jpegoptim: #{unknown_markers.join(', ')}" unless unknown_markers.empty?
|
35
|
-
markers & possible_markers
|
36
|
-
end
|
37
|
-
get_option!(options, :max_quality, 100){ |v| v.to_i }
|
38
|
-
end
|
39
30
|
end
|
40
31
|
end
|
41
32
|
end
|
@@ -3,24 +3,24 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Jpegtran < Worker
|
6
|
-
|
7
|
-
attr_reader :copy_chunks
|
6
|
+
option(:copy_chunks, false, 'Copy all chunks'){ |v| !!v }
|
8
7
|
|
9
|
-
|
10
|
-
attr_reader :progressive
|
8
|
+
option(:progressive, true, 'Create progressive JPEG file'){ |v| !!v }
|
11
9
|
|
12
|
-
|
13
|
-
args = %W[-optimize -outfile #{dst} #{src}]
|
14
|
-
args.unshift '-copy', copy_chunks ? 'all' : 'none'
|
15
|
-
args.unshift '-progressive' if progressive
|
16
|
-
execute(:jpegtran, *args) && optimized?(src, dst)
|
17
|
-
end
|
10
|
+
option(:jpegrescan, false, 'Use jpegtran through jpegrescan, ignore progressive option'){ |v| !!v }
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
def optimize(src, dst)
|
13
|
+
if jpegrescan
|
14
|
+
args = %W[#{src} #{dst}]
|
15
|
+
args.unshift '-s' unless copy_chunks
|
16
|
+
resolve_bin!(:jpegtran)
|
17
|
+
execute(:jpegrescan, *args) && optimized?(src, dst)
|
18
|
+
else
|
19
|
+
args = %W[-optimize -outfile #{dst} #{src}]
|
20
|
+
args.unshift '-copy', copy_chunks ? 'all' : 'none'
|
21
|
+
args.unshift '-progressive' if progressive
|
22
|
+
execute(:jpegtran, *args) && optimized?(src, dst)
|
23
|
+
end
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -3,11 +3,11 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Optipng < Worker
|
6
|
-
|
7
|
-
attr_reader :level
|
6
|
+
option(:level, 6, 'Optimization level preset 0 is least, 7 is best'){ |v| OptionHelpers.limit_with_range(v.to_i, 0..7) }
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
option(:interlace, false, TrueFalseNil, 'Interlace, true - interlace on, false - interlace off, nil - as is in original image') do |v|
|
9
|
+
v && true
|
10
|
+
end
|
11
11
|
|
12
12
|
def optimize(src, dst)
|
13
13
|
src.copy(dst)
|
@@ -15,13 +15,6 @@ class ImageOptim
|
|
15
15
|
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
|
16
16
|
execute(:optipng, *args) && optimized?(src, dst)
|
17
17
|
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def parse_options(options)
|
22
|
-
get_option!(options, :level, 6){ |v| limit_with_range(v.to_i, 0..7) }
|
23
|
-
get_option!(options, :interlace, false){ |v| v && true }
|
24
|
-
end
|
25
18
|
end
|
26
19
|
end
|
27
20
|
end
|
@@ -3,14 +3,12 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Pngcrush < Worker
|
6
|
-
|
7
|
-
|
6
|
+
option(:chunks, :alla, Array, 'List of chunks to remove or \'alla\' - all except tRNS/transparency or '\
|
7
|
+
'\'allb\' - all except tRNS and gAMA/gamma'){ |v| Array(v).map(&:to_s) }
|
8
8
|
|
9
|
-
|
10
|
-
attr_reader :fix
|
9
|
+
option(:fix, false, 'Fix otherwise fatal conditions such as bad CRCs'){ |v| !!v }
|
11
10
|
|
12
|
-
|
13
|
-
attr_reader :brute
|
11
|
+
option(:brute, false, 'Brute force try all methods, very time-consuming and generally not worthwhile'){ |v| !!v }
|
14
12
|
|
15
13
|
# Always run first
|
16
14
|
def run_order
|
@@ -26,14 +24,6 @@ class ImageOptim
|
|
26
24
|
args.unshift '-brute' if brute
|
27
25
|
execute(:pngcrush, *args) && optimized?(src, dst)
|
28
26
|
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def parse_options(options)
|
33
|
-
get_option!(options, :chunks, :alla){ |v| Array(v).map(&:to_s) }
|
34
|
-
get_option!(options, :fix, false){ |v| !!v }
|
35
|
-
get_option!(options, :brute, false){ |v| !!v }
|
36
|
-
end
|
37
27
|
end
|
38
28
|
end
|
39
29
|
end
|
@@ -3,11 +3,11 @@ require 'image_optim/worker'
|
|
3
3
|
class ImageOptim
|
4
4
|
class Worker
|
5
5
|
class Pngout < Worker
|
6
|
-
|
7
|
-
attr_reader :copy_chunks
|
6
|
+
option(:copy_chunks, false, 'Copy optional chunks'){ |v| !!v }
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
option(:strategy, 0, 'Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed') do |v|
|
9
|
+
OptionHelpers.limit_with_range(v.to_i, 0..4)
|
10
|
+
end
|
11
11
|
|
12
12
|
# Always run first
|
13
13
|
def run_order
|
@@ -18,13 +18,6 @@ class ImageOptim
|
|
18
18
|
args = %W[-k#{copy_chunks ? 1 : 0} -s#{strategy} -q -y #{src} #{dst}]
|
19
19
|
execute(:pngout, *args) && optimized?(src, dst)
|
20
20
|
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def parse_options(options)
|
25
|
-
get_option!(options, :copy_chunks, false){ |v| !!v }
|
26
|
-
get_option!(options, :strategy, 0){ |v| limit_with_range(v.to_i, 0..4) }
|
27
|
-
end
|
28
21
|
end
|
29
22
|
end
|
30
23
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
$: << File.expand_path('../../lib', __FILE__)
|
5
|
+
|
6
|
+
require 'image_optim'
|
7
|
+
|
8
|
+
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
9
|
+
puts "### #{klass.bin_sym}"
|
10
|
+
klass.option_definitions.each do |option_definition|
|
11
|
+
puts "* `:#{option_definition.name}` — #{option_definition.description} *(defaults to #{option_definition.default})*"
|
12
|
+
end
|
13
|
+
puts
|
14
|
+
end
|
data/vendor/jpegrescan
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/perl -ws
|
2
|
+
# jpegrescan by Loren Merritt
|
3
|
+
# Last updated: 2008-11-29 / 2013-03-19
|
4
|
+
# This code is public domain.
|
5
|
+
|
6
|
+
use File::Slurp;
|
7
|
+
use File::Temp qw/ tempfile /;
|
8
|
+
|
9
|
+
@ARGV==2 or die "usage: jpegrescan in.jpg out.jpg
|
10
|
+
tries various progressive scan orders
|
11
|
+
switches:
|
12
|
+
-s strip from all extra markers (`jpegtran -copy none` otherwise `jpegtran -copy all`)
|
13
|
+
-v verbose output
|
14
|
+
-q supress all output
|
15
|
+
";
|
16
|
+
$fin = $ARGV[0];
|
17
|
+
$fout = $ARGV[1];
|
18
|
+
(undef, $ftmp) = tempfile(SUFFIX => ".scan");
|
19
|
+
$jtmp = $fout;
|
20
|
+
$verbose = $v;
|
21
|
+
$quiet = $q;
|
22
|
+
@strip = $s ? ("-copy","none") : ("-copy","all");
|
23
|
+
undef $_ for $v,$q,$s;
|
24
|
+
undef $/;
|
25
|
+
$|=1;
|
26
|
+
|
27
|
+
# convert the input to baseline, just to make all the other conversions faster
|
28
|
+
# FIXME there's still a bunch of redundant computation in separate calls to jpegtran
|
29
|
+
open $OLDERR, ">&", STDERR;
|
30
|
+
open STDERR, ">", $ftmp;
|
31
|
+
open TRAN, "-|", "jpegtran", "-v", @strip, "-optimize", $fin or die;
|
32
|
+
write_file($jtmp, <TRAN>);
|
33
|
+
close TRAN;
|
34
|
+
open STDERR, ">&", $OLDERR;
|
35
|
+
|
36
|
+
$type = read_file($ftmp);
|
37
|
+
$type =~ /components=(\d+)/ or die;
|
38
|
+
$rgb = $1==3 ? 1 : $1==1 ? 0 : die "not RGB nor gray\n";
|
39
|
+
|
40
|
+
# FIXME optimize order for either progressive transfer or decoding speed
|
41
|
+
sub canonize {
|
42
|
+
my $txt = $prefix.$suffix.shift;
|
43
|
+
$txt =~ s/\s*;\s*/;\n/g;
|
44
|
+
$txt =~ s/^\s*//;
|
45
|
+
$txt =~ s/ +/ /g;
|
46
|
+
$txt =~ s/: (\d+) (\d+)/sprintf ": %2d %2d", $1, $2/ge;
|
47
|
+
# treat u and v identically. I shouldn't need to do this, but with jpegtran overhead it saves 9% speed. cost: .008% bitrate.
|
48
|
+
$txt =~ s/^2:.*\n//gm;
|
49
|
+
$txt =~ s/^1:(.+)\n/1:$1\n2:$1\n/gm;
|
50
|
+
# dc before ac, coarse before fine
|
51
|
+
my @txt = sort {"$a\n$b" =~ /: *(\d+) .* (\d);\n.*: *(\d+) .* (\d);/ or die; !$3 <=> !$1 or $4 <=> $2 or $a cmp $b;} split /\n/, $txt;
|
52
|
+
return join "\n", @txt;
|
53
|
+
}
|
54
|
+
|
55
|
+
sub try {
|
56
|
+
my $txt = canonize(shift);
|
57
|
+
return $memo{$txt} if $memo{$txt};
|
58
|
+
write_file($ftmp, $txt);
|
59
|
+
open TRAN, "-|", "jpegtran", @strip, "-scans", $ftmp, $jtmp or die;
|
60
|
+
$data = <TRAN>;
|
61
|
+
close TRAN;
|
62
|
+
my $s = length $data;
|
63
|
+
$s or die;
|
64
|
+
$memo{$txt} = $s;
|
65
|
+
!$quiet && print $verbose ? "$txt\n$s\n\n" : ".";
|
66
|
+
return $s;
|
67
|
+
}
|
68
|
+
|
69
|
+
sub triesn {
|
70
|
+
my($bmode, $bsize);
|
71
|
+
my ($limit, @modes) = @_;
|
72
|
+
my $overshoot = 0;
|
73
|
+
for(@modes) {
|
74
|
+
my $s = try($_);
|
75
|
+
if(!$bsize || $s < $bsize) {
|
76
|
+
$bsize = $s;
|
77
|
+
$bmode = $_;
|
78
|
+
$overshoot = 0;
|
79
|
+
} elsif(++$overshoot >= $limit) {
|
80
|
+
last;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
return $bmode;
|
84
|
+
}
|
85
|
+
|
86
|
+
sub tries { triesn(99, @_); }
|
87
|
+
|
88
|
+
$prefix = "";
|
89
|
+
$suffix = "";
|
90
|
+
|
91
|
+
if($rgb) {
|
92
|
+
# 012 helps very little
|
93
|
+
# 0/12 and 0/1/2 are pretty evenly matched in frequency, but 0/12 wins in total size if every image had to use the same mode
|
94
|
+
# dc refinement passes never help
|
95
|
+
$dc = tries(
|
96
|
+
# "0: 0 0 0 0; 1 2: 0 0 0 0;", # two scans expose a bug in Opera <= 11.61
|
97
|
+
"0: 0 0 0 0; 1: 0 0 0 0; 2: 0 0 0 0;");
|
98
|
+
# jpegtran won't let me omit dc entirely, but I can at least quantize it away to make the rest of the tests faster.
|
99
|
+
$prefix = "0 1 2: 0 0 0 9;";
|
100
|
+
} else {
|
101
|
+
$dc = "0: 0 0 0 0;";
|
102
|
+
$prefix = "0: 0 0 0 9;";
|
103
|
+
}
|
104
|
+
|
105
|
+
# luma can make use of up to 3 refinement passes.
|
106
|
+
# chroma can make use of up to 2 refinement passes.
|
107
|
+
# refinement passes have some chance of being split (luma: 4%,4%,4%. chroma: 20%,8%) but the total bit gain is negligible.
|
108
|
+
# msb pass should almost always be split (luma: 87%, chroma: 81%).
|
109
|
+
# I have no theoretical reason for this list of split positions, they're just the most common in practice.
|
110
|
+
# splitting into 3 ections is often slightly better, but the total number of bits saved is negligible.
|
111
|
+
# FIXME: penalize lots of refinement passes because it's slower to decode. if so, then also force overwrite if bigger than the input.
|
112
|
+
sub try_splits {
|
113
|
+
my $str = shift;
|
114
|
+
my %n = map {$_ => sprintf "$c: 1 %d $str; $c: %d 63 $str;", $_, $_+1} 2,5,8,12,18;
|
115
|
+
my $mode = triesn(2, "$c: 1 63 $str;", @n{2,8,5});
|
116
|
+
return $mode if $mode ne $n{8};
|
117
|
+
return triesn(1, $mode, @n{12,18});
|
118
|
+
}
|
119
|
+
|
120
|
+
foreach $c (0..$rgb) {
|
121
|
+
my @modes;
|
122
|
+
my $ml = "";
|
123
|
+
for(0..($c?2:3)) {
|
124
|
+
push @modes, "$c: 1 8 0 $_; $c: 9 63 0 $_;".$ml;
|
125
|
+
$ml .= sprintf("$c: 1 63 %d %d;", $_+1, $_);
|
126
|
+
}
|
127
|
+
my $refine = triesn(1, @modes);
|
128
|
+
$refine =~ s/.* (0 \d);//;
|
129
|
+
$ac .= $refine . try_splits($1);
|
130
|
+
}
|
131
|
+
|
132
|
+
$prefix = "";
|
133
|
+
undef %memo;
|
134
|
+
$mode = canonize($dc.$ac);
|
135
|
+
try($mode);
|
136
|
+
$size = $memo{$mode};
|
137
|
+
!$quiet && print "\n$mode\n$size\n";
|
138
|
+
$old_size = -s $fin;
|
139
|
+
!$quiet && printf "%+.2f%%\n", ($size/$old_size-1)*100;
|
140
|
+
if($size < $old_size) {
|
141
|
+
write_file($fout, $data);
|
142
|
+
}
|
143
|
+
unlink $ftmp;
|
metadata
CHANGED
@@ -1,109 +1,92 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_optim
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 7
|
9
|
-
- 3
|
10
|
-
version: 0.7.3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Ivan Kuchin
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2013-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
21
14
|
name: fspath
|
22
|
-
|
23
|
-
|
24
|
-
none: false
|
25
|
-
requirements:
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
26
17
|
- - ~>
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
hash: 9
|
29
|
-
segments:
|
30
|
-
- 2
|
31
|
-
- 0
|
32
|
-
- 3
|
18
|
+
- !ruby/object:Gem::Version
|
33
19
|
version: 2.0.3
|
34
20
|
type: :runtime
|
35
|
-
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: image_size
|
38
21
|
prerelease: false
|
39
|
-
|
40
|
-
|
41
|
-
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: image_size
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
42
31
|
- - ~>
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
hash: 23
|
45
|
-
segments:
|
46
|
-
- 1
|
47
|
-
- 1
|
48
|
-
- 2
|
32
|
+
- !ruby/object:Gem::Version
|
49
33
|
version: 1.1.2
|
50
34
|
type: :runtime
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: progress
|
54
35
|
prerelease: false
|
55
|
-
|
56
|
-
|
57
|
-
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: progress
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
58
45
|
- - ~>
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
hash: 31
|
61
|
-
segments:
|
62
|
-
- 2
|
63
|
-
- 4
|
64
|
-
- 0
|
46
|
+
- !ruby/object:Gem::Version
|
65
47
|
version: 2.4.0
|
66
48
|
type: :runtime
|
67
|
-
version_requirements: *id003
|
68
|
-
- !ruby/object:Gem::Dependency
|
69
|
-
name: in_threads
|
70
49
|
prerelease: false
|
71
|
-
|
72
|
-
|
73
|
-
requirements:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
74
52
|
- - ~>
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.4.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: in_threads
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
81
61
|
version: 1.1.1
|
82
62
|
type: :runtime
|
83
|
-
version_requirements: *id004
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: rspec
|
86
63
|
prerelease: false
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
96
76
|
type: :development
|
97
|
-
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
98
83
|
description:
|
99
84
|
email:
|
100
|
-
executables:
|
85
|
+
executables:
|
101
86
|
- image_optim
|
102
87
|
extensions: []
|
103
|
-
|
104
88
|
extra_rdoc_files: []
|
105
|
-
|
106
|
-
files:
|
89
|
+
files:
|
107
90
|
- .gitignore
|
108
91
|
- LICENSE.txt
|
109
92
|
- README.markdown
|
@@ -112,6 +95,7 @@ files:
|
|
112
95
|
- image_optim.gemspec
|
113
96
|
- lib/image_optim.rb
|
114
97
|
- lib/image_optim/image_path.rb
|
98
|
+
- lib/image_optim/option_definition.rb
|
115
99
|
- lib/image_optim/option_helpers.rb
|
116
100
|
- lib/image_optim/worker.rb
|
117
101
|
- lib/image_optim/worker/advpng.rb
|
@@ -121,6 +105,7 @@ files:
|
|
121
105
|
- lib/image_optim/worker/optipng.rb
|
122
106
|
- lib/image_optim/worker/pngcrush.rb
|
123
107
|
- lib/image_optim/worker/pngout.rb
|
108
|
+
- script/options_for_readme
|
124
109
|
- spec/image_optim_spec.rb
|
125
110
|
- spec/images/comparison.png
|
126
111
|
- spec/images/decompressed.jpeg
|
@@ -130,40 +115,33 @@ files:
|
|
130
115
|
- spec/images/transparency1.png
|
131
116
|
- spec/images/transparency2.png
|
132
117
|
- spec/images/vergroessert.jpg
|
118
|
+
- vendor/jpegrescan
|
133
119
|
homepage: http://github.com/toy/image_optim
|
134
|
-
licenses:
|
120
|
+
licenses:
|
135
121
|
- MIT
|
122
|
+
metadata: {}
|
136
123
|
post_install_message:
|
137
124
|
rdoc_options: []
|
138
|
-
|
139
|
-
require_paths:
|
125
|
+
require_paths:
|
140
126
|
- lib
|
141
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
none: false
|
152
|
-
requirements:
|
153
|
-
- - ">="
|
154
|
-
- !ruby/object:Gem::Version
|
155
|
-
hash: 3
|
156
|
-
segments:
|
157
|
-
- 0
|
158
|
-
version: "0"
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
159
137
|
requirements: []
|
160
|
-
|
161
138
|
rubyforge_project: image_optim
|
162
|
-
rubygems_version:
|
139
|
+
rubygems_version: 2.0.3
|
163
140
|
signing_key:
|
164
|
-
specification_version:
|
165
|
-
summary: Optimize (lossless compress) images (jpeg, png, gif) using external utilities
|
166
|
-
|
141
|
+
specification_version: 4
|
142
|
+
summary: Optimize (lossless compress) images (jpeg, png, gif) using external utilities
|
143
|
+
(advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)
|
144
|
+
test_files:
|
167
145
|
- spec/image_optim_spec.rb
|
168
146
|
- spec/images/comparison.png
|
169
147
|
- spec/images/decompressed.jpeg
|
@@ -173,4 +151,3 @@ test_files:
|
|
173
151
|
- spec/images/transparency1.png
|
174
152
|
- spec/images/transparency2.png
|
175
153
|
- spec/images/vergroessert.jpg
|
176
|
-
has_rdoc:
|