Apply linear transparent gradient to a portion of image with transparent background in python

Question:

I need to apply linear transparent gradient to only bottom 5% of the image. I have some idea that first I need to create mask on alpha channel. But I’m not sure how I can do that which affects only bottom 5% of the image. I’ve tried different methods bud no luck so far. The gradient should start at 5% height of the image at 100% alpha and 0% alpha at the end. Also, the images can be non-rectangular PNG with transparent background. I would really appreciate any help. Thank you.
Here is an example of what I need. But it can be either a rectangular or non-rectangular image.

Asked By: Z Dhillon

||

Answers:

Here is one way to do that in Python/OpenCV/Numpy for opaque images.

 - Read the input
 - Compute top and bottom heights for the alpha channel
 - Create a constant white image for top
 - Create a vertical gradient going from 255 to 0 for the bottom
 - Stack the top and bottom parts
 - Convert the image to 4 channels BGRA
 - Replace the alpha in the BGRA image with the stacked alpha
 - Save the result

Input:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread("lena.png")
ht, wd = img.shape[:2]

# compute 5% of ht and 95% of ht
# pct = 5
pct = 25    # temparily set pct to 25 percent for demonstration
ht2 = int(ht*pct/100)
ht3 = ht - ht2

# create opaque white image for top
top = np.full((ht3,wd), 255, dtype=np.uint8)

# create vertical gradient for bottom
btm = np.linspace(255, 0, ht2, endpoint=True, dtype=np.uint8)
btm = np.tile(btm, (wd,1))
btm = np.transpose(btm)

# stack top and bottom
alpha = np.vstack((top,btm))

# put alpha channel into image
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:,:,3] = alpha

# save result
cv2.imwrite('lena_fade.png', result)

# display results
# (note: display does not show transparency)
cv2.imshow('btm', btm)
cv2.imshow('alpha', alpha)
cv2.imshow('result', result)
cv2.waitKey(0)

Result:

enter image description here

ADDITION

Here is how to do that in ImageMagick 7.

magick lena.png 
-set option:wd "%w" 
-set option:ht "%h" 
-set option:ht2 "%[fx:round(25*ht/100)]" 
-set option:ht3 "%[fx:ht-ht2]" 
( -size "%[wd]x%[ht3]" xc:white ) 
( -size "%[wd]x%[ht2]" gradient:white-black ) 
( -clone 1,2 -append ) 
-delete 1,2 
-alpha off -compose copy_opacity -composite 
lena_fade2.png

Resulting Image:

enter image description here

Answered By: fmw42

Here is how to do that in ImageMagick 7 if you have an existing alpha channel in the input or not. It is slightly different. You basically extract the alpha channel from the input and multiply it with the one containing the gradient. Then put that new one into the original image replacing the existing alpha channel.

Input:

enter image description here

magick lena_circle.png 
-alpha set 
-set option:wd "%w" 
-set option:ht "%h" 
-set option:ht2 "%[fx:round(0.25*ht)]" 
-set option:ht3 "%[fx:ht-ht2]" 
( -size "%[wd]x%[ht3]" xc:white ) 
( -size "%[wd]x%[ht2]" gradient:white-black ) 
( -clone 1,2 -append ) 
-delete 1,2 
( -clone 0 -alpha extract ) 
( -clone 1,2 -compose multiply -composite ) 
-delete 1,2 
-alpha off -compose copy_opacity -composite 
lena_circle_fade3.png

Resulting Image:

enter image description here

Answered By: fmw42

Here is how to do that in Python/OpenCV if the image already has transparency. One needs to extract the image alpha, create a new alpha for the gradient and then multiply the two alpha channels together. Finally put the new alpha into the original image replacing the old alpha.

Input:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread("lena_circ.png", cv2.IMREAD_UNCHANGED)
ht, wd = img.shape[:2]

# extract the BGR component and the alpha component
bgr_circ = img[:,:,0:3]
alpha_circ = img[:,:,3]

# compute 5% of ht and 95% of ht
# pct = 5
pct = 50    # temparily set pct to 50 percent for demonstration
ht2 = int(ht*pct/100)
ht3 = ht - ht2

# create opaque white image for top
top = np.full((ht3,wd), 255, dtype=np.uint8)

# create vertical gradient for bottom
btm = np.linspace(255, 0, ht2, endpoint=True, dtype=np.uint8)
btm = np.tile(btm, (wd,1))
btm = np.transpose(btm)

# stack top and bottom
alpha = np.vstack((top,btm))

# multiply alpha and alpha_circ
alpha_new = (alpha * alpha_circ.astype(np.float64) / 255).clip(0,255).astype(np.uint8)

# put alpha channel into image
result = bgr_circ.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:,:,3] = alpha_new

# save result
cv2.imwrite('lena_circ_fade.png', result)

# display results
# (note: display does not show transparency)
cv2.imshow('btm', btm)
cv2.imshow('alpha', alpha)
cv2.imshow('alpha_circ', alpha_circ)
cv2.imshow('alpha_new', alpha_new)
cv2.imshow('result', result)
cv2.waitKey(0)

Result:

enter image description here

Answered By: fmw42