Blur images with Shrine and image_processing gem - ruby-on-rails

I am trying to blur images in my Ruby on Rails application, using the Shrine gem. This is my uploader file:
require "image_processing/mini_magick"
class ImageUploader < Shrine
Attacher.derivatives_processor do |original|
magick = ImageProcessing::MiniMagick.source(original)
{
blurred: magick.append('-blur 0x8').resize_to_limit!(1024, 1024)
}
end
end
I set up my model, controller and form in the most basic way, the same as in the Shrine Getting Started tutorial - https://shrinerb.com/docs/getting-started.
When I try to save an image I get following error:
*** MiniMagick::Error Exception: convert /tmp/shrine20191112-4479-1xo3vgk.jpg -auto-orient -blur 0x5 -resize 1024x1024> -sharpen 0x1 /tmp/image_processing20191112-4479-1w094sa.jpg failed with error:
convert: unrecognized option `-blur 0x5' # error/convert.c/ConvertImageCommand/893.
"
Without the append('-blur 0x8') it works just fine, what am I doing wrong? My ImageMagick version is 7.0.7-11.
Btw I wouldn't mind blurring the image with libvips, I just have more experience with ImageMagick so that's what I went with.

You need to specify each command-line argument separately, in this case -blur and 0x8:
magick.append('-blur', '0x8').resize_to_limit!(1024, 1024)
You can also call the #blur method, which will get applied as -blur through the magic of method_missing:
magick.blur('0x8').resize_to_limit!(1024, 1024)

I think the libvips equivalent would be:
require "image_processing/vips"
class ImageUploader < Shrine
Attacher.derivatives_processor do |original|
vips = ImageProcessing::Vips.source(original)
{
blurred: vips.resize_to_limit(1024, 1024).gaussblur(2).call
}
end
end
Since unknown methods are simply delegated to ruby-vips.
If you can, put the resize first, it's a lot quicker. You'll get more consistent results too, since the degree of blur won't depend on how large the resize is.

Related

How do I preserve the transparent background when converting an SVG file to PNG with MiniMagick in a Ruby on Rails application?

I am trying to automatically convert SVG images to PNG with Minimagick. I have a Rails application where I need to automatically convert uploaded SVG files. The app runs on Heroku. These SVGs typically have a transparent background.
This is the code I am using:
class MyUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
version :email do
process :convert_to_png
def full_filename (for_file = model.logo.file)
"#{model.friendly_id}-#{model.id}-logo-email.png"
end
end
def convert_to_png
manipulate! do |img|
img = img.format 'png'
img = img.density 300
img = img.resize '800x400'
img = img.background 'none'
end
end
end
This creates a png image with width 800. However the property background 'none' is not applied. The original image has a transparent background, and the resulting converted image has a white background instead.
I have seen that Minimagick calls the mogrify command of Imagemagick, and I have found the command I have to call to create the image I need, but I have trouble converting it into a function for the uploader. Using the same parameters in the same sequence for the uploader doesn't work because it throws an error if the line img = img.format 'png' isn't called first.
mogrify -density 300 -background none -resize 400x200 -format png myfile.svg
Note: I don't use this library so all of this is gleaned from the docs and the source code. If these don't work for you I will remove the post it was just too long for a comment
Here are few suggestions:
Maybe try combine_options instead? "MiniMagick::Image#combine_options takes multiple options and from them builds one single command." which is more in line with your desired CLI call
def convert_to_png
manipulate! do |img|
img.combine_options do |i|
i.density 300
i.background 'none'
i.resize '800x400'
i.format 'png'
end
end
end
Another option is to touch the Metal and generate that exact command.
MiniMagick::Tool::Mogrify.new do |mogrify|
mogrify << "input.svg"
mogrify.density(300)
mogrify.background('none')
mogrify.resize('800x400')
mogrify.format('png')
mogrify << "output.png"
end
#=> `mogrify input.svg -density 300 -background none -resize 800x400 -format png output.png`

Rails ActiveStorage: Attach a Vips Image

I'm struggling to attach a Vips:Image object to an ActiveStorage object.
I use Vips to compress a PNG image. I'm then looking to save this compressed PNG version into a second ActiveStorage attachment. The code is failing when attempting to attach. All I get is: Process finished with exit code -1073741819 (0xC0000005)
# compress the image and save the compressed version of the file as a PNG
# both img and img_to_compress are active storage attachments
def compress_charlie(img, img_to_compress)
# load the image as a Vips Image object
vips_img = Vips::Image.new_from_buffer (URI.open(img.url) { |f| f.read }), ''
# do compression, etc ... not bothering to show this code as it has no impact on the issue I have
# save the compressed png
img_to_compress.attach(
io: StringIO.new(vips_img .write_to_buffer('.png')),
filename: img.filename
)
end
Any help is appreciated, Charlie
Ruby 3.1
Rails 7.0.1
You're decompressing and recompressing twice. How about (untested):
def convert_to_png(img, img_to_compress)
# load the image as a Vips Image object
vips_img = Vips::Image.new_from_buffer (URI.open(img.url) { |f| f.read }), ''
# save as a PNG string
png = StringIO.new(vips_img.write_to_buffer('.png')),
img_to_compress.attach(io: png, filename: img.filename)
end
pngsave_buffer gives you a String with binary encoding, you don't need to save a second time. Try this test program:
require "vips"
x = Vips::Image.new_from_file ARGV[0]
y = x.pngsave_buffer
puts "y.class = #{y.class}"
I see:
$ ./pngsave.rb ~/pics/k2.jpg
y.class = String
If the image is already a PNG you'll be wasting a lot of time, of course. You could add something to detect the format and skip the conversion.

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:
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?
You can do it like it written on the carrier wave documentation
Just replace system("mogrify -resize '1200\>' #{file.file}") with system("mogrify -format jpg #{file.file}") and then remove original file.
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

Errors using the caption method in MiniMagick gem

I'm trying to build a rails app that generates an image by overlaying a text over a default image.
I'm almost there - by using carrierwave + mini magick i have this method working :
def generate_image
message = self.description.upcase
image = MiniMagick::Image.open('public/base.jpg')
image.combine_options do |c|
c.size '400x500'
c.gravity 'NorthWest'
c.fill 'black'
c.strokewidth '2'
c.pointsize '48'
c.interline_spacing '-9'
c.font "#{Rails.root}/public/black.ttf"
c.draw "text 40,40 '#{message}'"
c.antialias
end
self.image = image
end
the problem is that i want my text to fit the size i've specified - but I had no luck !
reading the Image Magick documentation I know that I should use the "capition" method instead of draw - but when I do so it throws out an orrible error :) :
failed with error: mogrify: no encode delegate for this image format `CAPTION' #
error/constitute.c/WriteImage/1167.
any clues ?
thanks !

Photo Failed to manipulate with MiniMagick, maybe it is not an image? error

I have the following code, which resizes and image and then adds some hex background. On localhost this works, but on heroku i get the following error
version :big do
process :offer_resize_and_pad
end
def offer_resize_and_pad
img = resize_and_pad(600, nil, model.hex, 'Center')
img
end
"Photo Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: Command (\"mogrify -thumbnail \"600x>\" -background \"#fff\" -gravity \"Center\" -extent \"600x\" /tmp/mini_magick20130217-2-1hts61y.jpg\") failed: {:status_code=>1, :output=>\"mogrify: Empty JPEG image (DNL not supported)/tmp/mini_magick20130217-2-1hts61y.jpg' # jpeg.c/EmitMessage/232.\n\"
I 've used RMagick instead of Mini_magick, with some syntax changes , and it seems to work :) .
include CarrierWave::RMagick
def offer_resize_and_pad
img = resize_and_pad(600, 280, model.hex, ::Magick::CenterGravity)
img
end

Resources