ImageProcessing
Provides higher-level image processing functionality that is commonly needed when accepting user uploads. Supports processing with VIPS and ImageMagick/GraphicsMagick.
The goal of this project is to have a single place where common image processing helper methods are maintained, instead of Paperclip, CarrierWave, Refile, Dragonfly and ActiveStorage each implementing their own versions.
Installation
gem "image_processing"
ruby-vips
The ImageProcessing::Vips
module contains processing macros that use the
ruby-vips gem, which you need to install:
# Gemfile
gem "ruby-vips", "~> 2.0"
Note that you'll need to have libvips 8.6 or higher installed; see the installation instructions for more details.
Usage
ImageProcessing::Vips
lets you define the processing pipeline using a
chainable API:
require "image_processing/vips"
processed = ImageProcessing::Vips
.source(file)
.autorot
.resize_to_limit(400, 400)
.convert("png")
.call
processed #=> #<File:/var/folders/.../image_processing-vips20180316-18446-1j247h6.png>
This allows easy branching when generating multiple derivatives:
pipeline = ImageProcessing::Vips
.source(file)
.autorot
.convert("png")
large = pipeline.resize_to_limit!(800, 800)
medium = pipeline.resize_to_limit!(500, 500)
small = pipeline.resize_to_limit!(300, 300)
The processing is executed on #call
or when a processing method is called
with a bang (!
).
processed = ImageProcessing::Vips
.convert("png")
.resize_to_limit(400, 400)
.call(image)
# OR
processed = ImageProcessing::Vips
.source(image) # declare source image
.convert("png")
.resize_to_limit(400, 400)
.call
# OR
processed = ImageProcessing::Vips
.source(image)
.convert("png")
.resize_to_limit!(400, 400) # bang method
The source image needs to be an object that responds to #path
or a
Vips::Image
object. The result is a Tempfile
object, or a Vips::Image
object if save: false
is passed in.
pipeline = ImageProcessing::Vips.source(image)
tempfile = pipeline.call
tempfile #=> #<Tempfile ...>
vips_image = pipeline.call(save: false)
vips_image #=> #<Vips::Image ...>
#resize_to_limit
Downsizes the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it's larger than the specified dimensions.
pipeline = ImageProcessing::Vips.source(image) # 600x800
result = pipeline.resize_to_limit!(400, 400)
Vips::Image.new_from_file(result.path).size
#=> [300, 400]
It's possible to omit one dimension, in which case the image will be resized only by the provided dimension.
pipeline.resize_to_limit!(400, nil)
# or
pipeline.resize_to_limit!(nil, 400)
Any additional options are forwarded to Vips::Image#thumbnail_image
:
pipeline.resize_to_limit!(400, 400, linear: true)
See vips_thumbnail()
for more details.
#resize_to_fit
Resizes the image to fit within the specified dimensions while retaining the original aspect ratio. Will downsize the image if it's larger than the specified dimensions or upsize if it's smaller.
pipeline = ImageProcessing::Vips.source(image) # 600x800
result = pipeline.resize_to_fit!(400, 400)
Vips::Image.new_from_file(result.path).size
#=> [300, 400]
It's possible to omit one dimension, in which case the image will be resized only by the provided dimension.
pipeline.resize_to_fit!(400, nil)
# or
pipeline.resize_to_fit!(nil, 400)
Any additional options are forwarded to Vips::Image#thumbnail_image
:
pipeline.resize_to_fit!(400, 400, linear: true)
See vips_thumbnail()
for more details.
#resize_to_fill
Resizes the image to fill the specified dimensions while retaining the original aspect ratio. If necessary, will crop the image in the larger dimension.
pipeline = ImageProcessing::Vips.source(image) # 600x800
result = pipeline.resize_to_fill!(400, 400)
Vips::Image.new_from_file(result.path).size
#=> [400, 400]
Any additional options are forwarded to Vips::Image#thumbnail_image
:
pipeline.resize_to_fill!(400, 400, crop: :attention) # smart crop
See vips_thumbnail()
for more details.
#resize_and_pad
Resizes 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).
pipeline = ImageProcessing::Vips.source(image) # 600x800
result = pipeline.resize_and_pad!(400, 400)
Vips::Image.new_from_file(result.path).size
#=> [400, 400]
You can specify the background color that will be used for padding:
pipeline.resize_and_pad!(400, 400, color: "RoyalBlue")
You can also specify the direction where the source image will be positioned:
pipeline.resize_and_pad!(400, 400, gravity: "north-west")
Any additional options are forwarded to Vips::Image#thumbnail_image
:
pipeline.resize_to_fill!(400, 400, linear: true)
See vips_thumbnail()
and vips_gravity()
for more details.
#convert
Specifies the output format.
pipeline = ImageProcessing::Vips.source(image)
result = pipeline.convert!("png")
File.extname(result.path)
#=> ".png"
By default the original format is retained when writing the image to a file. If the source file doesn't have a file extension, the format will default to JPEG.
#set
, #set_type
Sets Vips::Image
metadata. Delegates to Vips::Image#set
and
Vips::Image#set_type
.
pipeline = ImageProcessing::Vips.source(image)
pipeline.set("icc-profile-data", profile).call
# or
pipeline.set_type(Vips::BLOB_TYPE, "icc-profile-data", profile).call
#method_missing
Any unknown methods will be delegated to Vips::Image
.
ImageProcessing::Vips
.crop(0, 0, 300, 300)
.invert
.gaussblur(2)
# ...
#custom
Calls the provided block with the intermediary Vips::Image
object. The return
value of the provided block must be a Vips::Image
object.
ImageProcessing::Vips
.source(file)
.resize_to_limit(400, 400)
.custom { |image| image + image.invert }
.call
#loader
Specifies options that will be forwarded to Vips::Image.new_from_file
.
ImageProcessing::Vips
.loader(access: :sequential)
.resize_to_limit(400, 400)
.call(source)
See vips_jpegload()
, vips_pngload()
etc. for more details on
format-specific load options.
If you would like to have more control over loading, you can load the image
directly using Vips::Image
, and just pass the Vips::Image
object as the
source file.
vips_image = Vips::Image.magickload(file.path, n: -1)
ImageProcessing::Vips
.source(vips_image)
# ...
#saver
Specifies options that will be forwarded to Vips::Image#write_to_file
.
ImageProcessing::Vips
.saver(Q: 100)
.resize_to_limit(400, 400)
.call(source)
See vips_jpegsave()
, vips_pngsave()
etc. for more details on
format-specific save options.
If you would like to have more control over saving, you can call #call(save:
false)
to get the Vips::Image
object, and call the saver on it directly.
vips_image = ImageProcessing::Vips
.resize_to_limit(400, 400)
.call(save: false)
vips_image.write_to_file("/path/to/destination", **options)
MiniMagick
The ImageProcessing::MiniMagick
module contains processing methods that use
the MiniMagick gem, which you need to install:
# Gemfile
gem "mini_magick", ">= 4.3.5"
Typically you will include the module in your class:
require "image_processing/mini_magick"
include ImageProcessing::MiniMagick
original = File.open("path/to/image.jpg")
converted = convert(original, "png") # makes a converted copy
converted #=> #<File:/var/folders/.../mini_magick20151003-23030-9e1vjz.png (closed)>
File.exist?(original.path) #=> true
converted = convert!(original, "png") # converts the file in-place
converted #=> #<File:/var/folders/.../mini_magick20151003-23030-9e1vjz.png (closed)>
File.exist?(original.path) #=> false
You can also call processing methods directly on the module:
image = File.open("path/to/image.jpg")
ImageProcessing::MiniMagick.resize_to_fit(image, 400, 400)
Methods
The following is the list of processing methods provided by
ImageProcessing::MiniMagick
(each one has both a destructive and a
nondestructive version):
# Adjust an image so that its orientation is suitable for viewing.
auto_orient[!](file)
# Converts file to the specified format, and you can specify to convert only a
# certain page for multilayered formats.
convert[!](file, format, page = nil)
# Crop image to the defined area.
crop[!](file, width, height, x_offset, y_offset, gravity: "NorthWest")
# Resizes image to fit the specified dimensions (shrinks if larger, enlarges if
# smaller, but keeps the aspect ratio).
resize_to_fit[!](file, width, height)
# Resizes image in limit of the specified dimensions (shrinks if larger, keeps
# if smaller, but keeps the aspect ratio).
resize_to_limit[!](file, width, height)
# Resizes image to fill the specified dimensions (shrinks if larger,
# enlarges if smaller, crops the longer side).
resize_to_fill[!](file, width, height, gravity: "Center")
# Resizes image to the specified dimensions and pads missing space (shrinks if
# larger, enlarges if smaller, fills the shorter side with specified color).
resize_and_pad[!](file, width, height, background: "transparent", gravity: "Center")
# Resamples the image to a different resolution
resample[!](file, horizontal, vertical)
# Returns true if the given image is corrupted
corrupted?(file)
The #resize_to_limit[!]
and #resize_to_fit[!]
allow specifying only one
dimension:
resize_to_limit(image, 300, nil)
resize_to_fit(image, nil, 500)
Dropping to MiniMagick
If you want to do custom MiniMagick processing, each of the above optionally
yields an instance of MiniMagick::Tool
, which you can use for additional
processing:
convert(file, "png") do |cmd|
cmd.background("none")
end
There is also a helper method for doing MiniMagick processing directly (though note that this will process the image in-place!):
processed = with_minimagick(file) do |image|
image #=> #<MiniMagick::Image ...>
image.combine_options do |cmd|
# ...
end
end
processed #=> #<File ...>
Contributing
Test suite requires imagemagick
, graphicsmagick
and libvips
be installed.
On Mac OS you can install them with Homebrew:
$ brew install imagemagick graphicsmagick vips
Afterwards you can run tests with
$ rake test
Credits
The ImageProcessing::MiniMagick
functionality was extracted from
refile-mini_magick.