using Rails 7.2.1.1 and Ruby 3.3.5. Gemfile has gem "image_processing", "~> 1.13.0".
Basically I have a form that allows the company logo to be uploaded, and needs to accept jpeg, png, gif, webp, and svg versions. SVG files yield an ActiveStorage::InvariableError when the .variant method is used, so they need to be converted. I also discovered in testing that vips chokes on resizing .jfif files via the image_processing gem (which are coded as image/jpeg), so those also need to be detected and converted to jpeg.
I've been trying to implement a job with some basic image processing on images stored on S3 with active_storage. But getting the image data into the image_processing gem is driving me crazy!
I have this model:
class Company < ApplicationRecord # :nodoc:
has_one_attached :logo do |attachable|
attachable.variant :medium, resize_to_fit: [300, nil]
attachable.variant :large, resize_to_fit: [700, nil]
end
after_save :company_logos_job, if: :logos_changed?
private
def company_logos_job
CompanyLogoJob.perform_later(id)
end
def logos_changed?
true # implement later, slightly tricky
end
end
In the job, I tried several ways to get the image blob data into image_processing, but often ended up with i/o errors or file read errors. Working with ChatGPT, I've got it functional like this, but it feels wrong and seems totally inefficient to be creating and deleting two temporary files on disk for each job!
# This job runs after any change to the logo on a company object.
# It will convert the image to a png if it is an svg, and create the variants.
class CompanyLogoJob < ApplicationJob
queue_as :latency_5m
def perform(id)
company = Company.find(id)
if company.logo_light.attached?
convert_svg_to_png(company.logo_light) if company.logo_light.content_type == "image/svg+xml"
company.logo_light :medium
company.logo_light :large
end
end
def convert_svg_to_png(image)
filename = image.filename.to_s
# I've had to create a tempfile to get the image accepted by .source
Tempfile.create(["temp_image", ".svg"]) do |tempfile|
tempfile.write(image.download)
tempfile.rewind
# use VIPS to convert svg to png
png = ImageProcessing::Vips.source(tempfile.path)
.loader(loader: :svg)
.convert("png")
.resize_to_fit(700, nil)
.call
png_file = File.open(png.path, "rb")
image.purge
image.attach(io: StringIO.new(png_file.read), filename: "#{filename}.png", content_type: "image/png")
png_file.close
File.delete(png.path)
tempfile.close
File.delete(tempfile.path)
end
end
end
I've researched the image_processing gem, stack overflow, blog posts, etc for a few hours now, but still haven't found a way to streamline this that works. Is there a way to get blob data into ImageMagick or VIPs without saving local copies of the i/o files? I'm open to using VIPs, ImageMagick, and/or ditching the image_processing gem if it helps, but I feel like there's something really basic that I'm missing here.
Any help / insight, or links to good blogs/tutorial I may have missed are appreciated!
variantmethod?company.logo.variant(resize_to_fit: [700,nil], loader: :svg, convert: :png).processedand then doing whatever you might need to do using theActiveStorage::Previewe.g. downloading.ActiveStorage::InvariableError, even when using theloader: :svgtag.variable?. See this post:stackoverflow.com/questions/60686249/…