More mode for blending layers

pull/21/head
Kohaku-Blueleaf 2023-01-25 21:48:34 +08:00
parent bb32a2b65f
commit 5a402ba969
2 changed files with 100 additions and 10 deletions

View File

@ -2,26 +2,112 @@ from __future__ import annotations
from PIL import Image, ImageFilter, ImageColor
import numpy as np
import cv2
class Blend:
@classmethod
def method(cls, name):
return getattr(cls, name)
@staticmethod
def normal(target, blend, opacity, *args):
return target * opacity + blend * (1-opacity)
@staticmethod
def darken(target, blend, *args):
return np.minimum(target, blend)
@staticmethod
def multiply(target, blend, *args):
return target * blend
@staticmethod
def color_burn(target, blend, *args):
return 1 - (1-target)/blend
@staticmethod
def linear_burn(target, blend, *args):
return target+blend-1
@staticmethod
def lighten(target, blend, *args):
return np.maximum(target, blend)
@staticmethod
def screen(target, blend, *args):
return 1 - (1-target) * (1-blend)
@staticmethod
def color_dodge(target, blend, *args):
return target/(1-blend)
@staticmethod
def linear_dodge(target, blend, *args):
return target+blend
@staticmethod
def overlay(target, blend, *args):
return (target>0.5) * (1-(2-2*target)*(1-blend)) +\
(target<=0.5) * (2*target*blend)
@staticmethod
def soft_light(target, blend, *args):
return (blend>0.5) * (1 - (1-target)*(1-(blend-0.5))) +\
(blend<=0.5) * (target*(blend+0.5))
@staticmethod
def hard_light(target, blend, *args):
return (blend>0.5) * (1 - (1-target)*(2-2*blend)) +\
(blend<=0.5) * (2*target*blend)
@staticmethod
def vivid_light(target, blend, *args):
return (blend>0.5) * (1 - (1-target)/(2*blend-1)) +\
(blend<=0.5) * (target/(1-2*blend))
@staticmethod
def linear_light(target, blend, *args):
return (blend>0.5) * (target + 2*(blend-0.5)) +\
(blend<=0.5) * (target + 2*blend)
@staticmethod
def pin_light(target, blend, *args):
return (blend>0.5) * np.maximum(target,2*(blend-0.5)) +\
(blend<=0.5) * np.minimum(target,2*blend)
@staticmethod
def difference(target, blend, *args):
return np.abs(target - blend)
@staticmethod
def exclusion(target, blend, *args):
return 0.5 - 2*(target-0.5)*(blend-0.5)
blend_methods = [i for i in Blend.__dict__.keys() if i[0]!='_' and i!='method']
def run(layers):
def blend(bg, *args):
assert len(args)%4==0
chunks = [args[i*layers: i*layers+layers] for i in range(4)]
assert len(args)%5==0
chunks = [args[i*layers: i*layers+layers] for i in range(5)]
h, w, c = [i['image'] for i in chunks[-1] if i is not None][0].shape
base_img = np.array(Image.new(mode="RGB", size=(w, h), color=ImageColor.getcolor(bg, 'RGB')))
base_img = base_img.astype(np.float64)
base_img = base_img.astype(np.float64)/255
for alpha, mask_blur, mask_str, img in zip(*chunks):
for alpha, mask_blur, mask_str, mode, img in zip(*chunks):
if img is None or img['image'] is None: continue
img_now = Image.fromarray(img['image']).resize((w, h))
mask = Image.fromarray(img['mask'][:,:,0], mode='L')
img_now = np.array(img_now).astype(np.float64)
img_now = np.array(img_now).astype(np.float64)/255
mask = mask.resize((w, h)).filter(ImageFilter.GaussianBlur(mask_blur))
mask = np.expand_dims(np.array(mask)*mask_str/255, 2)
img_now = base_img*mask + img_now*(1-mask)
base_img = base_img*alpha + img_now*(1-alpha)
img_now = Blend.normal(base_img, img_now, mask)
base_img = Blend.method(mode)(img_now, base_img, alpha)
base_img = np.clip(base_img, 0, 1)
base_img *= 255
return Image.fromarray(base_img.astype(np.uint8), mode='RGB')
return blend

View File

@ -77,6 +77,7 @@ def add_tab():
all_alphas = []
all_mask_str = []
all_mask_blur = []
all_mode = []
img_blend_h_slider = gr.Slider(160, 1280, 320, step=10, label="Image preview height", elem_id='haku_img_h_blend')
with gr.Tabs(elem_id="haku_blend_layers_tabs"):
for i in range(1, layers+1):
@ -85,7 +86,7 @@ def add_tab():
gr.ImageMask(type='numpy', label=f"Layer{i}", elem_id=f'haku_img_blend{i}')
)
all_alphas.append(
gr.Slider(0, 1, 0.5, label=f"Layer{i} transparent")
gr.Slider(0, 1, 0.5 if i-1 else 1, label=f"Layer{i} opacity")
)
all_mask_blur.append(
gr.Slider(0, 32, 4, label=f"Layer{i} mask blur")
@ -93,6 +94,9 @@ def add_tab():
all_mask_str.append(
gr.Slider(0, 1, 1, label=f"Layer{i} mask strength")
)
all_mode.append(
gr.Dropdown(blend.blend_methods, value='normal', label='Blend mode')
)
bg_color = gr.ColorPicker('#FFFFFF', label='background color')
expand_btn = gr.Button("refresh", variant="primary")
@ -110,7 +114,7 @@ def add_tab():
color_btn = gr.Button("refresh", variant="primary")
with gr.TabItem('Blur', elem_id='haku_blur'):
blur_slider = gr.Slider(0, 32, 8, label="blur")
blur_slider = gr.Slider(0, 128, 8, label="blur")
blur_btn = gr.Button("refresh", variant="primary")
with gr.TabItem('Sketch', elem_id='haku_sketch'):
@ -171,7 +175,7 @@ def add_tab():
# blend
all_blend_set = [bg_color]
all_blend_set += all_alphas+all_mask_blur+all_mask_str
all_blend_set += all_alphas+all_mask_blur+all_mask_str+all_mode
all_blend_input = all_blend_set + all_layers
for component in all_blend_set:
component.change(blend.run(layers), all_blend_input, image_out)