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.
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:
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:
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:
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:
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:
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:
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:
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.
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:
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:
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:
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:
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:
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:
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: