Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Swift calculate color without transparency obtained by overlaying a color with an transparant color

Tags:

swift

colors

Colors overlaying

  • I have a original UIView that has a non transparant color orgColor.
  • This is overlayed by an overlay UIView that has a overlayColor with transparancy (alpha channel).

Given those two colors orgColor and overlayColor how do I calculate the calculatedColor that should not have any transparency but is exactly the same color on screen then as if both views would actually be overlayed?

I have written a skeleton function for this (to show the principle). But don't know how the calculate (I found a couple of posts on stack overflow regarding color blending but none would actually remove the transparency in the result...):

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
}
like image 491
HixField Avatar asked Aug 03 '18 19:08

HixField


2 Answers

The alpha of the overlay color (a2) tells you the percentage that color contributes to the total, and (1 - a2) tells you the percentage the original color contributes. From that, it is a straightforward calculation:

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
}

Intuitively this checks out. If the overlay color is fully opaque (a2 == 1) then the result will be the overlay color. If the overlay color is fully transparent (a2 == 0), the result will be the original color. If the overlay color's alpha is 0.5, you will get a blend that is exactly halfway between the two colors.


Testing it in a 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:

Playground output

like image 139
vacawama Avatar answered Nov 10 '22 11:11

vacawama


Based on @vacawama 's answer (all credit to him!) I wrote the following extension to 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)
    }

}
like image 24
HixField Avatar answered Nov 10 '22 11:11

HixField