Watermarking with MiniMagick - imagemagick

Versions:
Ruby 2.2.3
Rails 4.2.4
mini_magick: 4.2.10
Carrierwave 0.10.0
Description
I am trying to create a watermarker for a small gallery using CarrierWave as uploader.
I want the watermark to be sized compared to the current image. Therefore I am trying to use a .svg-file with different opacities and a transparent background.
I am using a watermarker based on Carrierwave add a watermark to processed images
require 'mini_magick'
class Watermarker
def initialize(original_path, watermark_path)
#original_path = original_path.to_s
#watermark_path = watermark_path.to_s
end
def watermark!(options = {})
options[:gravity] ||= 'SouthEast'
image = MiniMagick::Image.open(#original_path)
watermark = MiniMagick::Image.open(#watermark_path)
result = image.composite(watermark, 'png') do |c|
c.gravity options[:gravity]
end
result.write #original_path
end
end
And calling this as a process from my uploader.
My problems:
I cannot get the watermarker to input the picture with transparent background. I played around with:
https://github.com/minimagick/minimagick#composite
http://www.imagemagick.org/script/composite.php
But no progress.
I cannot adjust the size of the overlay image properly. There is a lot of settings for the geometry command but I'm stuck.
Any ideas and help would be great.

Related

Rails Api Save MiniMagick Image to Active Storage

I'm using MiniMagick to resize my image. My method looks something like that:
def resize
mini_img = MiniMagick::Image.new(img.tempfile.path)
mini_img.combine_options do |c|
c.resize '50x50^'
c.gravity 'center'
c.extent '50x50'
end
mini_img
end
Resize works, but the problem is when I try save mini_img to Active Storage, because I get error Could not find or build blob: expected attachable, got #<MiniMagick::Image. Can I somehow convert MiniMagick::Image (mini_img) to normal image and save it into Active Storage?
Yes, you can. Currently you are trying to save an instance of MiniMagick::Image to ActiveStorage, and that's why you receive that error. Instead you should attach the :io directly to ActiveStorage.
Using your example, if you wanted to attach mini_img to a hypothetical User, that's how you would do it:
User.first.attach io: StringIO.open(mini_img.to_blob), filename: "filename.extension"
In this example I am calling to_blob on mini_img, which is an instance of MiniMagick::Image, and passing it as argument to StringIO#open. Make sure to include the :filename option when attaching to ActiveStorage this way.
EXTRA
Since you already have the content_type when using MiniMagick you might want to provide it to ActiveStorage directly.
metadata = mini_img.data
User.first.attach io: StringIO.open(mini_img.to_blob), filename: metadata["baseName"], content_type: metadata["mimeType"], identify: false
For someone who will have a similar problem. I just changed method to:
def resize
MiniMagick::Image.new(img.tempfile.path).combine_options do |c|
c.resize '50x50^'
c.gravity 'center'
c.extent '50x50'
end
img
end
In this solution, MiniMagick only resized the photo and did nothing else with it, so I didn't have to convert it again.

Carrierwave - Multiple images upload too slow when adding or removing images

I'm following this article in CarrierWave Wiki https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Add-more-files-and-remove-single-file-when-using-default-multiple-file-uploads-feature to implement adding more images and removing images for a model in my system using CarrierWave Multiple Uploads feature.
The main code in that article is
def add_more_images(new_images)
images = #gallery.images
images += new_images
#gallery.images = images
end
def remove_image_at_index(index)
remain_images = #gallery.images # copy the array
deleted_image = remain_images.delete_at(index) # delete the target image
deleted_image.try(:remove!) # delete image from S3
#gallery.images = remain_images # re-assign back
end
It works. However, it is too slooooow. I have looked at the log and the overall processing time is as follow:
Upload 1 image: it takes 5000ms for example
Add 1 more image: it takes 8500ms (2 images)
Add 1 more image: it takes 12000ms (3 images)
Remove 1 image: it takes 8400ms (back to 2 images)
I have tested the sample app of this solution written by the author on my local machine and it was very slow too.
It seems like CarrierWave reuploads and re-processes all images even we only add or remove 1 image. I think because we are re-assigning back new array of images to #gallery so that it treats old images as new ones.
Also there is a related issue here https://github.com/carrierwaveuploader/carrierwave/issues/1704#issuecomment-259106600
Does anyone have any better solution for adding and removing images using CarrierWave multiple upload feature?
Thanks.
when you call model.images = remain_images, carrierwave will upload all images. So the more images you stored in a column, the longer it will take.
See: mount.rb#L300, mounter.rb#L40
I had this problem before, the following is my code:
new_images = self.logo_images.clone
4.times do |t|
next if !(image = params[:"logo_image#{t + 1}"])
new_images[t] = image
changed = true
end
self.logo_images = new_images if changed
...
self.save if changed
And this is the hack...
(works fine with carrierwave 1.0.0 and carrierwave-aws 1.1.0)
mounter = self.send(:_mounter, :logo_images)
4.times do |t|
next if !(image = params[:"logo_image#{t + 1}"])
uploader = mounter.blank_uploader
uploader.cache!(image)
mounter.uploaders[t] = uploader
changed = true
end
mounter.uploaders.each{|s| s.send(:original_filename=, s.file.filename) if !s.filename} if changed
...
self.save if changed

Process `auto_orient` with Cloudinary upload

I had a regular file upload that I now changed to using Cloudinary.
Upon upload I did the following to prevent orientation glitches when uploading images from a mobile device (See exif image rotation issue using carrierwave and rmagick to upload to s3 for details):
process :rotate
process :store_dimensions
def rotate
manipulate! do |image|
image.tap(&:auto_orient)
end
end
def store_dimensions
# This does not work with cloudinary #18
if file && model
model.width, model.height = ::MiniMagick::Image.open(file.file)[:dimensions]
end
end
Neither rotation nor storing the dimensions work, since I switched to cloudinary.
Now Cloudinary has an official tutuorial that shows how to do this but it simply does not work and other people seem to have the same issue and neither of the provided options worked for me:
I was able to get it working using a variation of the first option:
after_save :update_dimensions
def update_dimensions
if self.image != nil && self.image.metadata.present?
width = self.image.metadata["width"]
height = self.image.metadata["height"]
self.update_column(:width, width)
self.update_column(:height, height)
end
end
Important: since we're inside a after_save callback here it's crucial to use update_column so that we don't trigger another callback and end up in an infinite loop.
Fix for the provided solution:
self.image.present? returned false but self.image != nil returend true.

Get image dimensions using Refile

Using the Refile gem to handle file uploading in Rails, what is the best way to determine image height and width during / after it has been uploaded? There is no built in support for this AFAIK, and I can't figure out how to do it using MiniMagick.
#russellb's comment almost got me there, but wasn't quite correct. If you have a Refile::File called #file, you need to:
fileIO = #file.to_io.to_io
mm = MiniMagick::Image.open(fileIO)
mm.width # image width
mm.height # image height
Yes, that's two calls to #to_io >...< The first to_io gives you a Tempfile, which isn't what MiniMagick wants. Hope this helps someone!
-- update --
Additional wrinkle: this will fail if the file is very small (<~20kb, from: ruby-forum.com/topic/106583) because you won't get a tempfile from to_io, but a StringIO. You need to fork your code if you get a StringIO and do:
mm = MiniMagick::Image.read(fileio.read)
So my full code is now:
# usually this is a Tempfile; but if the image is small, it will be
# a StringIO instead >:[
fileio = file.to_io
if fileio.is_a?(StringIO)
mm = MiniMagick::Image.read(fileio.read)
else
file = fileio.to_io
mm = MiniMagick::Image.open(file)
end
Refile attachments have a to_io method (see Refile::File docs) which returns an IO object that you can pass to MiniMagick.
Assuming you have an Image model with a file attachment (id stored in a file_id string column) and width and height columns you can use the following callback:
class Image < ActiveRecord::Base
attachment :file
before_save :set_dimensions, if: :file_id_changed?
def set_dimensions
image = MiniMagick::Image.open(file.to_io)
self.width = image.width
self.height = image.height
end
end
Hope that helps.
You can use MiniMagick to do this (but need to be using the latest version).
image = MiniMagick::Image.open('my_image.jpg')
image.height #=> 300
image.width #=> 1300
This is all pretty well documented in the README.md for the gem: https://github.com/minimagick/minimagick

MiniMagick Gem: How do I create a new blank image without a file?

I want to combine two images (one background image, one text image) into one big image.
I believe I have the background image done since it's just based off a file. However, I'm having trouble trying to create an image from scratch. Any tips?
image = MiniMagick::Image.open("public/text_response_bg.png")
image.combine_options do |i|
i.size "1024x512"
end
text = MiniMagick::Image.new #<-- does not work
text.combine_options do |i|
i.size "700x200"
i.gravity 'center'
i.fill 'white'
i.caption 'blahblahblah'
end
result = image.composite(text) do |c|
c.compose "Over"
c.geometry "+20+20"
end
Create an image from scratch with the following Ruby Code:
MiniMagick::Tool::Convert.new do |i|
i.size "700x200"
i.gravity "center"
i.xc "white"
i.caption "blablabla"
i << "test_image.jpg"
end
MiniMagick provide a method MiniMagick::Image.create to create a new image but seems not work with this issues
Use ImageMagick primary command you can create a pure color image like
convert -size 800x600 xc:"#ffffff" write.jpg
So if not mind using system command to create image you can do:
cmd = "convert -size 800x600 xc:'#ffffff' WRITE_IMAGE.jpg"
system(cmd)
UPDATE: I use MiniMagick 3.8.0, and see latest version 4.0.1 has a MiniMagick::Shell class, think it could run that custom ImageMagick command directly.

Resources