You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    )

问题分析与修复

当前实现存在三个核心问题:

  1. 平滑因子逻辑错误:阴影和高光的过渡因子不符合Photoshop Blend If规则,且未结合前景alpha通道。
  2. RGB混合忽略alpha:复杂模式的RGB混合公式未考虑前景和背景的透明度,导致带蒙版的前景无法正确半透明混合。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 10:00:03