Files
2023-09-05 10:56:44 +08:00

133 lines
5.3 KiB
Python

"""Implement Photoshop color blend
ref: https://stackoverflow.com/questions/12121393/please-explain-this-color-blending-mode-formula-so-i-can-replicate-it-in-php-ima
"""
from typing import Literal
import cv2
import numpy as np
R = 0.3
G = 0.59
B = 0.11
def rgb2hsy(image):
"""image is normalized to [0,1]
"""
image = np.minimum(np.maximum(image, 0), 1)
r, g, b = image[:,:,0], image[:,:,1], image[:,:,2]
h = np.zeros_like(r)
s = np.zeros_like(g)
y = R * r + G * g + B * b
mask_gray = (r == g) & (g == b)
s[mask_gray] = 0
h[mask_gray] = 0
mask_sector_0 = ((r >= g) & (g >= b)) & ~mask_gray # Sector 0: 0° - 60°
s[mask_sector_0] = r[mask_sector_0] - b[mask_sector_0]
h[mask_sector_0] = 60 * (g[mask_sector_0] - b[mask_sector_0]) / s[mask_sector_0]
mask_sector_1 = (g > r) & (r >= b)
s[mask_sector_1] = g[mask_sector_1] - b[mask_sector_1]
h[mask_sector_1] = 60 * (g[mask_sector_1] - r[mask_sector_1]) / s[mask_sector_1] + 60
mask_sector_2 = (g >= b) & (b > r)
s[mask_sector_2] = g[mask_sector_2] - r[mask_sector_2]
h[mask_sector_2] = 60 * (b[mask_sector_2] - r[mask_sector_2]) / s[mask_sector_2] + 120
mask_sector_3 = (b > g) & (g > r)
s[mask_sector_3] = b[mask_sector_3] - r[mask_sector_3]
h[mask_sector_3] = 60 * (b[mask_sector_3] - g[mask_sector_3]) / s[mask_sector_3] + 180
mask_sector_4 = (b > r) & (r >= g)
s[mask_sector_4] = b[mask_sector_4] - g[mask_sector_4]
h[mask_sector_4] = 60 * (r[mask_sector_4] - g[mask_sector_4]) / s[mask_sector_4] + 240
mask_sector_5 = ~(mask_gray | mask_sector_0 | mask_sector_1 | mask_sector_2 | mask_sector_3 | mask_sector_4)
s[mask_sector_5] = r[mask_sector_5] - g[mask_sector_5]
h[mask_sector_5] = 60 * (r[mask_sector_5] - b[mask_sector_5]) / s[mask_sector_5] + 300
hsy = np.zeros_like(image)
hsy[:,:,0] = h % 360
hsy[:,:,1] = np.minimum(np.maximum(s, 0), 1)
hsy[:,:,2] = np.minimum(np.maximum(y, 0), 1)
return hsy
def hsy2rgb(image):
h, s, y = image[:, :, 0], image[:, :, 1], image[:, :, 2]
h = h % 360
s = np.minimum(np.maximum(s, 0), 1)
y = np.minimum(np.maximum(y, 0), 1)
r = np.zeros_like(h)
g = np.zeros_like(h)
b = np.zeros_like(h)
k = np.zeros_like(h)
mask_sector_0 = (h >= 0) & (h < 60)
k[mask_sector_0] = s[mask_sector_0] * h[mask_sector_0] / 60
b[mask_sector_0] = y[mask_sector_0] - R * s[mask_sector_0] - G * k[mask_sector_0]
r[mask_sector_0] = b[mask_sector_0] + s[mask_sector_0]
g[mask_sector_0] = b[mask_sector_0] + k[mask_sector_0]
mask_sector_1 = (h >= 60) & (h < 120)
k[mask_sector_1] = s[mask_sector_1] * (h[mask_sector_1] - 60) / 60
g[mask_sector_1] = y[mask_sector_1] + B * s[mask_sector_1] + R * k[mask_sector_1]
b[mask_sector_1] = g[mask_sector_1] - s[mask_sector_1]
r[mask_sector_1] = g[mask_sector_1] - k[mask_sector_1]
mask_sector_2 = (h >= 120) & (h < 180)
k[mask_sector_2] = s[mask_sector_2] * (h[mask_sector_2] - 120) / 60
r[mask_sector_2] = y[mask_sector_2] - G * s[mask_sector_2] - B * k[mask_sector_2]
g[mask_sector_2] = r[mask_sector_2] + s[mask_sector_2]
b[mask_sector_2] = r[mask_sector_2] + k[mask_sector_2]
mask_sector_3 = (h >= 180) & (h < 240)
k[mask_sector_3] = s[mask_sector_3] * (h[mask_sector_3] - 180) / 60
b[mask_sector_3] = y[mask_sector_3] + R * s[mask_sector_3] + G * k[mask_sector_3]
r[mask_sector_3] = b[mask_sector_3] - s[mask_sector_3]
g[mask_sector_3] = b[mask_sector_3] - k[mask_sector_3]
mask_sector_4 = (h >= 240) & (h < 300)
k[mask_sector_4] = s[mask_sector_4] * (h[mask_sector_4] - 240) / 60
g[mask_sector_4] = y[mask_sector_4] - B * s[mask_sector_4] - R * k[mask_sector_4]
b[mask_sector_4] = g[mask_sector_4] + s[mask_sector_4]
r[mask_sector_4] = g[mask_sector_4] + k[mask_sector_4]
mask_sector_5 = h >= 300
k[mask_sector_5] = s[mask_sector_5] * (h[mask_sector_5] - 300) / 60
r[mask_sector_5] = y[mask_sector_5] + G * s[mask_sector_5] + B * k[mask_sector_5]
g[mask_sector_5] = r[mask_sector_5] - s[mask_sector_5]
b[mask_sector_5] = r[mask_sector_5] - k[mask_sector_5]
return np.minimum(np.maximum(np.stack([r, g, b], axis=-1), 0), 1)
def color_blend(base_image, blend_image, mode: Literal["Hue", "Saturation", "Color", "Luminosity"]):
"""
Args:
base_image (cv2)
blend_image (cv2)
Return:
cv2
"""
base_image = cv2.cvtColor(base_image, cv2.COLOR_BGR2RGB).astype(np.float32)/255
blend_image = cv2.cvtColor(blend_image, cv2.COLOR_BGR2RGB).astype(np.float32)/255
# Convert to HLS color space using OpenCV
hsy_base = rgb2hsy(base_image)
hsy_blend = rgb2hsy(blend_image)
if mode=="Hue":
hsy_out = np.stack([hsy_blend[:,:,0], hsy_base[:,:,1], hsy_base[:,:,2]], axis=-1)
elif mode=="Saturation":
hsy_out = np.stack([hsy_base[:,:,0], hsy_blend[:,:,1], hsy_base[:,:,2]], axis=-1)
elif mode=="Color":
hsy_out = np.stack([hsy_blend[:,:,0], hsy_blend[:,:,1], hsy_base[:,:,2]], axis=-1)
elif mode=="Luminosity":
hsy_out = np.stack([hsy_base[:,:,0], hsy_base[:,:,1], hsy_blend[:,:,2]], axis=-1)
else: assert False, f"{mode} is not a valid mode"
rgb_out = hsy2rgb(hsy_out)
return cv2.cvtColor((rgb_out*255).astype(np.uint8), cv2.COLOR_RGB2BGR)