How to guarantee that an NSColor has enough saturation? - swift

I need to guarantee that a color has enough saturation.
I made this extension to NSColor but my tests give me weird values: tempColor always seems to have lots of saturation.
So basically my function always returns self?
Where's my mistake?
I've also tried without converting to NSCalibratedRGBColorSpace but that didn't help and the input color can be of different type anyway.
extension NSColor {
func withMinimumSaturation(minimumSaturation: CGFloat) -> NSColor {
// color could be hue/rgb/other so we convert to rgb
if var tempColor = self.colorUsingColorSpaceName(NSCalibratedRGBColorSpace) {
// prepare the values
var hue: CGFloat = 0.0
var saturation: CGFloat = 0.0
var brightness: CGFloat = 0.0
var alpha: CGFloat = 0.0
// populate the values
tempColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
// if color is not enough saturated
if saturation < minimumSaturation {
// return same color with more saturation
return NSColor(calibratedHue: hue, saturation: minimumSaturation, brightness: brightness, alpha: alpha)
}
}
// if detection fails, return same color
return self
}
}
Usage:
let source = myColorFromAListOfColors // NSColor
let guaranteed = source.withMinimumSaturation(0.15)
EDIT: actually it works. I've accepted Aaron's answer because it helped me understand the situation (I was comparing the colors with their redComponents and they were often similar).

Your method seems fine.
For colors with very low saturation, it returns a different color:
If I increase the minimum saturation from 0.15 to something more significant, like 0.45, the change is more significant:
So, this method works as I expect it would. I'm guessing you just need to tweak that 0.15 input to get the results you want.
It may help you to log the old and new saturations for debugging:
if saturation < minimumSaturation {
// return same color with more saturation
println("New saturation: \(minimumSaturation)")
return NSColor(calibratedHue: hue, saturation: minimumSaturation, brightness: brightness, alpha: alpha)
} else {
println("Discarding saturation: \(saturation)")
}

Related

How to convert a UIColor to a black and white UIColor

I am setting the background color of my label, but I would like to have the color be the black and white UIColor instead of the original UIColor.
self.MyLabel.backgroundColor = self.selectedColors.color
Looks like you'll need to convert your colour to grayscale.
While you can do this by averaging the R, G and B components of the colour, apple actually provide a nice method to grab the grayscale value:
func getWhite(_ white: UnsafeMutablePointer<CGFloat>?,
alpha: UnsafeMutablePointer<CGFloat>?) -> Bool
So to use this, you would first extract the grayscale colour and then init a new UIColor:
let originalColor = self.selectedColors.color
var white: CGFloat = 0
var alpha: CGFloat = 0
guard originalColor.getWhite(&white, alpha: &alpha) else {
// The color couldn't be converted! Handle this unexpected error
return
}
let newColor = UIColor(white: white, alpha: alpha)
self.MyLabel.backgroundColor = newColor
By thanks of #Sam answer, I write an extension for UIColor:
extension UIColor {
var grayScale: UIColor? {
var white: CGFloat = 0
var alpha: CGFloat = 0
guard self.getWhite(&white, alpha: &alpha) else {
return nil
}
return UIColor(white: white, alpha: alpha)
}
}
You can use it like this:
var grayScaleColorOfRed = UIColor.red.grayScale ?? UIColor.grey

Find hue, saturation, brightness and alpha of a UIColor

I have a red object:
red_object = UIColor.red.cgColor
I would like to get the hue saturation brightness and alpha parameters of this 'red' so I can recode my red &object using more specific parameters.
This exercise would be a once off but I need these parameters.
I have tried cgColor.components but I don't think this is the right function:
red_test = UIColor.red.cgColor
let output = red_test.components
print(output) = Optional([1.0, 0.0, 0.0, 1.0])
but if I run the following
let circlePathT = UIBezierPath(arcCenter: CGPoint(x: x_mid,y: y_mid), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let red_object = CAShapeLayer()
red_object.fillColor = UIColor(hue: 1, saturation: 0, brightness: 0, alpha: 1).cgColor
I get a black circle, not red.
More specifically I am expecting
red_object.fillColor = UIColor(hue: 1, saturation: 0, brightness: 0, alpha: 1).cgColor
to equal
red_test = UIColor.red.cgColor
but it isn't
Why you're getting the "wrong" component values
You'll find that the components of a CGColor is the values of the components in the color's color space. Since there is no hue-saturation-brighness-alpha color space model, these won't be the color's hue saturation, or brightness.
Instead, you're likely to find either red-green-blue-alpha values for colors in an RGB color space model (like UIColor.red) or white-alpha values for colors in a monochrome color space model (like UIColor.gray).
UIColor.red.cgColor.components // [1.0, 0.0, 0.0, 1.0]
UIColor.red.cgColor.colorSpace?.name. // kCGColorSpaceExtendedSRGB
UIColor.gray.cgColor.components // [0.5, 1.0]
UIColor.gray.cgColor.colorSpace?.name // kCGColorSpaceExtendedGray
Looking at the component values for red (1, 0, 0, 1) it makes sense that these wouldn't be hue, saturation, brightness, and alpha — since any color with a 0 brightness would be completely black, and any color with 0 saturation would be fully desaturated.
Instead, we would expect both the brightness and saturation of red to be 100%, like how they appear in the color picker:
How to get the hue, saturation, brightness, and alpha of a color
If you want to get the hue, saturation, brightness, and alpha of a color — no matter the color space it is in — you'd use getHue(_:saturation:brightness:alpha:) and pass four mutable pointers to CGFloat values:
var hue : CGFloat = 0
var saturation : CGFloat = 0
var brightness : CGFloat = 0
var alpha : CGFloat = 0
let couldBeConverted = UIColor.red.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
if couldBeConverted {
// The color is in a compatible color space, and the variables
// `hue`, `saturation`, `brightness`, and `alpha` have been
// changed to contain these values.
}
Note that this method is on UIColor. If you have a CGColor reference then you'll have to create a UIColor to read its HSBA components:
UIColor(cgColor: yourCGColor)

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:
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)
}
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):
Here are split complementary colors (original on top):
Here are triadic colors (original on top):
Here are tetradic colors (original on top):
Here are analagous colors (original in the middle):
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

Set random color for a cgfill in external function

I have a rectangle and I want to fill it with a random color.
The function for returning the random color should be seperate.
This is my idea but it does not work out, I think because of the type? Do I need to do this with pointers?
func getRandomColor() -> CGColorRef {
let color = CGColorCreate(CGColorSpaceCreateDeviceRGB(), [1.0, 0.5, 0.5, 0.2])
return color
}
func drawRect {
// ...
CGContextSetFillColorwithcolor(context,getRandomColor())
// ...
}
func randomColorComponent() -> CGFloat
{
return CGFloat(Float(arc4random()) / Float(UINT32_MAX))
}
func randomColorRef() -> CGColorRef
{
let red = randomColorComponent()
let green = randomColorComponent()
let blue = randomColorComponent()
return UIColor(red: red, green: green, blue: blue, alpha: 1).CGColor
}
and then somewhere in your drawRect use:
CGContextSetFillColorWithColor(context, randomColorRef())

Getting hue from UIColor yields wrong result

I'm doing the following in order to retrieve the hue from a UIColor():
let rgbColour = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
rgbColour.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
println("\(hue),\(saturation),\(brightness)")
Output:
1.0,1.0,1.0
According to this link, I'm meant to be getting 0.0,1.0,1.0 for RGB (red) 1.0,0.0,0.0.
Am I doing something wrong?
First of all the range of the red/green/blue components in UIColor is 0.0 .. 1.0,
not 0.0 .. 255.0, so you probably want
let rgbColour = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
But even then you get the output 1.0,1.0,1.0 and this is correct.
The hue component ranges from 0.0 to 1.0, which corresponds to the angle from 0º to 360º
in a color wheel (see for example HSL and HSV).
Therefore hue = 0.0 and hue = 1.0 describe an identical color.
If you need to normalize the hue component to the half-open interval
0.0 <= hue < 1.0 then you could do that with
hue = fmod(hue, 1.0)
To build on #Martin R's answer:
If you wanted to use HSB, you need to:
Divide the hue value by 360
Use decimals for the Saturation and Brightness values
So, say for example that Sketch is telling you the colour values in HSB are: Hue: 20, Saturation: 72 and Brightness: 96
In Xcode, create the colour as follows:
let myAwesomeColour = UIColor(hue: 20/360, saturation: 0.72, brightness: 0.96, alpha: 1.0)
Whether you use RGB or HSB is a matter of preference. The results are the same as far as Xcode is concerned, they both translate to a UIColor.