Search code examples
ruby-on-railspngjpegcarrierwave

Carrierwave: convert an uploaded PNG to JPG by replacing the original version (or: having versions with a different file format than original file)


I have the following model:

class ScreenshotUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  storage :file
  convert :jpg

  version :thumb do
    process resize_to_fill: [50, 50]
  end

  def extension_whitelist
    %w(jpg jpeg gif png)
  end

  version :print do
    process border: ['black']
    process quality: 80
  end
end

The upload of the image happens via pasting an image from the clipboard via https://github.com/layerssss/paste.js and is saved as a base64 encoded string into a <textarea>, then uploaded using the https://github.com/y9v/carrierwave-base64 gem:

class Finding < ApplicationRecord
  mount_base64_uploader :screenshot, ScreenshotUploader
end

In the HTML form, it looks like this:

upload form

After uploading, the result is the following files:

  • screenshot.png it's a PNG, not a JPG!
  • thumb_screenshot.jpg
  • print_screenshot.jpg

But I need the original file to be also converted to JPG, as I need to save disk space. How can I achieve this?


Solution

  • Adding to Vasiliy's answer, I came up with the following:

      after :store, :convert_original_to_jpg
    
      def convert_original_to_jpg(new_file)
        if version_name.nil?
          system("mogrify -format jpg -quality 80  #{file.file}")
          system("unlink #{file.file}") # Remove the old PNG file
          model.update_column mounted_as, "#{mounted_as}.jpg" # The filename in the DB also needs to be manually set to .jpg!
        end
      end
    

    While this works for creating the file, it does not when updating the file, as the new_file parameter then is nil, and thus all images are removed.

    I think this is some quirk that has to do with the carrierwave-base64 gem, and I don't have any motivation to dig into this any further. So the proposed solution might not be too useful, but for the sake of documentation I wanted to post it here.

    In my special case, I decided to let go of the idea of saving disk space by converting PNG to JPG. Instead, I simply set process quality: 80 to save at least some space on the versions.

    For the original PNG (which is saved in lossless state by carrierwave-base64 gem), I simply use the following code to shrink its quality:

      after :store, :optimise_images
    
      def optimise_images(new_file)
        return if Rails.env.test? # Optimising consumes quite some time, so let's disable it for tests
    
        if version_name.nil?
          image_optim = ImageOptim.new pngout: false,
                                       svgo: false,
                                       pngcrush: false,
                                       optipng: false,
                                       pngquant: {allow_lossy: true}, # Everything disabled except pngquant, to keep the performance at a good level
                                       advpng: false
          image_optim.optimize_images!(Dir["#{File.dirname(file.file)}/*.png"])
        end
      end