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

Added support for the "crop" method. #2721

Merged
merged 11 commits into from
Feb 12, 2024
Merged
56 changes: 48 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -979,14 +979,6 @@ class AvatarUploader < CarrierWave::Uploader::Base
end
```

#### List of available processing methods:

- `convert` - Changes the image encoding format to the given format(eg. jpg). This operation is treated specially to trigger the change of the file extension, so it matches with the format of the resulting file.
- `resize_to_limit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
- `resize_to_fit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. The image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
- `resize_to_fill` - Resize the image to fit within the specified dimensions while retaining the aspect ratio of the original image. If necessary, crop the image in the larger dimension. Optionally, a "gravity" may be specified, for example "Center", or "NorthEast".
- `resize_and_pad` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. If necessary, will pad the remaining area with the given color, which defaults to transparent (for gif and png, white for jpeg). Optionally, a "gravity" may be specified, as above.

See `carrierwave/processing/mini_magick.rb` for details.

### Using RMagick
Expand Down Expand Up @@ -1018,6 +1010,54 @@ end
Check out the manipulate! method, which makes it easy for you to write your own
manipulation methods.

### Using Vips

CarrierWave version 2.2.0 added support for the `libvips` image processing library, through [ImageProcessing::Vips](https://github.com/janko/image_processing/blob/master/doc/vips.md). Its functionality matches that of the RMagick and MiniMagick processors, but it uses less memory and offers [faster processing](https://github.com/libvips/libvips/wiki/Speed-and-memory-use). To use the Vips processing module you must first install `libvips`, for example:

````bash
$ sudo apt install libvips
````

You also need to tell your uploader to use Vips:

````ruby
class ImageFileUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
end
````

### List of available processing methods:

> [!NOTE]
> While the intetion is to provide uniform interfaces to al three processing libraries the availability and implementation of processing methods can <a href="supported-processing-methods">vary slightly between them</a>.

- `convert` - Changes the image encoding format to the given format (eg. jpg). This operation is treated specially to trigger the change of the file extension, so it matches with the format of the resulting file.
- `resize_to_limit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
- `resize_to_fit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. The image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
- `resize_to_fill` - Resize the image to fit within the specified dimensions while retaining the aspect ratio of the original image. If necessary, crop the image in the larger dimension. Optionally, a "gravity" may be specified, for example "Center", or "NorthEast".
- `resize_and_pad` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. If necessary, will pad the remaining area with the given color, which defaults to transparent (for gif and png, white for jpeg). Optionally, a "gravity" may be specified, as above.
- `crop` - Crop the image to the contents of a box with the specified height and width, positioned a given number of pixels from the top and left. The original image edge will be retained should the bottom and/or right edge of the box fall outside the image bounds.

#### Supported processing methods

The following table shows which processing methods are supported by each processing library, and which parameters they accept:

Method|RMagick|MiniMagick|Vips
------|-----------------|-----------------|-----------------|
`convert`|`format`|`format`, `page`<sup>1</sup>|`format`, `page`<sup>1</sup>
`resize_to_limit`|`width`, `height`|`width`, `height`|`width`, `height`
`resize_to_fit`|`width`, `height`|`width`, `height`|`width`, `height`
`resize_to_fill`|`width`, `height`, `gravity`<sup>2</sup>|`width`, `height`, `gravity`<sup>2</sup>|`width`, `height`
`resize_and_pad`|`width`, `height`, `background`, `gravity`<sup>2</sup>|`width`, `height`, `background`, `gravity`<sup>2</sup>|`width`, `height`, `background`, `gravity`<sup>2</sup>
`resize_to_geometry_string`|`geometry_string`<sup>3</sup>|*not implemented*|*not implemented*
`crop`|`left`, `top`, `width`, `height`|`left`, `top`, `width`, `height`|`left`, `top`, `width`, `height`

<sup>1</sup>`page` refers to the page number when converting from PDF, frame number when converting from GIF, and layer number when converting from PSD.

<sup>2</sup>`gravity` refers to an image position given as one of `Center`, `North`, `NorthWest`, `West`, `SouthWest`, `South`, `SouthEast`, `East`, or `NorthEast`.

<sup>3</sup>`geometry_string` is an [ImageMagick geometry string](https://rmagick.github.io/imusage.html#geometry).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice comparison table 👍


## Migrating from Paperclip

If you are using Paperclip, you can use the provided compatibility module:
Expand Down
1 change: 1 addition & 0 deletions carrierwave.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "fog-local"
s.add_development_dependency "fog-rackspace"
s.add_development_dependency "mini_magick", ">= 3.6.0"

if RUBY_ENGINE != 'jruby'
s.add_development_dependency "rmagick", ">= 2.16"
end
Expand Down
29 changes: 29 additions & 0 deletions lib/carrierwave/processing/mini_magick.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def resize_to_fill(width, height, gravity='Center')
def resize_and_pad(width, height, background=:transparent, gravity='Center')
process :resize_and_pad => [width, height, background, gravity]
end

def crop(left, top, width, height)
process :crop => [left, top, width, height]
end
end

##
Expand Down Expand Up @@ -210,6 +214,31 @@ def resize_and_pad(width, height, background=:transparent, gravity='Center', com
end
end

##
# Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
# by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
# outside the image bounds.
#
# === Parameters
#
# [left (integer)] left edge of area to extract
# [top (integer)] top edge of area to extract
# [width (Integer)] width of area to extract
# [height (Integer)] height of area to extract
#
# === Yields
#
# [MiniMagick::Image] additional manipulations to perform
#
def crop(left, top, width, height, combine_options: {}, &block)
width, height = resolve_dimensions(width, height)

minimagick!(block) do |builder|
builder.crop(left, top, width, height)
.apply(combine_options)
end
end

##
# Returns the width of the image in pixels.
#
Expand Down
31 changes: 31 additions & 0 deletions lib/carrierwave/processing/rmagick.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::Cen
def resize_to_geometry_string(geometry_string)
process :resize_to_geometry_string => [geometry_string]
end

def crop(left, top, width, height)
process :crop => [left, top, width, height]
end
end

##
Expand Down Expand Up @@ -260,6 +264,33 @@ def resize_to_geometry_string(geometry_string)
end
end

##
# Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
# by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
# outside the image bounds.
#
# === Parameters
#
# [left (integer)] left edge of area to extract
# [top (integer)] top edge of area to extract
# [width (Integer)] width of area to extract
# [height (Integer)] height of area to extract
#
# === Yields
#
# [Magick::Image] additional manipulations to perform
#
def crop(left, top, width, height, combine_options: {})
width = dimension_from width
height = dimension_from height

manipulate! do |img|
img.crop!(left, top, width, height)
img = yield(img) if block_given?
img
end
end

##
# Returns the width of the image.
#
Expand Down
31 changes: 31 additions & 0 deletions lib/carrierwave/processing/vips.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def resize_to_fill(width, height, gravity='centre')
def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
process :resize_and_pad => [width, height, background, gravity, alpha]
end

def crop(left, top, width, height)
process :crop => [left, top, width, height]
end
end

##
Expand Down Expand Up @@ -208,6 +212,33 @@ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, c
end
end

##
# Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
# by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
# outside the image bounds.
#
# === Parameters
#
# [left (integer)] left edge of area to extract
# [top (integer)] top edge of area to extract
# [width (Integer)] width of area to extract
# [height (Integer)] height of area to extract
#
# === Yields
#
# [Vips::Image] additional manipulations to perform
#
def crop(left, top, width, height, combine_options: {})
width, height = resolve_dimensions(width, height)
width = vips_image.width - left if width + left > vips_image.width
height = vips_image.height - top if height + top > vips_image.height

vips! do |builder|
builder.crop(left, top, width, height)
.apply(combine_options)
end
end

##
# Returns the width of the image in pixels.
#
Expand Down
12 changes: 12 additions & 0 deletions spec/processing/mini_magick_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@
end
end

describe "#crop" do
it "extracts an area defined from the left and top positions, with the given width and height" do
instance.crop(70, 40, 500, 400)
expect(instance).to have_dimensions(500, 400)
end

it "retains original image boundary if either edge of the cropping box falls outside it" do
instance.crop(140, 80, 500, 480)
expect(instance).to have_dimensions(500, 400)
end
end

describe "#width and #height" do
it "returns the width and height of the image" do
instance.resize_to_fill(200, 300)
Expand Down
12 changes: 12 additions & 0 deletions spec/processing/rmagick_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@
end
end

describe "#crop" do
it "extracts an area defined from the left and top positions, with the given width and height" do
instance.crop(70, 40, 500, 400)
expect(instance).to have_dimensions(500, 400)
end

it "retains original image boundary if either edge of the cropping box falls outside it" do
instance.crop(140, 80, 500, 480)
expect(instance).to have_dimensions(500, 400)
end
end

describe "#manipulate!" do
let(:image) { ::Magick::Image.read(landscape_file_path) }

Expand Down
12 changes: 12 additions & 0 deletions spec/processing/vips_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@
end
end

describe "#crop" do
it "extracts an area defined from the left and top positions, with the given width and height" do
instance.crop(70, 40, 500, 400)
expect(instance).to have_dimensions(500, 400)
end

it "retains original image boundary if either edge of the cropping box falls outside it" do
instance.crop(140, 80, 500, 480)
expect(instance).to have_dimensions(500, 400)
end
end

describe "#width and #height" do
it "returns the width and height of the image" do
instance.resize_to_fill(200, 300)
Expand Down