openstreetmap-image_optim 0.21.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +42 -0
  5. data/CHANGELOG.markdown +272 -0
  6. data/CONTRIBUTING.markdown +10 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.markdown +344 -0
  10. data/Vagrantfile +33 -0
  11. data/bin/image_optim +28 -0
  12. data/image_optim.gemspec +29 -0
  13. data/lib/image_optim.rb +228 -0
  14. data/lib/image_optim/bin_resolver.rb +144 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +105 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  17. data/lib/image_optim/bin_resolver/error.rb +6 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  19. data/lib/image_optim/cmd.rb +49 -0
  20. data/lib/image_optim/config.rb +205 -0
  21. data/lib/image_optim/configuration_error.rb +3 -0
  22. data/lib/image_optim/handler.rb +57 -0
  23. data/lib/image_optim/hash_helpers.rb +45 -0
  24. data/lib/image_optim/image_meta.rb +25 -0
  25. data/lib/image_optim/image_path.rb +68 -0
  26. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  27. data/lib/image_optim/option_definition.rb +32 -0
  28. data/lib/image_optim/option_helpers.rb +17 -0
  29. data/lib/image_optim/railtie.rb +38 -0
  30. data/lib/image_optim/runner.rb +139 -0
  31. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  32. data/lib/image_optim/runner/option_parser.rb +227 -0
  33. data/lib/image_optim/space.rb +29 -0
  34. data/lib/image_optim/true_false_nil.rb +16 -0
  35. data/lib/image_optim/worker.rb +159 -0
  36. data/lib/image_optim/worker/advpng.rb +35 -0
  37. data/lib/image_optim/worker/class_methods.rb +91 -0
  38. data/lib/image_optim/worker/gifsicle.rb +63 -0
  39. data/lib/image_optim/worker/jhead.rb +43 -0
  40. data/lib/image_optim/worker/jpegoptim.rb +58 -0
  41. data/lib/image_optim/worker/jpegrecompress.rb +44 -0
  42. data/lib/image_optim/worker/jpegtran.rb +46 -0
  43. data/lib/image_optim/worker/optipng.rb +45 -0
  44. data/lib/image_optim/worker/pngcrush.rb +54 -0
  45. data/lib/image_optim/worker/pngout.rb +38 -0
  46. data/lib/image_optim/worker/pngquant.rb +51 -0
  47. data/lib/image_optim/worker/svgo.rb +32 -0
  48. data/script/template/jquery-2.1.3.min.js +4 -0
  49. data/script/template/sortable-0.6.0.min.js +2 -0
  50. data/script/template/worker_analysis.erb +254 -0
  51. data/script/update_worker_options_in_readme +60 -0
  52. data/script/worker_analysis +599 -0
  53. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  54. data/spec/image_optim/bin_resolver/simple_version_spec.rb +57 -0
  55. data/spec/image_optim/bin_resolver_spec.rb +272 -0
  56. data/spec/image_optim/cmd_spec.rb +66 -0
  57. data/spec/image_optim/config_spec.rb +217 -0
  58. data/spec/image_optim/handler_spec.rb +95 -0
  59. data/spec/image_optim/hash_helpers_spec.rb +76 -0
  60. data/spec/image_optim/image_path_spec.rb +54 -0
  61. data/spec/image_optim/railtie_spec.rb +121 -0
  62. data/spec/image_optim/runner/glob_helpers_spec.rb +25 -0
  63. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  64. data/spec/image_optim/space_spec.rb +25 -0
  65. data/spec/image_optim/worker_spec.rb +192 -0
  66. data/spec/image_optim_spec.rb +242 -0
  67. data/spec/images/comparison.png +0 -0
  68. data/spec/images/decompressed.jpeg +0 -0
  69. data/spec/images/icecream.gif +0 -0
  70. data/spec/images/image.jpg +0 -0
  71. data/spec/images/invisiblepixels/generate +24 -0
  72. data/spec/images/invisiblepixels/image.png +0 -0
  73. data/spec/images/lena.jpg +0 -0
  74. data/spec/images/orient/0.jpg +0 -0
  75. data/spec/images/orient/1.jpg +0 -0
  76. data/spec/images/orient/2.jpg +0 -0
  77. data/spec/images/orient/3.jpg +0 -0
  78. data/spec/images/orient/4.jpg +0 -0
  79. data/spec/images/orient/5.jpg +0 -0
  80. data/spec/images/orient/6.jpg +0 -0
  81. data/spec/images/orient/7.jpg +0 -0
  82. data/spec/images/orient/8.jpg +0 -0
  83. data/spec/images/orient/generate +23 -0
  84. data/spec/images/orient/original.jpg +0 -0
  85. data/spec/images/quant/64.png +0 -0
  86. data/spec/images/quant/generate +25 -0
  87. data/spec/images/rails.png +0 -0
  88. data/spec/images/test.svg +3 -0
  89. data/spec/images/transparency1.png +0 -0
  90. data/spec/images/transparency2.png +0 -0
  91. data/spec/images/vergroessert.jpg +0 -0
  92. data/spec/spec_helper.rb +64 -0
  93. data/vendor/jpegrescan +143 -0
  94. metadata +308 -0
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/space'
3
+
4
+ describe ImageOptim::Space do
5
+ before do
6
+ stub_const('Space', ImageOptim::Space)
7
+ end
8
+
9
+ {
10
+ 0 => ' ',
11
+ 1 => ' 1B',
12
+ 10 => ' 10B',
13
+ 100 => ' 100B',
14
+ 1_000 => ' 1000B',
15
+ 10_000 => ' 9.8K',
16
+ 100_000 => ' 97.7K',
17
+ 1_000_000 => '976.6K',
18
+ 10_000_000 => ' 9.5M',
19
+ 100_000_000 => ' 95.4M',
20
+ }.each do |size, space|
21
+ it "converts #{size} to #{space}" do
22
+ expect(Space.space(size)).to eq(space)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,192 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/worker'
3
+ require 'image_optim/bin_resolver'
4
+
5
+ describe ImageOptim::Worker do
6
+ before do
7
+ stub_const('Worker', ImageOptim::Worker)
8
+ stub_const('BinResolver', ImageOptim::BinResolver)
9
+ end
10
+
11
+ describe :optimize do
12
+ it 'raises NotImplementedError' do
13
+ image_optim = ImageOptim.new
14
+ worker = Worker.new(image_optim, {})
15
+
16
+ expect do
17
+ worker.optimize(double, double)
18
+ end.to raise_error NotImplementedError
19
+ end
20
+ end
21
+
22
+ describe :create_all_by_format do
23
+ it 'passes arguments to create_all' do
24
+ image_optim = double
25
+ options_proc = proc{ true }
26
+
27
+ expect(Worker).to receive(:create_all) do |arg, &block|
28
+ expect(arg).to eq(image_optim)
29
+ expect(block).to eq(options_proc)
30
+ []
31
+ end
32
+
33
+ Worker.create_all_by_format(image_optim, &options_proc)
34
+ end
35
+
36
+ it 'create hash by format' do
37
+ workers = [
38
+ double(:image_formats => [:a]),
39
+ double(:image_formats => [:a, :b]),
40
+ double(:image_formats => [:b, :c]),
41
+ ]
42
+
43
+ expect(Worker).to receive(:create_all).and_return(workers)
44
+
45
+ worker_by_format = {
46
+ :a => [workers[0], workers[1]],
47
+ :b => [workers[1], workers[2]],
48
+ :c => [workers[2]],
49
+ }
50
+
51
+ expect(Worker.create_all_by_format(double)).to eq(worker_by_format)
52
+ end
53
+ end
54
+
55
+ describe :create_all do
56
+ def worker_double(override = {})
57
+ stubs = {:resolve_used_bins! => nil, :run_order => 0}.merge(override)
58
+ instance_double(Worker, stubs)
59
+ end
60
+
61
+ let(:image_optim){ double(:allow_lossy => false) }
62
+
63
+ it 'creates all workers for which options_proc returns true' do
64
+ workers = Array.new(3){ worker_double }
65
+ klasses = workers.map{ |worker| double(:init => worker) }
66
+ options_proc = proc do |klass|
67
+ klass == klasses[1] ? {:disable => true} : {}
68
+ end
69
+
70
+ allow(Worker).to receive(:klasses).and_return(klasses)
71
+
72
+ expect(Worker.create_all(image_optim, &options_proc)).
73
+ to eq([workers[0], workers[2]])
74
+ end
75
+
76
+ it 'handles workers initializing multiple instances' do
77
+ workers = [
78
+ worker_double,
79
+ [worker_double, worker_double, worker_double],
80
+ worker_double,
81
+ ]
82
+ klasses = workers.map{ |worker| double(:init => worker) }
83
+
84
+ allow(Worker).to receive(:klasses).and_return(klasses)
85
+
86
+ expect(Worker.create_all(image_optim){ {} }).
87
+ to eq(workers.flatten)
88
+ end
89
+
90
+ describe 'with missing workers' do
91
+ let(:workers) do
92
+ Array.new(3) do |i|
93
+ worker = worker_double
94
+ unless i == 1
95
+ allow(worker).to receive(:resolve_used_bins!).
96
+ and_raise(BinResolver::BinNotFound, "not found #{i}")
97
+ end
98
+ worker
99
+ end
100
+ end
101
+ let(:klasses){ workers.map{ |worker| double(:init => worker) } }
102
+
103
+ before do
104
+ allow(Worker).to receive(:klasses).and_return(klasses)
105
+ end
106
+
107
+ describe 'if skip_missing_workers is true' do
108
+ define :bin_not_found do |message|
109
+ match do |error|
110
+ error.is_a?(BinResolver::BinNotFound) && error.message == message
111
+ end
112
+ end
113
+
114
+ it 'shows warnings and returns resolved workers ' do
115
+ allow(image_optim).to receive(:skip_missing_workers).and_return(true)
116
+
117
+ expect(Worker).to receive(:warn).
118
+ once.with(bin_not_found('not found 0'))
119
+ expect(Worker).to receive(:warn).
120
+ once.with(bin_not_found('not found 2'))
121
+
122
+ expect(Worker.create_all(image_optim){ {} }).
123
+ to eq([workers[1]])
124
+ end
125
+ end
126
+
127
+ describe 'if skip_missing_workers is false' do
128
+ it 'fails with a joint exception' do
129
+ allow(image_optim).to receive(:skip_missing_workers).and_return(false)
130
+
131
+ expect do
132
+ Worker.create_all(image_optim){ {} }
133
+ end.to raise_error(BinResolver::Error, /not found 0\nnot found 2/)
134
+ end
135
+ end
136
+ end
137
+
138
+ it 'orders workers by run_order' do
139
+ image_optim = double(:allow_lossy => false)
140
+ run_orders = [10, -10, 0, 0, 0, 10, -10]
141
+ workers = run_orders.map do |run_order|
142
+ worker_double(:run_order => run_order)
143
+ end
144
+ klasses_list = workers.map{ |worker| double(:init => worker) }
145
+
146
+ [
147
+ klasses_list,
148
+ klasses_list.reverse,
149
+ klasses_list.shuffle,
150
+ ].each do |klasses|
151
+ allow(Worker).to receive(:klasses).and_return(klasses)
152
+
153
+ expected_order = klasses.map(&:init).sort_by.with_index do |worker, i|
154
+ [worker.run_order, i]
155
+ end
156
+
157
+ expect(Worker.create_all(image_optim){ {} }).to eq(expected_order)
158
+ end
159
+ end
160
+ end
161
+
162
+ describe :option do
163
+ it 'runs option block in context of worker' do
164
+ # don't add Abc to list of wokers
165
+ allow(ImageOptim::Worker).to receive(:inherited)
166
+
167
+ stub_const('Abc', Class.new(Worker) do
168
+ option(:test, 1, 'Test context') do |_v|
169
+ some_instance_method
170
+ end
171
+ end)
172
+
173
+ expect_any_instance_of(Abc).
174
+ to receive(:some_instance_method).and_return(20)
175
+ expect(Abc.new(ImageOptim.new).test).to eq(20)
176
+ end
177
+
178
+ it 'returns instance of OptionDefinition' do
179
+ # don't add Abc to list of wokers
180
+ allow(ImageOptim::Worker).to receive(:inherited)
181
+
182
+ definition = nil
183
+ Class.new(Worker) do
184
+ definition = option(:test, 1, 'Test'){ |v| v }
185
+ end
186
+
187
+ expect(definition).to be_an(ImageOptim::OptionDefinition)
188
+ expect(definition.name).to eq(:test)
189
+ expect(definition.default).to eq(1)
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,242 @@
1
+ require 'spec_helper'
2
+ require 'image_optim'
3
+ require 'image_optim/cmd'
4
+ require 'tempfile'
5
+ require 'English'
6
+
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
11
+
12
+ helpers = Module.new do
13
+ def temp_copy(image)
14
+ image.temp_path.tap{ |path| image.copy(path) }
15
+ end
16
+ end
17
+ include helpers
18
+ extend helpers
19
+
20
+ matcher :be_in_range do |expected|
21
+ match{ |actual| expected.include?(actual) }
22
+ end
23
+
24
+ before do
25
+ stub_const('Cmd', ImageOptim::Cmd)
26
+
27
+ allow(ImageOptim::Config).to receive(:global).and_return({})
28
+ allow(ImageOptim::Config).to receive(:local).and_return({})
29
+ end
30
+
31
+ disable_all_workers = Hash[ImageOptim::Worker.klasses.map do |klass|
32
+ [klass.bin_sym, false]
33
+ end]
34
+
35
+ ImageOptim::Worker.klasses.each do |worker_klass|
36
+ describe "#{worker_klass.bin_sym} worker" do
37
+ it 'optimizes at least one test image' do
38
+ options = disable_all_workers.dup
39
+ options.merge!(worker_klass.bin_sym => true)
40
+ options.merge!(:skip_missing_workers => false)
41
+
42
+ image_optim = ImageOptim.new(options)
43
+ if Array(worker_klass.init(image_optim)).empty?
44
+ image_optim = ImageOptim.new(options.merge(:allow_lossy => true))
45
+ end
46
+
47
+ expect(test_images.any? do |original|
48
+ image_optim.optimize_image(temp_copy(original))
49
+ end).to be true
50
+ end
51
+ end
52
+ end
53
+
54
+ describe :optimize_image do
55
+ define :have_same_data_as do |expected|
56
+ match{ |actual| actual.binread == expected.binread }
57
+ end
58
+
59
+ define :have_size do
60
+ match(&:size?)
61
+ end
62
+
63
+ describe 'optimizing images' do
64
+ rotated = images_dir / 'orient/original.jpg'
65
+ rotate_images = images_dir.glob('orient/?.jpg')
66
+
67
+ base_options = {:skip_missing_workers => false}
68
+ [
69
+ ['lossless', base_options, 0],
70
+ ['lossy', base_options.merge(:allow_lossy => true), 0.01],
71
+ ].each do |type, options, max_difference|
72
+ image_optim = ImageOptim.new(options)
73
+ describe type do
74
+ copies = test_images.map{ |image| temp_copy(image) }
75
+ pairs = image_optim.optimize_images(copies)
76
+ test_images.zip(*pairs.transpose).each do |original, copy, optimized|
77
+ it "optimizes #{original.relative_path_from(root_dir)}" do
78
+ expect(copy).to have_same_data_as(original)
79
+
80
+ expect(optimized).not_to be_nil
81
+ expect(optimized).to be_a(ImageOptim::ImagePath::Optimized)
82
+ expect(optimized).to have_size
83
+ expect(optimized).to be_smaller_than(original)
84
+ expect(optimized).not_to have_same_data_as(original)
85
+
86
+ compare_to = rotate_images.include?(original) ? rotated : original
87
+ expect(optimized).to be_similar_to(compare_to, max_difference)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ it 'ignores text file' do
95
+ original = ImageOptim::ImagePath.new(__FILE__)
96
+ copy = temp_copy(original)
97
+
98
+ expect(Tempfile).not_to receive(:new)
99
+ optimized_image = ImageOptim.optimize_image(copy)
100
+ expect(optimized_image).to be_nil
101
+ expect(copy.read).to eq(original.read)
102
+ end
103
+
104
+ {
105
+ :png => "\211PNG\r\n\032\n",
106
+ :jpeg => "\377\330",
107
+ }.each do |type, data|
108
+ it "ingores broken #{type}" do
109
+ path = FSPath.temp_file_path
110
+ path.write(data)
111
+ expect(ImageOptim::ImageMeta).to receive(:warn)
112
+ expect(ImageOptim.optimize_image(path)).to be_nil
113
+ end
114
+ end
115
+ end
116
+
117
+ describe :optimize_image! do
118
+ it 'optimizes image and replaces original' do
119
+ original = double
120
+ optimized = double(:original_size => 12_345)
121
+ optimized_wrap = double
122
+ image_optim = ImageOptim.new
123
+
124
+ allow(ImageOptim::ImagePath).to receive(:convert).
125
+ with(original).and_return(original)
126
+
127
+ expect(image_optim).to receive(:optimize_image).
128
+ with(original).and_return(optimized)
129
+ expect(optimized).to receive(:replace).with(original)
130
+ expect(ImageOptim::ImagePath::Optimized).to receive(:new).
131
+ with(original, 12_345).and_return(optimized_wrap)
132
+
133
+ expect(image_optim.optimize_image!(original)).to eq(optimized_wrap)
134
+ end
135
+
136
+ it 'returns nil if optimization fails' do
137
+ original = double
138
+ image_optim = ImageOptim.new
139
+
140
+ allow(ImageOptim::ImagePath).to receive(:convert).
141
+ with(original).and_return(original)
142
+
143
+ expect(image_optim).to receive(:optimize_image).
144
+ with(original).and_return(nil)
145
+ expect(ImageOptim::ImagePath::Optimized).not_to receive(:new)
146
+
147
+ expect(image_optim.optimize_image!(original)).to eq(nil)
148
+ end
149
+ end
150
+
151
+ describe :optimize_image_data do
152
+ it 'create temp file, optimizes image and returns data' do
153
+ data = double
154
+ temp = double(:path => double)
155
+ optimized = double
156
+ optimized_data = double
157
+ image_optim = ImageOptim.new
158
+
159
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
160
+ with(data).and_return(double(:format => 'xxx'))
161
+
162
+ expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
163
+ expect(temp).to receive(:binmode)
164
+ expect(temp).to receive(:write).with(data)
165
+ expect(temp).to receive(:close)
166
+ expect(image_optim).to receive(:optimize_image).
167
+ with(temp.path).and_return(optimized)
168
+ expect(optimized).to receive(:binread).and_return(optimized_data)
169
+
170
+ expect(image_optim.optimize_image_data(data)).to eq(optimized_data)
171
+ end
172
+
173
+ it 'returns nil if optimization fails' do
174
+ data = double
175
+ temp = double(:path => double)
176
+ image_optim = ImageOptim.new
177
+
178
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
179
+ with(data).and_return(double(:format => 'xxx'))
180
+
181
+ expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
182
+ expect(temp).to receive(:binmode)
183
+ expect(temp).to receive(:write).with(data)
184
+ expect(temp).to receive(:close)
185
+ expect(image_optim).to receive(:optimize_image).
186
+ with(temp.path).and_return(nil)
187
+
188
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
189
+ end
190
+
191
+ it 'returns nil if format can\'t be detected' do
192
+ data = double
193
+ image_optim = ImageOptim.new
194
+
195
+ allow(ImageOptim::ImageMeta).to receive(:for_data).
196
+ with(data).and_return(double(:format => nil))
197
+
198
+ expect(ImageOptim::ImagePath).not_to receive(:temp_file)
199
+ expect(image_optim).not_to receive(:optimize_image)
200
+
201
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
202
+ end
203
+ end
204
+
205
+ describe 'optimize multiple' do
206
+ %w[
207
+ optimize_images
208
+ optimize_images!
209
+ optimize_images_data
210
+ ].each do |list_method|
211
+ describe list_method do
212
+ method = list_method.sub('images', 'image')
213
+ describe 'without block' do
214
+ it 'optimizes images and returns array of results' do
215
+ image_optim = ImageOptim.new
216
+ results = test_images.map do |src|
217
+ dst = double
218
+ expect(image_optim).to receive(method).with(src).and_return(dst)
219
+ [src, dst]
220
+ end
221
+ expect(image_optim.send(list_method, test_images)).to eq(results)
222
+ end
223
+ end
224
+
225
+ describe 'given block' do
226
+ it 'optimizes images, yields path and result for each and '\
227
+ 'returns array of yield results' do
228
+ image_optim = ImageOptim.new
229
+ results = test_images.map do |src|
230
+ dst = double
231
+ expect(image_optim).to receive(method).with(src).and_return(dst)
232
+ [src, dst, :test]
233
+ end
234
+ expect(image_optim.send(list_method, test_images) do |src, dst|
235
+ [src, dst, :test]
236
+ end).to eq(results)
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end