使用Swift计算不带透明度的颜色,该颜色是通过覆盖具有透明度的颜色而获得的。

10

颜色叠加效果图

  • 我有一个原始的UIView,具有不透明的颜色orgColor
  • 这个UIView被一个带有透明度(alpha通道)overlayColor的叠加UIView覆盖。

给定这两种颜色orgColoroverlayColor,如何计算calculatedColor,它应该没有任何透明度,但在屏幕上与实际叠加视图相同的颜色?

我已经编写了一个骨架函数来展示原理。但是不知道如何计算(我在stackoverflow上找到了一些关于颜色混合的帖子,但是没有一个实际上会在结果中删除透明度...):

func calculateColor(orgColor: UIColor, overlayColor: UIColor) -> UIColor {
    var (r1, g1, b1, a1) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
    var (r2, g2, b2, a2) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))

    orgColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
    overlayColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

    //how to put the alpha in the r g b? and integrate the overlay r g b?

    return color
}
2个回答

5
重叠颜色的 alpha 值(a2)告诉你该颜色对总体的贡献百分比,而 (1 - a2) 告诉你原始颜色的贡献百分比。从这个角度来看,这是一个简单的计算:
func calculateColor(orgColor: UIColor, overlayColor: UIColor) -> UIColor {
    // Helper function to clamp values to range (0.0 ... 1.0)
    func clampValue(_ v: CGFloat) -> CGFloat {
        guard v > 0 else { return  0 }
        guard v < 1 else { return  1 }
        return v
    }

    var (r1, g1, b1, a1) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
    var (r2, g2, b2, a2) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))

    orgColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
    overlayColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

    // Make sure the input colors are well behaved
    // Components should be in the range (0.0 ... 1.0)
    r1 = clampValue(r1)
    g1 = clampValue(g1)
    b1 = clampValue(b1)

    r2 = clampValue(r2)
    g2 = clampValue(g2)
    b2 = clampValue(b2)
    a2 = clampValue(a2)

    let color = UIColor(  red: r1 * (1 - a2) + r2 * a2,
                        green: g1 * (1 - a2) + g2 * a2,
                         blue: b1 * (1 - a2) + b2 * a2,
                        alpha: 1)

    return color
}

直观上看,这是正确的。如果覆盖颜色完全不透明(a2 == 1),那么结果将是覆盖颜色。如果覆盖颜色完全透明(a2 == 0),则结果将是原始颜色。如果覆盖颜色的 alpha 值为 0.5,则会得到两种颜色之间恰好中间的混合颜色。

在 Playground 中测试:

// Make these colors anything you want
let c1 = UIColor(red: 27/255, green: 155/255, blue: 199/255, alpha: 1.0)
let c2 = UIColor(red: 188/255, green: 13/255, blue: 51/255, alpha: 0.37)

let c3 = calculateColor(orgColor: c1, overlayColor: c2)

let view1 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let view2 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

view1.backgroundColor = c1
view2.backgroundColor = c2

// Overlay view2 onto view1
view1.addSubview(view2)

// view3 has the calculated color
let view3 = UIView(frame: CGRect(x: 100, y: 0, width: 100, height: 100))
view3.backgroundColor = c3

// display the views side by side in view4
let view4 = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
view4.addSubview(view1)
view4.addSubview(view3)

// display this view and see that the two halves match
view4

游乐场输出:

Playground output


vacawama 这个完美地运作了!非常好的格式和完整的答案。只有一个备注:在颜色计算中,我们不应该使用 min(..., 1.0) 函数将值限制在 1.0 以下吗?(我在其他混合计算的答案中发现了这一点) - HixField
请检查我写的扩展名(所有功劳归于您!),我加入了min函数。 - HixField
如果输入值在0.0 ... 1.0范围内,那么min不必要的,因为它们应该在这个范围内。即使r11.0r21.0(它们的最大值),r1 *(1-a2)+ r2 * a2的组合也将达到1.0。尽管如此,仍然可以创建超出范围的UIColor,即使它们被解释为被夹在该范围内,UIColor.getRed(green:blue:alpha:)仍将返回超出范围的值。为了获得这些错误颜色的正确结果,有必要夹紧范围。请参见上面的修复。 - vacawama

3

基于@vacawama的答案(所有功劳归于他!)我编写了以下扩展UIColor:

extension UIColor {

    func solidColorWhenOverlayed(by overlayColor: UIColor) -> UIColor {
        // Helper function to clamp values to range (0.0 ... 1.0)
        func clampValue(_ v: CGFloat) -> CGFloat {
            guard v > 0 else { return  0 }
            guard v < 1 else { return  1 }
            return v
        }

        var (r1, g1, b1, a1) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
        var (r2, g2, b2, a2) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))

        getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
        overlayColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

        // Make sure the input colors are well behaved
        // Components should be in the range (0.0 ... 1.0)
        // This to compensate for any "bad" colors = colors which have component values out of range
        r1 = clampValue(r1)
        g1 = clampValue(g1)
        b1 = clampValue(b1)

        r2 = clampValue(r2)
        g2 = clampValue(g2)
        b2 = clampValue(b2)
        a2 = clampValue(a2)

        return UIColor(  red  : r1 * (1 - a2) + r2 * a2,
                         green: g1 * (1 - a2) + g2 * a2,
                         blue : b1 * (1 - a2) + b2 * a2,
                         alpha: 1)
    }

}

1
样式问题:我建议使用self.getRed而不是getRed,因为这样更清楚地说明了正在发生的事情。请参见我上面提出的夹紧问题。您的min解决方案将无法帮助错误的输入颜色。 - vacawama
我喜欢这个扩展。你可能想把它命名为 solidColorWhenOverlayed(by overlayColor: UIColor),这更符合 Swift 3/4 中不那么冗余的风格。 - vacawama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接