image_optim 0.7.3 → 0.8.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.
- 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:
|