When you write your next CarrierWave Show archive.org snapshot uploader, consider processing your images with libvips Show archive.org snapshot instead of ImageMagick.
Reasons for libvips
There are several upsides to using libvips over ImageMagick:
- libvips is considerably faster and uses less memory Show archive.org snapshot .
- ImageMagick has a large attack surface that has repeatedly caused security incidents in the past (compare ImageMagick CVEs Show archive.org snapshot with libvips CVEs Show archive.org snapshot ).
- Ubuntu is sometimes slow to fix the numerous ImageMagick vulnerabilities. E.g. this Ubuntu update from June 2021 Show archive.org snapshot is fixing ImageMagick CVEs from November 2020.
- We repeatedly had major pains upgrading ImageMagick across major releases of Ubuntu.
I also found a few downsides:
- Documentation and examples for libvips are sometimes sparse.
- When you do find examples, it is often for the libvips C API or the libvips command-line tools. It is not always obvious how to map an example to the Ruby API ( ruby-vips Show archive.org snapshot gem).
Installing libvips
Ubuntu packages
On a recent Ubuntu version, you can install libvips like this:
sudo apt install libvips42 libvips-dev libvips-tools 
If you're on an older Ubuntu LTS (20.04, 22.04), your package sources will contain outdated versions of libvips. You can install a modern libvips version using our PPA.
If you're hosting on makandra servers these packages have already been installed on your application servers.
RubyGem
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.
libvips is not vendored into the gem. You will still need the Ubuntu packages shown above.
Examples for common requirements
Basic resizing
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
Converting colors to sRGB
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.
Easy mode with 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
colourspacefunction ignores embedded color profiles.
Pro mode with icc_transform
 
If you need more control over the color conversion process, you can use the icc_transform method. This allows for the following:
- You can enable or disable LCMS black point compensation
- You can choose the rendering intent (:perceptual,:relative,:saturationor:absolute)
- You can choose the Profile Connection Space (:labor:xyz)
- You can control the target profile using an .iccfile
- You can control which profile to assume when an image has no embedded profile
- You can configure a transformation chain using multiple profiles
The 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
Stripping color profiles
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
  private
  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
Generating PDF previews
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
Stripping metadata
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
Orienting an image upright
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
#autorotfunction will segfault.
Writing custom processing methods
The examples above mostly using custom process methods you can register with .process. Here is how you write your own processing methods.
Reading image details
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
Changing the image
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
Info
Even though most libvips operations return a new, immutable
Vips::Image, libvips uses a clever internal representation that avoids the need to allocate memory for every intermediate image. See the How it works Show archive.org snapshot section on libvips.org for details.
Some destructive methods (drawing operations, writing metadata) 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
Note
You can also use the
vips! { |builder| ... }pattern here. This yields anImageProcessing::VipsShow archive.org snapshot object with some additional methods. However I found it more straightforward to use theVips::ImageAPI directly.