Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resize_to_cover #120

Merged
merged 7 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading