Python实现Photoshop Blend If混合选项的异常问题求助
Photoshop「Blend If」平滑过渡混合功能修复
背景
已完成Photoshop「Blending Options -> Blend If」的基础实现:当底层图层灰度像素处于[阴影值, 高光值]区间时,执行前景与底层混合,否则保留底层原始像素。对应代码如下:
from typing import Tuple import numpy as np def expand_as_rgba(image: np.ndarray) -> np.ndarray: # Add alpha-channels, if they are not provided if image.shape[2] == 3: return np.dstack((image, np.ones(image.shape[:2] + (1,)) * 255)).astype(np.uint8) return image def normal_blend_if( this_layer: np.ndarray, underlying_layer: np.ndarray, underlying_layer_shadows_range: Tuple = (0, 0), underlying_layer_highlights_range: Tuple = (255, 255), ) -> np.ndarray: bg_shadows, bg_highlights = underlying_layer_shadows_range, underlying_layer_highlights_range # Expand with alpha if missing foreground_array = expand_as_rgba(this_layer) / 255.0 background_array = expand_as_rgba(underlying_layer) / 255.0 # Extract the individual channels (R, G, B, A) foreground_r, foreground_g, foreground_b, foreground_a = np.rollaxis(foreground_array, axis=-1) background_r, background_g, background_b, background_a = np.rollaxis(background_array, axis=-1) # Calculate the luminosity of the background image background_luminosity = (0.299 * background_r + 0.587 * background_g + 0.114 * background_b) * 255.0 # Create the blend if condition based on the luminosity range blend_if = (background_luminosity >= bg_shadows[0]) & (background_luminosity <= bg_highlights[1]) blend_if_broadcast = np.expand_dims(blend_if, 2) foreground_rgb, background_rgb = foreground_array[:, :, :3], background_array[:, :, :3] fga_broadast, bga_broadcast = np.expand_dims(foreground_a, 2), np.expand_dims(background_a, 2) # Conditional blending blended_rgb = np.where( blend_if_broadcast, (foreground_rgb * fga_broadast + background_rgb * bga_broadcast * (1 - fga_broadast)) / ( fga_broadast + bga_broadcast * (1 - fga_broadast)), background_rgb ) blended_a = np.where( blend_if_broadcast, fga_broadast + bga_broadcast * (1 - fga_broadast), bga_broadcast ) # Combine the blended channels back into a single RGBA image blended_rgba = np.dstack((blended_rgb, blended_a)) # Scale the RGBA values back to the range [0, 255] blended_rgba = (blended_rgba * 255).astype(np.uint8) return blended_rgba
问题场景
需要实现带平滑过渡的复杂模式:阴影和高光阈值各设两个值实现渐变混合。但当前normal_complex_blend_if函数在前景图像带蒙版时无法正常工作,输出与预期不符。函数代码及调用方式如下:
复杂模式实现代码
def normal_complex_blend_if( this_layer: np.ndarray, underlying_layer: np.ndarray, underlying_layer_shadows_range: Tuple = (0, 0), underlying_layer_highlights_range: Tuple = (255, 255), ) -> np.ndarray: bg_shadows, bg_highlights = underlying_layer_shadows_range, underlying_layer_highlights_range # Expand with alpha if missing foreground_array = expand_as_rgba(this_layer) / 255.0 background_array = expand_as_rgba(underlying_layer) / 255.0 # Extract the individual channels (R, G, B, A) foreground_r, foreground_g, foreground_b, foreground_a = np.rollaxis(foreground_array, axis=-1) background_r, background_g, background_b, background_a = np.rollaxis(background_array, axis=-1) # Calculate the luminosity of the background image background_luminosity = (0.299 * background_r + 0.587 * background_g + 0.114 * background_b) * 255.0 # Create the blend if condition based on the luminosity range blend_if = (background_luminosity >= bg_shadows[0]) & (background_luminosity <= bg_highlights[1]) blend_if_broadcast = np.expand_dims(blend_if, 2) # Calculate the blending factors for the shadow and highlight ranges shadow_factor = np.interp(background_luminosity, [bg_shadows[0], bg_shadows[1]], [0, 1]) highlight_factor = np.interp(background_luminosity, [bg_highlights[0], bg_highlights[1]], [0, 1]) # Expand dimensions of alpha for further use fga_broadast, bga_broadcast = np.expand_dims(foreground_a, 2), np.expand_dims(background_a, 2) foreground_rgb, background_rgb = foreground_array[:, :, :3], background_array[:, :, :3] shadow_factor = np.expand_dims(shadow_factor, 2) highlight_factor = np.expand_dims(highlight_factor, 2) blended_rgb = np.where( blend_if_broadcast, foreground_rgb + (background_rgb - foreground_rgb) * (1 - shadow_factor), background_rgb + (foreground_rgb - background_rgb) * highlight_factor ) blended_a = np.where( blend_if_broadcast, fga_broadast + bga_broadcast * (1 - fga_broadast), bga_broadcast ) blended_rgba = np.dstack((blended_rgb, blended_a)) # Scale the RGBA values back to the range [0, 255] blended_rgba = (blended_rgba * 255).astype(np.uint8) return blended_rgba
调用代码
blended = normal_complex_blend_if( filtered_image, underlying_layer, underlying_layer_shadows_range=(10, 55), underlying_layer_highlights_range=(255, 255) )
问题分析与修复
当前实现存在三个核心问题:
- 平滑因子逻辑错误:阴影和高光的过渡因子不符合Photoshop Blend If规则,且未结合前景alpha通道。
- RGB混合忽略alpha:复杂模式的RGB混合公式未考虑前景和背景的透明度,导致带蒙版的前景无法正确半透明混合。
- alpha通道无平滑过渡:alpha通道仅做硬切换,未跟随RGB的混合强度渐变。
修复后的代码如下:
def normal_complex_blend_if_fixed( this_layer: np.ndarray, underlying_layer: np.ndarray, underlying_layer_shadows_range: Tuple = (0, 0), underlying_layer_highlights_range: Tuple = (255, 255), ) -> np.ndarray: bg_shadow_low, bg_shadow_high = underlying_layer_shadows_range bg_highlight_low, bg_highlight_high = underlying_layer_highlights_range # 转为RGBA并归一化 foreground = expand_as_rgba(this_layer) / 255.0 background = expand_as_rgba(underlying_layer) / 255.0 fg_rgb = foreground[..., :3] fg_a = foreground[..., 3:4] # 保持维度方便广播 bg_rgb = background[..., :3] bg_a = background[..., 3:4] # 计算底层亮度 bg_luminance = (0.299 * bg_rgb[...,0] + 0.587 * bg_rgb[...,1] + 0.114 * bg_rgb[...,2]) * 255.0 bg_luminance = bg_luminance[..., np.newaxis] # 扩展维度 # 计算阴影过渡因子:低于shadow_low时为0(不混合),高于shadow_high时为1(完全混合) shadow_factor = np.clip((bg_luminance - bg_shadow_low) / (bg_shadow_high - bg_shadow_low + 1e-8), 0.0, 1.0) # 计算高光过渡因子:高于highlight_high时为0(不混合),低于highlight_low时为1(完全混合) highlight_factor = np.clip((bg_highlight_high - bg_luminance) / (bg_highlight_high - bg_highlight_low + 1e-8), 0.0, 1.0) # 最终混合因子:阴影和高光因子的交集,即同时满足阴影和高光条件的混合强度 blend_factor = np.minimum(shadow_factor, highlight_factor) # 应用前景alpha到混合因子 blend_factor = blend_factor * fg_a # 计算RGB混合结果:遵循Normal混合模式的alpha规则 normal_blend_rgb = (fg_rgb * fg_a + bg_rgb * bg_a * (1 - fg_a)) / (fg_a + bg_a * (1 - fg_a) + 1e-8) blended_rgb = bg_rgb + (normal_blend_rgb - bg_rgb) * blend_factor # 计算alpha通道的平滑过渡 normal_blend_a = fg_a + bg_a * (1 - fg_a) blended_a = bg_a + (normal_blend_a - bg_a) * blend_factor # 合并通道并转为uint8 blended_rgba = np.concatenate([blended_rgb, blended_a], axis=-1) blended_rgba = np.clip(blended_rgba * 255, 0, 255).astype(np.uint8) return blended_rgba
修复说明
- 修正过渡因子逻辑:按照Photoshop Blend If规则计算阴影和高光过渡强度,区间外完全不混合,区间内平滑过渡。
- 融合前景蒙版:将前景alpha通道与过渡因子结合,保证带半透明蒙版的前景正确参与混合。
- 同步RGB与alpha平滑:让alpha通道跟随RGB混合强度渐变,避免边缘突兀。
- 增加数值稳定性:添加极小值
1e-8避免除法除零错误。
内容的提问来源于stack exchange,提问作者s.t.e.a.l.t.h
相关产品推荐
相关产品推荐

