Photoshop中的Multiply Blend Mode

Multiply是PS中常用的一种混合方式,中文版称为“正片叠底”模式。
说起来非常简单,就是颜色分量相乘。但实际上简单的相乘并没有考虑到alpha channel。
如果两个图层都是RGBA四个通道,Multiply的算法还得加入alpha。

为了搞清楚这个问题,我琢磨了好几个小时。最后从颜色含义入手,配合一些测试结果,终于推出了PS的混合公式:

OutAlpha = A1 + A2 - A1*A2
OutColor = (1.0 - (1.0 - C1)*A1) * (1.0 - (1.0 - C2)*A2)

可以看出这个公式对于A1和A2是对称的,所以被混合的两个图层没有前后之分。

推理过程如下:Multiply混合可以看作是两张幻灯片叠在一起,因为变得更厚所以透过的颜色变少,从而达到减色效果。在这里颜色分量Color是光可以透过的程度,1.0即表示该颜色分量全部可以透过。1.0-Color表示被吸收掉的部分。在PS的混合算法中,Alpha用来调节吸收程度。因为Alpha经常用于表示透明度,1.0表示完全不透明,即吸收程度为100%。

结合以上两个定义:

(1.0 - Color) * Alpha // 表示被吸收的颜色
1.0 - (1.0 - Color) * Alpha // 表示最终透过的颜色

把两个最终透过的颜色相乘,即得到我们需要的最终减色结果。

在忽略Alpha只有RGB混合的情况下,Alpha都取1.0,则公式可以化简为:

OutAlpha = 1.0
OutColor = C1 * C2

结果为颜色分量直接相乘,也就是到处都可以找到的对Multiply的算法的描述。

以下是c#版的实现……

static byte Clamp(int result)
{
    if (result < 0)
        return (byte)0;
    
    return result > 255 ? (byte)255 : (byte)result;
}

static byte MultiplyColor(int lhs, int rhs, int lhsAlpha, int rhsAlpha)
{
    if (lhsAlpha == 0)
        return Clamp(rhs);
    else if (rhsAlpha == 0)
        return Clamp(lhs);

    int lhsMultiply = (255 - (255 - lhs) * lhsAlpha / 255);
    int rhsMultiply = (255 - (255 - rhs) * rhsAlpha / 255);
    int result = rhsMultiply * lhsMultiply / 255;
    return Clamp(result);
}

// same as Photoshop multiply blend mode
static public void Multiply(Bitmap lhs, Bitmap rhs, Rectangle roi)
{
    BitmapData lhsData = SetImageToProcess(lhs, roi);
    BitmapData rhsData = SetImageToProcess(rhs, roi);

    int width = lhsData.Width;
    int height = lhsData.Height;
    int offset = lhsData.Stride;

    unsafe
    {
        byte* lhsPtr = (byte*)lhsData.Scan0;
        byte* rhsPtr = (byte*)rhsData.Scan0;

        for (int y = 0; y < height; ++y)
        {
            for (int x = 0; x < width * 4; x+=4)
            {
                int lhsAlpha = lhsPtr[x + 3];
                int rhsAlpha = rhsPtr[x + 3];

                // multiply color with alpha factor
                lhsPtr[x + 0] = MultiplyColor(lhsPtr[x + 0], rhsPtr[x + 0], lhsAlpha, rhsAlpha);
                lhsPtr[x + 1] = MultiplyColor(lhsPtr[x + 1], rhsPtr[x + 1], lhsAlpha, rhsAlpha);
                lhsPtr[x + 2] = MultiplyColor(lhsPtr[x + 2], rhsPtr[x + 2], lhsAlpha, rhsAlpha);

                // also blend the alpha channel
                int retAlpha = (lhsAlpha + rhsAlpha - lhsAlpha * rhsAlpha / 255);
                lhsPtr[x + 3] = Clamp(retAlpha);
            }
            lhsPtr += offset;
            rhsPtr += offset;
        }
    }
    lhs.UnlockBits(lhsData);
    rhs.UnlockBits(rhsData);
}

Leave a Reply

Your email address will not be published. Required fields are marked *