When you write your next CarrierWave Show archive.org snapshot uploader, consider processing your images with libvips Show archive.org snapshot instead of ImageMagick.
There are several upsides to using libvips over ImageMagick:
I also found a few downsides:
On Ubuntu you can install libvips like this:
sudo apt install libvips42 libvips-dev libvips-tools
If you're hosting on makandra servers Show archive.org snapshot these packages have already been installed on your application servers.
In your Ruby code you will interact with libvips using the
ruby-vips gem
Show archive.org snapshot
. This is already a dependency of carrierwave
, so you won't have to add anything else to your Gemfile
.
In your uploader, include CarrierWave::Vips
instead of CarrierWave::MiniMagick
. You can now use basic resize macros like resize_to_fit
or resize_to_fill
:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :resize_to_fit => [1000, 500]
version :thumbnail do
process :resize_to_fill => [64, 64]
end
end
Users may upload images with exotic color profiles, but browsers only support a few standard profiles. Because of this it is often a good idea to convert all image color to sRGB.
colourspace
If you're looking for a quick way to get rid of exotic color profiles, use the colourspace(:srgb)
method:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :convert_to_srgb
private
def convert_to_srgb
vips! do |builder|
builder.colourspace(:srgb)
end
end
end
Important
This requires a modern version of libvips, e.g. 8.15. In older versions the
colourspace
function ignores embedded color profiles.
icc_transform
If you need more control over the color conversion process, you can use the icc_transform
method. This allows for the following:
:perceptual
, :relative
, :saturation
or :absolute
):lab
or :xyz
).icc
fileThe uploader below converts images to the sRGB2014.icc
profile. The sRGB profile will also be embedded into the converted image.
Note that the uploader expects you to download
sRGB2014.icc
Show archive.org snapshot
and
ISOcoated_v2_eci.icc
Show archive.org snapshot
and place it in a lib
folder.
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
# The target profile for the converted file.
OUTPUT_PROFILE = 'lib/sRGB2014.icc'
# Input profiles used when an image has no embedded profile.
UNKNOWN_SRGB_INPUT_PROFILE = 'lib/sRGB2014.icc'
UNKNOWN_CMYK_INPUT_PROFILE = 'lib/ISOcoated_v2_eci.icc'
# Common options for the vips-icc-transform command. For additional options see:
# https://www.rubydoc.info/gems/ruby-vips/Vips/Image#icc_transform-instance_method
ICC_TRANSFORM_OPTIONS = {
intent: :relative, # one of [:perceptual, :relative, :saturation, :absolute]
black_point_compensation: true,
}
process :convert_to_srgb
private
def convert_to_srgb
if embedded_profile?
srgb_image = converted_image.icc_transform(OUTPUT_PROFILE, embedded: true, **ICC_TRANSFORM_OPTIONS)
elsif cmyk?
srgb_image = converted_image.icc_transform(OUTPUT_PROFILE, input_profile: UNKNOWN_SRGB_INPUT_PROFILE, **ICC_TRANSFORM_OPTIONS)
else
srgb_image = converted_image.icc_transform(OUTPUT_PROFILE, input_profile: UNKNOWN_SRGB_INPUT_PROFILE, **ICC_TRANSFORM_OPTIONS)
end
srgb_image.write_to_file(current_path)
end
def embedded_profile?
vips_image.get("icc-profile-data")
true
rescue Vips::Error
false
end
def cmyk?
vips_image.interpretation == :cmyk
end
end
The uploader below will strip any embedded color profiles from images. It does not convert any color values.
A use case is to remove embedded ICC profiles after converting to sRGB. As browsers will usually default to sRGB Show archive.org snapshot when no profile is embedded, this can be a way to save some bytes.
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :strip_icc_fields
prviate
def strip_icc_fields
fields = vips_image.get_fields.select { |field| field.start_with?('icc-') }
return if fields.blank?
stripped_image = vips_image.mutate do |mutable|
fields.each do |field|
mutable.remove!(field)
end
end
stripped_image.write_to_file(current_path)
end
end
The uploader below will use the standard convert
method to produce a JPG thumbnail of the first page of a PDF document.
Note that this uses the DoesCarrierwaveFormatPatches
trait from CarrierWave: How to generate versions with different file extensions.
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
include DoesCarrierwaveFormatPatches
version :thumb do
process :convert => 'jpg'
process :resize_to_fit => [200, 400]
set_file_specs file_type: 'image/jpeg', extension: :jpg
end
end
It's often a good idea to strip metadata (like EXIF headers) from an uploaded image, as this may contain private data like the photographer's camera or GPS position.
The uploader below will strip all metadata from an image:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :strip_all_metadata
private
def strip_all_metadata
fields = vips_image.get_fields
return if fields.blank?
stripped_image = vips_image.mutate do |mutable|
fields.each do |field|
mutable.remove!(field)
end
end
stripped_image.write_to_file(current_path)
end
end
Sometimes you want to strip metadata, but keep embedded ICC color profiles. You can do so like this:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :strip_metadata_except_icc_profile
private
def strip_metadata_except_icc_profile
fields = vips_image.get_fields.reject { |field| field.start_with?('icc-') }
return if fields.blank?
stripped_image = vips_image.mutate do |mutable|
fields.each do |field|
mutable.remove!(field)
end
end
stripped_image.write_to_file(current_path)
end
end
The uploader below will rotate the pixels of an image if its EXIF header indicates rotated input data. After conversion the image raster will be saved in an upright orientation.
Tip
This conversion is not as useful as it used to be. Chrome, Firefox and Safari will all honor EXIF orientation.
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :auto_orient
private
def auto_orient
rotated = vips_image.autorot
rotated.write_to_file(current_path)
end
end
Important
Image rotation a modern version of libvips, e.g. 8.15. In older versions the
#autorot
function will segfault.
The examples above mostly using custom process methods you can register with .process
. Here is how you write your own processing methods.
Your processing methods can use #vips_image
method to get a
Vips::Image
Show archive.org snapshot
instance for the image being processed:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :print_image_details
private
def print_image_details
"Width is #{vips_image.width}"
"Height is #{vips_image.height}"
"Metadata headers are #{vips_image.get_fields}"
end
end
The object returned by #vips_image
has many methods to edit the image. Check the
docs for Vips::Image
Show archive.org snapshot
to see which editing operation are available.
Vips::Image
has a mostly immutable API, where every editing operation returns a new Vips::Image
instance. You must write the results to disk for your changes to be persisted:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :scale_up
private
def scale_up
scaled = vips_image.scale(2)
scaled.write_to_file(current_path)
end
end
There are a few destructive methods that can only be called from inside a #mutate
block:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :set_copyright_field
private
def set_copyright_field
mutated = vips_image.mutate do |mutable|
mutable.set!("exif-ifd0-Copyright", "Copyright (c) #{Date.today.year} SuperApp")
end
mutated.write_to_file(current_path)
end
end
You can also
use mutate
to prevent the generation of unused intermediate images
Show archive.org snapshot
when you run multiple edit operations in a sequence. This will improve the performance of your processing method.
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::Vips
process :scale_and_blur
private
def set_copyright_field
mutated = vips_image.mutate do |mutable|
mutable.scale(2)
mutable.guassblur(20)
end
mutated.write_to_file(current_path)
end
end
Note
You can also use the
vips! { |builder| ... }
pattern here. This yields anImageProcessing::Vips
Show archive.org snapshot object with some additional methods. However I found it more straightforward to use theVips::Image
API directly.