discourse_image_optim 0.24.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,259 @@
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::Path.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
+ before do
21
+ stub_const('Cmd', ImageOptim::Cmd)
22
+ end
23
+
24
+ isolated_options_base = {:skip_missing_workers => false}
25
+ ImageOptim::Worker.klasses.each do |klass|
26
+ isolated_options_base[klass.bin_sym] = false
27
+ end
28
+
29
+ ImageOptim::Worker.klasses.each do |worker_klass|
30
+ describe "#{worker_klass.bin_sym} worker" do
31
+ it 'optimizes at least one test image' do
32
+ options = isolated_options_base.merge(worker_klass.bin_sym => true)
33
+
34
+ image_optim = ImageOptim.new(options)
35
+ if Array(worker_klass.init(image_optim)).empty?
36
+ image_optim = ImageOptim.new(options.merge(:allow_lossy => true))
37
+ end
38
+
39
+ expect(test_images.any? do |original|
40
+ image_optim.optimize_image(temp_copy(original))
41
+ end).to be true
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#optimize_image' do
47
+ define :have_same_data_as do |expected|
48
+ match{ |actual| actual.binread == expected.binread }
49
+ end
50
+
51
+ define :have_size do
52
+ match(&:size?)
53
+ end
54
+
55
+ describe 'optimizing images' do
56
+ rotated = images_dir / 'orient/original.jpg'
57
+ rotate_images = images_dir.glob('orient/?.jpg')
58
+
59
+ base_options = {:skip_missing_workers => false}
60
+ [
61
+ ['lossless', base_options, 0],
62
+ ['lossy', base_options.merge(:allow_lossy => true), 0.001],
63
+ ].each do |type, options, max_difference|
64
+ it "does it #{type}" do
65
+ image_optim = ImageOptim.new(options)
66
+ copies = test_images.map{ |image| temp_copy(image) }
67
+ pairs = image_optim.optimize_images(copies)
68
+ test_images.zip(*pairs.transpose).each do |original, copy, optimized|
69
+ expect(copy).to have_same_data_as(original)
70
+
71
+ expect(optimized).not_to be_nil
72
+ expect(optimized).to be_a(ImageOptim::OptimizedPath)
73
+ expect(optimized).to have_size
74
+ expect(optimized).to be_smaller_than(original)
75
+ expect(optimized).not_to have_same_data_as(original)
76
+
77
+ compare_to = rotate_images.include?(original) ? rotated : original
78
+ expect(optimized).to be_similar_to(compare_to, max_difference)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ it 'ignores text file' do
85
+ original = ImageOptim::Path.new(__FILE__)
86
+ copy = temp_copy(original)
87
+
88
+ expect(Tempfile).not_to receive(:new)
89
+ optimized_image = ImageOptim.optimize_image(copy)
90
+ expect(optimized_image).to be_nil
91
+ expect(copy.read).to eq(original.read)
92
+ end
93
+
94
+ {
95
+ :png => "\211PNG\r\n\032\n",
96
+ :jpeg => "\377\330",
97
+ }.each do |type, data|
98
+ it "ingores broken #{type}" do
99
+ path = FSPath.temp_file_path
100
+ path.binwrite(data)
101
+ expect(ImageOptim::ImageMeta).to receive(:warn)
102
+ expect(ImageOptim.optimize_image(path)).to be_nil
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#optimize_image!' do
108
+ it 'optimizes image and replaces original' do
109
+ original = double
110
+ optimized = double(:original_size => 12_345)
111
+ optimized_wrap = double
112
+ image_optim = ImageOptim.new
113
+
114
+ allow(ImageOptim::Path).to receive(:convert).
115
+ with(original).and_return(original)
116
+
117
+ expect(image_optim).to receive(:optimize_image).
118
+ with(original).and_return(optimized)
119
+ expect(optimized).to receive(:replace).with(original)
120
+ expect(ImageOptim::OptimizedPath).to receive(:new).
121
+ with(original, 12_345).and_return(optimized_wrap)
122
+
123
+ expect(image_optim.optimize_image!(original)).to eq(optimized_wrap)
124
+ end
125
+
126
+ it 'returns nil if optimization fails' do
127
+ original = double
128
+ image_optim = ImageOptim.new
129
+
130
+ allow(ImageOptim::Path).to receive(:convert).
131
+ with(original).and_return(original)
132
+
133
+ expect(image_optim).to receive(:optimize_image).
134
+ with(original).and_return(nil)
135
+ expect(ImageOptim::OptimizedPath).not_to receive(:new)
136
+
137
+ expect(image_optim.optimize_image!(original)).to eq(nil)
138
+ end
139
+ end
140
+
141
+ describe '#optimize_image_data' do
142
+ it 'create temp file, optimizes image and returns data' do
143
+ data = double
144
+ temp = double(:path => double)
145
+ optimized = double
146
+ optimized_data = double
147
+ image_optim = ImageOptim.new
148
+
149
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
150
+ with(data).and_return('xxx')
151
+
152
+ expect(ImageOptim::Path).to receive(:temp_file).and_yield(temp)
153
+ expect(temp).to receive(:binmode)
154
+ expect(temp).to receive(:write).with(data)
155
+ expect(temp).to receive(:close)
156
+ expect(image_optim).to receive(:optimize_image).
157
+ with(temp.path).and_return(optimized)
158
+ expect(optimized).to receive(:binread).and_return(optimized_data)
159
+
160
+ expect(image_optim.optimize_image_data(data)).to eq(optimized_data)
161
+ end
162
+
163
+ it 'returns nil if optimization fails' do
164
+ data = double
165
+ temp = double(:path => double)
166
+ image_optim = ImageOptim.new
167
+
168
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
169
+ with(data).and_return('xxx')
170
+
171
+ expect(ImageOptim::Path).to receive(:temp_file).and_yield(temp)
172
+ expect(temp).to receive(:binmode)
173
+ expect(temp).to receive(:write).with(data)
174
+ expect(temp).to receive(:close)
175
+ expect(image_optim).to receive(:optimize_image).
176
+ with(temp.path).and_return(nil)
177
+
178
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
179
+ end
180
+
181
+ it 'returns nil if format can\'t be detected' do
182
+ data = double
183
+ image_optim = ImageOptim.new
184
+
185
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
186
+ with(data).and_return(nil)
187
+
188
+ expect(ImageOptim::Path).not_to receive(:temp_file)
189
+ expect(image_optim).not_to receive(:optimize_image)
190
+
191
+ expect(image_optim.optimize_image_data(data)).to eq(nil)
192
+ end
193
+ end
194
+
195
+ %w[
196
+ optimize_images
197
+ optimize_images!
198
+ optimize_images_data
199
+ ].each do |list_method|
200
+ describe "##{list_method}" do
201
+ method = list_method.sub('images', 'image')
202
+ describe 'without block' do
203
+ it 'optimizes images and returns array of results' do
204
+ image_optim = ImageOptim.new
205
+ results = test_images.map do |src|
206
+ dst = double
207
+ allow(image_optim).to receive(method).with(src).and_return(dst)
208
+ [src, dst]
209
+ end
210
+ expect(image_optim.send(list_method, test_images)).to eq(results)
211
+ end
212
+ end
213
+
214
+ describe 'given block' do
215
+ it 'optimizes images, yields path and result for each and '\
216
+ 'returns array of yield results' do
217
+ image_optim = ImageOptim.new
218
+ results = test_images.map do |src|
219
+ dst = double
220
+ allow(image_optim).to receive(method).with(src).and_return(dst)
221
+ [src, dst, :test]
222
+ end
223
+ expect(image_optim.send(list_method, test_images) do |src, dst|
224
+ [src, dst, :test]
225
+ end).to eq(results)
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ %w[
232
+ optimize_image
233
+ optimize_image!
234
+ optimize_image_data
235
+ optimize_images
236
+ optimize_images!
237
+ optimize_images_data
238
+ ].each do |method|
239
+ describe ".#{method}" do
240
+ it 'is checkable by respond_to?' do
241
+ expect(ImageOptim).to respond_to(method)
242
+ end
243
+
244
+ it 'calls same method on a new ImageOptim instance' do
245
+ image_optim = instance_double(ImageOptim)
246
+ method_arg = double
247
+ method_block = proc{ :x }
248
+
249
+ allow(ImageOptim).to receive(:new).and_return(image_optim)
250
+ expect(image_optim).to receive(method) do |arg, &block|
251
+ expect(arg).to eq(method_arg)
252
+ expect(block).to eq(method_block)
253
+ end
254
+
255
+ ImageOptim.send(method, method_arg, &method_block)
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1 @@
1
+ ��
Binary file
Binary file
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Dir.chdir(File.dirname(__FILE__))
4
+
5
+ require 'shellwords'
6
+
7
+ side = 64
8
+
9
+ IO.popen(%W[
10
+ convert
11
+ -depth 8
12
+ -size #{side}x#{side}
13
+ -strip
14
+ rgba:-
15
+ PNG32:image.png
16
+ ].shelljoin, 'w') do |f|
17
+ side.times do |a|
18
+ side.times do |b|
19
+ alpha = [0, 1, 0x7f, 0xff][(a / 8 + b / 8) % 4]
20
+ f << [rand(256), rand(256), rand(256), alpha].pack('C*')
21
+ end
22
+ end
23
+ end
24
+ system 'image_optim --pngcrush-blacken=n image.png'
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,23 @@
1
+ #!/bin/sh
2
+
3
+ cd "$(dirname "$0")"
4
+
5
+ convert -size 2x2 xc:black -fill '#f00' -draw 'point 0,1' -fill '#0f0' -draw 'point 1,0' -fill '#00f' -draw 'point 1,1' -scale 64x64 original.png
6
+
7
+ convert original.png 0.jpg
8
+ convert original.png 1.jpg
9
+ convert original.png -flop 2.jpg
10
+ convert original.png -rotate 180 3.jpg
11
+ convert original.png -flip 4.jpg
12
+ convert original.png -transpose 5.jpg
13
+ convert original.png -rotate 270 6.jpg
14
+ convert original.png -transverse 7.jpg
15
+ convert original.png -rotate 90 8.jpg
16
+
17
+ convert original.png original.jpg
18
+ rm original.png
19
+
20
+ exiv2 -M"add Exif.Photo.UserComment image_optim-spec" *.jpg
21
+ for i in {1..8}; do
22
+ exiv2 -M"add Exif.Image.Orientation Short $i" $i.jpg
23
+ done
Binary file
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Dir.chdir(File.dirname(__FILE__))
4
+
5
+ require 'shellwords'
6
+
7
+ palettes = [64]
8
+ side = 256
9
+
10
+ palettes.each do |palette|
11
+ IO.popen(%W[
12
+ convert
13
+ -depth 8
14
+ -size #{side}x#{side}
15
+ -strip
16
+ rgb:-
17
+ PNG24:#{palette}.png
18
+ ].shelljoin, 'w') do |f|
19
+ (side * side).times do |i|
20
+ color = i * palette / (side * side) * 0x10000 / palette
21
+ f << [color / 0x100, color % 0x100, 0].pack('C*')
22
+ end
23
+ end
24
+ system "identify -format 'Wrote %f with %k unique colors\n' #{palette}.png"
25
+ end
Binary file
@@ -0,0 +1,3 @@
1
+ <svg width="90.000" height="100.000" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="36" cy="50" r="30" stroke="black" stroke-width="10" fill="red" />
3
+ </svg>
@@ -0,0 +1,93 @@
1
+ if ENV['CODECLIMATE']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ require 'image_optim/pack'
7
+ require 'image_optim/path'
8
+
9
+ ENV['PATH'] = [
10
+ ImageOptim::Pack.path,
11
+ ENV['PATH'],
12
+ ].compact.join File::PATH_SEPARATOR
13
+
14
+ RSpec.configure do |c|
15
+ c.before do
16
+ stub_const('ImageOptim::Config::GLOBAL_PATH', ImageOptim::Path::NULL)
17
+ stub_const('ImageOptim::Config::LOCAL_PATH', ImageOptim::Path::NULL)
18
+ ImageOptim.class_eval{ def pack; end }
19
+ end
20
+
21
+ c.order = :random
22
+ end
23
+
24
+ def flatten_animation(image)
25
+ if image.image_format == :gif
26
+ flattened = image.temp_path
27
+ command = %W[
28
+ convert
29
+ #{image}
30
+ -coalesce
31
+ -append
32
+ #{flattened}
33
+ ].shelljoin
34
+ expect(ImageOptim::Cmd.run(command)).to be_truthy
35
+ flattened
36
+ else
37
+ image
38
+ end
39
+ end
40
+
41
+ def mepp(image_a, image_b)
42
+ coalesce_a = flatten_animation(image_a)
43
+ coalesce_b = flatten_animation(image_b)
44
+ output = ImageOptim::Cmd.capture(%W[
45
+ compare
46
+ -metric MEPP
47
+ -alpha Background
48
+ #{coalesce_a.to_s.shellescape}
49
+ #{coalesce_b.to_s.shellescape}
50
+ #{ImageOptim::Path::NULL}
51
+ 2>&1
52
+ ].join(' '))
53
+ unless [0, 1].include?($CHILD_STATUS.exitstatus)
54
+ fail "compare #{image_a} with #{image_b} failed with `#{output}`"
55
+ end
56
+ num_r = '\d+(?:\.\d+(?:[eE][-+]?\d+)?)?'
57
+ output[/\((#{num_r}), #{num_r}\)/, 1].to_f
58
+ end
59
+
60
+ RSpec::Matchers.define :be_smaller_than do |expected|
61
+ match{ |actual| actual.size < expected.size }
62
+ end
63
+
64
+ RSpec::Matchers.define :be_similar_to do |expected, max_difference|
65
+ match do |actual|
66
+ @diff = mepp(actual, expected)
67
+ @diff <= max_difference
68
+ end
69
+ failure_message do |actual|
70
+ "expected #{actual} to have at most #{max_difference} difference from "\
71
+ "#{expected}, got normalized root-mean-square error of #{@diff}"
72
+ end
73
+ end
74
+
75
+ module CapabilityCheckHelpers
76
+ def any_file_modes_allowed?
77
+ Tempfile.open 'posix' do |f|
78
+ File.chmod(0, f.path)
79
+ return (File.stat(f.path).mode & 0o777).zero?
80
+ end
81
+ end
82
+
83
+ def inodes_supported?
84
+ !File.stat(__FILE__).ino.zero?
85
+ end
86
+
87
+ def signals_supported?
88
+ Process.kill(0, 0)
89
+ true
90
+ rescue
91
+ false
92
+ end
93
+ end