jr-paperclip 7.3.1 → 8.0.0.beta.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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +3 -1
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -0
- data/NEWS +13 -0
- data/README.md +116 -6
- data/UPGRADING +5 -0
- data/VIPS_MIGRATION_GUIDE.md +131 -0
- data/features/basic_integration.feature +27 -0
- data/features/step_definitions/attachment_steps.rb +17 -0
- data/gemfiles/7.0.gemfile +1 -0
- data/gemfiles/7.1.gemfile +1 -0
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/8.1.gemfile +1 -0
- data/lib/paperclip/attachment.rb +3 -2
- data/lib/paperclip/errors.rb +4 -5
- data/lib/paperclip/geometry.rb +3 -3
- data/lib/paperclip/geometry_detector_factory.rb +52 -12
- data/lib/paperclip/helpers.rb +18 -0
- data/lib/paperclip/processor.rb +36 -4
- data/lib/paperclip/thumbnail.rb +568 -62
- data/lib/paperclip/version.rb +1 -1
- data/lib/paperclip.rb +26 -9
- data/paperclip.gemspec +2 -1
- data/spec/paperclip/attachment_definitions_spec.rb +300 -0
- data/spec/paperclip/attachment_spec.rb +1 -1
- data/spec/paperclip/geometry_detector_spec.rb +81 -32
- data/spec/paperclip/geometry_spec.rb +8 -5
- data/spec/paperclip/helpers_spec.rb +49 -0
- data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +266 -0
- data/spec/paperclip/processor_spec.rb +35 -1
- data/spec/paperclip/style_spec.rb +58 -0
- data/spec/paperclip/thumbnail_custom_options_spec.rb +173 -0
- data/spec/paperclip/thumbnail_loader_options_spec.rb +53 -0
- data/spec/paperclip/thumbnail_security_spec.rb +42 -0
- data/spec/paperclip/thumbnail_spec.rb +1127 -172
- metadata +34 -2
|
@@ -8,9 +8,1012 @@ describe Paperclip::Thumbnail do
|
|
|
8
8
|
|
|
9
9
|
after { @file.close }
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
describe "backend selection" do
|
|
12
|
+
let(:attachment) { double("Attachment", options: {}) }
|
|
13
|
+
|
|
14
|
+
it "uses vips when specified" do
|
|
15
|
+
processor = described_class.new(@file, { geometry: "25x25", backend: :vips }, attachment)
|
|
16
|
+
expect(processor.backend).to eq(:vips)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "uses global default when no backend specified" do
|
|
20
|
+
Paperclip.options[:backend] = :image_magick
|
|
21
|
+
processor = described_class.new(@file, { geometry: "25x25" }, attachment)
|
|
22
|
+
expect(processor.backend).to eq(:image_magick)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "defaults to image_magick when backend is nil" do
|
|
26
|
+
original_backend = Paperclip.options[:backend]
|
|
27
|
+
Paperclip.options[:backend] = nil
|
|
28
|
+
processor = described_class.new(@file, { geometry: "25x25" }, attachment)
|
|
29
|
+
expect(processor.backend).to eq(:image_magick)
|
|
30
|
+
Paperclip.options[:backend] = original_backend
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "defaults to image_magick when backend is invalid" do
|
|
34
|
+
processor = described_class.new(@file, { geometry: "25x25", backend: :invalid_backend }, attachment)
|
|
35
|
+
expect(processor.backend).to eq(:image_magick)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "logs a warning when backend is invalid" do
|
|
39
|
+
expect(Paperclip).to receive(:log).with(/Warning: Invalid backend: invalid_backend/)
|
|
40
|
+
described_class.new(@file, { geometry: "25x25", backend: :invalid_backend }, attachment)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "uses backend from attachment options when not specified in processor options" do
|
|
44
|
+
attachment_with_backend = double("Attachment", options: { backend: :vips })
|
|
45
|
+
processor = described_class.new(@file, { geometry: "25x25" }, attachment_with_backend)
|
|
46
|
+
expect(processor.backend).to eq(:vips)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "prefers processor option backend over attachment option backend" do
|
|
50
|
+
attachment_with_backend = double("Attachment", options: { backend: :vips })
|
|
51
|
+
processor = described_class.new(@file, { geometry: "25x25", backend: :image_magick }, attachment_with_backend)
|
|
52
|
+
expect(processor.backend).to eq(:image_magick)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "per-style backend selection (integration)" do
|
|
57
|
+
let(:attachment) { double("Attachment", options: {}) }
|
|
58
|
+
|
|
59
|
+
it "processes same image with different backends per style" do
|
|
60
|
+
begin
|
|
61
|
+
require "vips"
|
|
62
|
+
rescue LoadError
|
|
63
|
+
skip "libvips not installed"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Simulate per-style backend selection as would happen with:
|
|
67
|
+
# styles: {
|
|
68
|
+
# vips_thumb: { geometry: "50x50#", backend: :vips },
|
|
69
|
+
# magick_thumb: { geometry: "50x50#", backend: :image_magick }
|
|
70
|
+
# }
|
|
71
|
+
|
|
72
|
+
# Process with vips backend
|
|
73
|
+
vips_processor = described_class.new(@file, {
|
|
74
|
+
geometry: "50x50#",
|
|
75
|
+
backend: :vips,
|
|
76
|
+
style: :vips_thumb
|
|
77
|
+
}, attachment)
|
|
78
|
+
vips_result = vips_processor.make
|
|
79
|
+
|
|
80
|
+
@file.rewind
|
|
81
|
+
|
|
82
|
+
# Process with image_magick backend
|
|
83
|
+
magick_processor = described_class.new(@file, {
|
|
84
|
+
geometry: "50x50#",
|
|
85
|
+
backend: :image_magick,
|
|
86
|
+
style: :magick_thumb
|
|
87
|
+
}, attachment)
|
|
88
|
+
magick_result = magick_processor.make
|
|
89
|
+
|
|
90
|
+
# Both should produce valid 50x50 images
|
|
91
|
+
vips_dims = `identify -format "%wx%h" "#{vips_result.path}"`.strip
|
|
92
|
+
magick_dims = `identify -format "%wx%h" "#{magick_result.path}"`.strip
|
|
93
|
+
|
|
94
|
+
expect(vips_dims).to eq("50x50")
|
|
95
|
+
expect(magick_dims).to eq("50x50")
|
|
96
|
+
|
|
97
|
+
# Verify they used different backends
|
|
98
|
+
expect(vips_processor.backend).to eq(:vips)
|
|
99
|
+
expect(magick_processor.backend).to eq(:image_magick)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "allows mixing backends with different geometries" do
|
|
103
|
+
begin
|
|
104
|
+
require "vips"
|
|
105
|
+
rescue LoadError
|
|
106
|
+
skip "libvips not installed"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Large preview with vips (faster for large images)
|
|
110
|
+
vips_processor = described_class.new(@file, {
|
|
111
|
+
geometry: "200x200>",
|
|
112
|
+
backend: :vips,
|
|
113
|
+
style: :preview
|
|
114
|
+
}, attachment)
|
|
115
|
+
vips_result = vips_processor.make
|
|
116
|
+
|
|
117
|
+
@file.rewind
|
|
118
|
+
|
|
119
|
+
# Small thumbnail with image_magick
|
|
120
|
+
magick_processor = described_class.new(@file, {
|
|
121
|
+
geometry: "32x32#",
|
|
122
|
+
backend: :image_magick,
|
|
123
|
+
style: :icon
|
|
124
|
+
}, attachment)
|
|
125
|
+
magick_result = magick_processor.make
|
|
126
|
+
|
|
127
|
+
# Verify dimensions
|
|
128
|
+
vips_dims = `identify -format "%wx%h" "#{vips_result.path}"`.strip
|
|
129
|
+
magick_dims = `identify -format "%wx%h" "#{magick_result.path}"`.strip
|
|
130
|
+
|
|
131
|
+
# Original is 434x66, so 200x200> should give 200x30 (shrink to fit)
|
|
132
|
+
expect(vips_dims).to eq("200x30")
|
|
133
|
+
# 32x32# should give exactly 32x32 (crop to fill)
|
|
134
|
+
expect(magick_dims).to eq("32x32")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "#convert_options?" do
|
|
139
|
+
it "returns false when convert_options is nil" do
|
|
140
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
141
|
+
expect(thumb.convert_options?).to be false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "returns false when convert_options is empty" do
|
|
145
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", convert_options: "")
|
|
146
|
+
expect(thumb.convert_options?).to be false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "returns true when convert_options is set" do
|
|
150
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", convert_options: "-strip")
|
|
151
|
+
expect(thumb.convert_options?).to be true
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe "#transformation_command" do
|
|
156
|
+
it "returns an array with resize command" do
|
|
157
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "100x100")
|
|
158
|
+
cmd = thumb.transformation_command
|
|
159
|
+
expect(cmd).to be_an(Array)
|
|
160
|
+
expect(cmd).to include("-auto-orient")
|
|
161
|
+
expect(cmd).to include("-resize")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "includes crop command when cropping" do
|
|
165
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "100x100#")
|
|
166
|
+
cmd = thumb.transformation_command
|
|
167
|
+
expect(cmd).to include("-crop")
|
|
168
|
+
expect(cmd).to include("+repage")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "logs warning when called with vips backend" do
|
|
172
|
+
begin
|
|
173
|
+
require "vips"
|
|
174
|
+
rescue LoadError
|
|
175
|
+
skip "libvips not installed"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "100x100", backend: :vips)
|
|
179
|
+
expect(Paperclip).to receive(:log).with(/Warning.*transformation_command.*vips/)
|
|
180
|
+
thumb.transformation_command
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe "#make" do
|
|
185
|
+
let(:attachment) { double("Attachment", options: {}) }
|
|
186
|
+
|
|
187
|
+
context "with vips backend" do
|
|
188
|
+
before do
|
|
189
|
+
begin
|
|
190
|
+
require "vips"
|
|
191
|
+
rescue LoadError
|
|
192
|
+
skip "libvips not installed"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "resizes image to specified dimensions" do
|
|
197
|
+
processor = described_class.new(@file, { geometry: "25x25>", backend: :vips }, attachment)
|
|
198
|
+
result = processor.make
|
|
199
|
+
|
|
200
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
201
|
+
expect(`#{cmd}`.chomp).to eq("25x4")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "crops to fill with # modifier" do
|
|
205
|
+
processor = described_class.new(@file, { geometry: "30x20#", backend: :vips }, attachment)
|
|
206
|
+
result = processor.make
|
|
207
|
+
|
|
208
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
209
|
+
expect(`#{cmd}`.chomp).to eq("30x20")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "auto-orients an image using autorot" do
|
|
213
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
214
|
+
processor = described_class.new(file, { geometry: "50x50", backend: :vips }, attachment)
|
|
215
|
+
|
|
216
|
+
# Verify it detects logical dimensions (rotated from 300x200 to 200x300)
|
|
217
|
+
expect(processor.current_geometry.width).to eq(200)
|
|
218
|
+
result = processor.make
|
|
219
|
+
|
|
220
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
221
|
+
# Original 300x200 with orientation 6 (90 deg CW).
|
|
222
|
+
# Post autorot: 200x300.
|
|
223
|
+
# Resize to fit 50x50: 33x50.
|
|
224
|
+
expect(`#{cmd}`.chomp).to eq("33x50")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "strips metadata when requested via convert_options" do
|
|
228
|
+
processor = described_class.new(@file, { geometry: "50x50", convert_options: "-strip", backend: :vips }, attachment)
|
|
229
|
+
result = processor.make
|
|
230
|
+
|
|
231
|
+
# identify -verbose shows less output when stripped
|
|
232
|
+
expect(`identify -verbose "#{result.path}"`).not_to include("exif:")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "handles exact dimensions with ! modifier" do
|
|
236
|
+
processor = described_class.new(@file, { geometry: "100x50!", backend: :vips }, attachment)
|
|
237
|
+
result = processor.make
|
|
238
|
+
|
|
239
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
240
|
+
expect(`#{cmd}`.chomp).to eq("100x50")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "stretches the image with ! modifier matching ImageMagick behavior" do
|
|
244
|
+
# Create a 3-stripe image: Red, Green, Blue
|
|
245
|
+
stripe_file = Tempfile.new(["stripe", ".png"])
|
|
246
|
+
Paperclip.run("convert", "-size 100x100 xc:red xc:green xc:blue +append #{stripe_file.path}")
|
|
247
|
+
file = File.new(stripe_file.path, "rb")
|
|
248
|
+
|
|
249
|
+
processor = described_class.new(file, { geometry: "100x100!", backend: :vips }, attachment)
|
|
250
|
+
result = processor.make
|
|
251
|
+
|
|
252
|
+
# Check color at x=10 (Red stripe).
|
|
253
|
+
# If cropped (Green center), it would be Green.
|
|
254
|
+
# If stretched, it is Red.
|
|
255
|
+
color = Paperclip.run("convert", "#{result.path}[1x1+10+50] -format \"%[pixel:p{0,0}]\" info:")
|
|
256
|
+
expect(color).to match(/red|#FF0000|rgb\(255,0,0\)|srgb\(255,0,0\)/i)
|
|
257
|
+
|
|
258
|
+
file.close
|
|
259
|
+
stripe_file.close
|
|
260
|
+
stripe_file.unlink
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it "handles percentage with % modifier" do
|
|
264
|
+
processor = described_class.new(@file, { geometry: "50%", backend: :vips }, attachment)
|
|
265
|
+
result = processor.make
|
|
266
|
+
|
|
267
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
268
|
+
# Original is 434x66. 50% is 217x33.
|
|
269
|
+
expect(`#{cmd}`.chomp).to eq("217x33")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "handles minimum dimensions with ^ modifier" do
|
|
273
|
+
processor = described_class.new(@file, { geometry: "100x100^", backend: :vips }, attachment)
|
|
274
|
+
result = processor.make
|
|
275
|
+
|
|
276
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
277
|
+
# Original 434x66.
|
|
278
|
+
# Resize to fill 100x100 means height becomes 100, width becomes 434 * (100/66) = 658.
|
|
279
|
+
expect(`#{cmd}`.chomp).to eq("658x100")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "handles enlarge only with < modifier" do
|
|
283
|
+
# Smaller image: 50x50.png
|
|
284
|
+
small_file = File.new(fixture_file("50x50.png"), "rb")
|
|
285
|
+
processor = described_class.new(small_file, { geometry: "100x100<", backend: :vips }, attachment)
|
|
286
|
+
result = processor.make
|
|
287
|
+
|
|
288
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
289
|
+
expect(`#{cmd}`.chomp).to eq("100x100")
|
|
290
|
+
|
|
291
|
+
# Larger image: 5k.png (434x66)
|
|
292
|
+
processor = described_class.new(@file, { geometry: "100x100<", backend: :vips }, attachment)
|
|
293
|
+
result = processor.make
|
|
294
|
+
expect(`identify -format "%wx%h" "#{result.path}"`.chomp).to eq("434x66")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "takes only the first frame of a PDF by default" do
|
|
298
|
+
pdf_file = File.new(fixture_file("twopage.pdf"), "rb")
|
|
299
|
+
processor = described_class.new(pdf_file, { geometry: "100x100", format: :png, backend: :vips }, attachment)
|
|
300
|
+
result = processor.make
|
|
301
|
+
|
|
302
|
+
cmd = %[identify -format "%n" "#{result.path}"]
|
|
303
|
+
expect(`#{cmd}`.chomp).to eq("1")
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "detects animated source correctly" do
|
|
307
|
+
animated_file = File.new(fixture_file("animated.gif"), "rb")
|
|
308
|
+
processor = described_class.new(animated_file, { geometry: "50x50", backend: :vips }, attachment)
|
|
309
|
+
expect(processor.send(:animated_source?)).to be true
|
|
310
|
+
|
|
311
|
+
static_file = File.new(fixture_file("5k.png"), "rb")
|
|
312
|
+
processor = described_class.new(static_file, { geometry: "50x50", backend: :vips }, attachment)
|
|
313
|
+
expect(processor.send(:animated_source?)).to be false
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "handles area-based resize with @ modifier" do
|
|
317
|
+
# Original is 434x66 = 28644 pixels
|
|
318
|
+
# 10000@ should resize to ~sqrt(10000/28644) * dimensions
|
|
319
|
+
processor = described_class.new(@file, { geometry: "10000@", backend: :vips }, attachment)
|
|
320
|
+
result = processor.make
|
|
321
|
+
|
|
322
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
323
|
+
dimensions = `#{cmd}`.chomp
|
|
324
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
325
|
+
area = width * height
|
|
326
|
+
# Should be approximately 10000 pixels (with some tolerance)
|
|
327
|
+
expect(area).to be_within(500).of(10000)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it "handles area-based shrink-only with @> modifier" do
|
|
331
|
+
# Original is 434x66 = 28644 pixels
|
|
332
|
+
# 10000@> should resize (smaller than 28644)
|
|
333
|
+
processor = described_class.new(@file, { geometry: "10000@>", backend: :vips }, attachment)
|
|
334
|
+
result = processor.make
|
|
335
|
+
|
|
336
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
337
|
+
dimensions = `#{cmd}`.chomp
|
|
338
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
339
|
+
area = width * height
|
|
340
|
+
expect(area).to be_within(500).of(10000)
|
|
341
|
+
|
|
342
|
+
# 50000@> should NOT resize (larger than 28644, and only_shrink is true)
|
|
343
|
+
processor = described_class.new(@file, { geometry: "50000@>", backend: :vips }, attachment)
|
|
344
|
+
result = processor.make
|
|
345
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
346
|
+
expect(`#{cmd}`.chomp).to eq("434x66")
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it "handles area-based shrink-only with >@ modifier" do
|
|
350
|
+
# Same as @> but different syntax
|
|
351
|
+
processor = described_class.new(@file, { geometry: "10000>@", backend: :vips }, attachment)
|
|
352
|
+
result = processor.make
|
|
353
|
+
|
|
354
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
355
|
+
dimensions = `#{cmd}`.chomp
|
|
356
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
357
|
+
area = width * height
|
|
358
|
+
expect(area).to be_within(500).of(10000)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
context "with image_magick backend" do
|
|
363
|
+
it "resizes image to specified dimensions" do
|
|
364
|
+
processor = described_class.new(@file, { geometry: "25x25>", backend: :image_magick }, attachment)
|
|
365
|
+
result = processor.make
|
|
366
|
+
|
|
367
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
368
|
+
expect(`#{cmd}`.chomp).to eq("25x4")
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
it "handles area-based resize with @ modifier" do
|
|
372
|
+
# Original is 434x66 = 28644 pixels
|
|
373
|
+
processor = described_class.new(@file, { geometry: "10000@", backend: :image_magick }, attachment)
|
|
374
|
+
result = processor.make
|
|
375
|
+
|
|
376
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
377
|
+
dimensions = `#{cmd}`.chomp
|
|
378
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
379
|
+
area = width * height
|
|
380
|
+
expect(area).to be_within(500).of(10000)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it "handles area-based shrink-only with @> modifier" do
|
|
384
|
+
processor = described_class.new(@file, { geometry: "10000@>", backend: :image_magick }, attachment)
|
|
385
|
+
result = processor.make
|
|
386
|
+
|
|
387
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
388
|
+
dimensions = `#{cmd}`.chomp
|
|
389
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
390
|
+
area = width * height
|
|
391
|
+
expect(area).to be_within(500).of(10000)
|
|
392
|
+
|
|
393
|
+
# Larger area should NOT resize
|
|
394
|
+
processor = described_class.new(@file, { geometry: "50000@>", backend: :image_magick }, attachment)
|
|
395
|
+
result = processor.make
|
|
396
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
397
|
+
expect(`#{cmd}`.chomp).to eq("434x66")
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it "applies cross-platform convert_options with vips without warning" do
|
|
401
|
+
begin
|
|
402
|
+
require "vips"
|
|
403
|
+
rescue LoadError
|
|
404
|
+
skip "libvips not installed"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# -strip is cross-platform, should work without warning
|
|
408
|
+
expect(Paperclip).not_to receive(:log).with(/Warning/)
|
|
409
|
+
processor = described_class.new(@file, {
|
|
410
|
+
geometry: "50x50",
|
|
411
|
+
backend: :vips,
|
|
412
|
+
convert_options: "-strip",
|
|
413
|
+
}, attachment)
|
|
414
|
+
processor.make
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "logs warning for ImageMagick-only convert_options with vips" do
|
|
418
|
+
begin
|
|
419
|
+
require "vips"
|
|
420
|
+
rescue LoadError
|
|
421
|
+
skip "libvips not installed"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# -density is ImageMagick-only, should warn
|
|
425
|
+
expect(Paperclip).to receive(:log).with(/Warning.*density.*not supported.*vips/)
|
|
426
|
+
processor = described_class.new(@file, {
|
|
427
|
+
geometry: "50x50",
|
|
428
|
+
backend: :vips,
|
|
429
|
+
convert_options: "-density 150",
|
|
430
|
+
}, attachment)
|
|
431
|
+
processor.make
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
describe "convert_options - individual options" do
|
|
437
|
+
let(:attachment) { double("Attachment", options: {}) }
|
|
438
|
+
|
|
439
|
+
# Helper to create a thumbnail with specific convert_options
|
|
440
|
+
def make_thumb_with_options(file, options_string)
|
|
441
|
+
thumb = Paperclip::Thumbnail.new(file, {
|
|
442
|
+
geometry: "100x100",
|
|
443
|
+
convert_options: options_string,
|
|
444
|
+
backend: :image_magick,
|
|
445
|
+
}, attachment)
|
|
446
|
+
thumb.make
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
describe "-strip" do
|
|
450
|
+
it "removes EXIF metadata" do
|
|
451
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
452
|
+
result = make_thumb_with_options(file, "-strip")
|
|
453
|
+
|
|
454
|
+
exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
|
|
455
|
+
expect(exif).to be_empty
|
|
456
|
+
file.close
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
describe "-quality" do
|
|
461
|
+
it "sets JPEG quality" do
|
|
462
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
463
|
+
result_low = make_thumb_with_options(file, "-quality 20")
|
|
464
|
+
file.rewind
|
|
465
|
+
result_high = make_thumb_with_options(file, "-quality 95")
|
|
466
|
+
|
|
467
|
+
# Lower quality should produce smaller file
|
|
468
|
+
expect(File.size(result_low.path)).to be < File.size(result_high.path)
|
|
469
|
+
file.close
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
describe "-colorspace" do
|
|
474
|
+
it "converts to grayscale" do
|
|
475
|
+
result = make_thumb_with_options(@file, "-colorspace Gray")
|
|
476
|
+
|
|
477
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
478
|
+
expect(colorspace).to eq("Gray")
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
it "converts to sRGB" do
|
|
482
|
+
result = make_thumb_with_options(@file, "-colorspace sRGB")
|
|
483
|
+
|
|
484
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
485
|
+
expect(colorspace).to eq("sRGB")
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
describe "-rotate" do
|
|
490
|
+
it "rotates the image by specified degrees" do
|
|
491
|
+
# Original is 434x66, after 90 degree rotation should be 66x434
|
|
492
|
+
result = make_thumb_with_options(@file, "-rotate 90")
|
|
493
|
+
|
|
494
|
+
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
495
|
+
# After resize to 100x100 and rotate, dimensions change
|
|
496
|
+
expect(dimensions).to match(/\d+x\d+/)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
describe "-flip" do
|
|
501
|
+
it "flips the image vertically" do
|
|
502
|
+
result = make_thumb_with_options(@file, "-flip")
|
|
503
|
+
expect(File.exist?(result.path)).to be true
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
describe "-flop" do
|
|
508
|
+
it "flops the image horizontally" do
|
|
509
|
+
result = make_thumb_with_options(@file, "-flop")
|
|
510
|
+
expect(File.exist?(result.path)).to be true
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
describe "-negate" do
|
|
515
|
+
it "negates the image colors" do
|
|
516
|
+
result = make_thumb_with_options(@file, "-negate")
|
|
517
|
+
expect(File.exist?(result.path)).to be true
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
describe "-normalize" do
|
|
522
|
+
it "normalizes the image" do
|
|
523
|
+
result = make_thumb_with_options(@file, "-normalize")
|
|
524
|
+
expect(File.exist?(result.path)).to be true
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
describe "-equalize" do
|
|
529
|
+
it "equalizes the image histogram" do
|
|
530
|
+
result = make_thumb_with_options(@file, "-equalize")
|
|
531
|
+
expect(File.exist?(result.path)).to be true
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
describe "-auto_orient" do
|
|
536
|
+
it "auto-orients the image" do
|
|
537
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
538
|
+
result = make_thumb_with_options(file, "-auto-orient")
|
|
539
|
+
expect(File.exist?(result.path)).to be true
|
|
540
|
+
file.close
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
describe "-blur" do
|
|
545
|
+
it "blurs the image" do
|
|
546
|
+
result = make_thumb_with_options(@file, "-blur 0x2")
|
|
547
|
+
expect(File.exist?(result.path)).to be true
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
describe "-sharpen" do
|
|
552
|
+
it "sharpens the image" do
|
|
553
|
+
result = make_thumb_with_options(@file, "-sharpen 0x1")
|
|
554
|
+
expect(File.exist?(result.path)).to be true
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
describe "-density" do
|
|
559
|
+
it "sets the image density" do
|
|
560
|
+
result = make_thumb_with_options(@file, "-density 150")
|
|
561
|
+
|
|
562
|
+
density = `identify -format "%x" "#{result.path}"`.strip
|
|
563
|
+
expect(density).to start_with("150")
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
describe "-depth" do
|
|
568
|
+
it "sets the bit depth" do
|
|
569
|
+
result = make_thumb_with_options(@file, "-depth 8")
|
|
570
|
+
expect(File.exist?(result.path)).to be true
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
describe "-interlace" do
|
|
575
|
+
it "sets interlacing mode" do
|
|
576
|
+
# Use JPEG to test interlacing (PNG reports format name instead)
|
|
577
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
578
|
+
thumb = Paperclip::Thumbnail.new(file, {
|
|
579
|
+
geometry: "100x100",
|
|
580
|
+
convert_options: "-interlace Plane",
|
|
581
|
+
backend: :image_magick,
|
|
582
|
+
format: :jpg,
|
|
583
|
+
}, attachment)
|
|
584
|
+
result = thumb.make
|
|
585
|
+
|
|
586
|
+
interlace = `identify -format "%[interlace]" "#{result.path}"`.strip
|
|
587
|
+
# Different ImageMagick versions report this differently
|
|
588
|
+
expect(interlace).to match(/Plane|JPEG|Line|None/i).or eq("")
|
|
589
|
+
file.close
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
describe "-gravity" do
|
|
594
|
+
it "sets gravity for subsequent operations" do
|
|
595
|
+
result = make_thumb_with_options(@file, "-gravity center")
|
|
596
|
+
expect(File.exist?(result.path)).to be true
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
describe "-crop" do
|
|
601
|
+
it "crops the image" do
|
|
602
|
+
# Use a square image for predictable crop results
|
|
603
|
+
file = File.new(fixture_file("50x50.png"), "rb")
|
|
604
|
+
thumb = Paperclip::Thumbnail.new(file, {
|
|
605
|
+
geometry: "50x50", # Keep original size
|
|
606
|
+
convert_options: "-crop 25x25+0+0 +repage",
|
|
607
|
+
backend: :image_magick,
|
|
608
|
+
}, attachment)
|
|
609
|
+
result = thumb.make
|
|
610
|
+
|
|
611
|
+
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
612
|
+
expect(dimensions).to eq("25x25")
|
|
613
|
+
file.close
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
describe "-extent" do
|
|
618
|
+
it "sets the image extent with padding" do
|
|
619
|
+
result = make_thumb_with_options(@file, "-background white -gravity center -extent 150x150")
|
|
620
|
+
|
|
621
|
+
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
622
|
+
expect(dimensions).to eq("150x150")
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
describe "-background" do
|
|
627
|
+
it "sets the background color" do
|
|
628
|
+
result = make_thumb_with_options(@file, "-background red -flatten")
|
|
629
|
+
expect(File.exist?(result.path)).to be true
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
describe "-flatten" do
|
|
634
|
+
it "flattens the image layers" do
|
|
635
|
+
result = make_thumb_with_options(@file, "-flatten")
|
|
636
|
+
expect(File.exist?(result.path)).to be true
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
describe "-alpha" do
|
|
641
|
+
it "modifies alpha channel" do
|
|
642
|
+
result = make_thumb_with_options(@file, "-alpha remove")
|
|
643
|
+
expect(File.exist?(result.path)).to be true
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
describe "-type" do
|
|
648
|
+
it "sets the image type" do
|
|
649
|
+
result = make_thumb_with_options(@file, "-type Grayscale")
|
|
650
|
+
expect(File.exist?(result.path)).to be true
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
describe "-monochrome" do
|
|
655
|
+
it "converts to black and white" do
|
|
656
|
+
result = make_thumb_with_options(@file, "-monochrome")
|
|
657
|
+
expect(File.exist?(result.path)).to be true
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
describe "-posterize" do
|
|
662
|
+
it "reduces color levels" do
|
|
663
|
+
result = make_thumb_with_options(@file, "-posterize 4")
|
|
664
|
+
expect(File.exist?(result.path)).to be true
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
describe "-colors" do
|
|
669
|
+
it "reduces the number of colors" do
|
|
670
|
+
result = make_thumb_with_options(@file, "-colors 16")
|
|
671
|
+
expect(File.exist?(result.path)).to be true
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
describe "-channel" do
|
|
676
|
+
it "selects image channels" do
|
|
677
|
+
result = make_thumb_with_options(@file, "-channel RGB")
|
|
678
|
+
expect(File.exist?(result.path)).to be true
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
describe "-transpose" do
|
|
683
|
+
it "transposes the image" do
|
|
684
|
+
result = make_thumb_with_options(@file, "-transpose")
|
|
685
|
+
expect(File.exist?(result.path)).to be true
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
describe "-transverse" do
|
|
690
|
+
it "transverses the image" do
|
|
691
|
+
result = make_thumb_with_options(@file, "-transverse")
|
|
692
|
+
expect(File.exist?(result.path)).to be true
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
describe "-trim" do
|
|
697
|
+
it "trims image edges" do
|
|
698
|
+
result = make_thumb_with_options(@file, "-trim")
|
|
699
|
+
expect(File.exist?(result.path)).to be true
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
describe "-dither" do
|
|
704
|
+
it "applies dithering" do
|
|
705
|
+
result = make_thumb_with_options(@file, "-dither FloydSteinberg")
|
|
706
|
+
expect(File.exist?(result.path)).to be true
|
|
707
|
+
end
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
describe "-sampling_factor" do
|
|
711
|
+
it "sets chroma subsampling" do
|
|
712
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
713
|
+
result = make_thumb_with_options(file, "-sampling-factor 4:2:0")
|
|
714
|
+
expect(File.exist?(result.path)).to be true
|
|
715
|
+
file.close
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
describe "-units" do
|
|
720
|
+
it "sets resolution units" do
|
|
721
|
+
result = make_thumb_with_options(@file, "-units PixelsPerInch")
|
|
722
|
+
expect(File.exist?(result.path)).to be true
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
describe "unknown options (fallback to append)" do
|
|
727
|
+
it "passes unknown options through to ImageMagick" do
|
|
728
|
+
# -modulate is not in our explicit list, should use append fallback
|
|
729
|
+
result = make_thumb_with_options(@file, "-modulate 100,50,100")
|
|
730
|
+
expect(File.exist?(result.path)).to be true
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
it "handles unknown flag-only options" do
|
|
734
|
+
# Use a harmless option that's not in our list
|
|
735
|
+
result = make_thumb_with_options(@file, "-verbose")
|
|
736
|
+
expect(File.exist?(result.path)).to be true
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
describe "multiple options combined" do
|
|
741
|
+
it "applies multiple options in sequence" do
|
|
742
|
+
result = make_thumb_with_options(@file, "-strip -quality 80 -colorspace Gray -sharpen 0x1")
|
|
743
|
+
|
|
744
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
745
|
+
expect(colorspace).to eq("Gray")
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
it "handles complex option combinations" do
|
|
749
|
+
result = make_thumb_with_options(@file, "-strip -density 72 -depth 8 -colorspace sRGB")
|
|
750
|
+
expect(File.exist?(result.path)).to be true
|
|
751
|
+
|
|
752
|
+
density = `identify -format "%x" "#{result.path}"`.strip
|
|
753
|
+
expect(density).to start_with("72")
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
describe "convert_options - cross-platform options with vips backend" do
|
|
759
|
+
let(:attachment) { double("Attachment", options: {}) }
|
|
760
|
+
|
|
761
|
+
before do
|
|
762
|
+
begin
|
|
763
|
+
require "vips"
|
|
764
|
+
rescue LoadError
|
|
765
|
+
skip "libvips not installed"
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
# Helper to create a thumbnail with vips backend and specific convert_options
|
|
770
|
+
def make_vips_thumb_with_options(file, options_string, extra_opts = {})
|
|
771
|
+
thumb = Paperclip::Thumbnail.new(file, {
|
|
772
|
+
geometry: "100x100",
|
|
773
|
+
convert_options: options_string,
|
|
774
|
+
backend: :vips,
|
|
775
|
+
}.merge(extra_opts), attachment)
|
|
776
|
+
thumb.make
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
describe "-strip" do
|
|
780
|
+
it "removes metadata from the image" do
|
|
781
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
782
|
+
result = make_vips_thumb_with_options(file, "-strip")
|
|
783
|
+
|
|
784
|
+
# Check that EXIF orientation is removed
|
|
785
|
+
exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
|
|
786
|
+
expect(exif).to be_empty
|
|
787
|
+
file.close
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
it "produces a valid image" do
|
|
791
|
+
result = make_vips_thumb_with_options(@file, "-strip")
|
|
792
|
+
expect(File.exist?(result.path)).to be true
|
|
793
|
+
expect(File.size(result.path)).to be > 0
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
describe "-quality" do
|
|
798
|
+
it "sets output quality for JPEG" do
|
|
799
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
800
|
+
result_low = make_vips_thumb_with_options(file, "-quality 20", format: :jpg)
|
|
801
|
+
file.rewind
|
|
802
|
+
result_high = make_vips_thumb_with_options(file, "-quality 95", format: :jpg)
|
|
803
|
+
|
|
804
|
+
# Lower quality should produce smaller file
|
|
805
|
+
expect(File.size(result_low.path)).to be < File.size(result_high.path)
|
|
806
|
+
file.close
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it "produces a valid image" do
|
|
810
|
+
result = make_vips_thumb_with_options(@file, "-quality 80")
|
|
811
|
+
expect(File.exist?(result.path)).to be true
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
describe "-rotate" do
|
|
816
|
+
it "rotates the image by specified degrees" do
|
|
817
|
+
# Original 434x66, after 90 degree rotation dimensions swap
|
|
818
|
+
result = make_vips_thumb_with_options(@file, "-rotate 90")
|
|
819
|
+
|
|
820
|
+
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
821
|
+
# After resize to fit 100x100, then rotate 90 degrees
|
|
822
|
+
# Original aspect ratio is ~6.5:1, fitting in 100x100 gives ~100x15
|
|
823
|
+
# After 90 degree rotation: ~15x100
|
|
824
|
+
expect(dimensions).to match(/\d+x\d+/)
|
|
825
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
826
|
+
# Width should be smaller than height after rotation
|
|
827
|
+
expect(width).to be < height
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
it "rotates by arbitrary angle" do
|
|
831
|
+
result = make_vips_thumb_with_options(@file, "-rotate 45")
|
|
832
|
+
expect(File.exist?(result.path)).to be true
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
describe "-flip" do
|
|
837
|
+
it "flips the image vertically" do
|
|
838
|
+
result = make_vips_thumb_with_options(@file, "-flip")
|
|
839
|
+
expect(File.exist?(result.path)).to be true
|
|
840
|
+
expect(File.size(result.path)).to be > 0
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
describe "-flop" do
|
|
845
|
+
it "flops the image horizontally" do
|
|
846
|
+
result = make_vips_thumb_with_options(@file, "-flop")
|
|
847
|
+
expect(File.exist?(result.path)).to be true
|
|
848
|
+
expect(File.size(result.path)).to be > 0
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
describe "-blur" do
|
|
853
|
+
it "applies gaussian blur to the image" do
|
|
854
|
+
result = make_vips_thumb_with_options(@file, "-blur 0x2")
|
|
855
|
+
expect(File.exist?(result.path)).to be true
|
|
856
|
+
expect(File.size(result.path)).to be > 0
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
it "handles different blur sigma values" do
|
|
860
|
+
result = make_vips_thumb_with_options(@file, "-blur 0x5")
|
|
861
|
+
expect(File.exist?(result.path)).to be true
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
describe "-gaussian_blur" do
|
|
866
|
+
it "applies gaussian blur" do
|
|
867
|
+
result = make_vips_thumb_with_options(@file, "-gaussian-blur 0x3")
|
|
868
|
+
expect(File.exist?(result.path)).to be true
|
|
869
|
+
expect(File.size(result.path)).to be > 0
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
describe "-sharpen" do
|
|
874
|
+
it "sharpens the image" do
|
|
875
|
+
result = make_vips_thumb_with_options(@file, "-sharpen 0x1")
|
|
876
|
+
expect(File.exist?(result.path)).to be true
|
|
877
|
+
expect(File.size(result.path)).to be > 0
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
describe "-colorspace" do
|
|
882
|
+
it "converts to grayscale (b-w)" do
|
|
883
|
+
result = make_vips_thumb_with_options(@file, "-colorspace Gray")
|
|
884
|
+
|
|
885
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
886
|
+
expect(colorspace).to eq("Gray")
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
it "converts to sRGB" do
|
|
890
|
+
result = make_vips_thumb_with_options(@file, "-colorspace sRGB")
|
|
891
|
+
|
|
892
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
893
|
+
expect(colorspace).to eq("sRGB")
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
it "converts to CMYK" do
|
|
897
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
898
|
+
result = make_vips_thumb_with_options(file, "-colorspace CMYK", format: :jpg)
|
|
899
|
+
|
|
900
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
901
|
+
expect(colorspace).to eq("CMYK")
|
|
902
|
+
file.close
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
describe "-flatten" do
|
|
907
|
+
it "flattens transparency to white background" do
|
|
908
|
+
result = make_vips_thumb_with_options(@file, "-flatten")
|
|
909
|
+
expect(File.exist?(result.path)).to be true
|
|
910
|
+
expect(File.size(result.path)).to be > 0
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
describe "-negate" do
|
|
915
|
+
it "inverts the image colors" do
|
|
916
|
+
result = make_vips_thumb_with_options(@file, "-negate")
|
|
917
|
+
expect(File.exist?(result.path)).to be true
|
|
918
|
+
expect(File.size(result.path)).to be > 0
|
|
919
|
+
end
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
describe "-auto-orient" do
|
|
923
|
+
it "auto-orients the image based on EXIF" do
|
|
924
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
925
|
+
result = make_vips_thumb_with_options(file, "-auto-orient")
|
|
926
|
+
expect(File.exist?(result.path)).to be true
|
|
927
|
+
|
|
928
|
+
# The rotated.jpg has orientation 6 (90 CW), so auto-orient should correct it
|
|
929
|
+
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
930
|
+
width, height = dimensions.split("x").map(&:to_i)
|
|
931
|
+
# After auto-orient, portrait orientation should be maintained
|
|
932
|
+
expect(height).to be > width
|
|
933
|
+
file.close
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
describe "-interlace" do
|
|
938
|
+
it "creates progressive/interlaced output for JPEG" do
|
|
939
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
940
|
+
result = make_vips_thumb_with_options(file, "-interlace Plane", format: :jpg)
|
|
941
|
+
expect(File.exist?(result.path)).to be true
|
|
942
|
+
expect(File.size(result.path)).to be > 0
|
|
943
|
+
file.close
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
it "creates interlaced output for PNG" do
|
|
947
|
+
result = make_vips_thumb_with_options(@file, "-interlace Line", format: :png)
|
|
948
|
+
expect(File.exist?(result.path)).to be true
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
describe "multiple cross-platform options combined" do
|
|
953
|
+
it "applies multiple options in sequence" do
|
|
954
|
+
result = make_vips_thumb_with_options(@file, "-strip -quality 80 -colorspace Gray")
|
|
955
|
+
|
|
956
|
+
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
957
|
+
expect(colorspace).to eq("Gray")
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
it "combines flip, flop, and rotate" do
|
|
961
|
+
result = make_vips_thumb_with_options(@file, "-flip -flop -rotate 180")
|
|
962
|
+
expect(File.exist?(result.path)).to be true
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
it "applies strip with quality and sharpen" do
|
|
966
|
+
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
967
|
+
result = make_vips_thumb_with_options(file, "-strip -quality 85 -sharpen 0x1", format: :jpg)
|
|
968
|
+
|
|
969
|
+
# Verify EXIF is stripped
|
|
970
|
+
exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
|
|
971
|
+
expect(exif).to be_empty
|
|
972
|
+
file.close
|
|
973
|
+
end
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
describe "ImageMagick-only options with vips (should warn)" do
|
|
977
|
+
it "logs warning for -density" do
|
|
978
|
+
expect(Paperclip).to receive(:log).with(/Warning.*density.*not supported.*vips/)
|
|
979
|
+
make_vips_thumb_with_options(@file, "-density 150")
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
it "logs warning for -depth" do
|
|
983
|
+
expect(Paperclip).to receive(:log).with(/Warning.*depth.*not supported.*vips/)
|
|
984
|
+
make_vips_thumb_with_options(@file, "-depth 8")
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
it "logs warning for -gravity" do
|
|
988
|
+
expect(Paperclip).to receive(:log).with(/Warning.*gravity.*not supported.*vips/)
|
|
989
|
+
make_vips_thumb_with_options(@file, "-gravity center")
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
it "logs warning for -crop" do
|
|
993
|
+
expect(Paperclip).to receive(:log).with(/Warning.*crop.*not supported.*vips/)
|
|
994
|
+
make_vips_thumb_with_options(@file, "-crop 50x50+0+0")
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
it "logs warning for -trim" do
|
|
998
|
+
expect(Paperclip).to receive(:log).with(/Warning.*trim.*not supported.*vips/)
|
|
999
|
+
make_vips_thumb_with_options(@file, "-trim")
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
it "logs warning for -normalize" do
|
|
1003
|
+
expect(Paperclip).to receive(:log).with(/Warning.*normalize.*not supported.*vips/)
|
|
1004
|
+
make_vips_thumb_with_options(@file, "-normalize")
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
it "logs warning for -monochrome" do
|
|
1008
|
+
expect(Paperclip).to receive(:log).with(/Warning.*monochrome.*not supported.*vips/)
|
|
1009
|
+
make_vips_thumb_with_options(@file, "-monochrome")
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
[%w[600x600> 434x66],
|
|
1015
|
+
%w[400x400> 400x61],
|
|
1016
|
+
%w[32x32< 434x66],
|
|
14
1017
|
[nil, "434x66"]].each do |args|
|
|
15
1018
|
context "being thumbnailed with a geometry of #{args[0]}" do
|
|
16
1019
|
before do
|
|
@@ -44,22 +1047,6 @@ describe Paperclip::Thumbnail do
|
|
|
44
1047
|
@thumb = Paperclip::Thumbnail.new(@file, geometry: "100x50#")
|
|
45
1048
|
end
|
|
46
1049
|
|
|
47
|
-
it "lets us know when a command isn't found versus a processing error" do
|
|
48
|
-
old_path = ENV["PATH"]
|
|
49
|
-
begin
|
|
50
|
-
Terrapin::CommandLine.path = ""
|
|
51
|
-
Paperclip.options[:command_path] = ""
|
|
52
|
-
ENV["PATH"] = ""
|
|
53
|
-
assert_raises(Paperclip::Errors::CommandNotFoundError) do
|
|
54
|
-
silence_stream(STDERR) do
|
|
55
|
-
@thumb.make
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
ensure
|
|
59
|
-
ENV["PATH"] = old_path
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
1050
|
it "reports its correct current and target geometries" do
|
|
64
1051
|
assert_equal "100x50#", @thumb.target_geometry.to_s
|
|
65
1052
|
assert_equal "434x66", @thumb.current_geometry.to_s
|
|
@@ -81,14 +1068,6 @@ describe Paperclip::Thumbnail do
|
|
|
81
1068
|
assert_equal nil, @thumb.source_file_options
|
|
82
1069
|
end
|
|
83
1070
|
|
|
84
|
-
it "sends the right command to convert when sent #make" do
|
|
85
|
-
expect(@thumb).to receive(:convert) do |*arg|
|
|
86
|
-
arg[0] == ':source -auto-orient -resize "x50" -crop "100x50+114+0" +repage :dest' &&
|
|
87
|
-
arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]"
|
|
88
|
-
end
|
|
89
|
-
@thumb.make
|
|
90
|
-
end
|
|
91
|
-
|
|
92
1071
|
it "creates the thumbnail when sent #make" do
|
|
93
1072
|
dst = @thumb.make
|
|
94
1073
|
assert_match /100x50/, `identify "#{dst.path}"`
|
|
@@ -107,21 +1086,14 @@ describe Paperclip::Thumbnail do
|
|
|
107
1086
|
|
|
108
1087
|
context "being thumbnailed with source file options set" do
|
|
109
1088
|
before do
|
|
1089
|
+
@file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
110
1090
|
@thumb = Paperclip::Thumbnail.new(@file,
|
|
111
1091
|
geometry: "100x50#",
|
|
112
|
-
source_file_options: "-
|
|
1092
|
+
source_file_options: "-density 300")
|
|
113
1093
|
end
|
|
114
1094
|
|
|
115
1095
|
it "has source_file_options value set" do
|
|
116
|
-
assert_equal
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
it "sends the right command to convert when sent #make" do
|
|
120
|
-
expect(@thumb).to receive(:convert) do |*arg|
|
|
121
|
-
arg[0] == '-strip :source -auto-orient -resize "x50" -crop "100x50+114+0" +repage :dest' &&
|
|
122
|
-
arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]"
|
|
123
|
-
end
|
|
124
|
-
@thumb.make
|
|
1096
|
+
assert_equal "-density 300", @thumb.source_file_options
|
|
125
1097
|
end
|
|
126
1098
|
|
|
127
1099
|
it "creates the thumbnail when sent #make" do
|
|
@@ -129,40 +1101,24 @@ describe Paperclip::Thumbnail do
|
|
|
129
1101
|
assert_match /100x50/, `identify "#{dst.path}"`
|
|
130
1102
|
end
|
|
131
1103
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
it "errors when trying to create the thumbnail" do
|
|
140
|
-
assert_raises(Paperclip::Error) do
|
|
141
|
-
silence_stream(STDERR) do
|
|
142
|
-
@thumb.make
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
1104
|
+
it "actually applies the source file options (sets density)" do
|
|
1105
|
+
# Verify result has the set density
|
|
1106
|
+
dst = @thumb.make
|
|
1107
|
+
cmd_new = %[identify -format "%x" "#{dst.path}"]
|
|
1108
|
+
expect(`#{cmd_new}`.chomp).to start_with("300")
|
|
146
1109
|
end
|
|
147
1110
|
end
|
|
148
1111
|
|
|
149
1112
|
context "being thumbnailed with convert options set" do
|
|
150
1113
|
before do
|
|
1114
|
+
@file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
151
1115
|
@thumb = Paperclip::Thumbnail.new(@file,
|
|
152
1116
|
geometry: "100x50#",
|
|
153
|
-
convert_options: "-strip
|
|
1117
|
+
convert_options: "-strip")
|
|
154
1118
|
end
|
|
155
1119
|
|
|
156
1120
|
it "has convert_options value set" do
|
|
157
|
-
assert_equal
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
it "sends the right command to convert when sent #make" do
|
|
161
|
-
expect(@thumb).to receive(:convert) do |*arg|
|
|
162
|
-
arg[0] == ':source -auto-orient -resize "x50" -crop "100x50+114+0" +repage -strip -depth 8 :dest' &&
|
|
163
|
-
arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]"
|
|
164
|
-
end
|
|
165
|
-
@thumb.make
|
|
1121
|
+
assert_equal "-strip", @thumb.convert_options
|
|
166
1122
|
end
|
|
167
1123
|
|
|
168
1124
|
it "creates the thumbnail when sent #make" do
|
|
@@ -170,37 +1126,57 @@ describe Paperclip::Thumbnail do
|
|
|
170
1126
|
assert_match /100x50/, `identify "#{dst.path}"`
|
|
171
1127
|
end
|
|
172
1128
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1129
|
+
it "actually applies the convert options (removes EXIF data)" do
|
|
1130
|
+
# Verify original has EXIF
|
|
1131
|
+
cmd_orig = %[identify -format "%[exif:orientation]" "#{@file.path}"]
|
|
1132
|
+
expect(`#{cmd_orig}`.chomp).to_not be_empty
|
|
1133
|
+
|
|
1134
|
+
# Verify result has no EXIF
|
|
1135
|
+
dst = @thumb.make
|
|
1136
|
+
cmd_new = %[identify -format "%[exif:orientation]" "#{dst.path}"]
|
|
1137
|
+
expect(`#{cmd_new}`.chomp).to be_empty
|
|
1138
|
+
end
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
context "error handling" do
|
|
1142
|
+
before do
|
|
1143
|
+
require "image_processing"
|
|
1144
|
+
# Use a valid image so initialization (geometry detection) succeeds
|
|
1145
|
+
@file = File.new(fixture_file("5k.png"), "rb")
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
context "with whiny enabled (default)" do
|
|
1149
|
+
it "raises an error when processing fails" do
|
|
1150
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
1151
|
+
allow(thumb).to receive(:build_pipeline).and_raise(Paperclip::Errors::CommandNotFoundError)
|
|
1152
|
+
|
|
1153
|
+
expect { thumb.make }.to raise_error(Paperclip::Errors::CommandNotFoundError)
|
|
188
1154
|
end
|
|
189
1155
|
|
|
190
|
-
it "
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
1156
|
+
it "raises a Paperclip::Error when an underlying processing error occurs" do
|
|
1157
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
1158
|
+
# Simulate a generic processing error
|
|
1159
|
+
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1160
|
+
|
|
1161
|
+
expect { thumb.make }.to raise_error(Paperclip::Error, /Processing failed/)
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
context "with whiny disabled" do
|
|
1166
|
+
it "returns the original file when processing fails" do
|
|
1167
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
|
|
1168
|
+
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1169
|
+
|
|
1170
|
+
result = thumb.make
|
|
1171
|
+
expect(result).to eq(@file)
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
it "logs the error" do
|
|
1175
|
+
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
|
|
1176
|
+
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1177
|
+
|
|
1178
|
+
expect(Paperclip).to receive(:log).with(/Processing failed/)
|
|
1179
|
+
thumb.make
|
|
204
1180
|
end
|
|
205
1181
|
end
|
|
206
1182
|
end
|
|
@@ -209,22 +1185,17 @@ describe Paperclip::Thumbnail do
|
|
|
209
1185
|
before do
|
|
210
1186
|
@thumb = Paperclip::Thumbnail.new(@file,
|
|
211
1187
|
geometry: "",
|
|
212
|
-
convert_options: "-gravity center -crop
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
it "does not get resized by default" do
|
|
216
|
-
assert !@thumb.transformation_command.include?("-resize")
|
|
1188
|
+
convert_options: "-gravity center -crop 300x300+0-0")
|
|
217
1189
|
end
|
|
218
|
-
end
|
|
219
1190
|
|
|
220
|
-
|
|
221
|
-
it "
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1191
|
+
# Verify that when geometry is blank, we don't resize, but still apply other options.
|
|
1192
|
+
it "does not resize the image via geometry but applies convert_options" do
|
|
1193
|
+
result = @thumb.make
|
|
1194
|
+
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
1195
|
+
# Original size is 434x66. The crop option (300x300) should result in 300x66.
|
|
1196
|
+
# This confirms that no geometry-based resize happened (which would have been to 0x0 or skipped),
|
|
1197
|
+
# but the convert_options crop was applied.
|
|
1198
|
+
expect(`#{cmd}`.chomp).to eq("300x66")
|
|
228
1199
|
end
|
|
229
1200
|
end
|
|
230
1201
|
|
|
@@ -233,29 +1204,20 @@ describe Paperclip::Thumbnail do
|
|
|
233
1204
|
Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
|
|
234
1205
|
end
|
|
235
1206
|
|
|
236
|
-
it "
|
|
1207
|
+
it "uses the custom parser" do
|
|
237
1208
|
GeoParser = Class.new do
|
|
238
|
-
def self.from_file(_file)
|
|
1209
|
+
def self.from_file(_file, _backend = nil)
|
|
239
1210
|
new
|
|
240
1211
|
end
|
|
241
1212
|
|
|
242
|
-
def
|
|
243
|
-
|
|
244
|
-
end
|
|
1213
|
+
def width; 100; end
|
|
1214
|
+
def height; 100; end
|
|
1215
|
+
def modifier; nil; end
|
|
1216
|
+
def auto_orient; end
|
|
245
1217
|
end
|
|
246
1218
|
|
|
247
1219
|
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", file_geometry_parser: ::GeoParser)
|
|
248
|
-
|
|
249
|
-
transformation_command = thumb.transformation_command
|
|
250
|
-
|
|
251
|
-
assert transformation_command.include?("-crop"),
|
|
252
|
-
%{expected #{transformation_command.inspect} to include '-crop'}
|
|
253
|
-
assert transformation_command.include?('"CROP"'),
|
|
254
|
-
%{expected #{transformation_command.inspect} to include '"CROP"'}
|
|
255
|
-
assert transformation_command.include?("-resize"),
|
|
256
|
-
%{expected #{transformation_command.inspect} to include '-resize'}
|
|
257
|
-
assert transformation_command.include?('"SCALE"'),
|
|
258
|
-
%{expected #{transformation_command.inspect} to include '"SCALE"'}
|
|
1220
|
+
expect(thumb.current_geometry).to be_a(GeoParser)
|
|
259
1221
|
end
|
|
260
1222
|
end
|
|
261
1223
|
|
|
@@ -264,7 +1226,7 @@ describe Paperclip::Thumbnail do
|
|
|
264
1226
|
Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
|
|
265
1227
|
end
|
|
266
1228
|
|
|
267
|
-
it "
|
|
1229
|
+
it "uses the custom parser" do
|
|
268
1230
|
GeoParser = Class.new do
|
|
269
1231
|
def self.parse(_s)
|
|
270
1232
|
new
|
|
@@ -273,14 +1235,14 @@ describe Paperclip::Thumbnail do
|
|
|
273
1235
|
def to_s
|
|
274
1236
|
"151x167"
|
|
275
1237
|
end
|
|
1238
|
+
|
|
1239
|
+
def width; 151; end
|
|
1240
|
+
def height; 167; end
|
|
1241
|
+
def modifier; nil; end
|
|
276
1242
|
end
|
|
277
1243
|
|
|
278
1244
|
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", string_geometry_parser: ::GeoParser)
|
|
279
|
-
|
|
280
|
-
transformation_command = thumb.transformation_command
|
|
281
|
-
|
|
282
|
-
assert transformation_command.include?('"151x167"'),
|
|
283
|
-
%{expected #{transformation_command.inspect} to include '151x167'}
|
|
1245
|
+
expect(thumb.target_geometry).to be_a(GeoParser)
|
|
284
1246
|
end
|
|
285
1247
|
end
|
|
286
1248
|
end
|
|
@@ -354,14 +1316,6 @@ describe Paperclip::Thumbnail do
|
|
|
354
1316
|
assert_equal 12, frames.size
|
|
355
1317
|
assert_frame_dimensions (45..50), frames
|
|
356
1318
|
end
|
|
357
|
-
|
|
358
|
-
it "uses the -coalesce option" do
|
|
359
|
-
assert_equal @thumb.transformation_command.first, "-coalesce"
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
it "uses the -layers 'optimize' option" do
|
|
363
|
-
assert_equal @thumb.transformation_command.last, '-layers "optimize"'
|
|
364
|
-
end
|
|
365
1319
|
end
|
|
366
1320
|
|
|
367
1321
|
context "with omitted output format" do
|
|
@@ -373,17 +1327,19 @@ describe Paperclip::Thumbnail do
|
|
|
373
1327
|
dst = @thumb.make
|
|
374
1328
|
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
375
1329
|
frames = `#{cmd}`.chomp.split(",")
|
|
1330
|
+
# image_processing might not preserve animation by default if not explicitly told to
|
|
1331
|
+
# or if the format is not explicitly set to gif.
|
|
1332
|
+
# But here we are testing default behavior.
|
|
1333
|
+
# If it fails with 1 frame, it means it collapsed it.
|
|
1334
|
+
# The original implementation preserved it.
|
|
1335
|
+
# We might need to force loader options for animated gifs if we want to preserve layers.
|
|
1336
|
+
# But image_processing/mini_magick should handle it if we don't flatten.
|
|
1337
|
+
|
|
1338
|
+
# If this fails, we might need to adjust the test expectation or implementation.
|
|
1339
|
+
# For now, let's assume we want to preserve behavior.
|
|
376
1340
|
assert_equal 12, frames.size
|
|
377
1341
|
assert_frame_dimensions (45..50), frames
|
|
378
1342
|
end
|
|
379
|
-
|
|
380
|
-
it "uses the -coalesce option" do
|
|
381
|
-
assert_equal @thumb.transformation_command.first, "-coalesce"
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
it "uses the -layers 'optimize' option" do
|
|
385
|
-
assert_equal @thumb.transformation_command.last, '-layers "optimize"'
|
|
386
|
-
end
|
|
387
1343
|
end
|
|
388
1344
|
|
|
389
1345
|
context "with unidentified source format" do
|
|
@@ -399,14 +1355,6 @@ describe Paperclip::Thumbnail do
|
|
|
399
1355
|
assert_equal 12, frames.size
|
|
400
1356
|
assert_frame_dimensions (55..60), frames
|
|
401
1357
|
end
|
|
402
|
-
|
|
403
|
-
it "uses the -coalesce option" do
|
|
404
|
-
assert_equal @thumb.transformation_command.first, "-coalesce"
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
it "uses the -layers 'optimize' option" do
|
|
408
|
-
assert_equal @thumb.transformation_command.last, '-layers "optimize"'
|
|
409
|
-
end
|
|
410
1358
|
end
|
|
411
1359
|
|
|
412
1360
|
context "with no source format" do
|
|
@@ -422,14 +1370,6 @@ describe Paperclip::Thumbnail do
|
|
|
422
1370
|
assert_equal 12, frames.size
|
|
423
1371
|
assert_frame_dimensions (60..70), frames
|
|
424
1372
|
end
|
|
425
|
-
|
|
426
|
-
it "uses the -coalesce option" do
|
|
427
|
-
assert_equal @thumb.transformation_command.first, "-coalesce"
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
it "uses the -layers 'optimize' option" do
|
|
431
|
-
assert_equal @thumb.transformation_command.last, '-layers "optimize"'
|
|
432
|
-
end
|
|
433
1373
|
end
|
|
434
1374
|
|
|
435
1375
|
context "with animated option set to false" do
|
|
@@ -446,7 +1386,14 @@ describe Paperclip::Thumbnail do
|
|
|
446
1386
|
it "creates the single frame thumbnail when sent #make" do
|
|
447
1387
|
dst = @thumb.make
|
|
448
1388
|
cmd = %[identify -format "%wx%h" "#{dst.path}"]
|
|
449
|
-
|
|
1389
|
+
# The output might be multiple frames if image_processing doesn't collapse them
|
|
1390
|
+
# But we expect single frame here.
|
|
1391
|
+
# If it fails, we might need to adjust the implementation to force single frame.
|
|
1392
|
+
# For now, let's check if it starts with 50x50.
|
|
1393
|
+
output = `#{cmd}`.chomp
|
|
1394
|
+
# If multiple frames, it will be 50x5050x50...
|
|
1395
|
+
# We want exactly 50x50
|
|
1396
|
+
assert_equal "50x50", output
|
|
450
1397
|
end
|
|
451
1398
|
end
|
|
452
1399
|
|
|
@@ -466,22 +1413,30 @@ describe Paperclip::Thumbnail do
|
|
|
466
1413
|
end
|
|
467
1414
|
end
|
|
468
1415
|
|
|
469
|
-
context "with
|
|
1416
|
+
context "with vips backend" do
|
|
470
1417
|
before do
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
)
|
|
1418
|
+
begin
|
|
1419
|
+
require "vips"
|
|
1420
|
+
rescue LoadError
|
|
1421
|
+
skip "libvips not installed"
|
|
1422
|
+
end
|
|
477
1423
|
end
|
|
478
1424
|
|
|
479
|
-
it "
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
1425
|
+
it "preserves animation when output is GIF" do
|
|
1426
|
+
processor = Paperclip::Thumbnail.new(@file, { geometry: "50x50", format: :gif, backend: :vips })
|
|
1427
|
+
dst = processor.make
|
|
1428
|
+
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1429
|
+
frames = `#{cmd}`.chomp.split(",")
|
|
1430
|
+
expect(frames.size).to eq(12)
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1433
|
+
it "collapses animation when animated: false is set" do
|
|
1434
|
+
processor = Paperclip::Thumbnail.new(@file, { geometry: "50x50", format: :gif, animated: false, backend: :vips })
|
|
1435
|
+
dst = processor.make
|
|
1436
|
+
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1437
|
+
frames = `#{cmd}`.chomp.split(",").reject(&:empty?)
|
|
1438
|
+
expect(frames.size).to eq(1)
|
|
1439
|
+
expect(frames.first).to eq("50x50")
|
|
485
1440
|
end
|
|
486
1441
|
end
|
|
487
1442
|
end
|