Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Computing complementary, triadic, tetradic, and analagous colors

I have created swift functions, where I send color value to and want to return triadic and tetrads values. It sort of works, but I am not happy about the color results. Can anyone help me to fine-tune the formula please?

I was following few sources, but the returned colours were too bright or saturated in comparison to several online web based color schemes. I know it's a matter of preference as well and I kinda like the results of the code below, but in some instances of colors the result of one color returned is way too close to the original one, so it's barely visible. It applies only to a few colors...

I was using the formula from here:

enter image description here

my code:

func getTriadColor(color: UIColor) -> (UIColor, UIColor){

    var hue : CGFloat = 0
    var saturation : CGFloat = 0
    var brightness : CGFloat = 0
    var alpha : CGFloat = 0

    let triadHue = CGFloat(color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha))

    let triadColor1 = UIColor(hue: (triadHue + 0.33) - 1.0, saturation: saturation, brightness: brightness, alpha: alpha)
    let triadColor2 = UIColor(hue: (triadHue + 0.66) - 1.0, saturation: saturation, brightness: brightness, alpha: alpha)


    return (triadColor1, triadColor2)

}

func getTetradColor(color: UIColor) -> (UIColor, UIColor, UIColor){

    var hue : CGFloat = 0
    var saturation : CGFloat = 0
    var brightness : CGFloat = 0
    var alpha : CGFloat = 0

    let tetradHue = CGFloat(color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha))

    let tetradColor1 = UIColor(hue: (tetradHue + 0.25) - 1.0, saturation: saturation, brightness: brightness, alpha: alpha)
    let tetradColor2 = UIColor(hue: (tetradHue + 0.5) - 1.0, saturation: saturation, brightness: brightness, alpha: alpha)
    let tetradColor3 = UIColor(hue: (tetradHue + 0.75) - 1.0, saturation: saturation, brightness: brightness, alpha: alpha)



    return (tetradColor1, tetradColor2, tetradColor3)
}

And I also found nice clean code for finding complementary color, which I am very happy about the results

func getComplementColor(color: UIColor) -> UIColor{

    let ciColor = CIColor(color: color)

    let compRed: CGFloat = 1.0 - ciColor.red
    let compGreen: CGFloat = 1.0 - ciColor.green
    let compBlue: CGFloat = 1.0 - ciColor.blue

    return UIColor(red: compRed, green: compGreen, blue: compBlue, alpha: 1.0)
}
like image 220
Alessign Avatar asked May 05 '16 16:05

Alessign


1 Answers

Your screen shot is of this web page. (Wayback Machine link because, six years later, the page has been deleted.) The formulas on that page are incorrect, because they specify the use of the absolute value function instead of the modulo function. That is, for example, your screen shot defines

H1 = |(H0 + 180°) - 360°|

but consider what this gives for the input H0 = 90°:

H1 = |(90° + 180°) - 360°| = |270° - 360°| = |-90°| = 90°

Do you think that the complementary hue of H0 = 90° is H1 = 90°, the same hue?

The correct formula is

H1 = (H0 + 180°) mod 360°

where “mod” is short for “modulo” and means “the remainder after dividing by”. In other words, if the answer would be above 360°, subtract 360°. For H0 = 90°, this gives the correct answer of H1 = 270°.

But you don't even have this problem in your code, because you didn't use the absolute value function (or the modulo function) in your code. Since you're not doing anything to keep your hue values in the range 0…1, your hue values that are less than zero are clipped to zero, and your hue values above one are clipped to one (and both zero and one mean red).

Your getComplementColor is also not at all the standard definition of the “complementary color”.

Here are the correct definitions:

extension UIColor {

    var complement: UIColor {
        return self.withHueOffset(0.5)
    }

    var splitComplement0: UIColor {
        return self.withHueOffset(150 / 360)
    }

    var splitComplement1: UIColor {
        return self.withHueOffset(210 / 360)
    }

    var triadic0: UIColor {
        return self.withHueOffset(120 / 360)
    }

    var triadic1: UIColor {
        return self.withHueOffset(240 / 360)
    }

    var tetradic0: UIColor {
        return self.withHueOffset(0.25)
    }

    var tetradic1: UIColor {
        return self.complement
    }

    var tetradic2: UIColor {
        return self.withHueOffset(0.75)
    }

    var analagous0: UIColor {
        return self.withHueOffset(-1 / 12)
    }

    var analagous1: UIColor {
        return self.withHueOffset(1 / 12)
    }

    func withHueOffset(offset: CGFloat) -> UIColor {
        var h: CGFloat = 0
        var s: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        self.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
        return UIColor(hue: fmod(h + offset, 1), saturation: s, brightness: b, alpha: a)
    }
}

Here are some examples of complementary colors (original on top, complementary beneath):

complementary

Here are split complementary colors (original on top):

split complementary

Here are triadic colors (original on top):

triadic

Here are tetradic colors (original on top):

tetradic

Here are analagous colors (original in the middle):

analagous

Here is the playground I used to generate those images:

import XCPlayground
import UIKit

let view = UIView(frame: CGRectMake(0, 0, 320, 480))
view.backgroundColor = [#Color(colorLiteralRed: 0.9607843137254902, green: 0.9607843137254902, blue: 0.9607843137254902, alpha: 1)#]

let vStack = UIStackView(frame: view.bounds)
vStack.autoresizingMask = [ .FlexibleWidth, .FlexibleHeight ]
view.addSubview(vStack)
vStack.axis = .Vertical
vStack.distribution = .FillEqually
vStack.alignment = .Fill
vStack.spacing = 10

typealias ColorTransform = (UIColor) -> UIColor

func tile(color color: UIColor) -> UIView {
    let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = color
    return view
}

func strip(transforms: [ColorTransform]) -> UIStackView {
    let strip = UIStackView()
    strip.translatesAutoresizingMaskIntoConstraints = false
    strip.axis = .Vertical
    strip.distribution = .FillEqually
    strip.alignment = .Fill
    strip.spacing = 0

    let hStacks = (0 ..< transforms.count).map { (i: Int) -> UIStackView in
        let stack = UIStackView()
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.axis = .Horizontal
        stack.distribution = .FillEqually
        stack.alignment = .Fill
        stack.spacing = 4
        strip.addArrangedSubview(stack)
        return stack
    }

    for h in 0 ..< 10 {
        let hue = CGFloat(h) / 10
        let color = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1)
        for (i, transform) in transforms.enumerate() {
            hStacks[i].addArrangedSubview(tile(color: transform(color)))
        }
    }

    return strip
}

vStack.addArrangedSubview(strip([
    { $0 },
    { $0.complement }]))

vStack.addArrangedSubview(strip([
    { $0 },
    { $0.splitComplement0 },
    { $0.splitComplement1 }]))

vStack.addArrangedSubview(strip([
    { $0 },
    { $0.triadic0 },
    { $0.triadic1 }]))

vStack.addArrangedSubview(strip([
    { $0 },
    { $0.tetradic0 },
    { $0.tetradic1 },
    { $0.tetradic2 }]))

vStack.addArrangedSubview(strip([
    { $0.analagous0 },
    { $0 },
    { $0.analagous1 }]))

XCPlaygroundPage.currentPage.liveView = view
like image 190
rob mayoff Avatar answered Nov 15 '22 08:11

rob mayoff