Skip to content

Efficiently converting image formats

Zhigun Ivan edited this page Feb 2, 2017 · 7 revisions

When uploading images, adding more that 1 process to a version can cause RMagick or MiniMagick to run multiple commands. This leads to slower processing times and increased file sizes.

MiniMagick has a solution for this.

https://github.com/minimagick/minimagick/blob/master/lib/mini_magick/image.rb#L380

    # You can use multiple commands together using this method. Very easy to use!
    #
    # @example
    #   image.combine_options do |c|
    #     c.draw "image Over 0,0 10,10 '#{MINUS_IMAGE_PATH}'"
    #     c.thumbnail "300x500>"
    #     c.background background
    #   end
    #

So to use this we can create an uploader like this.

class PhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  version :normal do
    process :efficient_conversion => [640, 960]
  end

private

  def efficient_conversion(width, height)
    manipulate! do |img|
      img.combine_options do |c|
        c.fuzz        "3%"
        c.trim
        c.resize      "#{width}x#{height}>"
        c.resize      "#{width}x#{height}<"
      end
      img
    end
  end
end

Changing the format

There is a catch with changing the format. To change format we must change the name of the output file. This is how ImageMagick changes the format. So we need to use the MiniMagicks#format method, luckily format takes a block just like combine_options.

  def efficient_conversion(width, height)
    manipulate! do |img|
      img.format("png") do |c|
        c.fuzz        "3%"
        c.trim
        c.resize      "#{width}x#{height}>"
        c.resize      "#{width}x#{height}<"
      end
      img
    end
  end

There is another catch with changing the format. Using the format method will only change the name of the tmp file. The final version of the file is named by carrier wave and even though the file it creates will be a genuine file of the specified format with the correct mime type, it's extension will be not be changed. We need to use the filename method to set this.

  def filename
    "#{model.nicely_formatted_filename}.png"
  end

Stripping out the useless color profiles

TLDR; use this

        c.push        '+profile'
        c.+           "!xmp,*"
        c.profile     "path/to/color_profiles/sRGB_v4_ICC_preference_displayclass.icc"
  • Most of ImageMagick's args look like -resize \"640x960\".
  • Adding a profile is done with -profile.
  • Removing a profile is done with +profile. Not exactly intuitive, but it does the job.
  • MiniMagick uses method missing to create the args, so c.rotate "90" turns into -rotate \"90\".

To add an arg that starts with a '+', like the +profile arg, we need to use the push method followed by the + method. The + method turns the previously pushed arg into a '+' arg and then adds the passed options to it.

This code will strip all profiles except 'xmp' then add the 'sRGB.icc' profile.

        c.push        '+profile'
        c.+           "!xmp,*"
        c.profile     "path/to/color_profiles/sRGB_v4_ICC_preference_displayclass.icc"

Full example

class PhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  storage :file

  version :normal do
    process :mogrify => [{
      :resolution => '108x369'
    }]
  end

  def default_url
    "#{model.class.to_s.underscore.downcase}/#{mounted_as}/missing/" + [version_name, 'missing.png'].compact.join('_')
  end

  def extension_white_list
    %w(jpg jpeg gif png bmp tif tiff)
  end

  def filename
    "#{model.nicely_formatted_filename}.png"
  end

private

  def mogrify(options = {})
    manipulate! do |img|
      img.format("png") do |c|
        c.fuzz        "3%"
        c.trim
        c.rotate      "#{options[:rotate]}" if options.has_key?(:rotate)
        c.resize      "#{options[:resolution]}>" if options.has_key?(:resolution)
        c.resize      "#{options[:resolution]}<" if options.has_key?(:resolution)
        c.push        '+profile'
        c.+           "!xmp,*"
        c.profile     "#{Rails.root}/lib/color_profiles/sRGB_v4_ICC_preference_displayclass.icc"
        c.colorspace  "sRGB"
      end
      img
    end
  end
end
Clone this wiki locally