openstreetmap-image_optim 0.21.0.1

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 (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