vips - How to achieve edge feather effect - lua

I'm using the vips library for manipulating some images, specifically its Lua binding, lua-vips, and I'm trying to find a way to do a feather effect on the edge of an image.
It's the first time I try a library for this kind of task and I've been looking at this list of functions available, but still no idea on how to it. It's not complex shape, just a basic rectangular image whose top and bottom edges should blend smoothly with the background (another image that I'm currently using vips_composite() on).
Supposing that a "feather_edges" method existed, it would be something like:
local bg = vips.Image.new_from_file("foo.png")
local img = vips.Image.new_from_file("bar.png") --smaller than `bg`
img = img:feather_edges(6) --imagine a 6px feather
bg:composite(img, 'over')
But still it would be nice to specify what parts of the image should be feathered. Any ideas on how to do it?

You need to pull the alpha out of the top image, mask off the edges with a black border, blur the alpha to feather the edges, reattach, then compose.
Something like:
#!/usr/bin/luajit
vips = require 'vips'
function feather_edges(image, sigma)
-- split to alpha + image data
local alpha = image:extract_band(image:bands() - 1)
local image = image:extract_band(0, {n = image:bands() - 1})
-- we need to place a black border on the alpha we can then feather into,
-- and scale this border with sigma
local margin = sigma * 2
alpha = alpha
:crop(margin, margin,
image:width() - 2 * margin, image:height() - 2 * margin)
:embed(margin, margin, image:width(), image:height())
:gaussblur(sigma)
-- and reattach
return image:bandjoin(alpha)
end
bg = vips.Image.new_from_file(arg[1], {access = "sequential"})
fg = vips.Image.new_from_file(arg[2], {access = "sequential"})
fg = feather_edges(fg, 10)
out = bg:composite(fg, "over", {x = 100, y = 100})
out:write_to_file(arg[3])

As jcupitt said, we need to pull the alpha band from the image, blur it, join it again and composite it with the background, but using the function as it was, left a thin black border around the foreground image.
To overcome that, we need to copy the image, resize it according to the sigma parameter, extract the alpha band from the reduced copy, blur it, and replace the alpha band of the original image with it. Like this, the border of the original image will be completely covered by the transparent parts of the alpha.
local function featherEdges(img, sigma)
local copy = img:copy()
:resize(1, { vscale = (img:height() - sigma * 2) / img:height() })
:embed(0, sigma, img:width(), img:height())
local alpha = copy
:extract_band(copy:bands() - 1)
:gaussblur(sigma)
return img
:extract_band(0, { n = img:bands() - 1 })
:bandjoin(alpha)
end

Related

Using map_coordinates to upscale an image

I am tasked with creating a low-resolution version of an image with the same shape by reducing the image's shape randomly (to lose data) and expanding it back. However, I cannot use any of the 'resize' methods such as in scikit/opencv.. and only allowed to use scipy.ndimage.zoom and map_coordinates.
I've managed to do the following: (im is an grayscale image)
factor = np.random.uniform(0.25, 1)
zoomed_im = ndimage.zoom(im, zoom)
height_range = np.arange(0, im.shape[0])
width_range = np.arange(0, im.shape[1])
col, row = np.meshgrid(width_range, height_range)
zoom_out = map_coordinates(input=zoomed_in, coordinates=[row, col])
however I get the same zoomed in image with the rest of the pixels added as black. I understand this is due to the default parameters of map_coordinates being :
mode='constant'
cval = 0.0
How can I enlarge the image back using interpolation to the same original shape?
You can use a different step size in np.arange():
factor = np.random.uniform(0.25, 1)
zoomed_im = ndimage.zoom(im, factor)
height_range = np.arange(0, im.shape[0]*factor, step=factor)
width_range = np.arange(0, im.shape[1]*factor, step=factor)

How to rotate a non-squared image in frequency domain

I want to rotate an image in frequency domain. Inspired in the answers in Image rotation and scaling the frequency domain? I managed to rotate square images. (See the following python script using OpenCV)
M = cv2.imread("lenna.png")
M=np.float32(M)
hanning=cv2.createHanningWindow((M.shape[1],M.shape[0]),cv2.CV_32F)
M=hanning*M
sM = fftshift(M)
rotation_center=(M.shape[1]/2,M.shape[0]/2)
rot_matrix=cv2.getRotationMatrix2D(rotation_center,angle,1.0)
FsM = fftshift(cv2.dft(sM,flags = cv2.DFT_COMPLEX_OUTPUT))
rFsM=cv2.warpAffine(FsM,rot_matrix,(FsM.shape[1],FsM.shape[0]),flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
IrFsM = ifftshift(cv2.idft(ifftshift(rFsM),flags=cv2.DFT_REAL_OUTPUT))
This works fine with squared images. (Better results could be achieved by padding the image)
However, when only using a non-squared portion of the image, the rotation in frequency domain shows some kind of shearing effect.
Any idea on how to achieve this? Obivously I could pad the image to make it square, however the final purpose of all this is to rotate FFTs as fast as possible for an iterative image registration algorithm and this would slightly slow down the algorithm.
Following the suggestion of #CrisLuengo I found the affine transform needed to avoid padding the image. Obviously it will depend on the image size and the application but for my case avoidding the padding is very interesting.
The modified script looks now like:
#rot_matrix=cv2.getRotationMatrix2D(rotation_center,angle,1.0)
kx=1.0
ky=1.0
if(M.shape[0]>M.shape[1]):
kx= float(M.shape[0]) / M.shape[1]
else:
ky=float(M.shape[1])/M.shape[0]
affine_transform = np.zeros([2, 3])
affine_transform[0, 0] = np.cos(angle)
affine_transform[0, 1] = np.sin(angle)*ky/kx
affine_transform[0, 2] = (1-np.cos(angle))*rotation_center[0]-ky/kx*np.sin(angle)*rotation_center[1]
affine_transform[1, 0] = -np.sin(angle)*kx/ky
affine_transform[1, 1] = np.cos(angle)
affine_transform[1, 2] = kx/ky*np.sin(angle)*rotation_center[0]+(1-np.cos(angle))*rotation_center[1]
FsM = fftshift(cv2.dft(sM,flags = cv2.DFT_COMPLEX_OUTPUT))
rFsM=cv2.warpAffine(FsM,affine_transform, (FsM.shape[1],FsM.shape[0]),flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
IrFsM = ifftshift(cv2.idft(ifftshift(rFsM),flags=cv2.DFT_REAL_OUTPUT))

Change icon color by means of awesome wm

In awesome wm 3.5 you can create custom widgets using cairo to draw its visual. I want a widget which displays monochrome PNG icon (like wibox.widget.imagebox do it) and allows quickly change its color. I tried modify several lines in draw function of wibox.widget.imagebox
local cairo = require("lgi").cairo
--- Draw an imagebox with the given cairo context in the given geometry.
function imagebox:draw(wibox, cr, width, height)
if not self._image then return end
if width == 0 or height == 0 then return end
cr:save()
if not self.resize_forbidden then
-- Let's scale the image so that it fits into (width, height)
local w = self._image:get_width()
local h = self._image:get_height()
local aspect = width / w
local aspect_h = height / h
if aspect > aspect_h then aspect = aspect_h end
cr:scale(aspect, aspect)
end
-- Here is my modifications
cr:set_source_surface(self._image, 0, 0)
cr:paint()
cr:set_operator(cairo.Operator.IN)
cr:set_source_rgba(0, 0, 1, 0.5)
cr:paint()
-- End of my my modifications
-- This is original draw code how it was
--cr:set_source_surface(self._image, 0, 0)
--cr:paint()
cr:restore()
end
But it doesn't work. I tried set several other cairo's compositing operators and most of them works not as expected. Wrong overlapping areas and black regions instead of wibox background color. SOURCE and OVER are only works right. Where did I make mistake?
The mistake is your understanding of cairo's drawing methods. Black/transparent is just what IN leaves behind in the places that it did not touch. In other words, you are first drawing something else over the background and thus the background is lost.
Try this instead:
local pat = require("lgi").cairo.Pattern
cr:set_source_rgba(0, 0, 1, 0.5)
cr:mask(pat.create_for_surface(self._image), 0, 0)

how to embed a watermark on an image using edge in matlab?

in a school project i would like to do the following step to have a watermaked image in matlab
extract the edges from an image
insert a mark on this edge
reconstruct the image
extract the mark
could some one give me a link to have a good idea how to do it or help me to do that?
thank you in advance
You want to add a watermark to an image? Why not just overlay the whole thing.
if you have an image
img = imread('myimage.jpg')
wm = imread('watermark.jpg')
You can just resize the watermark to the size of the image
wm_rs = imresize(wm, [size(img,1) size(img,2)], 'lanczos2');
img_wm(wm_rs ~= 0) = wm_rs; %This sets non-black pixels to be the watermark. (You'll have to slightly modify this for color images)
If you want to put it on the edges of the image, you can extract them like this
edges = edge(rgb2gray(img),'canny')
Then you can set the pixels where the edges exist to be watermark pixels
img_wm = img;
img_wm(edges ~= 0) = wm_rs(edges~=0);
Instead of direct assignment you can play around with using a mix of the img and wm_rs pixel values if you want transparency.
You'll probably have to adjust some of what I said to color images, but most should be the same.
Here, is a nice and simple example how you can embed watermarks using MATLAB (in the spatial domain): http://imageprocessingblog.com/digital-watermarking/
see example below(R2017b or later release):
% your params
img = imread('printedtext.png');
Transparency = 0.6;
fontColor = [1,1,1]; % RGB,range [0,1]
position = [700,200];
%% add watermark
mask = zeros(size(img),'like',img);
outimg = insertText(mask,position,'china', ...
'BoxOpacity',0,...
'FontSize',200,...
'TextColor', 'white');
bwMask = imbinarize(rgb2gray(outimg));
finalImg = labeloverlay(img,bwMask,...
'Transparency',Transparency,...
'Colormap',fontColor);
imshow(finalImg)

Colorizing image ignores alpha channel — why and how to fix?

Here's what I'm trying to do: On the left is a generic, uncolorized RGBA image that I've created off-screen and cached for speed (it's very slow to create initially, but very fast to colorize with any color later, as needed). It's a square image with a circular swirl. Inside the circle, the image has an alpha/opacity of 1. Outside the circle, it has an alpha/opacity of 0. I've displayed it here inside a UIView with a background color of [UIColor scrollViewTexturedBackgroundColor]. On the right is what happens when I attempt to colorize the image by filling a solid red rectangle over the top of it after setting CGContextSetBlendMode(context, kCGBlendModeColor).
That's not what I want, nor what I expected. Evidently, colorizing a completely transparent pixel (e.g., alpha value of 0) results in the full-on fill color for some strange reason, rather than remaining transparent as I would have expected.
What I want is actually this:
Now, in this particular case, I can set the clipping region to a circle, so that the area outside the circle remains untouched — and that's what I've done here as a workaround.
But in my app, I also need to be able to colorize arbitrary shapes where I don't know the clipping/outline path. One example is colorizing white text by overlaying a gradient. How is this done? I suspect there must be some way to do it efficiently — and generally, with no weird path/clipping tricks — using image masks... but I have yet to find a tutorial on this. Obviously it's possible because I've seen colored-gradient text in other games.
Incidentally, what I can't do is start with a gradient and clip/clear away parts I don't need — because (as shown in the example above) my uncolorized source images are, in general, grayscale rather than pure white. So I really need to start with the uncolorized image and then colorize it.
p.s. — kCGBlendModeMultiply also has the same flaws / shortcomings / idiosyncrasies when it comes to colorizing partially transparent images. Does anyone know why Apple decided to do it that way? It's as if the Quartz colorizing code treats RGBA(0,0,0,0) as RGBA(0,0,0,1), i.e., it completely ignores and destroys the alpha channel.
One approach that you can take that will work is to construct a mask from the original image and then invoke the CGContextClipToMask() method before rendering your image with the multiply blend mode set. Here is the CoreGraphics code that would set the mask before drawing the image to color.
CGContextRef context = [frameBuffer createBitmapContext];
CGRect bounds = CGRectMake( 0.0f, 0.0f, width, height );
CGContextClipToMask(context, bounds, maskImage.CGImage);
CGContextDrawImage(context, bounds, greyImage.CGImage);
The slightly more tricky part will be to take the original image and generate a maskImage. What you can do for that is write a loop that will examine each pixel and write either a black or white pixel as the mask value. If the original pixel in the image to color is completely transparent, then write a black pixel, otherwise write a white pixel. Note that the mask value will be a 24BPP image. Here is some code to give you the right idea.
uint32_t *inPixels = (uint32_t*) MEMORY_ADDR_OF_ORIGINAL_IMAGE;
uint32_t *maskPixels = malloc(numPixels * sizeof(uint32_t));
uint32_t *maskPixelsPtr = maskPixels;
for (int rowi = 0; rowi < height; rowi++) {
for (int coli = 0; coli < width; coli++) {
uint32_t inPixel = *inPixels++;
uint32_t inAlpha = (inPixel >> 24) & 0xFF;
uint32_t cval = 0;
if (inAlpha != 0) {
cval = 0xFF;
}
uint32_t outPixel = (0xFF << 24) | (cval << 16) | (cval << 8) | cval;
*maskPixelsPtr++ = outPixel;
}
}
You will of course need to fill in all the details and create the graphics contexts and so on. But the general idea is to simply create your own mask to filter out drawing of the red parts around the outside of the circle.

Resources