Skip to content

Commit

Permalink
Add resize_to_cover (#120)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
brendon authored Jun 6, 2024
1 parent fd9980b commit 8c85ad9
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

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

* Add `#cover` that allows one to resize an image to cover a given width and height without cropping
the excess. (@brendon)

## 1.12.2 (2022-03-01)

* Prevent remote shell execution when using `#apply` with operations coming from user input (@janko)
Expand Down
14 changes: 14 additions & 0 deletions doc/minimagick.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ the [MiniMagick] gem (which is installed with the image_processing gem).
* [`#resize_to_fit`](#resize_to_fit)
* [`#resize_to_fill`](#resize_to_fill)
* [`#resize_and_pad`](#resize_and_pad)
* [`#cover`](#cover)
* [`#crop`](#crop)
* [`#rotate`](#rotate)
* [`#composite`](#composite)
Expand Down Expand Up @@ -189,6 +190,19 @@ It accepts `:gravity` for specifying the [gravity] to apply while cropping
pipeline.resize_and_pad!(400, 400, gravity: "north-west")
```

#### `#cover`

Resizes the image to cover the specified dimensions while retaining the
original aspect ratio. The overflowing areas will not be cropped.

```rb
pipeline = ImageProcessing::MiniMagick.source(image) # 600x800

result = pipeline.cover!(300, 300)

MiniMagick::Image.new(result.path).dimensions #=> [300, 400]
```

#### `#crop`

Extracts an area from an image. The first two arguments are left & top edges of
Expand Down
22 changes: 22 additions & 0 deletions doc/vips.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The `ImageProcessing::Vips` module contains processing macros that use the
* [`#resize_to_fit`](#resize_to_fit)
* [`#resize_to_fill`](#resize_to_fill)
* [`#resize_and_pad`](#resize_and_pad)
* [`#cover`](#cover)
* [`#crop`](#crop)
* [`#rotate`](#rotate)
* [`#composite`](#composite)
Expand Down Expand Up @@ -221,6 +222,27 @@ pipeline.resize_to_fill!(400, 400, linear: true)

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

#### `#cover`

Resizes the image to cover the specified dimensions while retaining the
original aspect ratio. The overflowing areas will not be cropped.

```rb
pipeline = ImageProcessing::Vips.source(image) # 600x800

result = pipeline.cover!(300, 300)

Vips::Image.new_from_file(result.path).size #=> [300, 400]
```

Any additional options (except `crop`) are forwarded to [`Vips::Image#thumbnail_image`]:

```rb
pipeline.cover!(400, 400, linear: true)
```

See [`vips_thumbnail()`] for more details.

#### `#crop`

Extracts an area from an image. The first two arguments are left & top edges of
Expand Down
6 changes: 6 additions & 0 deletions lib/image_processing/mini_magick.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ def resize_and_pad(width, height, background: :transparent, gravity: "Center", *
magick.extent "#{width}x#{height}"
end

# Resizes the image to cover the specified dimensions, without
# cropping the excess.
def cover(width, height, **options)
thumbnail("#{width}x#{height}^", **options)
end

# Crops the image with the specified crop points.
def crop(*args)
case args.count
Expand Down
15 changes: 15 additions & 0 deletions lib/image_processing/vips.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ def resize_and_pad(width, height, gravity: "centre", extend: nil, background: ni
image.gravity(gravity, width, height, extend: extend, background: background)
end

# Resizes the image to cover the specified dimensions, without
# cropping the excess.
def cover(width, height, **options)
image_ratio = Rational(image.width, image.height)
thumbnail_ratio = Rational(width, height)

if image_ratio > thumbnail_ratio
width = ::Vips::MAX_COORD
else
height = ::Vips::MAX_COORD
end

thumbnail(width, height, **options, crop: :none)
end

# Rotates the image by an arbitrary angle.
def rotate(degrees, **options)
image.similarity(angle: degrees, **options)
Expand Down
Binary file added test/fixtures/cover.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions test/mini_magick_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
before do
@portrait = fixture_image("portrait.jpg")
@landscape = fixture_image("landscape.jpg")
@square = fixture_image("square.jpg")
end

it "applies imagemagick operations" do
Expand Down Expand Up @@ -366,6 +367,61 @@
end
end

describe "#cover" do
before do
@portrait_pipeline = ImageProcessing::MiniMagick.source(@portrait)
@landscape_pipeline = ImageProcessing::MiniMagick.source(@landscape)
@square_pipeline = ImageProcessing::MiniMagick.source(@square)
end

it "resizes the portrait image to fill out the given landscape dimensions" do
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 200)
end

it "resizes the portrait image to fill out the given portrait dimensions" do
assert_dimensions [225, 300], @portrait_pipeline.cover!(200, 300)
end

it "resizes the portrait image to fill out the given square dimensions" do
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 300)
end

it "resizes the landscape image to fill out the given portrait dimensions" do
assert_dimensions [400, 300], @landscape_pipeline.cover!(200, 300)
end

it "resizes the landscape image to fill out the given landscape dimensions" do
assert_dimensions [300, 225], @landscape_pipeline.cover!(300, 200)
end

it "resizes the landscape image to fill out the given square dimensions" do
assert_dimensions [400, 300], @landscape_pipeline.cover!(300, 300)
end

it "resizes the square image to fill out the given portrait dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(200, 300)
end

it "resizes the square image to fill out the given landscape dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(300, 200)
end

it "resizes the square image to fill out the given square dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(300, 300)
end

it "produces correct image" do
expected = fixture_image("cover.jpg")
assert_similar expected, @portrait_pipeline.cover!(300, 200)
end

it "accepts sharpening options" do
sharpened = @portrait_pipeline.cover!(400, 400, sharpen: { sigma: 1 })
normal = @portrait_pipeline.cover!(400, 400, sharpen: false)
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
end
end

describe "#crop" do
before do
@pipeline = ImageProcessing::MiniMagick.source(@portrait)
Expand Down
67 changes: 67 additions & 0 deletions test/vips_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
before do
@portrait = fixture_image("portrait.jpg")
@landscape = fixture_image("landscape.jpg")
@square = fixture_image("square.jpg")
end

it "applies vips operations" do
Expand Down Expand Up @@ -322,6 +323,72 @@
end
end

describe "#cover" do
before do
@portrait_pipeline = ImageProcessing::Vips.source(@portrait)
@landscape_pipeline = ImageProcessing::Vips.source(@landscape)
@square_pipeline = ImageProcessing::Vips.source(@square)
end

it "resizes the portrait image to fill out the given landscape dimensions" do
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 200)
end

it "resizes the portrait image to fill out the given portrait dimensions" do
assert_dimensions [225, 300], @portrait_pipeline.cover!(200, 300)
end

it "resizes the portrait image to fill out the given square dimensions" do
assert_dimensions [300, 400], @portrait_pipeline.cover!(300, 300)
end

it "resizes the landscape image to fill out the given portrait dimensions" do
assert_dimensions [400, 300], @landscape_pipeline.cover!(200, 300)
end

it "resizes the landscape image to fill out the given landscape dimensions" do
assert_dimensions [300, 225], @landscape_pipeline.cover!(300, 200)
end

it "resizes the landscape image to fill out the given square dimensions" do
assert_dimensions [400, 300], @landscape_pipeline.cover!(300, 300)
end

it "resizes the square image to fill out the given portrait dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(200, 300)
end

it "resizes the square image to fill out the given landscape dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(300, 200)
end

it "resizes the square image to fill out the given square dimensions" do
assert_dimensions [300, 300], @square_pipeline.cover!(300, 300)
end

it "produces correct image" do
expected = fixture_image("cover.jpg")
assert_similar expected, @portrait_pipeline.cover!(300, 200)
end

it "accepts thumbnail options except :crop" do
attention = @portrait_pipeline.cover!(400, 400, crop: :attention)
centre = @portrait_pipeline.cover!(400, 400, crop: :centre)
assert_similar centre, attention
end

it "accepts sharpening options" do
sharpened = @portrait_pipeline.cover!(400, 400, sharpen: ImageProcessing::Vips::Processor::SHARPEN_MASK)
normal = @portrait_pipeline.cover!(400, 400, sharpen: false)
assert sharpened.size > normal.size, "Expected sharpened thumbnail to have bigger filesize than not sharpened thumbnail"
end

it "sharpening uses integer precision" do
sharpened = @portrait_pipeline.cover(400, 400).call(save: false)
assert_equal :uchar, sharpened.format
end
end

describe "#rotate" do
before do
@pipeline = ImageProcessing::Vips.source(@portrait)
Expand Down

0 comments on commit 8c85ad9

Please sign in to comment.