RMagick changing image extents with gravity - ruby-on-rails

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?

Related

How to remove blank space from cropped image om opencv?

I am trying to crop a specific part of a frame in opencv to get a cropped image of the detections from mobilenet ssd model. The code to crop the image is like this
for box_id in boxes_ids:
x,y,w,h,id = box_id
crop=frame[y:h,x:w]
cv2.imshow("d",crop)
cv2.waitKey(5)
This code is producing a blank space towards the right of all the images that I extract :
Please tell me how can i fix this.
try using Pillow that helps
def trim(im, color):
bg = Image.new(im.mode, im.size, color)
diff = ImageChops.difference(im, bg)
diff = ImageChops.add(diff, diff)
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
This function will probably take it out, just be carefull that this will only work if the segment of image has consistent pixels
as said before in the comments, there is a minimum window width, and smaller crops will be drawn on some neutral background.
but maybe it's more intuitive to draw the crop into an empty image, conserving its original position:
for box_id in boxes_ids:
x,y,w,h,id = box_id
draw = np.zeros(frame.shape, np.uint8)
draw[y:h,x:w] = frame[y:h,x:w]
cv2.imshow("d",draw)
cv2.waitKey(5)

Love2D - how to math.floor the love.graphics.scale result?

I'm using the Gamera library for my game's camera and so as to be compatible with multiple screen resolutions on mobile I'm using the setScale function with Gamera to adjust the size accordingly. Gamera uses love.graphics.scale to scale the graphics and unfortunately this results in a sub pixel figure for the graphic size since the scale ratio is a decimal number. The screen visible area is resized to perfect integers however so there is no problem there.
This sub pixel figure for the graphic size causes pixel bleed when drawing tiles with Sprite batches. I've tried "nearest neighbour" image filtering for these problem images, which was a workaround but didn't fix the pixel bleed for the player's y axis. Generally I thought there must be a better way since all that really needs to be done is to math.floor the scale. The 2048x2048 level size is resized to something like 1742.8234. If it was simply 1742 there would be no problem.
This is the relevant draw function in Gamera:
function gamera:draw(f)
love.graphics.setScissor(self:getWindow())
love.graphics.push()
local scale = self.scale
love.graphics.scale(scale)
love.graphics.translate((self.w2 + self.l) / scale, (self.h2+self.t) / scale)
love.graphics.rotate(-self.angle)
love.graphics.translate(-self.x, -self.y)
f(self:getVisible())
love.graphics.pop()
love.graphics.setScissor()
end
If you want to use math.floor on your scale, all you have to do is to replace any occurance of scale where you want to do that with math.floor(scale).
function gamera:draw(f)
love.graphics.setScissor(self:getWindow())
love.graphics.push()
local scale = self.scale
love.graphics.scale(math.floor(scale))
love.graphics.translate((self.w2 + self.l) / math.floor(scale), (self.h2+self.t) / math.floor(scale))
love.graphics.rotate(-self.angle)
love.graphics.translate(-self.x, -self.y)
f(self:getVisible())
love.graphics.pop()
love.graphics.setScissor()
end
As programmers are lazy and we do not want to calculate things more often than necessary this solution is not very pretty. So we forget what we did above and simply replace
local scale = self.scale
with
local scale = math.floor(self.scale)
now within that function scale is uses as intended.
If we want keep that change beyond that function's scope we could as well do
self.scale = math.floor(self.scale)
local scale = self.scale
Edit due to comment
If you want to use ensure that love.graphics.scale() results in integer dimensions you have to use an appropriate scale.
Sticking to your example:
2048 shall be shrinked to 1742:
love.graphics.scale(1742/2048)
If you let's say want to shrink it to 30%:
love.graphics.scale(math.floor(0.3*2048)/2048)
would result in 614 instead of 614.4
Simple math.

Wrong result using function fillPoly in opencv for very large images

I have a hard time solving the issue with mask creation.My image is large,
40959px X 24575px and im trying to create a mask for it.
I noticed that i dont have a problem for images up to certain size(I tested about 33000px X 22000px), but for dimensions larger than that i get an error inside my mask(Error is that it gets black in the middle of the polygon and white region extends itself to the left edge.Result should be without black area inside polygon and no white area extending to the left edge of image).
So my code looks like this:
pixel_points_list = latLonToPixel(dataSet, lat_lon_pairs)
print pixel_points_list
# This is the list im getting
#[[213, 6259], [22301, 23608], [25363, 22223], [27477, 23608], [35058, 18433], [12168, 282], [213, 6259]]
image = cv2.imread(in_tmpImgFilePath,-1)
print image.shape
#Value of image.shape: (24575, 40959, 4)
mask = np.zeros(image.shape, dtype=np.uint8)
roi_corners = np.array([pixel_points_list], dtype=np.int32)
print roi_corners
#contents of roi_corners_array:
"""
[[[ 213 6259]
[22301 23608]
[25363 22223]
[27477 23608]
[35058 18433]
[12168 282]
[ 213 6259]]]
"""
channel_count = image.shape[2]
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
cv2.imwrite("mask.tif",mask)
And this is the mask im getting with those coordinates(minified mask):
You see that in the middle of the mask the mask is mirrored.I took those points from pixel_points_list and drawn them on coordinate system and im getting valid polygon, but when using fillPoly im getting wrong results.
Here is even simpler example where i have only 4(5) points:
roi_corners = array([[ 213 6259]
[22301 23608]
[35058 18433]
[12168 282]
[ 213 6259]])
And i get
Does anyone have a clue why does this happen?
Thanks!
The issue is in the function CollectPolyEdges, called by fillPoly (and drawContours, fillConvexPoly, etc...).
Internally, it's assumed that the point coordinates (of integer type int32) have meaningful values only in the 16 lowest bits. In practice, you can draw correctly only if your points have coordinates up to 32768 (which is exactly the maximum x coordinate you can draw in your image.)
This can't be considered as a bug, since your images are extremely large.
As a workaround, you can try to scale your mask and your points by a given factor, fill the poly on the smaller mask, and then re-scale the mask back to original size
As #DanMaĆĄek pointed out in the comments, this is in fact a bug, not fixed, yet.
In the bug discussion, there is another workaround mentioned. It consists on drawing using multiple ROIs with size less than 32768, correcting coordinates for each ROI using the offset parameter in fillPoly.

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")

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