How to convert user upload images to webp in ruby on rails
Asked Answered
H

2

5

I want to convert user uploading images in webp for site performance. I know webp now support only on two browsers, Google Chrome and Opera.

I am using carrierwave for image uploading to s3.

Not find any support of converting image to webp in carrierwave.

image-magick have libraries libwebp which can convert images to webp.

Is there any gem of that in rails?

How I can convert image to carrierwave and save it also. Solution other than carrierwave also works.

Heaver answered 18/4, 2018 at 13:50 Comment(5)
Perhaps RMagick at github.com/rmagick-temp/rmagick along with the webp delegate library at developers.google.com/speed/webp/downloadBradytelic
but is there any support of it in carrierwave?Heaver
Sorry, I do not know Carrierwave nor how it utilizes Imagemagick.Bradytelic
@WaqasAhad did you get anywhere with it?Malinger
@ChrisEdwards not yet...if you find update me also pleaseHeaver
A
8

I had similar task but without uploading to S3. I used webp-ffi gem to write custom converter.

This is my solution that helped me.

First you need to install requirements specified in README file of webp-ffi gem.

After that add webp-ffi gem to your project:

gem 'webp-ffi'

If you don't need to store original images you can just add custom method to your uploader and call it with process method of CarrierWave. Here is my hack for converting images to webp format:

class MyImageUploader < CarrierWave::Uploader::Base
  storage :file

  process convert_to_webp: [{ quality: 80, method: 5 }]

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  private

  def convert_to_webp(options = {})
    # Build path for new file
    webp_path = "#{path}.webp"

    # Encode (convert) image to webp format with passed options
    WebP.encode(path, webp_path, options)

    # HACK: Changing of this two instance variables is the only way 
    #   I found to make CarrierWave save new file that was created 
    #   by encoding original image.
    @filename = webp_path.split('/').pop

    @file = CarrierWave::SanitizedFile.new(
      tempfile: webp_path,
      filename: webp_path,
      content_type: 'image/webp'
    )
  end
end

You can move this method to some module located in your project (make sure it will autoload correctly). For example I put this code in app/services/web_p_converter.rb:

module WebPConverter
  def convert_to_webp(options = {})
    webp_path = "#{path}.webp"

    WebP.encode(path, webp_path, options)

    @filename = webp_path.split('/').pop

    @file = CarrierWave::SanitizedFile.new(
      tempfile: webp_path,
      filename: webp_path,
      content_type: 'image/webp'
    )
  end
end

Now I can include this module in every uploader that needs webp conversion:

class MyImageUploader < CarrierWave::Uploader::Base
  include WebPConverter

  storage :file

  process convert_to_webp: [{ quality: 80, method: 5 }]

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

But if you need to store original version of file and create a version inside uploader you will need to use this hack:

class MyImageUploader < CarrierWave::Uploader::Base
  include WebPConverter

  storage :file

  version :webp do
    process convert_to_webp: [{ quality: 80, method: 5 }]

    def full_filename(file)
      return "#{version_name}_#{filename}" if filename.split('.').last == 'webp'

      "#{version_name}_#{file}.webp"
    end
  end

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

This has to be done because CarrierWave uses original image name to build paths to other versions.

Also you can move logic from #full_filename into a method. For example I moved the logic of building full filename into WebPConverter module so it look something like this:

module WebPConverter
  # ...

  def build_webp_full_filename(filename, version_name)
    return "#{version_name}_#{filename}" if filename.split('.').last == 'webp'

    "#{version_name}_#{filename}.webp"
  end
end

And from now I can use it for my versions that needs conversion to webp:

class MyImageUploader < CarrierWave::Uploader::Base
  include WebPConverter

  storage :file

  version :webp do
    process convert_to_webp: [{ quality: 80, method: 5 }]

    def full_filename(file)
      build_webp_full_filename(file, version_name)
    end
  end

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

Checkout carrierwave-webp gem that I used as example to create my solution to this problem (this gem did not worked for me for some reason).

Also checkout my simple app that I made to demonstrate working solution.

Acrylyl answered 17/6, 2019 at 23:17 Comment(2)
Excellent solution but it doesn't remove the image.Doreathadoreen
Nope. The webp image. I had to clean this up with a separate callback method.Doreathadoreen
G
0
module WebPConverter
  private

  def build_webp_full_filename(filename, version_name)
    return "#{version_name}_#{filename}" if filename.split('.').last == 'webp'

    "#{version_name}_#{filename}.webp"
  end
end

and in ImageUploader

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  include CarrierWave::WebP::Converter
  include WebPConverter

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  version :thumb do
    process resize_to_fit: [50, 50]
  end
  
  version :img_quality do
    process resize_to_fit: [600, 600]
    process quality: 60
  end

  version :webp do
    process convert_to_webp: [{ quality: 60, method: 5 }]
    process resize_to_fit: [600, 600]
    def full_filename(file)
      build_webp_full_filename(file, version_name)
    end
  end

  def extension_whitelist
    %w[jpg jpeg gif png]
  end
end

and providing different formats depending on the browser gem 'browser'

in views:

<%= image_tag set_browser(deals_first(@category).brand.logo) %>

helper

 def set_browser(object)
    browser.chrome? || browser.opera? || browser.platform.android? ? 
    object.webp.url : object.quality.url
  end
Godolphin answered 2/7, 2020 at 12:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.