jr-paperclip 8.0.1 → 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.
- checksums.yaml +4 -4
- data/NEWS +9 -0
- data/lib/paperclip/thumbnail.rb +18 -15
- data/lib/paperclip/version.rb +1 -1
- metadata +3 -245
- data/.github/FUNDING.yml +0 -3
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -18
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- data/.github/workflows/reviewdog.yml +0 -23
- data/.github/workflows/tests.yml +0 -56
- data/.gitignore +0 -19
- data/.qlty/.gitignore +0 -7
- data/.qlty/qlty.toml +0 -89
- data/Appraisals +0 -29
- data/Gemfile +0 -18
- data/bin/console +0 -11
- data/features/basic_integration.feature +0 -112
- data/features/migration.feature +0 -29
- data/features/rake_tasks.feature +0 -62
- data/features/step_definitions/attachment_steps.rb +0 -138
- data/features/step_definitions/html_steps.rb +0 -15
- data/features/step_definitions/rails_steps.rb +0 -271
- data/features/step_definitions/s3_steps.rb +0 -16
- data/features/step_definitions/web_steps.rb +0 -106
- data/features/support/env.rb +0 -12
- data/features/support/file_helpers.rb +0 -34
- data/features/support/fixtures/boot_config.txt +0 -15
- data/features/support/fixtures/gemfile.txt +0 -5
- data/features/support/fixtures/preinitializer.txt +0 -20
- data/features/support/paths.rb +0 -28
- data/features/support/rails.rb +0 -39
- data/features/support/selectors.rb +0 -19
- data/features/support/webmock_setup.rb +0 -8
- data/gemfiles/7.0.gemfile +0 -21
- data/gemfiles/7.1.gemfile +0 -21
- data/gemfiles/7.2.gemfile +0 -21
- data/gemfiles/8.0.gemfile +0 -21
- data/gemfiles/8.1.gemfile +0 -21
- data/paperclip.gemspec +0 -52
- data/spec/database.yml +0 -4
- data/spec/paperclip/attachment_definitions_spec.rb +0 -313
- data/spec/paperclip/attachment_processing_spec.rb +0 -79
- data/spec/paperclip/attachment_registry_spec.rb +0 -158
- data/spec/paperclip/attachment_spec.rb +0 -1617
- data/spec/paperclip/content_type_detector_spec.rb +0 -58
- data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -40
- data/spec/paperclip/filename_cleaner_spec.rb +0 -13
- data/spec/paperclip/geometry_detector_spec.rb +0 -96
- data/spec/paperclip/geometry_parser_spec.rb +0 -73
- data/spec/paperclip/geometry_spec.rb +0 -270
- data/spec/paperclip/glue_spec.rb +0 -63
- data/spec/paperclip/has_attached_file_spec.rb +0 -78
- data/spec/paperclip/helpers_spec.rb +0 -49
- data/spec/paperclip/integration_spec.rb +0 -702
- data/spec/paperclip/interpolations_spec.rb +0 -270
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +0 -160
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +0 -167
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +0 -88
- data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +0 -17
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +0 -134
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +0 -142
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +0 -8
- data/spec/paperclip/io_adapters/nil_adapter_spec.rb +0 -25
- data/spec/paperclip/io_adapters/registry_spec.rb +0 -35
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +0 -64
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +0 -146
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +0 -231
- data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +0 -266
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +0 -19
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +0 -108
- data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +0 -69
- data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +0 -88
- data/spec/paperclip/media_type_spoof_detector_spec.rb +0 -126
- data/spec/paperclip/meta_class_spec.rb +0 -30
- data/spec/paperclip/migration_guide_example_spec.rb +0 -44
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +0 -88
- data/spec/paperclip/paperclip_spec.rb +0 -196
- data/spec/paperclip/plural_cache_spec.rb +0 -37
- data/spec/paperclip/processor_helpers_spec.rb +0 -57
- data/spec/paperclip/processor_spec.rb +0 -60
- data/spec/paperclip/rails_environment_spec.rb +0 -30
- data/spec/paperclip/rake_spec.rb +0 -103
- data/spec/paperclip/schema_spec.rb +0 -298
- data/spec/paperclip/storage/filesystem_spec.rb +0 -102
- data/spec/paperclip/storage/fog_spec.rb +0 -606
- data/spec/paperclip/storage/s3_live_spec.rb +0 -188
- data/spec/paperclip/storage/s3_spec.rb +0 -1974
- data/spec/paperclip/style_spec.rb +0 -309
- data/spec/paperclip/tempfile_factory_spec.rb +0 -33
- data/spec/paperclip/tempfile_spec.rb +0 -35
- data/spec/paperclip/thumbnail_custom_options_spec.rb +0 -225
- data/spec/paperclip/thumbnail_loader_options_spec.rb +0 -53
- data/spec/paperclip/thumbnail_security_spec.rb +0 -42
- data/spec/paperclip/thumbnail_spec.rb +0 -1460
- data/spec/paperclip/url_generator_spec.rb +0 -231
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +0 -410
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +0 -249
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +0 -85
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +0 -325
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +0 -48
- data/spec/paperclip/validators_spec.rb +0 -179
- data/spec/spec_helper.rb +0 -52
- data/spec/support/assertions.rb +0 -84
- data/spec/support/fake_model.rb +0 -24
- data/spec/support/fake_rails.rb +0 -12
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/aws_s3.yml +0 -13
- data/spec/support/fixtures/bad.png +0 -1
- data/spec/support/fixtures/empty.html +0 -1
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +0 -8
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/s3.yml +0 -8
- data/spec/support/fixtures/sample.xlsm +0 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +0 -1
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/matchers/accept.rb +0 -5
- data/spec/support/matchers/exist.rb +0 -5
- data/spec/support/matchers/have_column.rb +0 -23
- data/spec/support/mock_attachment.rb +0 -24
- data/spec/support/mock_interpolator.rb +0 -24
- data/spec/support/mock_url_generator_builder.rb +0 -26
- data/spec/support/model_reconstruction.rb +0 -72
- data/spec/support/reporting.rb +0 -11
- data/spec/support/test_data.rb +0 -13
- data/spec/support/version_helper.rb +0 -9
|
@@ -1,1460 +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 "-flip" do
|
|
834
|
-
it "flips the image vertically" do
|
|
835
|
-
result = make_vips_thumb_with_options(@file, "-flip")
|
|
836
|
-
expect(File.exist?(result.path)).to be true
|
|
837
|
-
expect(File.size(result.path)).to be > 0
|
|
838
|
-
end
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
describe "-flop" do
|
|
842
|
-
it "flops the image horizontally" do
|
|
843
|
-
result = make_vips_thumb_with_options(@file, "-flop")
|
|
844
|
-
expect(File.exist?(result.path)).to be true
|
|
845
|
-
expect(File.size(result.path)).to be > 0
|
|
846
|
-
end
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
describe "-blur" do
|
|
850
|
-
it "applies gaussian blur to the image" do
|
|
851
|
-
result = make_vips_thumb_with_options(@file, "-blur 0x2")
|
|
852
|
-
expect(File.exist?(result.path)).to be true
|
|
853
|
-
expect(File.size(result.path)).to be > 0
|
|
854
|
-
end
|
|
855
|
-
|
|
856
|
-
it "handles different blur sigma values" do
|
|
857
|
-
result = make_vips_thumb_with_options(@file, "-blur 0x5")
|
|
858
|
-
expect(File.exist?(result.path)).to be true
|
|
859
|
-
end
|
|
860
|
-
end
|
|
861
|
-
|
|
862
|
-
describe "-gaussian_blur" do
|
|
863
|
-
it "applies gaussian blur" do
|
|
864
|
-
result = make_vips_thumb_with_options(@file, "-gaussian-blur 0x3")
|
|
865
|
-
expect(File.exist?(result.path)).to be true
|
|
866
|
-
expect(File.size(result.path)).to be > 0
|
|
867
|
-
end
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
describe "-sharpen" do
|
|
871
|
-
it "sharpens the image" do
|
|
872
|
-
result = make_vips_thumb_with_options(@file, "-sharpen 0x1")
|
|
873
|
-
expect(File.exist?(result.path)).to be true
|
|
874
|
-
expect(File.size(result.path)).to be > 0
|
|
875
|
-
end
|
|
876
|
-
end
|
|
877
|
-
|
|
878
|
-
describe "-colorspace" do
|
|
879
|
-
it "converts to grayscale (b-w)" do
|
|
880
|
-
result = make_vips_thumb_with_options(@file, "-colorspace Gray")
|
|
881
|
-
|
|
882
|
-
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
883
|
-
expect(colorspace).to eq("Gray")
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
it "converts to sRGB" do
|
|
887
|
-
result = make_vips_thumb_with_options(@file, "-colorspace sRGB")
|
|
888
|
-
|
|
889
|
-
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
890
|
-
expect(colorspace).to eq("sRGB")
|
|
891
|
-
end
|
|
892
|
-
|
|
893
|
-
it "converts to CMYK" do
|
|
894
|
-
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
895
|
-
result = make_vips_thumb_with_options(file, "-colorspace CMYK", format: :jpg)
|
|
896
|
-
|
|
897
|
-
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
898
|
-
expect(colorspace).to eq("CMYK")
|
|
899
|
-
file.close
|
|
900
|
-
end
|
|
901
|
-
end
|
|
902
|
-
|
|
903
|
-
describe "-flatten" do
|
|
904
|
-
it "flattens transparency to white background" do
|
|
905
|
-
result = make_vips_thumb_with_options(@file, "-flatten")
|
|
906
|
-
expect(File.exist?(result.path)).to be true
|
|
907
|
-
expect(File.size(result.path)).to be > 0
|
|
908
|
-
end
|
|
909
|
-
end
|
|
910
|
-
|
|
911
|
-
describe "-negate" do
|
|
912
|
-
it "inverts the image colors" do
|
|
913
|
-
result = make_vips_thumb_with_options(@file, "-negate")
|
|
914
|
-
expect(File.exist?(result.path)).to be true
|
|
915
|
-
expect(File.size(result.path)).to be > 0
|
|
916
|
-
end
|
|
917
|
-
end
|
|
918
|
-
|
|
919
|
-
describe "-auto-orient" do
|
|
920
|
-
it "auto-orients the image based on EXIF" do
|
|
921
|
-
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
922
|
-
result = make_vips_thumb_with_options(file, "-auto-orient")
|
|
923
|
-
expect(File.exist?(result.path)).to be true
|
|
924
|
-
|
|
925
|
-
# The rotated.jpg has orientation 6 (90 CW), so auto-orient should correct it
|
|
926
|
-
dimensions = `identify -format "%wx%h" "#{result.path}"`.strip
|
|
927
|
-
width, height = dimensions.split("x").map(&:to_i)
|
|
928
|
-
# After auto-orient, portrait orientation should be maintained
|
|
929
|
-
expect(height).to be > width
|
|
930
|
-
file.close
|
|
931
|
-
end
|
|
932
|
-
end
|
|
933
|
-
|
|
934
|
-
describe "-interlace" do
|
|
935
|
-
it "creates progressive/interlaced output for JPEG" do
|
|
936
|
-
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
937
|
-
result = make_vips_thumb_with_options(file, "-interlace Plane", format: :jpg)
|
|
938
|
-
expect(File.exist?(result.path)).to be true
|
|
939
|
-
expect(File.size(result.path)).to be > 0
|
|
940
|
-
file.close
|
|
941
|
-
end
|
|
942
|
-
|
|
943
|
-
it "creates interlaced output for PNG" do
|
|
944
|
-
result = make_vips_thumb_with_options(@file, "-interlace Line", format: :png)
|
|
945
|
-
expect(File.exist?(result.path)).to be true
|
|
946
|
-
end
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
describe "multiple cross-platform options combined" do
|
|
950
|
-
it "applies multiple options in sequence" do
|
|
951
|
-
result = make_vips_thumb_with_options(@file, "-strip -quality 80 -colorspace Gray")
|
|
952
|
-
|
|
953
|
-
colorspace = `identify -format "%[colorspace]" "#{result.path}"`.strip
|
|
954
|
-
expect(colorspace).to eq("Gray")
|
|
955
|
-
end
|
|
956
|
-
|
|
957
|
-
it "combines flip, flop, and rotate" do
|
|
958
|
-
result = make_vips_thumb_with_options(@file, "-flip -flop -rotate 180")
|
|
959
|
-
expect(File.exist?(result.path)).to be true
|
|
960
|
-
end
|
|
961
|
-
|
|
962
|
-
it "applies strip with quality and sharpen" do
|
|
963
|
-
file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
964
|
-
result = make_vips_thumb_with_options(file, "-strip -quality 85 -sharpen 0x1", format: :jpg)
|
|
965
|
-
|
|
966
|
-
# Verify EXIF is stripped
|
|
967
|
-
exif = `identify -format "%[exif:orientation]" "#{result.path}" 2>/dev/null`.strip
|
|
968
|
-
expect(exif).to be_empty
|
|
969
|
-
file.close
|
|
970
|
-
end
|
|
971
|
-
end
|
|
972
|
-
|
|
973
|
-
describe "ImageMagick-only options with vips (should warn)" do
|
|
974
|
-
it "logs warning for -density" do
|
|
975
|
-
expect(Paperclip).to receive(:log).with(/Warning.*density.*not supported.*vips/)
|
|
976
|
-
make_vips_thumb_with_options(@file, "-density 150")
|
|
977
|
-
end
|
|
978
|
-
|
|
979
|
-
it "logs warning for -depth" do
|
|
980
|
-
expect(Paperclip).to receive(:log).with(/Warning.*depth.*not supported.*vips/)
|
|
981
|
-
make_vips_thumb_with_options(@file, "-depth 8")
|
|
982
|
-
end
|
|
983
|
-
|
|
984
|
-
it "logs warning for -gravity" do
|
|
985
|
-
expect(Paperclip).to receive(:log).with(/Warning.*gravity.*not supported.*vips/)
|
|
986
|
-
make_vips_thumb_with_options(@file, "-gravity center")
|
|
987
|
-
end
|
|
988
|
-
|
|
989
|
-
it "logs warning for -crop" do
|
|
990
|
-
expect(Paperclip).to receive(:log).with(/Warning.*crop.*not supported.*vips/)
|
|
991
|
-
make_vips_thumb_with_options(@file, "-crop 50x50+0+0")
|
|
992
|
-
end
|
|
993
|
-
|
|
994
|
-
it "logs warning for -trim" do
|
|
995
|
-
expect(Paperclip).to receive(:log).with(/Warning.*trim.*not supported.*vips/)
|
|
996
|
-
make_vips_thumb_with_options(@file, "-trim")
|
|
997
|
-
end
|
|
998
|
-
|
|
999
|
-
it "logs warning for -normalize" do
|
|
1000
|
-
expect(Paperclip).to receive(:log).with(/Warning.*normalize.*not supported.*vips/)
|
|
1001
|
-
make_vips_thumb_with_options(@file, "-normalize")
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
|
-
it "logs warning for -monochrome" do
|
|
1005
|
-
expect(Paperclip).to receive(:log).with(/Warning.*monochrome.*not supported.*vips/)
|
|
1006
|
-
make_vips_thumb_with_options(@file, "-monochrome")
|
|
1007
|
-
end
|
|
1008
|
-
end
|
|
1009
|
-
end
|
|
1010
|
-
|
|
1011
|
-
[%w[600x600> 434x66],
|
|
1012
|
-
%w[400x400> 400x61],
|
|
1013
|
-
%w[32x32< 434x66],
|
|
1014
|
-
[nil, "434x66"]].each do |args|
|
|
1015
|
-
context "being thumbnailed with a geometry of #{args[0]}" do
|
|
1016
|
-
before do
|
|
1017
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: args[0])
|
|
1018
|
-
end
|
|
1019
|
-
|
|
1020
|
-
it "starts with dimensions of 434x66" do
|
|
1021
|
-
cmd = %[identify -format "%wx%h" "#{@file.path}"]
|
|
1022
|
-
assert_equal "434x66", `#{cmd}`.chomp
|
|
1023
|
-
end
|
|
1024
|
-
|
|
1025
|
-
it "reports the correct target geometry" do
|
|
1026
|
-
assert_equal args[0].to_s, @thumb.target_geometry.to_s
|
|
1027
|
-
end
|
|
1028
|
-
|
|
1029
|
-
context "when made" do
|
|
1030
|
-
before do
|
|
1031
|
-
@thumb_result = @thumb.make
|
|
1032
|
-
end
|
|
1033
|
-
|
|
1034
|
-
it "is the size we expect it to be" do
|
|
1035
|
-
cmd = %[identify -format "%wx%h" "#{@thumb_result.path}"]
|
|
1036
|
-
assert_equal args[1], `#{cmd}`.chomp
|
|
1037
|
-
end
|
|
1038
|
-
end
|
|
1039
|
-
end
|
|
1040
|
-
end
|
|
1041
|
-
|
|
1042
|
-
context "being thumbnailed at 100x50 with cropping" do
|
|
1043
|
-
before do
|
|
1044
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "100x50#")
|
|
1045
|
-
end
|
|
1046
|
-
|
|
1047
|
-
it "reports its correct current and target geometries" do
|
|
1048
|
-
assert_equal "100x50#", @thumb.target_geometry.to_s
|
|
1049
|
-
assert_equal "434x66", @thumb.current_geometry.to_s
|
|
1050
|
-
end
|
|
1051
|
-
|
|
1052
|
-
it "reports its correct format" do
|
|
1053
|
-
assert_nil @thumb.format
|
|
1054
|
-
end
|
|
1055
|
-
|
|
1056
|
-
it "has whiny turned on by default" do
|
|
1057
|
-
assert @thumb.whiny
|
|
1058
|
-
end
|
|
1059
|
-
|
|
1060
|
-
it "has convert_options set to nil by default" do
|
|
1061
|
-
assert_equal nil, @thumb.convert_options
|
|
1062
|
-
end
|
|
1063
|
-
|
|
1064
|
-
it "has source_file_options set to nil by default" do
|
|
1065
|
-
assert_equal nil, @thumb.source_file_options
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
it "creates the thumbnail when sent #make" do
|
|
1069
|
-
dst = @thumb.make
|
|
1070
|
-
assert_match /100x50/, `identify "#{dst.path}"`
|
|
1071
|
-
end
|
|
1072
|
-
end
|
|
1073
|
-
|
|
1074
|
-
it "crops a EXIF-rotated image properly" do
|
|
1075
|
-
file = File.new(fixture_file("rotated.jpg"))
|
|
1076
|
-
thumb = Paperclip::Thumbnail.new(file, geometry: "50x50#")
|
|
1077
|
-
|
|
1078
|
-
output_file = thumb.make
|
|
1079
|
-
|
|
1080
|
-
command = Terrapin::CommandLine.new("identify", "-format %wx%h :file")
|
|
1081
|
-
assert_equal "50x50", command.run(file: output_file.path).strip
|
|
1082
|
-
end
|
|
1083
|
-
|
|
1084
|
-
context "being thumbnailed with source file options set" do
|
|
1085
|
-
before do
|
|
1086
|
-
@file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
1087
|
-
@thumb = Paperclip::Thumbnail.new(@file,
|
|
1088
|
-
geometry: "100x50#",
|
|
1089
|
-
source_file_options: "-density 300")
|
|
1090
|
-
end
|
|
1091
|
-
|
|
1092
|
-
it "has source_file_options value set" do
|
|
1093
|
-
assert_equal "-density 300", @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
|
-
|
|
1101
|
-
it "actually applies the source file options (sets density)" do
|
|
1102
|
-
# Verify result has the set density
|
|
1103
|
-
dst = @thumb.make
|
|
1104
|
-
cmd_new = %[identify -format "%x" "#{dst.path}"]
|
|
1105
|
-
expect(`#{cmd_new}`.chomp).to start_with("300")
|
|
1106
|
-
end
|
|
1107
|
-
end
|
|
1108
|
-
|
|
1109
|
-
context "being thumbnailed with convert options set" do
|
|
1110
|
-
before do
|
|
1111
|
-
@file = File.new(fixture_file("rotated.jpg"), "rb")
|
|
1112
|
-
@thumb = Paperclip::Thumbnail.new(@file,
|
|
1113
|
-
geometry: "100x50#",
|
|
1114
|
-
convert_options: "-strip")
|
|
1115
|
-
end
|
|
1116
|
-
|
|
1117
|
-
it "has convert_options value set" do
|
|
1118
|
-
assert_equal "-strip", @thumb.convert_options
|
|
1119
|
-
end
|
|
1120
|
-
|
|
1121
|
-
it "creates the thumbnail when sent #make" do
|
|
1122
|
-
dst = @thumb.make
|
|
1123
|
-
assert_match /100x50/, `identify "#{dst.path}"`
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
|
-
it "actually applies the convert options (removes EXIF data)" do
|
|
1127
|
-
# Verify original has EXIF
|
|
1128
|
-
cmd_orig = %[identify -format "%[exif:orientation]" "#{@file.path}"]
|
|
1129
|
-
expect(`#{cmd_orig}`.chomp).to_not be_empty
|
|
1130
|
-
|
|
1131
|
-
# Verify result has no EXIF
|
|
1132
|
-
dst = @thumb.make
|
|
1133
|
-
cmd_new = %[identify -format "%[exif:orientation]" "#{dst.path}"]
|
|
1134
|
-
expect(`#{cmd_new}`.chomp).to be_empty
|
|
1135
|
-
end
|
|
1136
|
-
end
|
|
1137
|
-
|
|
1138
|
-
context "error handling" do
|
|
1139
|
-
before do
|
|
1140
|
-
require "image_processing"
|
|
1141
|
-
# Use a valid image so initialization (geometry detection) succeeds
|
|
1142
|
-
@file = File.new(fixture_file("5k.png"), "rb")
|
|
1143
|
-
end
|
|
1144
|
-
|
|
1145
|
-
context "with whiny enabled (default)" do
|
|
1146
|
-
it "raises an error when processing fails" do
|
|
1147
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
1148
|
-
allow(thumb).to receive(:build_pipeline).and_raise(Paperclip::Errors::CommandNotFoundError)
|
|
1149
|
-
|
|
1150
|
-
expect { thumb.make }.to raise_error(Paperclip::Errors::CommandNotFoundError)
|
|
1151
|
-
end
|
|
1152
|
-
|
|
1153
|
-
it "raises a Paperclip::Error when an underlying processing error occurs" do
|
|
1154
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
1155
|
-
# Simulate a generic processing error
|
|
1156
|
-
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1157
|
-
|
|
1158
|
-
expect { thumb.make }.to raise_error(Paperclip::Error, /Processing failed/)
|
|
1159
|
-
end
|
|
1160
|
-
end
|
|
1161
|
-
|
|
1162
|
-
context "with whiny disabled" do
|
|
1163
|
-
it "returns the original file when processing fails" do
|
|
1164
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
|
|
1165
|
-
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1166
|
-
|
|
1167
|
-
result = thumb.make
|
|
1168
|
-
expect(result).to eq(@file)
|
|
1169
|
-
end
|
|
1170
|
-
|
|
1171
|
-
it "logs the error" do
|
|
1172
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", whiny: false)
|
|
1173
|
-
allow(thumb).to receive(:build_pipeline).and_raise(ImageProcessing::Error.new("Processing failed"))
|
|
1174
|
-
|
|
1175
|
-
expect(Paperclip).to receive(:log).with(/Processing failed/)
|
|
1176
|
-
thumb.make
|
|
1177
|
-
end
|
|
1178
|
-
end
|
|
1179
|
-
end
|
|
1180
|
-
|
|
1181
|
-
context "being thumbnailed with a blank geometry string" do
|
|
1182
|
-
before do
|
|
1183
|
-
@thumb = Paperclip::Thumbnail.new(@file,
|
|
1184
|
-
geometry: "",
|
|
1185
|
-
convert_options: "-gravity center -crop 300x300+0-0")
|
|
1186
|
-
end
|
|
1187
|
-
|
|
1188
|
-
# Verify that when geometry is blank, we don't resize, but still apply other options.
|
|
1189
|
-
it "does not resize the image via geometry but applies convert_options" do
|
|
1190
|
-
result = @thumb.make
|
|
1191
|
-
cmd = %[identify -format "%wx%h" "#{result.path}"]
|
|
1192
|
-
# Original size is 434x66. The crop option (300x300) should result in 300x66.
|
|
1193
|
-
# This confirms that no geometry-based resize happened (which would have been to 0x0 or skipped),
|
|
1194
|
-
# but the convert_options crop was applied.
|
|
1195
|
-
expect(`#{cmd}`.chomp).to eq("300x66")
|
|
1196
|
-
end
|
|
1197
|
-
end
|
|
1198
|
-
|
|
1199
|
-
context "passing a custom file geometry parser" do
|
|
1200
|
-
after do
|
|
1201
|
-
Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
|
-
it "uses the custom parser" do
|
|
1205
|
-
GeoParser = Class.new do
|
|
1206
|
-
def self.from_file(_file, _backend = nil)
|
|
1207
|
-
new
|
|
1208
|
-
end
|
|
1209
|
-
|
|
1210
|
-
def width; 100; end
|
|
1211
|
-
|
|
1212
|
-
def height; 100; end
|
|
1213
|
-
|
|
1214
|
-
def modifier; nil; end
|
|
1215
|
-
|
|
1216
|
-
def auto_orient; end
|
|
1217
|
-
end
|
|
1218
|
-
|
|
1219
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", file_geometry_parser: ::GeoParser)
|
|
1220
|
-
expect(thumb.current_geometry).to be_a(GeoParser)
|
|
1221
|
-
end
|
|
1222
|
-
end
|
|
1223
|
-
|
|
1224
|
-
context "passing a custom geometry string parser" do
|
|
1225
|
-
after do
|
|
1226
|
-
Object.send(:remove_const, :GeoParser) if Object.const_defined?(:GeoParser)
|
|
1227
|
-
end
|
|
1228
|
-
|
|
1229
|
-
it "uses the custom parser" do
|
|
1230
|
-
GeoParser = Class.new do
|
|
1231
|
-
def self.parse(_s)
|
|
1232
|
-
new
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
|
-
def to_s
|
|
1236
|
-
"151x167"
|
|
1237
|
-
end
|
|
1238
|
-
|
|
1239
|
-
def width; 151; end
|
|
1240
|
-
|
|
1241
|
-
def height; 167; end
|
|
1242
|
-
|
|
1243
|
-
def modifier; nil; end
|
|
1244
|
-
end
|
|
1245
|
-
|
|
1246
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", string_geometry_parser: ::GeoParser)
|
|
1247
|
-
expect(thumb.target_geometry).to be_a(GeoParser)
|
|
1248
|
-
end
|
|
1249
|
-
end
|
|
1250
|
-
end
|
|
1251
|
-
|
|
1252
|
-
context "A multipage PDF" do
|
|
1253
|
-
before do
|
|
1254
|
-
@file = File.new(fixture_file("twopage.pdf"), "rb")
|
|
1255
|
-
end
|
|
1256
|
-
|
|
1257
|
-
after { @file.close }
|
|
1258
|
-
|
|
1259
|
-
it "starts with two pages with dimensions 612x792" do
|
|
1260
|
-
cmd = %[identify -format "%wx%h" "#{@file.path}"]
|
|
1261
|
-
assert_equal "612x792" * 2, `#{cmd}`.chomp
|
|
1262
|
-
end
|
|
1263
|
-
|
|
1264
|
-
context "being thumbnailed at 100x100 with cropping" do
|
|
1265
|
-
before do
|
|
1266
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "100x100#", format: :png)
|
|
1267
|
-
end
|
|
1268
|
-
|
|
1269
|
-
it "reports its correct current and target geometries" do
|
|
1270
|
-
assert_equal "100x100#", @thumb.target_geometry.to_s
|
|
1271
|
-
assert_equal "612x792", @thumb.current_geometry.to_s
|
|
1272
|
-
end
|
|
1273
|
-
|
|
1274
|
-
it "reports its correct format" do
|
|
1275
|
-
assert_equal :png, @thumb.format
|
|
1276
|
-
end
|
|
1277
|
-
|
|
1278
|
-
it "creates the thumbnail when sent #make" do
|
|
1279
|
-
dst = @thumb.make
|
|
1280
|
-
assert_match /100x100/, `identify "#{dst.path}"`
|
|
1281
|
-
end
|
|
1282
|
-
end
|
|
1283
|
-
end
|
|
1284
|
-
|
|
1285
|
-
context "An animated gif" do
|
|
1286
|
-
before do
|
|
1287
|
-
@file = File.new(fixture_file("animated.gif"), "rb")
|
|
1288
|
-
end
|
|
1289
|
-
|
|
1290
|
-
after { @file.close }
|
|
1291
|
-
|
|
1292
|
-
it "starts with 12 frames with size 100x100" do
|
|
1293
|
-
cmd = %[identify -format "%wx%h" "#{@file.path}"]
|
|
1294
|
-
assert_equal "100x100" * 12, `#{cmd}`.chomp
|
|
1295
|
-
end
|
|
1296
|
-
|
|
1297
|
-
context "with static output" do
|
|
1298
|
-
before do
|
|
1299
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :jpg)
|
|
1300
|
-
end
|
|
1301
|
-
|
|
1302
|
-
it "creates the single frame thumbnail when sent #make" do
|
|
1303
|
-
dst = @thumb.make
|
|
1304
|
-
cmd = %[identify -format "%wx%h" "#{dst.path}"]
|
|
1305
|
-
assert_equal "50x50", `#{cmd}`.chomp
|
|
1306
|
-
end
|
|
1307
|
-
end
|
|
1308
|
-
|
|
1309
|
-
context "with animated output format" do
|
|
1310
|
-
before do
|
|
1311
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :gif)
|
|
1312
|
-
end
|
|
1313
|
-
|
|
1314
|
-
it "creates the 12 frames thumbnail when sent #make" do
|
|
1315
|
-
dst = @thumb.make
|
|
1316
|
-
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1317
|
-
frames = `#{cmd}`.chomp.split(",")
|
|
1318
|
-
assert_equal 12, frames.size
|
|
1319
|
-
assert_frame_dimensions (45..50), frames
|
|
1320
|
-
end
|
|
1321
|
-
end
|
|
1322
|
-
|
|
1323
|
-
context "with omitted output format" do
|
|
1324
|
-
before do
|
|
1325
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50")
|
|
1326
|
-
end
|
|
1327
|
-
|
|
1328
|
-
it "creates the 12 frames thumbnail when sent #make" do
|
|
1329
|
-
dst = @thumb.make
|
|
1330
|
-
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1331
|
-
frames = `#{cmd}`.chomp.split(",")
|
|
1332
|
-
# image_processing might not preserve animation by default if not explicitly told to
|
|
1333
|
-
# or if the format is not explicitly set to gif.
|
|
1334
|
-
# But here we are testing default behavior.
|
|
1335
|
-
# If it fails with 1 frame, it means it collapsed it.
|
|
1336
|
-
# The original implementation preserved it.
|
|
1337
|
-
# We might need to force loader options for animated gifs if we want to preserve layers.
|
|
1338
|
-
# But image_processing/mini_magick should handle it if we don't flatten.
|
|
1339
|
-
|
|
1340
|
-
# If this fails, we might need to adjust the test expectation or implementation.
|
|
1341
|
-
# For now, let's assume we want to preserve behavior.
|
|
1342
|
-
assert_equal 12, frames.size
|
|
1343
|
-
assert_frame_dimensions (45..50), frames
|
|
1344
|
-
end
|
|
1345
|
-
end
|
|
1346
|
-
|
|
1347
|
-
context "with unidentified source format" do
|
|
1348
|
-
before do
|
|
1349
|
-
@unidentified_file = File.new(fixture_file("animated.unknown"), "rb")
|
|
1350
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "60x60")
|
|
1351
|
-
end
|
|
1352
|
-
|
|
1353
|
-
it "creates the 12 frames thumbnail when sent #make" do
|
|
1354
|
-
dst = @thumb.make
|
|
1355
|
-
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1356
|
-
frames = `#{cmd}`.chomp.split(",")
|
|
1357
|
-
assert_equal 12, frames.size
|
|
1358
|
-
assert_frame_dimensions (55..60), frames
|
|
1359
|
-
end
|
|
1360
|
-
end
|
|
1361
|
-
|
|
1362
|
-
context "with no source format" do
|
|
1363
|
-
before do
|
|
1364
|
-
@unidentified_file = File.new(fixture_file("animated"), "rb")
|
|
1365
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "70x70")
|
|
1366
|
-
end
|
|
1367
|
-
|
|
1368
|
-
it "creates the 12 frames thumbnail when sent #make" do
|
|
1369
|
-
dst = @thumb.make
|
|
1370
|
-
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1371
|
-
frames = `#{cmd}`.chomp.split(",")
|
|
1372
|
-
assert_equal 12, frames.size
|
|
1373
|
-
assert_frame_dimensions (60..70), frames
|
|
1374
|
-
end
|
|
1375
|
-
end
|
|
1376
|
-
|
|
1377
|
-
context "with animated option set to false" do
|
|
1378
|
-
before do
|
|
1379
|
-
@thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", animated: false)
|
|
1380
|
-
end
|
|
1381
|
-
|
|
1382
|
-
it "outputs the gif format" do
|
|
1383
|
-
dst = @thumb.make
|
|
1384
|
-
cmd = %[identify "#{dst.path}"]
|
|
1385
|
-
assert_match /GIF/, `#{cmd}`.chomp
|
|
1386
|
-
end
|
|
1387
|
-
|
|
1388
|
-
it "creates the single frame thumbnail when sent #make" do
|
|
1389
|
-
dst = @thumb.make
|
|
1390
|
-
cmd = %[identify -format "%wx%h" "#{dst.path}"]
|
|
1391
|
-
# The output might be multiple frames if image_processing doesn't collapse them
|
|
1392
|
-
# But we expect single frame here.
|
|
1393
|
-
# If it fails, we might need to adjust the implementation to force single frame.
|
|
1394
|
-
# For now, let's check if it starts with 50x50.
|
|
1395
|
-
output = `#{cmd}`.chomp
|
|
1396
|
-
# If multiple frames, it will be 50x5050x50...
|
|
1397
|
-
# We want exactly 50x50
|
|
1398
|
-
assert_equal "50x50", output
|
|
1399
|
-
end
|
|
1400
|
-
end
|
|
1401
|
-
|
|
1402
|
-
context "with a specified frame_index" do
|
|
1403
|
-
before do
|
|
1404
|
-
@thumb = Paperclip::Thumbnail.new(
|
|
1405
|
-
@file,
|
|
1406
|
-
geometry: "50x50",
|
|
1407
|
-
frame_index: 5,
|
|
1408
|
-
format: :jpg,
|
|
1409
|
-
)
|
|
1410
|
-
end
|
|
1411
|
-
|
|
1412
|
-
it "creates the thumbnail from the frame index when sent #make" do
|
|
1413
|
-
@thumb.make
|
|
1414
|
-
assert_equal 5, @thumb.frame_index
|
|
1415
|
-
end
|
|
1416
|
-
end
|
|
1417
|
-
|
|
1418
|
-
context "with vips backend" do
|
|
1419
|
-
before do
|
|
1420
|
-
require "vips"
|
|
1421
|
-
rescue LoadError
|
|
1422
|
-
skip "libvips not installed"
|
|
1423
|
-
end
|
|
1424
|
-
|
|
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,
|
|
1435
|
-
{ geometry: "50x50", format: :gif, animated: false, backend: :vips })
|
|
1436
|
-
dst = processor.make
|
|
1437
|
-
cmd = %[identify -format "%wx%h," "#{dst.path}"]
|
|
1438
|
-
frames = `#{cmd}`.chomp.split(",").reject(&:empty?)
|
|
1439
|
-
expect(frames.size).to eq(1)
|
|
1440
|
-
expect(frames.first).to eq("50x50")
|
|
1441
|
-
end
|
|
1442
|
-
end
|
|
1443
|
-
end
|
|
1444
|
-
|
|
1445
|
-
context "with a really long file name" do
|
|
1446
|
-
before do
|
|
1447
|
-
tempfile = Tempfile.new("f")
|
|
1448
|
-
tempfile_additional_chars = tempfile.path.split("/")[-1].length + 15
|
|
1449
|
-
image_file = File.new(fixture_file("5k.png"), "rb")
|
|
1450
|
-
@file = Tempfile.new("f" * (255 - tempfile_additional_chars))
|
|
1451
|
-
@file.write(image_file.read)
|
|
1452
|
-
@file.rewind
|
|
1453
|
-
end
|
|
1454
|
-
|
|
1455
|
-
it "does not throw Errno::ENAMETOOLONG" do
|
|
1456
|
-
thumb = Paperclip::Thumbnail.new(@file, geometry: "50x50", format: :gif)
|
|
1457
|
-
expect { thumb.make }.to_not raise_error
|
|
1458
|
-
end
|
|
1459
|
-
end
|
|
1460
|
-
end
|