jr-paperclip 8.0.2 → 8.0.3

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS +4 -0
  3. data/lib/paperclip/version.rb +1 -1
  4. metadata +3 -247
  5. data/.github/FUNDING.yml +0 -3
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -18
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  8. data/.github/workflows/reviewdog.yml +0 -23
  9. data/.github/workflows/tests.yml +0 -56
  10. data/.gitignore +0 -19
  11. data/.qlty/.gitignore +0 -7
  12. data/.qlty/qlty.toml +0 -89
  13. data/Appraisals +0 -29
  14. data/Gemfile +0 -18
  15. data/bin/console +0 -11
  16. data/features/basic_integration.feature +0 -112
  17. data/features/migration.feature +0 -29
  18. data/features/rake_tasks.feature +0 -62
  19. data/features/step_definitions/attachment_steps.rb +0 -138
  20. data/features/step_definitions/html_steps.rb +0 -15
  21. data/features/step_definitions/rails_steps.rb +0 -271
  22. data/features/step_definitions/s3_steps.rb +0 -16
  23. data/features/step_definitions/web_steps.rb +0 -106
  24. data/features/support/env.rb +0 -12
  25. data/features/support/file_helpers.rb +0 -34
  26. data/features/support/fixtures/boot_config.txt +0 -15
  27. data/features/support/fixtures/gemfile.txt +0 -5
  28. data/features/support/fixtures/preinitializer.txt +0 -20
  29. data/features/support/paths.rb +0 -28
  30. data/features/support/rails.rb +0 -39
  31. data/features/support/selectors.rb +0 -19
  32. data/features/support/webmock_setup.rb +0 -8
  33. data/gemfiles/7.0.gemfile +0 -21
  34. data/gemfiles/7.1.gemfile +0 -21
  35. data/gemfiles/7.2.gemfile +0 -21
  36. data/gemfiles/8.0.gemfile +0 -21
  37. data/gemfiles/8.1.gemfile +0 -21
  38. data/paperclip.gemspec +0 -52
  39. data/spec/database.yml +0 -4
  40. data/spec/paperclip/attachment_definitions_spec.rb +0 -313
  41. data/spec/paperclip/attachment_processing_spec.rb +0 -79
  42. data/spec/paperclip/attachment_registry_spec.rb +0 -158
  43. data/spec/paperclip/attachment_spec.rb +0 -1617
  44. data/spec/paperclip/content_type_detector_spec.rb +0 -58
  45. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -40
  46. data/spec/paperclip/filename_cleaner_spec.rb +0 -13
  47. data/spec/paperclip/geometry_detector_spec.rb +0 -96
  48. data/spec/paperclip/geometry_parser_spec.rb +0 -73
  49. data/spec/paperclip/geometry_spec.rb +0 -270
  50. data/spec/paperclip/glue_spec.rb +0 -63
  51. data/spec/paperclip/has_attached_file_spec.rb +0 -78
  52. data/spec/paperclip/helpers_spec.rb +0 -49
  53. data/spec/paperclip/integration_spec.rb +0 -702
  54. data/spec/paperclip/interpolations_spec.rb +0 -270
  55. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +0 -160
  56. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +0 -167
  57. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +0 -88
  58. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +0 -17
  59. data/spec/paperclip/io_adapters/file_adapter_spec.rb +0 -134
  60. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +0 -142
  61. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +0 -8
  62. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +0 -25
  63. data/spec/paperclip/io_adapters/registry_spec.rb +0 -35
  64. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +0 -64
  65. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +0 -146
  66. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +0 -231
  67. data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +0 -266
  68. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +0 -19
  69. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +0 -108
  70. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +0 -69
  71. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +0 -88
  72. data/spec/paperclip/media_type_spoof_detector_spec.rb +0 -126
  73. data/spec/paperclip/meta_class_spec.rb +0 -30
  74. data/spec/paperclip/migration_guide_example_spec.rb +0 -44
  75. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +0 -88
  76. data/spec/paperclip/paperclip_spec.rb +0 -196
  77. data/spec/paperclip/plural_cache_spec.rb +0 -37
  78. data/spec/paperclip/processor_helpers_spec.rb +0 -57
  79. data/spec/paperclip/processor_spec.rb +0 -60
  80. data/spec/paperclip/rails_environment_spec.rb +0 -30
  81. data/spec/paperclip/rake_spec.rb +0 -103
  82. data/spec/paperclip/schema_spec.rb +0 -298
  83. data/spec/paperclip/storage/filesystem_spec.rb +0 -102
  84. data/spec/paperclip/storage/fog_spec.rb +0 -606
  85. data/spec/paperclip/storage/s3_live_spec.rb +0 -188
  86. data/spec/paperclip/storage/s3_spec.rb +0 -1974
  87. data/spec/paperclip/style_spec.rb +0 -309
  88. data/spec/paperclip/tempfile_factory_spec.rb +0 -33
  89. data/spec/paperclip/tempfile_spec.rb +0 -35
  90. data/spec/paperclip/thumbnail_custom_options_spec.rb +0 -225
  91. data/spec/paperclip/thumbnail_loader_options_spec.rb +0 -53
  92. data/spec/paperclip/thumbnail_security_spec.rb +0 -42
  93. data/spec/paperclip/thumbnail_spec.rb +0 -1488
  94. data/spec/paperclip/url_generator_spec.rb +0 -231
  95. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +0 -410
  96. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +0 -249
  97. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +0 -85
  98. data/spec/paperclip/validators/attachment_size_validator_spec.rb +0 -325
  99. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +0 -48
  100. data/spec/paperclip/validators_spec.rb +0 -179
  101. data/spec/spec_helper.rb +0 -52
  102. data/spec/support/assertions.rb +0 -84
  103. data/spec/support/fake_model.rb +0 -24
  104. data/spec/support/fake_rails.rb +0 -12
  105. data/spec/support/fixtures/12k.png +0 -0
  106. data/spec/support/fixtures/50x50.png +0 -0
  107. data/spec/support/fixtures/5k.png +0 -0
  108. data/spec/support/fixtures/animated +0 -0
  109. data/spec/support/fixtures/animated.gif +0 -0
  110. data/spec/support/fixtures/animated.unknown +0 -0
  111. data/spec/support/fixtures/aws_s3.yml +0 -13
  112. data/spec/support/fixtures/bad.png +0 -1
  113. data/spec/support/fixtures/big_image.jpg +0 -0
  114. data/spec/support/fixtures/empty.html +0 -1
  115. data/spec/support/fixtures/empty.xlsx +0 -0
  116. data/spec/support/fixtures/fog.yml +0 -8
  117. data/spec/support/fixtures/rotated.jpg +0 -0
  118. data/spec/support/fixtures/s3.yml +0 -8
  119. data/spec/support/fixtures/sample.xlsm +0 -0
  120. data/spec/support/fixtures/spaced file.jpg +0 -0
  121. data/spec/support/fixtures/spaced file.png +0 -0
  122. data/spec/support/fixtures/text.txt +0 -1
  123. data/spec/support/fixtures/twopage.pdf +0 -0
  124. data/spec/support/fixtures/uppercase.PNG +0 -0
  125. data/spec/support/matchers/accept.rb +0 -5
  126. data/spec/support/matchers/exist.rb +0 -5
  127. data/spec/support/matchers/have_column.rb +0 -23
  128. data/spec/support/mock_attachment.rb +0 -24
  129. data/spec/support/mock_interpolator.rb +0 -24
  130. data/spec/support/mock_url_generator_builder.rb +0 -26
  131. data/spec/support/model_reconstruction.rb +0 -72
  132. data/spec/support/reporting.rb +0 -11
  133. data/spec/support/test_data.rb +0 -13
  134. data/spec/support/version_helper.rb +0 -9
@@ -1,1488 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Paperclip::Thumbnail do
4
- context "An image" do
5
- before do
6
- @file = File.new(fixture_file("5k.png"), "rb")
7
- end
8
-
9
- after { @file.close }
10
-
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
- require "vips"
190
- rescue LoadError
191
- skip "libvips not installed"
192
- end
193
-
194
- it "resizes image to specified dimensions" do
195
- processor = described_class.new(@file, { geometry: "25x25>", backend: :vips }, attachment)
196
- result = processor.make
197
-
198
- cmd = %[identify -format "%wx%h" "#{result.path}"]
199
- expect(`#{cmd}`.chomp).to eq("25x4")
200
- end
201
-
202
- it "crops to fill with # modifier" do
203
- processor = described_class.new(@file, { geometry: "30x20#", backend: :vips }, attachment)
204
- result = processor.make
205
-
206
- cmd = %[identify -format "%wx%h" "#{result.path}"]
207
- expect(`#{cmd}`.chomp).to eq("30x20")
208
- end
209
-
210
- it "auto-orients an image using autorot" do
211
- file = File.new(fixture_file("rotated.jpg"), "rb")
212
- processor = described_class.new(file, { geometry: "50x50", backend: :vips }, attachment)
213
-
214
- # Verify it detects logical dimensions (rotated from 300x200 to 200x300)
215
- expect(processor.current_geometry.width).to eq(200)
216
- result = processor.make
217
-
218
- cmd = %[identify -format "%wx%h" "#{result.path}"]
219
- # Original 300x200 with orientation 6 (90 deg CW).
220
- # Post autorot: 200x300.
221
- # Resize to fit 50x50: 33x50.
222
- expect(`#{cmd}`.chomp).to eq("33x50")
223
- end
224
-
225
- it "strips metadata when requested via convert_options" do
226
- processor = described_class.new(@file, { geometry: "50x50", convert_options: "-strip", backend: :vips },
227
- attachment)
228
- result = processor.make
229
-
230
- # identify -verbose shows less output when stripped
231
- expect(`identify -verbose "#{result.path}"`).not_to include("exif:")
232
- end
233
-
234
- it "handles exact dimensions with ! modifier" do
235
- processor = described_class.new(@file, { geometry: "100x50!", backend: :vips }, attachment)
236
- result = processor.make
237
-
238
- cmd = %[identify -format "%wx%h" "#{result.path}"]
239
- expect(`#{cmd}`.chomp).to eq("100x50")
240
- end
241
-
242
- it "stretches the image with ! modifier matching ImageMagick behavior" do
243
- # Create a 3-stripe image: Red, Green, Blue
244
- stripe_file = Tempfile.new(["stripe", ".png"])
245
- Paperclip.run("convert", "-size 100x100 xc:red xc:green xc:blue +append #{stripe_file.path}")
246
- file = File.new(stripe_file.path, "rb")
247
-
248
- processor = described_class.new(file, { geometry: "100x100!", backend: :vips }, attachment)
249
- result = processor.make
250
-
251
- # Check color at x=10 (Red stripe).
252
- # If cropped (Green center), it would be Green.
253
- # If stretched, it is Red.
254
- color = Paperclip.run("convert", "#{result.path}[1x1+10+50] -format \"%[pixel:p{0,0}]\" info:")
255
- expect(color).to match(/red|#FF0000|rgb\(255,0,0\)|srgb\(255,0,0\)/i)
256
-
257
- file.close
258
- stripe_file.close
259
- stripe_file.unlink
260
- end
261
-
262
- it "handles percentage with % modifier" do
263
- processor = described_class.new(@file, { geometry: "50%", backend: :vips }, attachment)
264
- result = processor.make
265
-
266
- cmd = %[identify -format "%wx%h" "#{result.path}"]
267
- # Original is 434x66. 50% is 217x33.
268
- expect(`#{cmd}`.chomp).to eq("217x33")
269
- end
270
-
271
- it "handles minimum dimensions with ^ modifier" do
272
- processor = described_class.new(@file, { geometry: "100x100^", backend: :vips }, attachment)
273
- result = processor.make
274
-
275
- cmd = %[identify -format "%wx%h" "#{result.path}"]
276
- # Original 434x66.
277
- # Resize to fill 100x100 means height becomes 100, width becomes 434 * (100/66) = 658.
278
- expect(`#{cmd}`.chomp).to eq("658x100")
279
- end
280
-
281
- it "handles enlarge only with < modifier" do
282
- # Smaller image: 50x50.png
283
- small_file = File.new(fixture_file("50x50.png"), "rb")
284
- processor = described_class.new(small_file, { geometry: "100x100<", backend: :vips }, attachment)
285
- result = processor.make
286
-
287
- cmd = %[identify -format "%wx%h" "#{result.path}"]
288
- expect(`#{cmd}`.chomp).to eq("100x100")
289
-
290
- # Larger image: 5k.png (434x66)
291
- processor = described_class.new(@file, { geometry: "100x100<", backend: :vips }, attachment)
292
- result = processor.make
293
- expect(`identify -format "%wx%h" "#{result.path}"`.chomp).to eq("434x66")
294
- end
295
-
296
- it "takes only the first frame of a PDF by default" do
297
- pdf_file = File.new(fixture_file("twopage.pdf"), "rb")
298
- processor = described_class.new(pdf_file, { geometry: "100x100", format: :png, backend: :vips }, attachment)
299
- result = processor.make
300
-
301
- cmd = %[identify -format "%n" "#{result.path}"]
302
- expect(`#{cmd}`.chomp).to eq("1")
303
- end
304
-
305
- it "detects animated source correctly" do
306
- animated_file = File.new(fixture_file("animated.gif"), "rb")
307
- processor = described_class.new(animated_file, { geometry: "50x50", backend: :vips }, attachment)
308
- expect(processor.send(:animated_source?)).to be true
309
-
310
- static_file = File.new(fixture_file("5k.png"), "rb")
311
- processor = described_class.new(static_file, { geometry: "50x50", backend: :vips }, attachment)
312
- expect(processor.send(:animated_source?)).to be false
313
- end
314
-
315
- it "handles area-based resize with @ modifier" do
316
- # Original is 434x66 = 28644 pixels
317
- # 10000@ should resize to ~sqrt(10000/28644) * dimensions
318
- processor = described_class.new(@file, { geometry: "10000@", backend: :vips }, attachment)
319
- result = processor.make
320
-
321
- cmd = %[identify -format "%wx%h" "#{result.path}"]
322
- dimensions = `#{cmd}`.chomp
323
- width, height = dimensions.split("x").map(&:to_i)
324
- area = width * height
325
- # Should be approximately 10000 pixels (with some tolerance)
326
- expect(area).to be_within(500).of(10000)
327
- end
328
-
329
- it "handles area-based shrink-only with @> modifier" do
330
- # Original is 434x66 = 28644 pixels
331
- # 10000@> should resize (smaller than 28644)
332
- processor = described_class.new(@file, { geometry: "10000@>", backend: :vips }, attachment)
333
- result = processor.make
334
-
335
- cmd = %[identify -format "%wx%h" "#{result.path}"]
336
- dimensions = `#{cmd}`.chomp
337
- width, height = dimensions.split("x").map(&:to_i)
338
- area = width * height
339
- expect(area).to be_within(500).of(10000)
340
-
341
- # 50000@> should NOT resize (larger than 28644, and only_shrink is true)
342
- processor = described_class.new(@file, { geometry: "50000@>", backend: :vips }, attachment)
343
- result = processor.make
344
- cmd = %[identify -format "%wx%h" "#{result.path}"]
345
- expect(`#{cmd}`.chomp).to eq("434x66")
346
- end
347
-
348
- it "handles area-based shrink-only with >@ modifier" do
349
- # Same as @> but different syntax
350
- processor = described_class.new(@file, { geometry: "10000>@", backend: :vips }, attachment)
351
- result = processor.make
352
-
353
- cmd = %[identify -format "%wx%h" "#{result.path}"]
354
- dimensions = `#{cmd}`.chomp
355
- width, height = dimensions.split("x").map(&:to_i)
356
- area = width * height
357
- expect(area).to be_within(500).of(10000)
358
- end
359
- end
360
-
361
- context "with image_magick backend" do
362
- it "resizes image to specified dimensions" do
363
- processor = described_class.new(@file, { geometry: "25x25>", backend: :image_magick }, attachment)
364
- result = processor.make
365
-
366
- cmd = %[identify -format "%wx%h" "#{result.path}"]
367
- expect(`#{cmd}`.chomp).to eq("25x4")
368
- end
369
-
370
- it "handles area-based resize with @ modifier" do
371
- # Original is 434x66 = 28644 pixels
372
- processor = described_class.new(@file, { geometry: "10000@", backend: :image_magick }, attachment)
373
- result = processor.make
374
-
375
- cmd = %[identify -format "%wx%h" "#{result.path}"]
376
- dimensions = `#{cmd}`.chomp
377
- width, height = dimensions.split("x").map(&:to_i)
378
- area = width * height
379
- expect(area).to be_within(500).of(10000)
380
- end
381
-
382
- it "handles area-based shrink-only with @> modifier" do
383
- processor = described_class.new(@file, { geometry: "10000@>", backend: :image_magick }, attachment)
384
- result = processor.make
385
-
386
- cmd = %[identify -format "%wx%h" "#{result.path}"]
387
- dimensions = `#{cmd}`.chomp
388
- width, height = dimensions.split("x").map(&:to_i)
389
- area = width * height
390
- expect(area).to be_within(500).of(10000)
391
-
392
- # Larger area should NOT resize
393
- processor = described_class.new(@file, { geometry: "50000@>", backend: :image_magick }, attachment)
394
- result = processor.make
395
- cmd = %[identify -format "%wx%h" "#{result.path}"]
396
- expect(`#{cmd}`.chomp).to eq("434x66")
397
- end
398
-
399
- it "applies cross-platform convert_options with vips without warning" do
400
- begin
401
- require "vips"
402
- rescue LoadError
403
- skip "libvips not installed"
404
- end
405
-
406
- # -strip is cross-platform, should work without warning
407
- expect(Paperclip).not_to receive(:log).with(/Warning/)
408
- processor = described_class.new(@file, {
409
- geometry: "50x50",
410
- backend: :vips,
411
- convert_options: "-strip",
412
- }, attachment)
413
- processor.make
414
- end
415
-
416
- it "logs warning for ImageMagick-only convert_options with vips" do
417
- begin
418
- require "vips"
419
- rescue LoadError
420
- skip "libvips not installed"
421
- end
422
-
423
- # -density is ImageMagick-only, should warn
424
- expect(Paperclip).to receive(:log).with(/Warning.*density.*not supported.*vips/)
425
- processor = described_class.new(@file, {
426
- geometry: "50x50",
427
- backend: :vips,
428
- convert_options: "-density 150",
429
- }, attachment)
430
- processor.make
431
- end
432
- end
433
- end
434
-
435
- describe "convert_options - individual options" do
436
- let(:attachment) { double("Attachment", options: {}) }
437
-
438
- # Helper to create a thumbnail with specific convert_options
439
- def make_thumb_with_options(file, options_string)
440
- thumb = Paperclip::Thumbnail.new(file, {
441
- geometry: "100x100",
442
- convert_options: options_string,
443
- backend: :image_magick,
444
- }, attachment)
445
- thumb.make
446
- end
447
-
448
- describe "-strip" do
449
- it "removes EXIF metadata" do
450
- file = File.new(fixture_file("rotated.jpg"), "rb")
451
- result = make_thumb_with_options(file, "-strip")
452
-
453
- exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
454
- expect(exif).to be_empty
455
- file.close
456
- end
457
- end
458
-
459
- describe "-quality" do
460
- it "sets JPEG quality" do
461
- file = File.new(fixture_file("rotated.jpg"), "rb")
462
- result_low = make_thumb_with_options(file, "-quality 20")
463
- file.rewind
464
- result_high = make_thumb_with_options(file, "-quality 95")
465
-
466
- # Lower quality should produce smaller file
467
- expect(File.size(result_low.path)).to be < File.size(result_high.path)
468
- file.close
469
- end
470
- end
471
-
472
- describe "-colorspace" do
473
- it "converts to grayscale" do
474
- result = make_thumb_with_options(@file, "-colorspace Gray")
475
-
476
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
477
- expect(colorspace).to eq("Gray")
478
- end
479
-
480
- it "converts to sRGB" do
481
- result = make_thumb_with_options(@file, "-colorspace sRGB")
482
-
483
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
484
- expect(colorspace).to eq("sRGB")
485
- end
486
- end
487
-
488
- describe "-rotate" do
489
- it "rotates the image by specified degrees" do
490
- # Original is 434x66, after 90 degree rotation should be 66x434
491
- result = make_thumb_with_options(@file, "-rotate 90")
492
-
493
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
494
- # After resize to 100x100 and rotate, dimensions change
495
- expect(dimensions).to match(/\d+x\d+/)
496
- end
497
- end
498
-
499
- describe "-flip" do
500
- it "flips the image vertically" do
501
- result = make_thumb_with_options(@file, "-flip")
502
- expect(File.exist?(result.path)).to be true
503
- end
504
- end
505
-
506
- describe "-flop" do
507
- it "flops the image horizontally" do
508
- result = make_thumb_with_options(@file, "-flop")
509
- expect(File.exist?(result.path)).to be true
510
- end
511
- end
512
-
513
- describe "-negate" do
514
- it "negates the image colors" do
515
- result = make_thumb_with_options(@file, "-negate")
516
- expect(File.exist?(result.path)).to be true
517
- end
518
- end
519
-
520
- describe "-normalize" do
521
- it "normalizes the image" do
522
- result = make_thumb_with_options(@file, "-normalize")
523
- expect(File.exist?(result.path)).to be true
524
- end
525
- end
526
-
527
- describe "-equalize" do
528
- it "equalizes the image histogram" do
529
- result = make_thumb_with_options(@file, "-equalize")
530
- expect(File.exist?(result.path)).to be true
531
- end
532
- end
533
-
534
- describe "-auto_orient" do
535
- it "auto-orients the image" do
536
- file = File.new(fixture_file("rotated.jpg"), "rb")
537
- result = make_thumb_with_options(file, "-auto-orient")
538
- expect(File.exist?(result.path)).to be true
539
- file.close
540
- end
541
- end
542
-
543
- describe "-blur" do
544
- it "blurs the image" do
545
- result = make_thumb_with_options(@file, "-blur 0x2")
546
- expect(File.exist?(result.path)).to be true
547
- end
548
- end
549
-
550
- describe "-sharpen" do
551
- it "sharpens the image" do
552
- result = make_thumb_with_options(@file, "-sharpen 0x1")
553
- expect(File.exist?(result.path)).to be true
554
- end
555
- end
556
-
557
- describe "-density" do
558
- it "sets the image density" do
559
- result = make_thumb_with_options(@file, "-density 150")
560
-
561
- density = `identify -format "%x" "#{result.path}"`.strip
562
- expect(density).to start_with("150")
563
- end
564
- end
565
-
566
- describe "-depth" do
567
- it "sets the bit depth" do
568
- result = make_thumb_with_options(@file, "-depth 8")
569
- expect(File.exist?(result.path)).to be true
570
- end
571
- end
572
-
573
- describe "-interlace" do
574
- it "sets interlacing mode" do
575
- # Use JPEG to test interlacing (PNG reports format name instead)
576
- file = File.new(fixture_file("rotated.jpg"), "rb")
577
- thumb = Paperclip::Thumbnail.new(file, {
578
- geometry: "100x100",
579
- convert_options: "-interlace Plane",
580
- backend: :image_magick,
581
- format: :jpg,
582
- }, attachment)
583
- result = thumb.make
584
-
585
- interlace = `identify -format "%[interlace]" "#{result.path}"`.strip
586
- # Different ImageMagick versions report this differently
587
- expect(interlace).to match(/Plane|JPEG|Line|None/i).or eq("")
588
- file.close
589
- end
590
- end
591
-
592
- describe "-gravity" do
593
- it "sets gravity for subsequent operations" do
594
- result = make_thumb_with_options(@file, "-gravity center")
595
- expect(File.exist?(result.path)).to be true
596
- end
597
- end
598
-
599
- describe "-crop" do
600
- it "crops the image" do
601
- # Use a square image for predictable crop results
602
- file = File.new(fixture_file("50x50.png"), "rb")
603
- thumb = Paperclip::Thumbnail.new(file, {
604
- geometry: "50x50", # Keep original size
605
- convert_options: "-crop 25x25+0+0 +repage",
606
- backend: :image_magick,
607
- }, attachment)
608
- result = thumb.make
609
-
610
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
611
- expect(dimensions).to eq("25x25")
612
- file.close
613
- end
614
- end
615
-
616
- describe "-extent" do
617
- it "sets the image extent with padding" do
618
- result = make_thumb_with_options(@file, "-background white -gravity center -extent 150x150")
619
-
620
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
621
- expect(dimensions).to eq("150x150")
622
- end
623
- end
624
-
625
- describe "-background" do
626
- it "sets the background color" do
627
- result = make_thumb_with_options(@file, "-background red -flatten")
628
- expect(File.exist?(result.path)).to be true
629
- end
630
- end
631
-
632
- describe "-flatten" do
633
- it "flattens the image layers" do
634
- result = make_thumb_with_options(@file, "-flatten")
635
- expect(File.exist?(result.path)).to be true
636
- end
637
- end
638
-
639
- describe "-alpha" do
640
- it "modifies alpha channel" do
641
- result = make_thumb_with_options(@file, "-alpha remove")
642
- expect(File.exist?(result.path)).to be true
643
- end
644
- end
645
-
646
- describe "-type" do
647
- it "sets the image type" do
648
- result = make_thumb_with_options(@file, "-type Grayscale")
649
- expect(File.exist?(result.path)).to be true
650
- end
651
- end
652
-
653
- describe "-monochrome" do
654
- it "converts to black and white" do
655
- result = make_thumb_with_options(@file, "-monochrome")
656
- expect(File.exist?(result.path)).to be true
657
- end
658
- end
659
-
660
- describe "-posterize" do
661
- it "reduces color levels" do
662
- result = make_thumb_with_options(@file, "-posterize 4")
663
- expect(File.exist?(result.path)).to be true
664
- end
665
- end
666
-
667
- describe "-colors" do
668
- it "reduces the number of colors" do
669
- result = make_thumb_with_options(@file, "-colors 16")
670
- expect(File.exist?(result.path)).to be true
671
- end
672
- end
673
-
674
- describe "-channel" do
675
- it "selects image channels" do
676
- result = make_thumb_with_options(@file, "-channel RGB")
677
- expect(File.exist?(result.path)).to be true
678
- end
679
- end
680
-
681
- describe "-transpose" do
682
- it "transposes the image" do
683
- result = make_thumb_with_options(@file, "-transpose")
684
- expect(File.exist?(result.path)).to be true
685
- end
686
- end
687
-
688
- describe "-transverse" do
689
- it "transverses the image" do
690
- result = make_thumb_with_options(@file, "-transverse")
691
- expect(File.exist?(result.path)).to be true
692
- end
693
- end
694
-
695
- describe "-trim" do
696
- it "trims image edges" do
697
- result = make_thumb_with_options(@file, "-trim")
698
- expect(File.exist?(result.path)).to be true
699
- end
700
- end
701
-
702
- describe "-dither" do
703
- it "applies dithering" do
704
- result = make_thumb_with_options(@file, "-dither FloydSteinberg")
705
- expect(File.exist?(result.path)).to be true
706
- end
707
- end
708
-
709
- describe "-sampling_factor" do
710
- it "sets chroma subsampling" do
711
- file = File.new(fixture_file("rotated.jpg"), "rb")
712
- result = make_thumb_with_options(file, "-sampling-factor 4:2:0")
713
- expect(File.exist?(result.path)).to be true
714
- file.close
715
- end
716
- end
717
-
718
- describe "-units" do
719
- it "sets resolution units" do
720
- result = make_thumb_with_options(@file, "-units PixelsPerInch")
721
- expect(File.exist?(result.path)).to be true
722
- end
723
- end
724
-
725
- describe "unknown options (fallback to append)" do
726
- it "passes unknown options through to ImageMagick" do
727
- # -modulate is not in our explicit list, should use append fallback
728
- result = make_thumb_with_options(@file, "-modulate 100,50,100")
729
- expect(File.exist?(result.path)).to be true
730
- end
731
-
732
- it "handles unknown flag-only options" do
733
- # Use a harmless option that's not in our list
734
- result = make_thumb_with_options(@file, "-verbose")
735
- expect(File.exist?(result.path)).to be true
736
- end
737
- end
738
-
739
- describe "multiple options combined" do
740
- it "applies multiple options in sequence" do
741
- result = make_thumb_with_options(@file, "-strip -quality 80 -colorspace Gray -sharpen 0x1")
742
-
743
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
744
- expect(colorspace).to eq("Gray")
745
- end
746
-
747
- it "handles complex option combinations" do
748
- result = make_thumb_with_options(@file, "-strip -density 72 -depth 8 -colorspace sRGB")
749
- expect(File.exist?(result.path)).to be true
750
-
751
- density = `identify -format "%x" "#{result.path}"`.strip
752
- expect(density).to start_with("72")
753
- end
754
- end
755
- end
756
-
757
- describe "convert_options - cross-platform options with vips backend" do
758
- let(:attachment) { double("Attachment", options: {}) }
759
-
760
- before do
761
- require "vips"
762
- rescue LoadError
763
- skip "libvips not installed"
764
- end
765
-
766
- # Helper to create a thumbnail with vips backend and specific convert_options
767
- def make_vips_thumb_with_options(file, options_string, extra_opts = {})
768
- thumb = Paperclip::Thumbnail.new(file, {
769
- geometry: "100x100",
770
- convert_options: options_string,
771
- backend: :vips,
772
- }.merge(extra_opts), attachment)
773
- thumb.make
774
- end
775
-
776
- describe "-strip" do
777
- it "removes metadata from the image" do
778
- file = File.new(fixture_file("rotated.jpg"), "rb")
779
- result = make_vips_thumb_with_options(file, "-strip")
780
-
781
- # Check that EXIF orientation is removed
782
- exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
783
- expect(exif).to be_empty
784
- file.close
785
- end
786
-
787
- it "produces a valid image" do
788
- result = make_vips_thumb_with_options(@file, "-strip")
789
- expect(File.exist?(result.path)).to be true
790
- expect(File.size(result.path)).to be > 0
791
- end
792
- end
793
-
794
- describe "-quality" do
795
- it "sets output quality for JPEG" do
796
- file = File.new(fixture_file("rotated.jpg"), "rb")
797
- result_low = make_vips_thumb_with_options(file, "-quality 20", format: :jpg)
798
- file.rewind
799
- result_high = make_vips_thumb_with_options(file, "-quality 95", format: :jpg)
800
-
801
- # Lower quality should produce smaller file
802
- expect(File.size(result_low.path)).to be < File.size(result_high.path)
803
- file.close
804
- end
805
-
806
- it "produces a valid image" do
807
- result = make_vips_thumb_with_options(@file, "-quality 80")
808
- expect(File.exist?(result.path)).to be true
809
- end
810
- end
811
-
812
- describe "-rotate" do
813
- it "rotates the image by specified degrees" do
814
- # Original 434x66, after 90 degree rotation dimensions swap
815
- result = make_vips_thumb_with_options(@file, "-rotate 90")
816
-
817
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
818
- # After resize to fit 100x100, then rotate 90 degrees
819
- # Original aspect ratio is ~6.5:1, fitting in 100x100 gives ~100x15
820
- # After 90 degree rotation: ~15x100
821
- expect(dimensions).to match(/\d+x\d+/)
822
- width, height = dimensions.split("x").map(&:to_i)
823
- # Width should be smaller than height after rotation
824
- expect(width).to be < height
825
- end
826
-
827
- it "rotates by arbitrary angle" do
828
- result = make_vips_thumb_with_options(@file, "-rotate 45")
829
- expect(File.exist?(result.path)).to be true
830
- end
831
- end
832
-
833
- describe "-rot90" do
834
- it "rotates the image 90 degrees" do
835
- result = make_vips_thumb_with_options(@file, "-rot90")
836
- expect(File.exist?(result.path)).to be true
837
-
838
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
839
- # Resized to 100x15, then rotated 90 -> 15x100
840
- expect(dimensions).to eq("15x100")
841
- end
842
- end
843
-
844
- describe "-rot180" do
845
- it "rotates the image 180 degrees" do
846
- File.open(fixture_file("big_image.jpg"), "rb") do |file|
847
- thumb = Paperclip::Thumbnail.new(file, {
848
- geometry: "2000x2000",
849
- convert_options: "-rot180",
850
- backend: :vips,
851
- }, attachment)
852
- result = thumb.make
853
- expect(File.exist?(result.path)).to be true
854
-
855
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
856
- expect(dimensions).to eq("2000x1581")
857
- end
858
- end
859
- end
860
-
861
- describe "-flip" do
862
- it "flips the image vertically" do
863
- result = make_vips_thumb_with_options(@file, "-flip")
864
- expect(File.exist?(result.path)).to be true
865
- expect(File.size(result.path)).to be > 0
866
- end
867
- end
868
-
869
- describe "-flop" do
870
- it "flops the image horizontally" do
871
- result = make_vips_thumb_with_options(@file, "-flop")
872
- expect(File.exist?(result.path)).to be true
873
- expect(File.size(result.path)).to be > 0
874
- end
875
- end
876
-
877
- describe "-blur" do
878
- it "applies gaussian blur to the image" do
879
- result = make_vips_thumb_with_options(@file, "-blur 0x2")
880
- expect(File.exist?(result.path)).to be true
881
- expect(File.size(result.path)).to be > 0
882
- end
883
-
884
- it "handles different blur sigma values" do
885
- result = make_vips_thumb_with_options(@file, "-blur 0x5")
886
- expect(File.exist?(result.path)).to be true
887
- end
888
- end
889
-
890
- describe "-gaussian_blur" do
891
- it "applies gaussian blur" do
892
- result = make_vips_thumb_with_options(@file, "-gaussian-blur 0x3")
893
- expect(File.exist?(result.path)).to be true
894
- expect(File.size(result.path)).to be > 0
895
- end
896
- end
897
-
898
- describe "-sharpen" do
899
- it "sharpens the image" do
900
- result = make_vips_thumb_with_options(@file, "-sharpen 0x1")
901
- expect(File.exist?(result.path)).to be true
902
- expect(File.size(result.path)).to be > 0
903
- end
904
- end
905
-
906
- describe "-colorspace" do
907
- it "converts to grayscale (b-w)" do
908
- result = make_vips_thumb_with_options(@file, "-colorspace Gray")
909
-
910
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
911
- expect(colorspace).to eq("Gray")
912
- end
913
-
914
- it "converts to sRGB" do
915
- result = make_vips_thumb_with_options(@file, "-colorspace sRGB")
916
-
917
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
918
- expect(colorspace).to eq("sRGB")
919
- end
920
-
921
- it "converts to CMYK" do
922
- file = File.new(fixture_file("rotated.jpg"), "rb")
923
- result = make_vips_thumb_with_options(file, "-colorspace CMYK", format: :jpg)
924
-
925
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
926
- expect(colorspace).to eq("CMYK")
927
- file.close
928
- end
929
- end
930
-
931
- describe "-flatten" do
932
- it "flattens transparency to white background" do
933
- result = make_vips_thumb_with_options(@file, "-flatten")
934
- expect(File.exist?(result.path)).to be true
935
- expect(File.size(result.path)).to be > 0
936
- end
937
- end
938
-
939
- describe "-negate" do
940
- it "inverts the image colors" do
941
- result = make_vips_thumb_with_options(@file, "-negate")
942
- expect(File.exist?(result.path)).to be true
943
- expect(File.size(result.path)).to be > 0
944
- end
945
- end
946
-
947
- describe "-auto-orient" do
948
- it "auto-orients the image based on EXIF" do
949
- file = File.new(fixture_file("rotated.jpg"), "rb")
950
- result = make_vips_thumb_with_options(file, "-auto-orient")
951
- expect(File.exist?(result.path)).to be true
952
-
953
- # The rotated.jpg has orientation 6 (90 CW), so auto-orient should correct it
954
- dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
955
- width, height = dimensions.split("x").map(&:to_i)
956
- # After auto-orient, portrait orientation should be maintained
957
- expect(height).to be > width
958
- file.close
959
- end
960
- end
961
-
962
- describe "-interlace" do
963
- it "creates progressive/interlaced output for JPEG" do
964
- file = File.new(fixture_file("rotated.jpg"), "rb")
965
- result = make_vips_thumb_with_options(file, "-interlace Plane", format: :jpg)
966
- expect(File.exist?(result.path)).to be true
967
- expect(File.size(result.path)).to be > 0
968
- file.close
969
- end
970
-
971
- it "creates interlaced output for PNG" do
972
- result = make_vips_thumb_with_options(@file, "-interlace Line", format: :png)
973
- expect(File.exist?(result.path)).to be true
974
- end
975
- end
976
-
977
- describe "multiple cross-platform options combined" do
978
- it "applies multiple options in sequence" do
979
- result = make_vips_thumb_with_options(@file, "-strip -quality 80 -colorspace Gray")
980
-
981
- colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
982
- expect(colorspace).to eq("Gray")
983
- end
984
-
985
- it "combines flip, flop, and rotate" do
986
- result = make_vips_thumb_with_options(@file, "-flip -flop -rotate 180")
987
- expect(File.exist?(result.path)).to be true
988
- end
989
-
990
- it "applies strip with quality and sharpen" do
991
- file = File.new(fixture_file("rotated.jpg"), "rb")
992
- result = make_vips_thumb_with_options(file, "-strip -quality 85 -sharpen 0x1", format: :jpg)
993
-
994
- # Verify EXIF is stripped
995
- exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
996
- expect(exif).to be_empty
997
- file.close
998
- end
999
- end
1000
-
1001
- describe "ImageMagick-only options with vips (should warn)" do
1002
- it "logs warning for -density" do
1003
- expect(Paperclip).to receive(:log).with(/Warning.*density.*not supported.*vips/)
1004
- make_vips_thumb_with_options(@file, "-density 150")
1005
- end
1006
-
1007
- it "logs warning for -depth" do
1008
- expect(Paperclip).to receive(:log).with(/Warning.*depth.*not supported.*vips/)
1009
- make_vips_thumb_with_options(@file, "-depth 8")
1010
- end
1011
-
1012
- it "logs warning for -gravity" do
1013
- expect(Paperclip).to receive(:log).with(/Warning.*gravity.*not supported.*vips/)
1014
- make_vips_thumb_with_options(@file, "-gravity center")
1015
- end
1016
-
1017
- it "logs warning for -crop" do
1018
- expect(Paperclip).to receive(:log).with(/Warning.*crop.*not supported.*vips/)
1019
- make_vips_thumb_with_options(@file, "-crop 50x50+0+0")
1020
- end
1021
-
1022
- it "logs warning for -trim" do
1023
- expect(Paperclip).to receive(:log).with(/Warning.*trim.*not supported.*vips/)
1024
- make_vips_thumb_with_options(@file, "-trim")
1025
- end
1026
-
1027
- it "logs warning for -normalize" do
1028
- expect(Paperclip).to receive(:log).with(/Warning.*normalize.*not supported.*vips/)
1029
- make_vips_thumb_with_options(@file, "-normalize")
1030
- end
1031
-
1032
- it "logs warning for -monochrome" do
1033
- expect(Paperclip).to receive(:log).with(/Warning.*monochrome.*not supported.*vips/)
1034
- make_vips_thumb_with_options(@file, "-monochrome")
1035
- end
1036
- end
1037
- end
1038
-
1039
- [%w[600x600> 434x66],
1040
- %w[400x400> 400x61],
1041
- %w[32x32< 434x66],
1042
- [nil, "434x66"]].each do |args|
1043
- context "being thumbnailed with a geometry of #{args[0]}" do
1044
- before do
1045
- @thumb = Paperclip::Thumbnail.new(@file, geometry: args[0])
1046
- end
1047
-
1048
- it "starts with dimensions of 434x66" do
1049
- cmd = %[identify -format "%wx%h" "#{@file.path}"]
1050
- assert_equal "434x66", `#{cmd}`.chomp
1051
- end
1052
-
1053
- it "reports the correct target geometry" do
1054
- assert_equal args[0].to_s, @thumb.target_geometry.to_s
1055
- end
1056
-
1057
- context "when made" do
1058
- before do
1059
- @thumb_result = @thumb.make
1060
- end
1061
-
1062
- it "is the size we expect it to be" do
1063
- cmd = %[identify -format "%wx%h" "#{@thumb_result.path}"]
1064
- assert_equal args[1], `#{cmd}`.chomp
1065
- end
1066
- end
1067
- end
1068
- end
1069
-
1070
- context "being thumbnailed at 100x50 with cropping" do
1071
- before do
1072
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "100x50#")
1073
- end
1074
-
1075
- it "reports its correct current and target geometries" do
1076
- assert_equal "100x50#", @thumb.target_geometry.to_s
1077
- assert_equal "434x66", @thumb.current_geometry.to_s
1078
- end
1079
-
1080
- it "reports its correct format" do
1081
- assert_nil @thumb.format
1082
- end
1083
-
1084
- it "has whiny turned on by default" do
1085
- assert @thumb.whiny
1086
- end
1087
-
1088
- it "has convert_options set to nil by default" do
1089
- assert_equal nil, @thumb.convert_options
1090
- end
1091
-
1092
- it "has source_file_options set to nil by default" do
1093
- assert_equal nil, @thumb.source_file_options
1094
- end
1095
-
1096
- it "creates the thumbnail when sent #make" do
1097
- dst = @thumb.make
1098
- assert_match /100x50/, `identify "#{dst.path}"`
1099
- end
1100
- end
1101
-
1102
- it "crops a EXIF-rotated image properly" do
1103
- file = File.new(fixture_file("rotated.jpg"))
1104
- thumb = Paperclip::Thumbnail.new(file, geometry: "50x50#")
1105
-
1106
- output_file = thumb.make
1107
-
1108
- command = Terrapin::CommandLine.new("identify", "-format %wx%h :file")
1109
- assert_equal "50x50", command.run(file: output_file.path).strip
1110
- end
1111
-
1112
- context "being thumbnailed with source file options set" do
1113
- before do
1114
- @file = File.new(fixture_file("rotated.jpg"), "rb")
1115
- @thumb = Paperclip::Thumbnail.new(@file,
1116
- geometry: "100x50#",
1117
- source_file_options: "-density 300")
1118
- end
1119
-
1120
- it "has source_file_options value set" do
1121
- assert_equal "-density 300", @thumb.source_file_options
1122
- end
1123
-
1124
- it "creates the thumbnail when sent #make" do
1125
- dst = @thumb.make
1126
- assert_match /100x50/, `identify "#{dst.path}"`
1127
- end
1128
-
1129
- it "actually applies the source file options (sets density)" do
1130
- # Verify result has the set density
1131
- dst = @thumb.make
1132
- cmd_new = %[identify -format "%x" "#{dst.path}"]
1133
- expect(`#{cmd_new}`.chomp).to start_with("300")
1134
- end
1135
- end
1136
-
1137
- context "being thumbnailed with convert options set" do
1138
- before do
1139
- @file = File.new(fixture_file("rotated.jpg"), "rb")
1140
- @thumb = Paperclip::Thumbnail.new(@file,
1141
- geometry: "100x50#",
1142
- convert_options: "-strip")
1143
- end
1144
-
1145
- it "has convert_options value set" do
1146
- assert_equal "-strip", @thumb.convert_options
1147
- end
1148
-
1149
- it "creates the thumbnail when sent #make" do
1150
- dst = @thumb.make
1151
- assert_match /100x50/, `identify "#{dst.path}"`
1152
- end
1153
-
1154
- it "actually applies the convert options (removes EXIF data)" do
1155
- # Verify original has EXIF
1156
- cmd_orig = %[identify -format "%[exif:orientation]" "#{@file.path}"]
1157
- expect(`#{cmd_orig}`.chomp).to_not be_empty
1158
-
1159
- # Verify result has no EXIF
1160
- dst = @thumb.make
1161
- cmd_new = %[identify -format "%[exif:orientation]" "#{dst.path}"]
1162
- expect(`#{cmd_new}`.chomp).to be_empty
1163
- end
1164
- end
1165
-
1166
- context "error handling" do
1167
- before do
1168
- require "image_processing"
1169
- # Use a valid image so initialization (geometry detection) succeeds
1170
- @file = File.new(fixture_file("5k.png"), "rb")
1171
- end
1172
-
1173
- context "with whiny enabled (default)" do
1174
- it "raises an error when processing fails" do
1175
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
1176
- allow(thumb).to receive(:build_pipeline).and_raise(Paperclip::Errors::CommandNotFoundError)
1177
-
1178
- expect { thumb.make }.to raise_error(Paperclip::Errors::CommandNotFoundError)
1179
- end
1180
-
1181
- it "raises a Paperclip::Error when an underlying processing error occurs" do
1182
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
1183
- # Simulate a generic processing error
1184
- allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
1185
-
1186
- expect { thumb.make }.to raise_error(Paperclip::Error, /Processing failed/)
1187
- end
1188
- end
1189
-
1190
- context "with whiny disabled" do
1191
- it "returns the original file when processing fails" do
1192
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
1193
- allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
1194
-
1195
- result = thumb.make
1196
- expect(result).to eq(@file)
1197
- end
1198
-
1199
- it "logs the error" do
1200
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
1201
- allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
1202
-
1203
- expect(Paperclip).to receive(:log).with(/Processing failed/)
1204
- thumb.make
1205
- end
1206
- end
1207
- end
1208
-
1209
- context "being thumbnailed with a blank geometry string" do
1210
- before do
1211
- @thumb = Paperclip::Thumbnail.new(@file,
1212
- geometry: "",
1213
- convert_options: "-gravity center -crop 300x300+0-0")
1214
- end
1215
-
1216
- # Verify that when geometry is blank, we don't resize, but still apply other options.
1217
- it "does not resize the image via geometry but applies convert_options" do
1218
- result = @thumb.make
1219
- cmd = %[identify -format "%wx%h" "#{result.path}"]
1220
- # Original size is 434x66. The crop option (300x300) should result in 300x66.
1221
- # This confirms that no geometry-based resize happened (which would have been to 0x0 or skipped),
1222
- # but the convert_options crop was applied.
1223
- expect(`#{cmd}`.chomp).to eq("300x66")
1224
- end
1225
- end
1226
-
1227
- context "passing a custom file geometry parser" do
1228
- after do
1229
- Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
1230
- end
1231
-
1232
- it "uses the custom parser" do
1233
- GeoParser = Class.new do
1234
- def self.from_file(_file, _backend = nil)
1235
- new
1236
- end
1237
-
1238
- def width; 100; end
1239
-
1240
- def height; 100; end
1241
-
1242
- def modifier; nil; end
1243
-
1244
- def auto_orient; end
1245
- end
1246
-
1247
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", file_geometry_parser: ::GeoParser)
1248
- expect(thumb.current_geometry).to be_a(GeoParser)
1249
- end
1250
- end
1251
-
1252
- context "passing a custom geometry string parser" do
1253
- after do
1254
- Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
1255
- end
1256
-
1257
- it "uses the custom parser" do
1258
- GeoParser = Class.new do
1259
- def self.parse(_s)
1260
- new
1261
- end
1262
-
1263
- def to_s
1264
- "151x167"
1265
- end
1266
-
1267
- def width; 151; end
1268
-
1269
- def height; 167; end
1270
-
1271
- def modifier; nil; end
1272
- end
1273
-
1274
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", string_geometry_parser: ::GeoParser)
1275
- expect(thumb.target_geometry).to be_a(GeoParser)
1276
- end
1277
- end
1278
- end
1279
-
1280
- context "A multipage PDF" do
1281
- before do
1282
- @file = File.new(fixture_file("twopage.pdf"), "rb")
1283
- end
1284
-
1285
- after { @file.close }
1286
-
1287
- it "starts with two pages with dimensions 612x792" do
1288
- cmd = %[identify -format "%wx%h" "#{@file.path}"]
1289
- assert_equal "612x792" * 2, `#{cmd}`.chomp
1290
- end
1291
-
1292
- context "being thumbnailed at 100x100 with cropping" do
1293
- before do
1294
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "100x100#", format: :png)
1295
- end
1296
-
1297
- it "reports its correct current and target geometries" do
1298
- assert_equal "100x100#", @thumb.target_geometry.to_s
1299
- assert_equal "612x792", @thumb.current_geometry.to_s
1300
- end
1301
-
1302
- it "reports its correct format" do
1303
- assert_equal :png, @thumb.format
1304
- end
1305
-
1306
- it "creates the thumbnail when sent #make" do
1307
- dst = @thumb.make
1308
- assert_match /100x100/, `identify "#{dst.path}"`
1309
- end
1310
- end
1311
- end
1312
-
1313
- context "An animated gif" do
1314
- before do
1315
- @file = File.new(fixture_file("animated.gif"), "rb")
1316
- end
1317
-
1318
- after { @file.close }
1319
-
1320
- it "starts with 12 frames with size 100x100" do
1321
- cmd = %[identify -format "%wx%h" "#{@file.path}"]
1322
- assert_equal "100x100" * 12, `#{cmd}`.chomp
1323
- end
1324
-
1325
- context "with static output" do
1326
- before do
1327
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :jpg)
1328
- end
1329
-
1330
- it "creates the single frame thumbnail when sent #make" do
1331
- dst = @thumb.make
1332
- cmd = %[identify -format "%wx%h" "#{dst.path}"]
1333
- assert_equal "50x50", `#{cmd}`.chomp
1334
- end
1335
- end
1336
-
1337
- context "with animated output format" do
1338
- before do
1339
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :gif)
1340
- end
1341
-
1342
- it "creates the 12 frames thumbnail when sent #make" do
1343
- dst = @thumb.make
1344
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1345
- frames = `#{cmd}`.chomp.split(",")
1346
- assert_equal 12, frames.size
1347
- assert_frame_dimensions (45..50), frames
1348
- end
1349
- end
1350
-
1351
- context "with omitted output format" do
1352
- before do
1353
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
1354
- end
1355
-
1356
- it "creates the 12 frames thumbnail when sent #make" do
1357
- dst = @thumb.make
1358
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1359
- frames = `#{cmd}`.chomp.split(",")
1360
- # image_processing might not preserve animation by default if not explicitly told to
1361
- # or if the format is not explicitly set to gif.
1362
- # But here we are testing default behavior.
1363
- # If it fails with 1 frame, it means it collapsed it.
1364
- # The original implementation preserved it.
1365
- # We might need to force loader options for animated gifs if we want to preserve layers.
1366
- # But image_processing/mini_magick should handle it if we don't flatten.
1367
-
1368
- # If this fails, we might need to adjust the test expectation or implementation.
1369
- # For now, let's assume we want to preserve behavior.
1370
- assert_equal 12, frames.size
1371
- assert_frame_dimensions (45..50), frames
1372
- end
1373
- end
1374
-
1375
- context "with unidentified source format" do
1376
- before do
1377
- @unidentified_file = File.new(fixture_file("animated.unknown"), "rb")
1378
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "60x60")
1379
- end
1380
-
1381
- it "creates the 12 frames thumbnail when sent #make" do
1382
- dst = @thumb.make
1383
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1384
- frames = `#{cmd}`.chomp.split(",")
1385
- assert_equal 12, frames.size
1386
- assert_frame_dimensions (55..60), frames
1387
- end
1388
- end
1389
-
1390
- context "with no source format" do
1391
- before do
1392
- @unidentified_file = File.new(fixture_file("animated"), "rb")
1393
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "70x70")
1394
- end
1395
-
1396
- it "creates the 12 frames thumbnail when sent #make" do
1397
- dst = @thumb.make
1398
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1399
- frames = `#{cmd}`.chomp.split(",")
1400
- assert_equal 12, frames.size
1401
- assert_frame_dimensions (60..70), frames
1402
- end
1403
- end
1404
-
1405
- context "with animated option set to false" do
1406
- before do
1407
- @thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", animated: false)
1408
- end
1409
-
1410
- it "outputs the gif format" do
1411
- dst = @thumb.make
1412
- cmd = %[identify "#{dst.path}"]
1413
- assert_match /GIF/, `#{cmd}`.chomp
1414
- end
1415
-
1416
- it "creates the single frame thumbnail when sent #make" do
1417
- dst = @thumb.make
1418
- cmd = %[identify -format "%wx%h" "#{dst.path}"]
1419
- # The output might be multiple frames if image_processing doesn't collapse them
1420
- # But we expect single frame here.
1421
- # If it fails, we might need to adjust the implementation to force single frame.
1422
- # For now, let's check if it starts with 50x50.
1423
- output = `#{cmd}`.chomp
1424
- # If multiple frames, it will be 50x5050x50...
1425
- # We want exactly 50x50
1426
- assert_equal "50x50", output
1427
- end
1428
- end
1429
-
1430
- context "with a specified frame_index" do
1431
- before do
1432
- @thumb = Paperclip::Thumbnail.new(
1433
- @file,
1434
- geometry: "50x50",
1435
- frame_index: 5,
1436
- format: :jpg,
1437
- )
1438
- end
1439
-
1440
- it "creates the thumbnail from the frame index when sent #make" do
1441
- @thumb.make
1442
- assert_equal 5, @thumb.frame_index
1443
- end
1444
- end
1445
-
1446
- context "with vips backend" do
1447
- before do
1448
- require "vips"
1449
- rescue LoadError
1450
- skip "libvips not installed"
1451
- end
1452
-
1453
- it "preserves animation when output is GIF" do
1454
- processor = Paperclip::Thumbnail.new(@file, { geometry: "50x50", format: :gif, backend: :vips })
1455
- dst = processor.make
1456
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1457
- frames = `#{cmd}`.chomp.split(",")
1458
- expect(frames.size).to eq(12)
1459
- end
1460
-
1461
- it "collapses animation when animated: false is set" do
1462
- processor = Paperclip::Thumbnail.new(@file,
1463
- { geometry: "50x50", format: :gif, animated: false, backend: :vips })
1464
- dst = processor.make
1465
- cmd = %[identify -format "%wx%h," "#{dst.path}"]
1466
- frames = `#{cmd}`.chomp.split(",").reject(&:empty?)
1467
- expect(frames.size).to eq(1)
1468
- expect(frames.first).to eq("50x50")
1469
- end
1470
- end
1471
- end
1472
-
1473
- context "with a really long file name" do
1474
- before do
1475
- tempfile = Tempfile.new("f")
1476
- tempfile_additional_chars = tempfile.path.split("/")[-1].length + 15
1477
- image_file = File.new(fixture_file("5k.png"), "rb")
1478
- @file = Tempfile.new("f" * (255 - tempfile_additional_chars))
1479
- @file.write(image_file.read)
1480
- @file.rewind
1481
- end
1482
-
1483
- it "does not throw Errno::ENAMETOOLONG" do
1484
- thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :gif)
1485
- expect { thumb.make }.to_not raise_error
1486
- end
1487
- end
1488
- end