0

I am building an app that allows users to select two colors and see the result of mixing them. For example, the user selects red (#ff0000) and blue (#0000ff) and the result is purple (#ff00ff).

I started trying by writing 3 methods in a UIColor extension:

  • hexColor converts an Int to a color
  • intValue returns the color's integer representation. i.e. the reverse of hexColor
  • hexDescription returns the string representation of the color, such as "#ff00ff"

Here are the implementations, just in case this is needed:

public static func hexColor(hex: Int32) -> UIColor {
    return UIColor.init(red: CGFloat((hex>>16)&0xFF) / 255.0, green: CGFloat((hex>>8)&0xFF) / 255.0, blue: CGFloat(hex&0xFF) / 255.0, alpha: 1.0)
}

public func intValue() -> Int {
    var hexString = self.hexDescription()
    hexString = hexString.substringFromIndex(hexString.startIndex.successor())
    return Int(hexString, radix: 16)!
}

public func hexDescription() -> String {
    var rF: CGFloat = 0,
    gF: CGFloat = 0,
    bF: CGFloat = 0,
    aF: CGFloat = 0
    self.getRed(&rF, green: &gF, blue: &bF, alpha: &aF)
    let r = Int(rF * 255.0)
    let g = Int(gF * 255.0)
    let b = Int(bF * 255.0)

    return "#" + String(format: "%02x%02x%02x", r, g, b)
}

Then I thought about how can I actually mix the colors. My first try is to get the average of the HSV values:

public func mixWith(color: UIColor) -> UIColor {
    var myHue: CGFloat = 0
    var mySat: CGFloat = 0
    var myVal: CGFloat = 0
    var otherHue: CGFloat = 0
    var otherSat: CGFloat = 0
    var otherVal: CGFloat = 0
    self.getHue(&myHue, saturation: &mySat, brightness: &myVal, alpha: nil)
    color.getHue(&otherHue, saturation: &otherSat, brightness: &otherVal, alpha: nil)

    let averageHue = (myHue + otherHue) / 2.0
    let averageSat = (mySat + otherSat) / 2.0
    let averageVal = (myVal + otherVal) / 2.0

    return UIColor(hue: averageHue, saturation: averageSat, brightness: averageVal, alpha: 1.0)
}

But this failed. When I mix blue and yellow, I get #00ff7f but it should be white.

Then I try to get the average of the int values:

public func mixWith2(color: UIColor) -> UIColor {
    let average = (self.intValue() + color.intValue()) / 2
    return UIColor.hexColor(Int32(average))
}

But again, blue mix with yellow is not white using the above method.

At the end, I decided to use bitwise operators. I tested |, & and ^. Surprisingly, this returns white!

UIColor.hexColor(Int32(UIColor.blueColor().intValue() | 
    UIColor.yellowColor().intValue()))

and so does this:

UIColor.hexColor(Int32(UIColor.blueColor().intValue() ^ 
    UIColor.yellowColor().intValue()))

I did some other tests and this method passed all of them!

Is this a correct (gives correct result all the time) method of mixing two colors? If yes, how does it work?

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Please look at http://stackoverflow.com/questions/13132743/can-i-mix-two-uicolor-together – Parth Dabhi Apr 01 '16 at 03:50
  • I am not asking *how*. I m asking about whether | can be used to mix colors. @ParthDabhi – Sweeper Apr 01 '16 at 03:55
  • 3
    No you cannot use | to combine two colors – Parth Dabhi Apr 01 '16 at 04:01
  • @ParthDabhi But why `UIColor.hexColor(Int32(UIColor.blueColor().intValue() | UIColor.yellowColor().intValue()))` returns white color? It is the correct result! I also tried mixing blue and red and it gives purple! Am I just being lucky? And I also corrected some stuff in the post. – Sweeper Apr 01 '16 at 05:51
  • You're "getting lucky" because you're only mixing pure colors. – David Berry Apr 01 '16 at 06:12
  • @DavidBerry "pure colors"? I don't need to mix colors with alpha. All my colors have an alpha value of 1.0 – Sweeper Apr 01 '16 at 06:13
  • By pure, I mean colors that are either 0.0 or 255.0 in each component, with no overlap between components. Eg., mixing red and orange should leave you with a redder orange, using your method you'll just wind up with the same orange. – David Berry Apr 01 '16 at 06:15
  • @DavidBerry Oh, you're right! Thanks for letting me know! – Sweeper Apr 01 '16 at 06:20
  • 1
    There are many possible algorithms to "mix colors", depending on what process you try to simulate. What you *perhaps* want is the sum or maximum of each individual R/G/B component. Note that your `mixWith2()` method operates on a single integer instead of the components, that cannot be right. – Martin R Apr 01 '16 at 06:25

1 Answers1

1

The short answer is "no you can't" as in the comments, perhaps the easiest way of blending two colors is using a weighted average:

extension UIColor {
    func blend(rhs:UIColor, midpoint left:CGFloat = 0.50) -> NSColor {
        let right = 1.0 - left

        var lr : CGFloat = 0
        var lg : CGFloat = 0
        var lb : CGFloat = 0
        var la : CGFloat = 0
        getRed(&lr, green: &lg, blue: &lb, alpha: &la)

        var rr : CGFloat = 0
        var rg : CGFloat = 0
        var rb : CGFloat = 0
        var ra : CGFloat = 0
        rhs.getRed(&rr, green: &rg, blue: &rb, alpha: &ra)

        return UIColor(
            red: lr * left + rr * right,
            green: lg * left + rg * right,
            blue: lb * left + rb * right,
            alpha: la * left + ra * right
        )
    }
}
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Thank you! I saw this solution in one of the answers linked in the comments. Thank you for translating it to swift. – Sweeper Apr 01 '16 at 07:14
  • `NSColor` or `UIColor` ? – Note that blending "blue" and "yellow" with this method gives 50% gray and not white as desired in the question. – Martin R Apr 01 '16 at 07:22
  • Yeah, this halves (actually reduces each by the midpoint value) the saturation, I'm putting together a method that I *think* works correctly using HSV which will maintain the saturation. (And combining blue and yellow should give green, not white :) – David Berry Apr 01 '16 at 07:27
  • That depends on what color model you use, see for example http://stackoverflow.com/a/4256156/1187415 or the discussion in http://stackoverflow.com/questions/14819058/mixing-two-colors-naturally-in-javascript. – Martin R Apr 01 '16 at 07:30
  • trudat, and it's 0230, and I'm tired, so this is as far as I get with it tonight :) – David Berry Apr 01 '16 at 07:36
  • HSV blending is promising with the biggest issue being trying to get the Hue blending correct because what you want is the mid point of the shortest distance around the circle (for subtractive) I think, at least intuitively that seems like the right idea, anyway, as noted, late :) – David Berry Apr 01 '16 at 07:38
  • @DavidBerry If you did think of a method that makes blue + yellow = white, please tell me! Thank you! – Sweeper Apr 01 '16 at 08:32
  • Instead of mixing the channels, just saturation add them, i.e., cap each channel at 1.0. – David Berry Apr 01 '16 at 08:41