image_optim 0.17.1 → 0.18.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.
Files changed (43) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -18
  4. data/CHANGELOG.markdown +10 -0
  5. data/README.markdown +31 -1
  6. data/bin/image_optim +3 -137
  7. data/image_optim.gemspec +6 -3
  8. data/lib/image_optim.rb +20 -3
  9. data/lib/image_optim/bin_resolver.rb +28 -1
  10. data/lib/image_optim/bin_resolver/bin.rb +17 -7
  11. data/lib/image_optim/cmd.rb +49 -0
  12. data/lib/image_optim/config.rb +64 -4
  13. data/lib/image_optim/image_path.rb +5 -0
  14. data/lib/image_optim/option_definition.rb +5 -3
  15. data/lib/image_optim/runner.rb +1 -2
  16. data/lib/image_optim/runner/option_parser.rb +216 -0
  17. data/lib/image_optim/worker.rb +32 -17
  18. data/lib/image_optim/worker/advpng.rb +7 -1
  19. data/lib/image_optim/worker/gifsicle.rb +16 -3
  20. data/lib/image_optim/worker/jhead.rb +15 -8
  21. data/lib/image_optim/worker/jpegoptim.rb +6 -2
  22. data/lib/image_optim/worker/jpegtran.rb +10 -3
  23. data/lib/image_optim/worker/optipng.rb +6 -1
  24. data/lib/image_optim/worker/pngcrush.rb +8 -1
  25. data/lib/image_optim/worker/pngout.rb +8 -1
  26. data/lib/image_optim/worker/svgo.rb +4 -1
  27. data/script/worker_analysis +523 -0
  28. data/script/worker_analysis.haml +153 -0
  29. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +4 -5
  30. data/spec/image_optim/bin_resolver/simple_version_spec.rb +44 -21
  31. data/spec/image_optim/bin_resolver_spec.rb +63 -29
  32. data/spec/image_optim/cmd_spec.rb +66 -0
  33. data/spec/image_optim/config_spec.rb +38 -38
  34. data/spec/image_optim/handler_spec.rb +15 -12
  35. data/spec/image_optim/hash_helpers_spec.rb +14 -13
  36. data/spec/image_optim/image_path_spec.rb +22 -7
  37. data/spec/image_optim/runner/glob_helpers_spec.rb +6 -5
  38. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  39. data/spec/image_optim/space_spec.rb +5 -4
  40. data/spec/image_optim/worker_spec.rb +6 -5
  41. data/spec/image_optim_spec.rb +209 -237
  42. data/spec/spec_helper.rb +3 -0
  43. metadata +43 -11
@@ -1,9 +1,10 @@
1
- $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim/space'
4
3
 
5
4
  describe ImageOptim::Space do
6
- Space = ImageOptim::Space
5
+ before do
6
+ stub_const('Space', ImageOptim::Space)
7
+ end
7
8
 
8
9
  {
9
10
  0 => ' ',
@@ -17,7 +18,7 @@ describe ImageOptim::Space do
17
18
  10_000_000 => ' 9.5M',
18
19
  100_000_000 => ' 95.4M',
19
20
  }.each do |size, space|
20
- it "should convert #{size} to #{space}" do
21
+ it "converts #{size} to #{space}" do
21
22
  expect(Space.space(size)).to eq(space)
22
23
  end
23
24
  end
@@ -1,12 +1,13 @@
1
- $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim/worker'
4
3
 
5
4
  describe ImageOptim::Worker do
6
- Worker = ImageOptim::Worker
5
+ before do
6
+ stub_const('Worker', ImageOptim::Worker)
7
+ end
7
8
 
8
- describe 'optimize' do
9
- it 'should raise NotImplementedError' do
9
+ describe :optimize do
10
+ it 'raises NotImplementedError' do
10
11
  image_optim = ImageOptim.new
11
12
  worker = Worker.new(image_optim, {})
12
13
 
@@ -1,52 +1,35 @@
1
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
- require 'rspec'
1
+ require 'spec_helper'
3
2
  require 'image_optim'
3
+ require 'image_optim/cmd'
4
4
  require 'tempfile'
5
+ require 'English'
5
6
 
6
- TEST_IMAGES = ImageOptim::ImagePath.new(__FILE__).dirname.glob('images/**/*.*')
7
-
8
- Fixnum.class_eval do
9
- def in_range?(range)
10
- range.include?(self)
11
- end
12
- end
13
-
14
- Tempfile.class_eval do
15
- def self.init_count
16
- class_variable_get(:@@init_count)
17
- end
18
-
19
- def self.init_count=(value)
20
- class_variable_set(:@@init_count, value)
21
- end
22
-
23
- def self.reset_init_count
24
- self.init_count = 0
25
- end
26
-
27
- reset_init_count
7
+ describe ImageOptim do
8
+ root_dir = ImageOptim::ImagePath.new(__FILE__).dirname.dirname
9
+ images_dir = root_dir / 'spec/images'
10
+ test_images = images_dir.glob('**/*.*').freeze
28
11
 
29
- alias_method :initialize_orig, :initialize
30
- def initialize(*args, &block)
31
- self.class.init_count += 1
32
- initialize_orig(*args, &block)
12
+ helpers = Module.new do
13
+ def temp_copy(image)
14
+ image.temp_path.tap{ |path| image.copy(path) }
15
+ end
33
16
  end
34
- end
17
+ include helpers
18
+ extend helpers
35
19
 
36
- ImageOptim::ImagePath.class_eval do
37
- def temp_copy
38
- temp_path.tap{ |path| copy(path) }
20
+ matcher :be_in_range do |expected|
21
+ match{ |actual| expected.include?(actual) }
39
22
  end
40
- end
41
23
 
42
- describe ImageOptim do
43
24
  before do
25
+ stub_const('Cmd', ImageOptim::Cmd)
26
+
44
27
  allow(ImageOptim::Config).to receive(:global).and_return({})
45
28
  allow(ImageOptim::Config).to receive(:local).and_return({})
46
29
  end
47
30
 
48
31
  describe 'workers' do
49
- it 'should be ordered by run_order' do
32
+ they 'are ordered by run_order' do
50
33
  image_optim = ImageOptim.new
51
34
  original_klasses = ImageOptim::Worker.klasses
52
35
  formats = original_klasses.map do |klass|
@@ -55,8 +38,8 @@ describe ImageOptim do
55
38
 
56
39
  [
57
40
  original_klasses,
58
- original_klasses.reverse,
59
- original_klasses.shuffle,
41
+ original_klasses.to_a.reverse,
42
+ original_klasses.to_a.shuffle,
60
43
  ].each do |klasses|
61
44
  expect(ImageOptim::Worker).to receive(:klasses).and_return(klasses)
62
45
 
@@ -75,265 +58,254 @@ describe ImageOptim do
75
58
  end
76
59
  end
77
60
 
78
- describe 'worker' do
79
- base_options = Hash[ImageOptim::Worker.klasses.map do |klass|
80
- [klass.bin_sym, false]
81
- end]
82
-
83
- ImageOptim::Worker.klasses.each do |worker_klass|
84
- describe worker_klass.bin_sym do
85
- it 'should optimize at least one test image' do
86
- options = base_options.merge(worker_klass.bin_sym => true)
87
- image_optim = ImageOptim.new(options)
88
- expect(TEST_IMAGES.any? do |original|
89
- image_optim.optimize_image(original.temp_copy)
90
- end).to be true
91
- end
61
+ disable_all_workers = Hash[ImageOptim::Worker.klasses.map do |klass|
62
+ [klass.bin_sym, false]
63
+ end]
64
+
65
+ ImageOptim::Worker.klasses.each do |worker_klass|
66
+ describe "#{worker_klass.bin_sym} worker" do
67
+ it 'optimizes at least one test image' do
68
+ options = disable_all_workers.merge(worker_klass.bin_sym => true)
69
+ image_optim = ImageOptim.new(options)
70
+ expect(test_images.any? do |original|
71
+ image_optim.optimize_image(temp_copy(original))
72
+ end).to be true
92
73
  end
93
74
  end
94
75
  end
95
76
 
96
- describe 'isolated' do
97
- describe 'optimize' do
98
- TEST_IMAGES.each do |original|
99
- it "should optimize #{original}" do
100
- copy = original.temp_copy
101
-
102
- Tempfile.reset_init_count
103
- image_optim = ImageOptim.new
104
- optimized_image = image_optim.optimize_image(copy)
105
- expect(optimized_image).to be_a(ImageOptim::ImagePath::Optimized)
106
- expect(optimized_image.size).to be_in_range(1...original.size)
107
- expect(optimized_image.read).not_to eq(original.read)
108
- expect(copy.read).to eq(original.read)
109
-
110
- if image_optim.workers_for_image(original).length > 1
111
- expect(Tempfile.init_count).to be_in_range(1..2)
112
- else
113
- expect(Tempfile.init_count).to eq(1)
114
- end
115
- end
77
+ describe :optimize_image do
78
+ def flatten_animation(image)
79
+ if image.format == :gif
80
+ flattened = image.temp_path
81
+ flatten_command = %W[
82
+ convert
83
+ #{image.to_s.shellescape}
84
+ -coalesce
85
+ -append
86
+ #{flattened.to_s.shellescape}
87
+ ].join(' ')
88
+ expect(Cmd.run(flatten_command)).to be_truthy
89
+ flattened
90
+ else
91
+ image
116
92
  end
117
93
  end
118
94
 
119
- describe 'optimize in place' do
120
- TEST_IMAGES.each do |original|
121
- it "should optimize #{original}" do
122
- copy = original.temp_copy
123
-
124
- Tempfile.reset_init_count
125
- image_optim = ImageOptim.new
126
- expect(image_optim.optimize_image!(copy)).to be_truthy
127
- expect(copy.size).to be_in_range(1...original.size)
128
- expect(copy.read).not_to eq(original.read)
129
-
130
- if image_optim.workers_for_image(original).length > 1
131
- expect(Tempfile.init_count).to be_in_range(2..3)
132
- else
133
- expect(Tempfile.init_count).to eq(2)
134
- end
135
- end
95
+ def nrmse(image_a, image_b)
96
+ coalesce_a = flatten_animation(image_a)
97
+ coalesce_b = flatten_animation(image_b)
98
+ nrmse_command = %W[
99
+ compare
100
+ -metric RMSE
101
+ #{coalesce_a.to_s.shellescape}
102
+ #{coalesce_b.to_s.shellescape}
103
+ /dev/null
104
+ 2>&1
105
+ ].join(' ')
106
+ output = Cmd.capture(nrmse_command)
107
+ if [0, 1].include?($CHILD_STATUS.exitstatus)
108
+ output[/\((\d+(\.\d+)?)\)/, 1].to_f
109
+ else
110
+ fail "compare #{image_a} with #{image_b} failed with `#{output}`"
136
111
  end
137
112
  end
138
113
 
139
- describe 'optimize image data' do
140
- TEST_IMAGES.each do |original|
141
- it "should optimize #{original}" do
142
- image_optim = ImageOptim.new
143
- optimized_data = image_optim.optimize_image_data(original.read)
144
- expect(optimized_data).not_to be_nil
114
+ define :have_same_data_as do |expected|
115
+ match{ |actual| actual.binread == expected.binread }
116
+ end
145
117
 
146
- expected_path = image_optim.optimize_image(original.temp_copy)
147
- expect(optimized_data).to eq(expected_path.open('rb', &:read))
118
+ define :have_size do
119
+ match(&:size?)
120
+ end
148
121
 
149
- expect(image_optim.optimize_image_data(optimized_data)).to be_nil
150
- end
122
+ define :be_smaller_than do |expected|
123
+ match{ |actual| actual.size < expected.size }
124
+ end
125
+
126
+ define :be_pixel_identical_to do |expected|
127
+ match do |actual|
128
+ @diff = nrmse(actual, expected)
129
+ @diff == 0
130
+ end
131
+ failure_message do |actual|
132
+ "expected #{actual} to be pixel identical to #{expected}, got "\
133
+ "normalized root-mean-square error of #{@diff}"
151
134
  end
152
135
  end
153
136
 
154
- describe 'stop optimizing' do
155
- TEST_IMAGES.each do |original|
156
- it "should stop optimizing #{original}" do
157
- copy = original.temp_copy
137
+ describe 'optimizing images' do
138
+ rotated = images_dir / 'orient/original.jpg'
139
+ rotate_images = images_dir.glob('orient/?.jpg')
158
140
 
159
- tries = 0
160
- 10.times do
161
- tries += 1
162
- break unless ImageOptim.optimize_image!(copy)
163
- end
164
- expect(tries).to be_in_range(2...3)
141
+ copies = test_images.map{ |image| temp_copy(image) }
142
+ original_by_copy = Hash[copies.zip(test_images)]
143
+
144
+ ImageOptim.optimize_images(copies) do |copy, optimized|
145
+ fail 'expected copy to not be nil' if copy.nil?
146
+ original = original_by_copy[copy]
147
+
148
+ it "optimizes #{original.relative_path_from(root_dir)}" do
149
+ expect(copy).to have_same_data_as(original)
150
+
151
+ expect(optimized).not_to be_nil
152
+ expect(optimized).to be_a(ImageOptim::ImagePath::Optimized)
153
+ expect(optimized).to have_size
154
+ expect(optimized).to be_smaller_than(original)
155
+ expect(optimized).not_to have_same_data_as(original)
156
+
157
+ compare_to = rotate_images.include?(original) ? rotated : original
158
+ expect(optimized).to be_pixel_identical_to(compare_to)
165
159
  end
166
160
  end
167
161
  end
168
- end
169
162
 
170
- describe 'bunch' do
171
- it 'should optimize' do
172
- copies = TEST_IMAGES.map(&:temp_copy)
173
- results = ImageOptim.optimize_images(copies)
174
- zipped = TEST_IMAGES.zip(copies, results)
175
- zipped.each do |original, copy, result|
176
- expect(result[0]).to eq(copy)
177
- expect(result[1]).to be_a(ImageOptim::ImagePath::Optimized)
178
- expect(result[1].size).to be_in_range(1...original.size)
179
- expect(copy.read).to eq(original.read)
180
- end
163
+ it 'ignores text file' do
164
+ original = ImageOptim::ImagePath.new(__FILE__)
165
+ copy = temp_copy(original)
166
+
167
+ expect(Tempfile).not_to receive(:new)
168
+ optimized_image = ImageOptim.optimize_image(copy)
169
+ expect(optimized_image).to be_nil
170
+ expect(copy.read).to eq(original.read)
181
171
  end
182
172
 
183
- it 'should optimize in place' do
184
- copies = TEST_IMAGES.map(&:temp_copy)
185
- results = ImageOptim.optimize_images!(copies)
186
- zipped = TEST_IMAGES.zip(copies, results)
187
- zipped.each do |original, copy, result|
188
- expect(result[0]).to eq(copy)
189
- expect(result[1]).to be_a(ImageOptim::ImagePath::Optimized)
190
- expect(copy.size).to be_in_range(1...original.size)
173
+ {
174
+ :png => "\211PNG\r\n\032\n",
175
+ :jpeg => "\377\330",
176
+ }.each do |type, data|
177
+ it "ingores broken #{type}" do
178
+ path = FSPath.temp_file_path
179
+ path.write(data)
180
+ expect(ImageOptim::ImageMeta).to receive(:warn)
181
+ expect(ImageOptim.optimize_image(path)).to be_nil
191
182
  end
192
183
  end
184
+ end
193
185
 
194
- it 'should optimize datas' do
195
- results = ImageOptim.optimize_images_data(TEST_IMAGES.map(&:read))
196
- zipped = TEST_IMAGES.zip(results)
197
- zipped.each do |original, result|
198
- expect(result[0]).to eq(original.read)
199
- expect(result[1]).to be_a(String)
200
- expect(result[1].size).to be_in_range(1...original.size)
186
+ describe :optimize_image! do
187
+ it 'optimizes image and replaces original' do
188
+ original = double
189
+ optimized = double(:original_size => 12_345)
190
+ optimized_wrap = double
191
+ image_optim = ImageOptim.new
201
192
 
202
- expected_path = ImageOptim.optimize_image(original.temp_copy)
203
- expect(result[1]).to eq(expected_path.open('rb', &:read))
204
- end
193
+ allow(ImageOptim::ImagePath).to receive(:convert).
194
+ with(original).and_return(original)
195
+
196
+ expect(image_optim).to receive(:optimize_image).
197
+ with(original).and_return(optimized)
198
+ expect(optimized).to receive(:replace).with(original)
199
+ expect(ImageOptim::ImagePath::Optimized).to receive(:new).
200
+ with(original, 12_345).and_return(optimized_wrap)
201
+
202
+ expect(image_optim.optimize_image!(original)).to eq(optimized_wrap)
203
+ end
204
+
205
+ it 'returns nil if optimization fails' do
206
+ original = double
207
+ image_optim = ImageOptim.new
208
+
209
+ allow(ImageOptim::ImagePath).to receive(:convert).
210
+ with(original).and_return(original)
211
+
212
+ expect(image_optim).to receive(:optimize_image).
213
+ with(original).and_return(nil)
214
+ expect(ImageOptim::ImagePath::Optimized).not_to receive(:new)
215
+
216
+ expect(image_optim.optimize_image!(original)).to eq(nil)
205
217
  end
206
218
  end
207
219
 
208
- describe 'unsupported' do
209
- let(:original){ ImageOptim::ImagePath.new(__FILE__) }
220
+ describe :optimize_image_data do
221
+ it 'create temp file, optimizes image and returns data' do
222
+ data = double
223
+ temp = double(:path => double)
224
+ optimized = double
225
+ optimized_data = double
226
+ image_optim = ImageOptim.new
210
227
 
211
- it 'should ignore' do
212
- copy = original.temp_copy
228
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
229
+ with(data).and_return(double(:format => 'xxx'))
213
230
 
214
- Tempfile.reset_init_count
215
- optimized_image = ImageOptim.optimize_image(copy)
216
- expect(Tempfile.init_count).to eq(0)
217
- expect(optimized_image).to be_nil
218
- expect(copy.read).to eq(original.read)
231
+ expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
232
+ expect(temp).to receive(:binmode)
233
+ expect(temp).to receive(:write).with(data)
234
+ expect(temp).to receive(:close)
235
+ expect(image_optim).to receive(:optimize_image).
236
+ with(temp.path).and_return(optimized)
237
+ expect(optimized).to receive(:binread).and_return(optimized_data)
238
+
239
+ expect(image_optim.optimize_image_data(data)).to eq(optimized_data)
219
240
  end
220
241
 
221
- it 'should ignore in place' do
222
- copy = original.temp_copy
242
+ it 'returns nil if optimization fails' do
243
+ data = double
244
+ temp = double(:path => double)
245
+ image_optim = ImageOptim.new
223
246
 
224
- Tempfile.reset_init_count
225
- expect(ImageOptim.optimize_image!(copy)).not_to be_truthy
226
- expect(Tempfile.init_count).to eq(0)
227
- expect(copy.read).to eq(original.read)
247
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
248
+ with(data).and_return(double(:format => 'xxx'))
249
+
250
+ expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
251
+ expect(temp).to receive(:binmode)
252
+ expect(temp).to receive(:write).with(data)
253
+ expect(temp).to receive(:close)
254
+ expect(image_optim).to receive(:optimize_image).
255
+ with(temp.path).and_return(nil)
256
+
257
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
228
258
  end
229
259
 
230
- {
231
- :png => "\211PNG\r\n\032\n",
232
- :jpeg => "\377\330",
233
- }.each do |type, data|
234
- describe "broken #{type}" do
235
- before do
236
- expect(ImageOptim::ImageMeta).to receive(:warn)
237
- end
260
+ it 'returns nil if format can\'t be detected' do
261
+ data = double
262
+ image_optim = ImageOptim.new
238
263
 
239
- it 'should ignore path' do
240
- path = FSPath.temp_file_path
241
- path.write(data)
242
- expect(ImageOptim.optimize_image(path)).to be_nil
243
- end
264
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
265
+ with(data).and_return(double(:format => nil))
244
266
 
245
- it 'should ignore data' do
246
- expect(ImageOptim.optimize_image_data(data)).to be_nil
247
- end
248
- end
267
+ expect(ImageOptim::ImagePath).not_to receive(:temp_file)
268
+ expect(image_optim).not_to receive(:optimize_image)
269
+
270
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
249
271
  end
250
272
  end
251
273
 
252
274
  describe 'optimize multiple' do
253
- let(:srcs){ ('a'..'z').to_a }
254
-
255
- %w[optimize_images optimize_images!].each do |list_method|
275
+ %w[
276
+ optimize_images
277
+ optimize_images!
278
+ optimize_images_data
279
+ ].each do |list_method|
256
280
  describe list_method do
257
281
  method = list_method.sub('images', 'image')
258
282
  describe 'without block' do
259
- it 'should optimize images and return array of results' do
283
+ it 'optimizes images and returns array of results' do
260
284
  image_optim = ImageOptim.new
261
- dsts = srcs.map do |src|
262
- dst = "#{src}_"
285
+ results = test_images.map do |src|
286
+ dst = double
263
287
  expect(image_optim).to receive(method).with(src).and_return(dst)
264
- dst
288
+ [src, dst]
265
289
  end
266
- expect(image_optim.send(list_method, srcs)).to eq(srcs.zip(dsts))
290
+ expect(image_optim.send(list_method, test_images)).to eq(results)
267
291
  end
268
292
  end
269
293
 
270
294
  describe 'given block' do
271
- it 'should optimize images, yield path and result for each and '\
272
- 'return array of yield results' do
295
+ it 'optimizes images, yields path and result for each and '\
296
+ 'returns array of yield results' do
273
297
  image_optim = ImageOptim.new
274
- results = srcs.map do |src|
275
- dst = "#{src}_"
298
+ results = test_images.map do |src|
299
+ dst = double
276
300
  expect(image_optim).to receive(method).with(src).and_return(dst)
277
- "#{src} #{dst}"
301
+ [src, dst, :test]
278
302
  end
279
- expect(image_optim.send(list_method, srcs) do |src, dst|
280
- "#{src} #{dst}"
303
+ expect(image_optim.send(list_method, test_images) do |src, dst|
304
+ [src, dst, :test]
281
305
  end).to eq(results)
282
306
  end
283
307
  end
284
308
  end
285
309
  end
286
310
  end
287
-
288
- describe 'losslessness' do
289
- images_dir = ImageOptim::ImagePath.new(__FILE__).dirname / 'images'
290
- rotated = images_dir / 'orient/original.jpg'
291
- rotate_images = images_dir.glob('orient/?.jpg')
292
-
293
- def flatten_animation(image)
294
- if image.format == :gif
295
- flattened = image.temp_path
296
- flatten_command = %W[
297
- convert
298
- #{image.to_s.shellescape}
299
- -coalesce
300
- -append
301
- #{flattened.to_s.shellescape}
302
- ].join(' ')
303
- expect(system(flatten_command)).to be_truthy
304
- flattened
305
- else
306
- image
307
- end
308
- end
309
-
310
- def check_lossless_optimization(original, optimized)
311
- expect(optimized).not_to be_nil
312
- original = flatten_animation(original)
313
- optimized = flatten_animation(optimized)
314
- nrmse_command = %W[
315
- compare
316
- -metric RMSE
317
- #{original.to_s.shellescape}
318
- #{optimized.to_s.shellescape}
319
- /dev/null
320
- 2>&1
321
- ].join(' ')
322
- nrmse = `#{nrmse_command}`[/\((\d+(\.\d+)?)\)/, 1]
323
- expect(nrmse).not_to be_nil
324
- expect(nrmse.to_f).to eq(0)
325
- end
326
-
327
- rotate_images.each do |image|
328
- it "should rotate and optimize #{image} losslessly" do
329
- check_lossless_optimization(rotated, ImageOptim.optimize_image(image))
330
- end
331
- end
332
-
333
- (TEST_IMAGES - rotate_images).each do |image|
334
- it "should optimize #{image} losslessly" do
335
- check_lossless_optimization(image, ImageOptim.optimize_image(image))
336
- end
337
- end
338
- end
339
311
  end