Skip to content

Commit 8c85ad9

Browse files
authored
Add resize_to_cover (#120)
* Add resize_to_cover * Add an initial resize_to_cover test * Fix method name and add passing tests for VIPS * Add cover method for MiniMagick and add tests * Rearrange VIPS tests to match source code order * Update CHANGELOG.md * Add documentation for cover to both engines
1 parent fd9980b commit 8c85ad9

File tree

9 files changed

+183
-0
lines changed

9 files changed

+183
-0
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
* [minimagick] Don't allow calling Kernel options via `loader`/`saver` options (@janko)
44

5+
* Add `#cover` that allows one to resize an image to cover a given width and height without cropping
6+
the excess. (@brendon)
7+
58
## 1.12.2 (2022-03-01)
69

710
* Prevent remote shell execution when using `#apply` with operations coming from user input (@janko)

doc/minimagick.md

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ the [MiniMagick] gem (which is installed with the image_processing gem).
1515
* [`#resize_to_fit`](#resize_to_fit)
1616
* [`#resize_to_fill`](#resize_to_fill)
1717
* [`#resize_and_pad`](#resize_and_pad)
18+
* [`#cover`](#cover)
1819
* [`#crop`](#crop)
1920
* [`#rotate`](#rotate)
2021
* [`#composite`](#composite)
@@ -189,6 +190,19 @@ It accepts `:gravity` for specifying the [gravity] to apply while cropping
189190
pipeline.resize_and_pad!(400, 400, gravity: "north-west")
190191
```
191192

193+
#### `#cover`
194+
195+
Resizes the image to cover the specified dimensions while retaining the
196+
original aspect ratio. The overflowing areas will not be cropped.
197+
198+
```rb
199+
pipeline = ImageProcessing::MiniMagick.source(image) # 600x800
200+
201+
result = pipeline.cover!(300, 300)
202+
203+
MiniMagick::Image.new(result.path).dimensions #=> [300, 400]
204+
```
205+
192206
#### `#crop`
193207

194208
Extracts an area from an image. The first two arguments are left & top edges of

doc/vips.md

+22
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The `ImageProcessing::Vips` module contains processing macros that use the
1414
* [`#resize_to_fit`](#resize_to_fit)
1515
* [`#resize_to_fill`](#resize_to_fill)
1616
* [`#resize_and_pad`](#resize_and_pad)
17+
* [`#cover`](#cover)
1718
* [`#crop`](#crop)
1819
* [`#rotate`](#rotate)
1920
* [`#composite`](#composite)
@@ -221,6 +222,27 @@ pipeline.resize_to_fill!(400, 400, linear: true)
221222

222223
See [`vips_thumbnail()`] and [`vips_gravity()`] for more details.
223224

225+
#### `#cover`
226+
227+
Resizes the image to cover the specified dimensions while retaining the
228+
original aspect ratio. The overflowing areas will not be cropped.
229+
230+
```rb
231+
pipeline = ImageProcessing::Vips.source(image) # 600x800
232+
233+
result = pipeline.cover!(300, 300)
234+
235+
Vips::Image.new_from_file(result.path).size #=> [300, 400]
236+
```
237+
238+
Any additional options (except `crop`) are forwarded to [`Vips::Image#thumbnail_image`]:
239+
240+
```rb
241+
pipeline.cover!(400, 400, linear: true)
242+
```
243+
244+
See [`vips_thumbnail()`] for more details.
245+
224246
#### `#crop`
225247

226248
Extracts an area from an image. The first two arguments are left & top edges of

lib/image_processing/mini_magick.rb

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def resize_and_pad(width, height, background: :transparent, gravity: "Center", *
8686
magick.extent "#{width}x#{height}"
8787
end
8888

89+
# Resizes the image to cover the specified dimensions, without
90+
# cropping the excess.
91+
def cover(width, height, **options)
92+
thumbnail("#{width}x#{height}^", **options)
93+
end
94+
8995
# Crops the image with the specified crop points.
9096
def crop(*args)
9197
case args.count

lib/image_processing/vips.rb

+15
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ def resize_and_pad(width, height, gravity: "centre", extend: nil, background: ni
9090
image.gravity(gravity, width, height, extend: extend, background: background)
9191
end
9292

93+
# Resizes the image to cover the specified dimensions, without
94+
# cropping the excess.
95+
def cover(width, height, **options)
96+
image_ratio = Rational(image.width, image.height)
97+
thumbnail_ratio = Rational(width, height)
98+
99+
if image_ratio > thumbnail_ratio
100+
width = ::Vips::MAX_COORD
101+
else
102+
height = ::Vips::MAX_COORD
103+
end
104+
105+
thumbnail(width, height, **options, crop: :none)
106+
end
107+
93108
# Rotates the image by an arbitrary angle.
94109
def rotate(degrees, **options)
95110
image.similarity(angle: degrees, **options)

test/fixtures/cover.jpg

50.5 KB
Loading

test/fixtures/square.jpg

129 KB
Loading

test/mini_magick_test.rb

+56
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
before do
99
@portrait = fixture_image("portrait.jpg")
1010
@landscape = fixture_image("landscape.jpg")
11+
@square = fixture_image("square.jpg")
1112
end
1213

1314
it "applies imagemagick operations" do
@@ -366,6 +367,61 @@
366367
end
367368
end
368369

370+
describe "#cover" do
371+
before do
372+
@portrait_pipeline = ImageProcessing::MiniMagick.source(@portrait)
373+
@landscape_pipeline = ImageProcessing::MiniMagick.source(@landscape)
374+
@square_pipeline = ImageProcessing::MiniMagick.source(@square)
375+
end
376+
377+
it "resizes the portrait image to fill out the given landscape dimensions" do
378+
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 200)
379+
end
380+
381+
it "resizes the portrait image to fill out the given portrait dimensions" do
382+
assert_dimensions [225, 300], @portrait_pipeline.cover!(200, 300)
383+
end
384+
385+
it "resizes the portrait image to fill out the given square dimensions" do
386+
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 300)
387+
end
388+
389+
it "resizes the landscape image to fill out the given portrait dimensions" do
390+
assert_dimensions [400, 300], @landscape_pipeline.cover!(200, 300)
391+
end
392+
393+
it "resizes the landscape image to fill out the given landscape dimensions" do
394+
assert_dimensions [300, 225], @landscape_pipeline.cover!(300, 200)
395+
end
396+
397+
it "resizes the landscape image to fill out the given square dimensions" do
398+
assert_dimensions [400, 300], @landscape_pipeline.cover!(300, 300)
399+
end
400+
401+
it "resizes the square image to fill out the given portrait dimensions" do
402+
assert_dimensions [300, 300], @square_pipeline.cover!(200, 300)
403+
end
404+
405+
it "resizes the square image to fill out the given landscape dimensions" do
406+
assert_dimensions [300, 300], @square_pipeline.cover!(300, 200)
407+
end
408+
409+
it "resizes the square image to fill out the given square dimensions" do
410+
assert_dimensions [300, 300], @square_pipeline.cover!(300, 300)
411+
end
412+
413+
it "produces correct image" do
414+
expected = fixture_image("cover.jpg")
415+
assert_similar expected, @portrait_pipeline.cover!(300, 200)
416+
end
417+
418+
it "accepts sharpening options" do
419+
sharpened = @portrait_pipeline.cover!(400, 400, sharpen: { sigma: 1 })
420+
normal = @portrait_pipeline.cover!(400, 400, sharpen: false)
421+
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
422+
end
423+
end
424+
369425
describe "#crop" do
370426
before do
371427
@pipeline = ImageProcessing::MiniMagick.source(@portrait)

test/vips_test.rb

+67
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
before do
77
@portrait = fixture_image("portrait.jpg")
88
@landscape = fixture_image("landscape.jpg")
9+
@square = fixture_image("square.jpg")
910
end
1011

1112
it "applies vips operations" do
@@ -322,6 +323,72 @@
322323
end
323324
end
324325

326+
describe "#cover" do
327+
before do
328+
@portrait_pipeline = ImageProcessing::Vips.source(@portrait)
329+
@landscape_pipeline = ImageProcessing::Vips.source(@landscape)
330+
@square_pipeline = ImageProcessing::Vips.source(@square)
331+
end
332+
333+
it "resizes the portrait image to fill out the given landscape dimensions" do
334+
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 200)
335+
end
336+
337+
it "resizes the portrait image to fill out the given portrait dimensions" do
338+
assert_dimensions [225, 300], @portrait_pipeline.cover!(200, 300)
339+
end
340+
341+
it "resizes the portrait image to fill out the given square dimensions" do
342+
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 300)
343+
end
344+
345+
it "resizes the landscape image to fill out the given portrait dimensions" do
346+
assert_dimensions [400, 300], @landscape_pipeline.cover!(200, 300)
347+
end
348+
349+
it "resizes the landscape image to fill out the given landscape dimensions" do
350+
assert_dimensions [300, 225], @landscape_pipeline.cover!(300, 200)
351+
end
352+
353+
it "resizes the landscape image to fill out the given square dimensions" do
354+
assert_dimensions [400, 300], @landscape_pipeline.cover!(300, 300)
355+
end
356+
357+
it "resizes the square image to fill out the given portrait dimensions" do
358+
assert_dimensions [300, 300], @square_pipeline.cover!(200, 300)
359+
end
360+
361+
it "resizes the square image to fill out the given landscape dimensions" do
362+
assert_dimensions [300, 300], @square_pipeline.cover!(300, 200)
363+
end
364+
365+
it "resizes the square image to fill out the given square dimensions" do
366+
assert_dimensions [300, 300], @square_pipeline.cover!(300, 300)
367+
end
368+
369+
it "produces correct image" do
370+
expected = fixture_image("cover.jpg")
371+
assert_similar expected, @portrait_pipeline.cover!(300, 200)
372+
end
373+
374+
it "accepts thumbnail options except :crop" do
375+
attention = @portrait_pipeline.cover!(400, 400, crop: :attention)
376+
centre = @portrait_pipeline.cover!(400, 400, crop: :centre)
377+
assert_similar centre, attention
378+
end
379+
380+
it "accepts sharpening options" do
381+
sharpened = @portrait_pipeline.cover!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
382+
normal = @portrait_pipeline.cover!(400, 400, sharpen: false)
383+
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
384+
end
385+
386+
it "sharpening uses integer precision" do
387+
sharpened = @portrait_pipeline.cover(400, 400).call(save: false)
388+
assert_equal :uchar, sharpened.format
389+
end
390+
end
391+
325392
describe "#rotate" do
326393
before do
327394
@pipeline = ImageProcessing::Vips.source(@portrait)

0 commit comments

Comments
 (0)