mutate
blocks are only required for draw operations, and for writing metadata. You cannot call operations like scale()
on mutable images.
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.
Changes
- When you write your next [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) uploader, consider processing your images with [libvips](https://www.libvips.org/) instead of ImageMagick.
- # Reasons for libvips
- There are several **upsides** to using libvips over ImageMagick:
- - [libvips is considerably faster and uses less memory](https://github.com/libvips/libvips/wiki/Speed-and-memory-use).
- - ImageMagick has a large attack surface that has repeatedly caused security incidents in the past (compare [ImageMagick CVEs](https://www.cvedetails.com/vulnerability-list/vendor_id-1749/Imagemagick.html) with [libvips CVEs](https://www.cvedetails.com/vulnerability-list/vendor_id-22008/Libvips-Project.html)).
- - Ubuntu is sometimes slow to fix the numerous ImageMagick vulnerabilities. E.g. [this Ubuntu update from June 2021](http://changelogs.ubuntu.com/changelogs/pool/universe/i/imagemagick/imagemagick_6.9.10.23+dfsg-2.1ubuntu11.4/changelog) is fixing ImageMagick CVEs from November 2020.
- - We repeatedly had [major pains](https://makandracards.com/makandra/487405-issues-and-their-solutions-after-an-upgrade-to-ubuntu-20-04#section-fix-imagemagick) 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](https://github.com/libvips/ruby-vips) gem).
- # Installing libvips
- ## Ubuntu packages
- On a recent Ubuntu version, you can install libvips like this:
- ```bash
- 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](https://makandracards.com/makandra/621073-installing-modern-libvips-versions-from-our-ppa).
- If you're hosting on [makandra servers](https://opscomplete.com/ruby) 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](https://github.com/libvips/ruby-vips). 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](https://makandracards.com/makandra/62567-carrierwave-built-in-resize-methods) like `resize_to_fit` or `resize_to_fill`:
- ```ruby
- 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](https://makandracards.com/makandra/473154-always-convert-and-strip-user-provided-images-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:
- ```ruby
- 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](https://makandracards.com/makandra/621073-installing-modern-libvips-versions-from-our-ppa) of libvips, e.g. 8.15. In older versions the `colourspace` function 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`, `:saturation` or `:absolute`)
- - You can choose the Profile Connection Space (`:lab` or `:xyz`)
- - You can control the target profile using an `.icc` file
- - 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](https://www.color.org/srgbprofiles.xalter) and [ISOcoated_v2_eci.icc](https://www.pointprepress.de/download/index.php) and place it in a `lib` folder.
- ```ruby
- 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](https://kornel.ski/en/color) when no profile is embedded, this can be a way to save some bytes.
- ```ruby
- 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](https://makandracards.com/makandra/480893-carrierwave-how-to-generate-versions-with-different-file-extensions).
- ```ruby
- 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:
- ```ruby
- 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:
- ```ruby
- 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.
- ```ruby
- 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](https://makandracards.com/makandra/621073-installing-modern-libvips-versions-from-our-ppa) of libvips, e.g. 8.15. In older versions the `#autorot` function 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`](https://www.rubydoc.info/gems/ruby-vips/Vips/Image) instance for the image being processed:
- ```ruby
- 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`](https://www.rubydoc.info/gems/ruby-vips/Vips/Image) 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:
- ```ruby
- 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:- +> [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](https://www.libvips.org/API/current/How-it-works.html) section on libvips.org for details.
- +
- +Some destructive methods (drawing operations, writing metadata) can only be called from inside a `#mutate` block:
- ```ruby
- 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
- ```
-### Chaining multiple writes efficiently--You can also [use `mutate` to prevent the generation of unused intermediate images](https://www.libvips.org/2021/03/08/ruby-vips-mutate.html) when you run multiple edit operations in a sequence. This will improve the performance of your processing method.--```ruby-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]
Posted by Henning Koch to makandra dev (2024-07-01 08:16)