Always convert and strip user-provided images to sRGB

Debugging image color profiles is hard. You can't trust your eyes in this matter, as the image rendering depends on multiple factors. At least the operation system, browser or image viewer software and monitor influence the resulting image colors on your screen.

When we offer our users the possibility to upload images, they will most likely contain tons of EXIF metadata and sometimes exotic color profiles like eciRGB Show archive.org snapshot . We want to get rid of the metadata, as it might contain sensitive information like GPS coordinates. Since we cannot be sure that every browser is able to display the user provided color profile, we recommend converting it to a well known color space (like sRGB) and thus removing all other color profiles.

The following article guides you through the implementation of the color profile conversion with ImageMagick Show archive.org snapshot and CarrierWave Show archive.org snapshot .

TL;DR: If you don't convert the image before stripping the metadata and profiles, it will be displayed with wrong colors on your website. Always set a color profile after using ImageMagick's -strip method.

Image with the "eciRGB" Format (original) original image -stripped original image converted to "sRGB" before -strip
Image Image Image

The profile conversion - metadata stripping pipeline

With ImageMagick, it can be achieved like this:

convert original.jpg -profile sRGB2014.icc -strip -set profile sRGB2014.icc version.jpg

Use mogrify if you want to alter the original image. If you are using Carrierwave, this image processor might come in handy:

def convert_to_srgb
  if /^image/.match?(content_type)
    manipulate! do |image|
      image.combine_options do |c|
        c.profile('path/to/sRGB2014.icc')
        c.strip
        c.set('profile', 'path/to/sRGB2014.icc')
      end

      image = yield(image) if block_given?
      image
    end
  end
end

With this conversion in place, user provided images should be displayed correctly in all major browsers. Only Firefox Mobile on Android does not seem to use the attached profile.

Notes

  • -profile triggers an (expensive) image conversion each time it is called. It implies -set profile.
  • -set profile only adds the profile's metadata to the image.

Testing

ImageMagick provides a compare utility which can be used to measure color differences between two images. You just have to resize and convert them to the same dimensions and color profile (which should be different from both used profiles for this purpose).

With Imagemagick:

mogrify -resize '500x500' -profile sRGB_v4_ICC_preference.icc original.jpg
convert -resize '500x500' -profile sRGB_v4_ICC_preference.icc version.jpg
compare original.jpg version.jpg -metric PHASH /dev/null

With Ruby / CarrierWave

def normalize_image!(image)
  # For images to be comparable with ImageMagick, they must
  # be of the same size and color profile.
  image.manipulate! do |m|
    m.combine_options do |c|
      c.resize '500x500'
      c.profile 'sRGB_v4_ICC_preference.icc'
    end
  end
end

def color_difference!(image, other_image)
  normalize_image!(image)
  normalize_image!(other_image)

  compare = ['compare']
  compare << image.path
  compare << other_image.path
  compare << '-metric'
  compare << 'PHASH' # See https://imagemagick.org/script/command-line-options.php#metric
  compare << '/dev/null'
  # For reasons, the color difference is written to STDERR
  stdout, stderr, status = MiniMagick::Shell.new.execute(compare)
  color_difference = stderr.to_f
  color_difference
end

# A spec might look like this, given original and version to be CarrierWave versions
expect(color_difference!(original, version)).to <= 5

Notes

Data Migration

If you add the color profile conversion to an existing Uploader, don't forget to recreate all versions. You could use a Sidekiq Worker on a low-priority queue if many image records and versions are affected.

References

The v4 ICC specification is widely used and is referred to in many International and other de-facto standards. It was first approved as an International Standard, ISO 15076-1, in 2005 and revised in 2010.

Michael Leimstädtner About 4 years ago