discourse_image_optim 0.24.4

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