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.
@@ -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=
@@ -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 (options are described in comments for ImageOptim, Worker and all workers):
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.
@@ -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 NUMBER', Integer, 'Number of threads or disable (defaults to number of processors)') do |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 NUMBER', Integer, 'Nice level (defaults to 10)') do |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.underscored_name.to_sym
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
- op.on('-v', '--verbose', 'Verbose info') do |verbose|
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 = []
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_optim'
5
- s.version = '0.7.3'
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']
@@ -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.underscored_name.to_sym)
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 nil
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
@@ -15,7 +15,7 @@ class ImageOptim
15
15
  end
16
16
 
17
17
  # Ensure number is in range
18
- def limit_with_range(number, range)
18
+ def self.limit_with_range(number, range)
19
19
  if range.include?(number)
20
20
  number
21
21
  elsif number < range.first
@@ -7,7 +7,7 @@ require 'image_optim'
7
7
  class ImageOptim
8
8
  class Worker
9
9
  class << self
10
- # List of avaliable workers
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
- # Undercored class name
21
- def underscored_name
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
- parse_options(options)
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
- # Compression level: 0 - don't compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme (defaults to 4)
7
- attr_reader :level
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
- # Turn on interlacing (defaults to false)
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
- # List of extra markers to strip: comments, exif, iptc, icc (defaults to 'all')
7
- attr_reader :strip
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
- # Maximum image quality factor (defaults to 100)
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
- # Copy all chunks or none (defaults to false)
7
- attr_reader :copy_chunks
6
+ option(:copy_chunks, false, 'Copy all chunks'){ |v| !!v }
8
7
 
9
- # Create progressive JPEG file (defaults to true)
10
- attr_reader :progressive
8
+ option(:progressive, true, 'Create progressive JPEG file'){ |v| !!v }
11
9
 
12
- def optimize(src, dst)
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
- private
20
-
21
- def parse_options(options)
22
- get_option!(options, :copy_chunks, false){ |v| !!v }
23
- get_option!(options, :progressive, true){ |v| !!v }
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
- # Optimization level preset 0..7 (0 is least, 7 is best, defaults to 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
- # Interlace, true - interlace on, false - interlace off, nil - as is in original image (defaults to false)
10
- attr_reader :interlace
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
- # List of chunks to remove or 'alla' - all except tRNS/transparency or 'allb' - all except tRNS and gAMA/gamma (defaults to 'alla')
7
- attr_reader :chunks
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
- # Fix otherwise fatal conditions such as bad CRCs (defaults to false)
10
- attr_reader :fix
9
+ option(:fix, false, 'Fix otherwise fatal conditions such as bad CRCs'){ |v| !!v }
11
10
 
12
- # Brute force try all methods, very time-consuming and generally not worthwhile (defaults to false)
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
- # Copy optional chunks (defaults to false)
7
- attr_reader :copy_chunks
6
+ option(:copy_chunks, false, 'Copy optional chunks'){ |v| !!v }
8
7
 
9
- # Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed (defaults to 0)
10
- attr_reader :strategy
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
@@ -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
- hash: 5
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
- date: 2013-02-24 00:00:00 Z
19
- dependencies:
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
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
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
- requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
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
- requirement: &id003 !ruby/object:Gem::Requirement
56
- none: false
57
- requirements:
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
- requirement: &id004 !ruby/object:Gem::Requirement
72
- none: false
73
- requirements:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
74
52
  - - ~>
75
- - !ruby/object:Gem::Version
76
- hash: 17
77
- segments:
78
- - 1
79
- - 1
80
- - 1
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
- requirement: &id005 !ruby/object:Gem::Requirement
88
- none: false
89
- requirements:
90
- - - ">="
91
- - !ruby/object:Gem::Version
92
- hash: 3
93
- segments:
94
- - 0
95
- version: "0"
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
- version_requirements: *id005
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
- none: false
143
- requirements:
144
- - - ">="
145
- - !ruby/object:Gem::Version
146
- hash: 3
147
- segments:
148
- - 0
149
- version: "0"
150
- required_rubygems_version: !ruby/object:Gem::Requirement
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: 1.8.24
139
+ rubygems_version: 2.0.3
163
140
  signing_key:
164
- specification_version: 3
165
- summary: Optimize (lossless compress) images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)
166
- test_files:
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: