Take center part of image while resizing - ruby-on-rails

I have a problem with resizing image with Carrierwave-MiniMagick-ImageMagick.
I wrote custom method of resizing, cause I need to merge 2 images together and make some processing on them, so standard process methods from MiniMagick are not enough. The problem is with resize method. I need to take center part of the image, but it returns me the top part.
def merge
manipulate! do |img|
img.resize '180x140^' # problem is here
...
img
end
end
Thanks for any help!

I would approach this as follows:
Resize image to a 180x180 square
Remove (180-140)/2 from the top
Remove (180-140)/2 from the bottom
Something like this should do it:
def merge
manipulate! do |img|
img.resize '180x180' # resize to 180px square
img.shave '0x20' # Removes 20px from top and bottom edges
img # Returned image should be 180x140, cropped from the centre
end
end
Of course, this assumes your input image is always a square. If it wasn't square and you've got your heart set on the 180x140 ratio, you could do something like this:
def merge
manipulate! do |img|
if img[:width] <= img[:height]
# Image is tall ...
img.resize '180' # resize to 180px wide
pixels_to_remove = ((img[:height] - 140)/2).round # calculate amount to remove
img.shave "0x#{pixels_to_remove}" # shave off the top and bottom
else
# Image is wide
img.resize 'x140' # resize to 140px high
pixels_to_remove = ((img[:width] - 180)/2).round # calculate amount to remove
img.shave "#{pixels_to_remove}x0" # shave off the sides
end
img # Returned image should be 180x140, cropped from the centre
end
end

This is what resize_to_fill does:
Resize the image to fit within the specified dimensions while retaining the aspect ratio of the original image. If necessary, crop the image in the larger dimension.
Example:
image = ImageList.new(Rails.root.join('app/assets/images/image.jpg'))
thumb = image.resize_to_fill(1200, 630)
thumb.write('thumb.jpg')
The method takes a third argument which is gravity, but it's CenterGravity by default.

You should use crop instead of resize.
Take a look at crop ImageMagick description of crop command here:
http://www.imagemagick.org/script/command-line-options.php#crop
MiniMagick just a wrapper around ImageMagick, so all arguments are the same.

Related

Imagemagick rails image composition adding unwanted white background

I have the following code:
begin
big_image = Magick::ImageList.new
#this is an image containing first row of images
first_row = Magick::ImageList.new
#adding images to the first row (Image.read returns an Array, this is why .first is needed)
first_row.push(Magick::Image.read(Rails.root.join("app","assets","images","logo.png")).first)
if #model.avatar.exists?
image = Magick::Image.read(#model.avatar.path).first
image = image.resize_to_fit("450", "401")
first_row.push(image)
end
#adding first row to big image and specify that we want images in first row to be appended in a single image on the same row - argument false on append does that
big_image.push (first_row.append(false))
fileName = #model.id.to_s + ".png"
big_image.append(true).write(Rails.root.join("app","assets","images","shared_logo",fileName))
rescue => e
puts "Errors! -- #{e.inspect}"
end
The codes puts two image on the same row. The images are png. The problem is that the second image has an height less than the first one. Image magick fill the remaining part with an unwanted white background. I want to keep the transparency on the combined image.
You could do something like:
manipulate! do |img|
img.combine_options do |c|
c.background "transparent"
c.gravity "center"
c.extent "450x401"
end
end
This is for RMagick if I'm not mistaken, the names might be slightly different (although I think combine options is using mongrifies methods?).

Unable to retain transparency using ImageMagick's composite

I have two images (both png) with transparency. I am using the MiniMagick gem to crop two copies of a single image into two other images. I'm then wanting to compose one of these images on top of the other, retaining the transparency all the way down.
Using the following code, it is respecting the transparency of image2, but once it is placed on top of image1 (which is what I'm after), the transparency of image1 is changed to black! I need to retain the transparency, but I'm really not sure how to properly use the alpha transparency stuff, if that is even the proper tool here.
image = MiniMagick::Image.open("skin.png")
image1 = MiniMagick::Image.open(image.path)
image2 = MiniMagick::Image.open(image.path)
# Crop and scale image1
MiniMagick::Tool::Mogrify.new do |m|
m.crop '8x8+8+8'
m.scale '144x144'
m.background 'transparent'
m.extent '160x160-8-8'
m << image1.path
end
# Crop and scale image2
MiniMagick::Tool::Mogrify.new do |m|
m.crop '8x8+40+8'
m.scale '160x160'
m << image2.path
end
result = image1.composite(image2) do |c|
c.compose 'Over'
c.alpha 'On'
end
result.write "public/skins/#{profile}.png"
send_file "public/skins/#{profile}.png"
Thanks.
MiniMagick's composite is processed as jpg with initial value.
http://www.rubydoc.info/github/probablycorey/mini_magick/MiniMagick/Image#composite-instance_method
The code below is working for me.
result = image1.composite(image2, 'png') do |c|
c.channel "A"
c.alpha 'Activate'
c.compose 'Over'
end

RMagick - how to create a thumbnail with an automatic height?

I am using RMagick for creating thumbnails like this:
img = Magick::Image.read(image_url).first
target = Magick::Image.new(110, 110) do
self.background_color = 'white'
end
img.resize_to_fit!(110, 110)
target.composite(img, Magick::CenterGravity, Magick::CopyCompositeOp).write(thumb_path)
This works well - I'll load the current image, create a "space" for the new thumb and then will place it there.
However, I would need to create a thumb where would be the width 110px and the height would be automatically counted... How to do this?
Thank you
You'd rather use resize_to_fill!
Doc here
image = Magick::Image.read(image_url).first
image.format = "JPG"
image.change_geometry!("110X110") { |cols, rows| image.thumbnail! cols, rows }
image.write("<path to save thumbnail>")
This turns out to be super easy! ImageMagick and GraphicsMagick both maintain aspect ratios properly, so in your case, just give the max width you want the image to be. See http://www.imagemagick.org/script/command-line-processing.php#geometry to learn more about the magick dimension operators.
If you find that you're ruby process' RAM consumption is growing, you may want to switch to an external-exec image library, like https://github.com/mceachen/micro_magick. Also, switching to GraphicsMagick is an all-around win, BTW, giving better image encoding and in less time.
require 'micro_magick'
img = MicroMagick::Convert.new("input.png")
img.resize("110") # this restricts to width, if you want to restrict to height, use "x345"
img.unsharp(1.5) # This runs an "unsharp mask" convolution filter, and is optional
img.write("output.png")

RMagick changing image extents with gravity

I've got an image that i'd like to 'pad' with white space and centre.
In most cases I need to resize the image from 16 or 32 pixels up to 32 pixels.
If the image is 16 pixels, I want to add 8px of white space on each side, making it a 32 pixel image (with the original floating in the middle).
If it's a 32 pixel image, then nothing changes.
I'm using RMagick to do the conversion:
image.change_geometry!("#{size}x#{size}") { |cols, rows, img|
newimg = img.extent(cols, rows)
newimg.write("#{RAILS_ROOT}#{path}/#{name}.png")
}
Which is working OK, but the smaller images are in the top left of the new image, not centred.
I was looking at the gravity setting, it seems to be what I need, but I can't work out how to specify it in the call?
Thanks in advance.
Check the implementation of the following carrierwave function
http://rubydoc.info/gems/carrierwave/0.5.1/CarrierWave/RMagick#resize_and_pad-instance_method
This is a version of the above method by using only RMagick dependency
require 'RMagick'
include Magick
module Converter
def self.resize_and_pad(img, new_img_path, width, height, background=:transparent, gravity=::Magick::CenterGravity)
img.resize_to_fit!(width, height)
new_img = ::Magick::Image.new(width, height)
if background == :transparent
filled = new_img.matte_floodfill(1, 1)
else
filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
end
# destroy_image(new_img)
filled.composite!(img, gravity, ::Magick::OverCompositeOp)
# destroy_image(img)
# filled = yield(filled) if block_given?
# filled
filled.write new_img_path
end
end
The extent() method takes two more parameters, x & y offsets, which is where the image will be placed within the extent. If you're asking extent for a 100x100 image, for example, and your original is only 50x50, you'd do img.extent(100, 100, 25, 25) -- which would set the image to start at offset 25,25 (thus centering it).
NOTE: There's some issue with extent expecting to use negative offset values (in which case you'd want to do -25, -25) -- check this:
why is the behavior of extent (imagemagick) not uniform across my machines?

Prawn: css like overflow: hidden for bounding boxes with images

I want to clip an image if it goes beyond the dimensions of a bounding box. Just like how CSS overflow: hidden would do it. Eg.
pdf.grid([0, 0], [3, 27]).bounding_box do
pdf.image image_file
end
Now currently, this image would overflow outside the bounding box if its larger than it. Is there any way to clip the image when it goes beyond the bounding box. ? I know this is possible for text when using text_box.
you can set the size of the image or get the image to scale so it fits within a certain area while maintaining proportions, do not believe you can crop an image.
If your page is not dynamic, that is the image area will always be the same this should be OK.
pdf.image "image_file_path_&_name", :position => :center, :fit => [100,450];
This is based on v0.8.4.
Unfortunately, there seems to exist no proper way to crop an image to a bounding box at the moment. Faced with this problem I figured out this beauty:
class SamplePdf
include Prawn::View
def initialize
crop_width = 100 # your width here
crop_height = 50 # your height here
image_path = '/path/to/your_image.jpg'
bounding_box [0, 0], width: crop_width, height: crop_height do
pdf_obj, _ = build_image_object(image_path)
x, y = document.send(:image_position, crop_width, crop_height, {})
document.send(:move_text_position, crop_height)
label = "I#{document.send(:next_image_id)}"
document.state.page.xobjects.merge!(label => pdf_obj)
cm_params = PDF::Core.real_params([crop_width, 0, 0, crop_height, x, y - crop_height])
document.renderer.add_content("\nq\n#{cm_params} cm\n/#{label} Do\nQ")
end
end
end
It basically adapts the Prawn::Images#image method, but skips the calculation of the image's dimensions and the scaling respectively.
It's not exactly a clean solution. Please keep me posted if you find a better one.
You should keep in mind though that this snippet leverages some implementation details which are not part of Prawn's public API and can change anytime.
At the time of writing Prawn 2.0.1 was the most recent version.

Resources