Murgiuf
Murgiuf

Reputation: 41

Rails: Active Storage variant generates an additional file

So after reading the official documentations, I'm using the Active Storage variant this way

<%= image_tag user.avatar.variant(resize: "100x100") %>

The problem is that when I look at my S3 storage I notice that I have the original file (for example a size 22kb) and the variant file which contains a 2kb file (the resized one).

I know in the documentation says:

.."When the browser hits the variant URL, Active Storage will lazily transform the original blob into the specified format and redirect to its new service location."

But is there a way to prevent Active Storage to generate an additional variant file? Like making the variant one, the original. So instead of 24kb of resurces in the S3 I have only 2kb.

Much thanks!

EDIT:

I manage to solve the issue applying some answers, If anyone comes accross this:

def resize_avatar2

  avatar_f = avatar.last
  if avatar_f.nil?
    return
  end

  resized_image = MiniMagick::Image.read(avatar_f.download)
  resized_image = resize_with_crop(resized_image, 300, 300)
  v_filename = avatar_f.filename
  v_content_type = avatar_f.content_type
  avatar.all.each { |imagen| imagen.purge }

  avatar.attach(
    io: File.open(resized_image.path),
    filename: v_filename,
    content_type: v_content_type)
end

Create a helper, include it with this methods:

    def resize_with_nocrop(image, w, h)

    w_original = image[:width].to_f
    h_original = image[:height].to_f

    if (w_original*h != h_original * w)
      if w_original*h >= h_original * w
        # long width
        w_result = w
        h_result = w_result * (h_original / w_original)
      elsif w_original*h <= h_original * w
        # long height
        h_result = h
        w_result = h_result * (w_original / h_original)
      end
    else
       # good proportions
       h_result = h
       w_result = w
    end

    #
    image.resize("#{w_result}x#{h_result}")
    return image
  end

  def resize_with_crop(img, w, h, options = {})
  gravity = options[:gravity] || :center

  w_original, h_original = [img[:width].to_f, img[:height].to_f]

  op_resize = ''

  # check proportions
  if w_original * h < h_original * w
    op_resize = "#{w.to_i}x"
    w_result = w
    h_result = (h_original * w / w_original)
  else
    op_resize = "x#{h.to_i}"
    w_result = (w_original * h / h_original)
    h_result = h
  end

  w_offset, h_offset = crop_offsets_by_gravity(gravity, [w_result, h_result], [ w, h])

  img.combine_options do |i|
    i.resize(op_resize)
    i.gravity(gravity)
    i.crop "#{w.to_i}x#{h.to_i}+#{w_offset}+#{h_offset}!"
  end

  img
end

GRAVITY_TYPES = [ :north_west, :north, :north_east, :east, :south_east, :south, :south_west, :west, :center ]
def crop_offsets_by_gravity(gravity, original_dimensions, cropped_dimensions)
  raise(ArgumentError, "Gravity must be one of #{GRAVITY_TYPES.inspect}") unless GRAVITY_TYPES.include?(gravity.to_sym)
  raise(ArgumentError, "Original dimensions must be supplied as a [ width, height ] array") unless original_dimensions.kind_of?(Enumerable) && original_dimensions.size == 2
  raise(ArgumentError, "Cropped dimensions must be supplied as a [ width, height ] array") unless cropped_dimensions.kind_of?(Enumerable) && cropped_dimensions.size == 2

  original_width, original_height = original_dimensions
  cropped_width, cropped_height = cropped_dimensions

  vertical_offset = case gravity
    when :north_west, :north, :north_east then 0
    when :center, :east, :west then [ ((original_height - cropped_height) / 2.0).to_i, 0 ].min
    when :south_west, :south, :south_east then (original_height - cropped_height).to_i
  end

  horizontal_offset = case gravity
    when :north_west, :west, :south_west then 0
    when :center, :north, :south then [ ((original_width - cropped_width) / 2.0).to_i, 0 ].min
    when :north_east, :east, :south_east then (original_width - cropped_width).to_i
  end

  return [ horizontal_offset, vertical_offset ]
end

Upvotes: 1

Views: 3158

Answers (1)

localhostdotdev
localhostdotdev

Reputation: 1895

If you want to replace the originals with the variants, I think the best way is to resize the image before it's uploaded.

e.g. Using params[:user][:avatar] to find the path of the temp file, run ffmpeg on it (there are gems for it) and then upload the result.

Alternatively you could do processing on the image in job after it's uploaded:

  • In your model that has image (User), add an after_create :resize_image
  • Then a resize_image method with ImagesJob.resize(self).perform_later
  • Then in your ImagesJob class (resize(user) method), you would:
    • Generate the variant: url = user.avatar.variant(resize: "100x100").url (Rails's generated URL, not the S3 one)
    • Fetch the image: image = open(url).read (require 'open-uri' needed)
    • Find the variant's S3 URL, delete it, find the original image url, delete it (you need to go throught the ActiveStorage::Blob (active_storage_blobs table) for that.
    • Upload image as the new image user.avatar.attach(StringIO.new(image)); user.save

(Not tested but probably quite close)

Upvotes: 2

Related Questions