Find hue, saturation, brightness and alpha of a UIColor - swift

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)

Related

How to convert HSV(HSB) to range 0...1

I get HSB as 29, 90, 100.
How to convert it to the range 0...1?
UIColor is initialized only in this range, so I have a question.
let red: CGFloat = 1
let green: CGFloat = 0.5372
let blue: CGFloat = 0.0941
let hueOut = 29
let satOut = 90
let brightnessOut = 100
let color = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
///r 1,0 g 0,537 b 0,094 a 1,0
let color2 = UIColor(hue: hueOut, saturation: satOut, brightness: brightnessOut, alpha: 1.0)
///r -9,0 g 0,0 b -3,0 a 1,0
Looks like your hue range is 0...360 and your saturation and brightness are 0...100. You just need to convert your integer to double and divide by 360 or 100:
let color2 = UIColor(hue: Double(hueOut)/360, saturation: Double(satOut)/100, brightness: Double(brightnessOut)/100, alpha: 1.0)
This will result in r 1.0 g 0.535 b 0.1 a 1.0

How to use alpha component in gradient colors with GKNoise and SKTexture?

I'm using GKNoise with a gradient map to generate color noise, getting a CGImage via SKTexture, on Mac OS. In particular I'm using a GKPerlinNoiseSource and setting two gradient colors, at values -1.0 and 1.0.
It works as expected if the two colors are opaque. If one or both colors has an alpha component less than 1.0, I expect the output to have transparency. However, it looks to me like the alpha is completely ignored in GKNoise's gradient color input (treated as if it's always 1.0) - or, if not ignored there, ignored in the SKTexture rendering of the image output.
I have found a couple of SO questions which reference limitations with SKTexture in other uses, including a possible workaround with SKView rendering, however that doesn't apply here because I'm only using an SKTexture instance to get a CGImage (not using SKView or otherwise using anything from SpriteKit) - and FWIW those questions focus on iOS. For reference:
Export SKTexture to a UIImage with alpha channel
SKSpriteNode created from SKTexture(data:size:) issues with Alpha (Opacity)
I'm looking for ideas on how to make alpha components in the gradient colors work using GKNoise/SKTexture.
Below is the test output image, and the code that reproduces it. In the view, both CGImages draw identically; I expect the one drawn with the red alpha=0.5 to be darker in the red parts when the background is black, lighter when it's white, etc.
import Foundation
import GameplayKit
class GKNoiseGradientIssue {
var redColor_opaque = NSColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
var redColor_halfAlpha = NSColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
var blueColor_opaque = NSColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
var opaqueImage: CGImage
var halfAlphaImage: CGImage
init() {
let noiseSource = GKPerlinNoiseSource(frequency: 0.15,
octaveCount: 7,
persistence: 1.25,
lacunarity: 0.5,
seed: 12345)
let opaqueGradient: [NSNumber: NSColor] = [-1.0: redColor_opaque, 1.0: blueColor_opaque]
let opaqueNoise = GKNoise(noiseSource, gradientColors: opaqueGradient)
let opaqueNoiseMap = GKNoiseMap(opaqueNoise,
size: [200.0, 200.0],
origin: [0.0, 0.0],
sampleCount: [200, 200],
seamless: true)
let opaqueTexture = SKTexture(noiseMap: opaqueNoiseMap)
self.opaqueImage = opaqueTexture.cgImage()
let halfAlphaGradient: [NSNumber: NSColor] = [-1.0: redColor_halfAlpha, 1.0: blueColor_opaque]
let halfAlphaNoise = GKNoise(noiseSource, gradientColors: halfAlphaGradient)
let halfAlphaNoiseMap = GKNoiseMap(halfAlphaNoise,
size: [200.0, 200.0],
origin: [0.0, 0.0],
sampleCount: [200, 200],
seamless: true)
let halfAlphaTexture = SKTexture(noiseMap: halfAlphaNoiseMap)
self.halfAlphaImage = halfAlphaTexture.cgImage()
}
}
class GradientIssueView: NSView {
var issue: GKNoiseGradientIssue?
override func awakeFromNib() {
self.issue = GKNoiseGradientIssue()
}
override func draw(_ dirtyRect: NSRect) {
NSColor.black.setFill()
self.bounds.fill()
if let cgc = NSGraphicsContext.current?.cgContext {
cgc.draw(self.issue!.opaqueImage,
in: CGRect(origin: CGPoint(x: 10.0, y: 10.0),
size: CGSize(width: 200.0, height: 200.0)))
cgc.draw(self.issue!.halfAlphaImage,
in: CGRect(origin: CGPoint(x: 10.0, y: 220.0),
size: CGSize(width: 200.0, height: 200.0)))
}
}
}
Just following up here, for anyone else who might encounter this... While I never got a definitive reply from Apple Developer forums I did come to the conclusion that SKTexture + GKNoise only supports opaque colors, so the question as asked has no answer: one can't do this with SKTexture. Instead, I updated my approach to just use GKNoiseMap directly and calculate my own gradients.
An advantage of this approach is that I can also now create gradients from a noise source using more than two colors. I also have more options for creatively skewing or clamping the noise values before they're converted to colors.
To use GKNoiseMap directly instead of with the two-color gradients and SKTexture approach, create the noise with no reference to gradient colors, and create the GKNoiseMap the same way:
let myNoise = GKNoise(noiseSource)
let noiseMap = GKNoiseMap(myNoise,
size: [myModelWidth, myModelHeight],
origin: [myX, myY],
sampleCount: [myPointWidth, myPointHeight],
seamless: true)
Then when rendering, just call the noiseMap value() method to get the noise value for each point in your view or image output, and then calculate your own color gradient for that point including the alpha (if desired). Note that voronoi noise will be between 0.0 and 1.0 while other perlin-based noise will be between -1.0 and 1.0; to simplify here I'll assume one just wants a gradient between two colors at 0.0 and 1.0.
Given two colors in .deviceRGB color space (rgbColor1 and rgbColor2), and the noise value unitNoiseValue as a CGFloat,
let r_range = rgbColor2.redComponent - rgbColor1.redComponent
let g_range = rgbColor2.greenComponent - rgbColor1.greenComponent
let b_range = rgbColor2.blueComponent - rgbColor1.blueComponent
let a_range = rgbColor2.alphaComponent - rgbColor1.alphaComponent
let r = noiseUnitValue * r_range + rgbColor1.redComponent
let g = noiseUnitValue * g_range + rgbColor1.greenComponent
let b = noiseUnitValue * b_range + rgbColor1.blueComponent
let a = noiseUnitValue * a_range + rgbColor2.alphaComponent
return NSColor(red: r, green: g, blue: b, alpha: a)
In order to make this performant it may be helpful to limit the discrete levels of each gradient (e.g. 256 levels), and to cache calculated colors for large images and store in a hash that can be looked up quickly, and similar techniques. Also, because the conversion here just maps a unit value, you can have other skewing of your noise value if desired before calculation of the gradient color, such as additional fade or normalization or other 'shaping' of the input value.

How do you fade the bottom of an image into the background? [duplicate]

I've read examples around here but I cannot do it the way I desire, somehow my gradient examples are stuck on the middle of the screen, not working as expected.
I have a UICollectionView which fills the whole screen with vertical scroll.
I want the top and bottom of the UICollectionView to be black and the middle to be transparent (since I'm using a black blackgroundColor).
I tried to apply gradients, but somehow I'm failing to accomplish what I want.
Here's my code:
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [UIColor.black.cgColor, UIColor.clear.cgColor, UIColor.black.cgColor]
gradient.startPoint = CGPoint(x: 1, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
view.layer.mask = gradient
The code above is placing the gradient in the middle of the screen but inverted. It is transparent on top and bottom of the screen and the middle section if fading to complete black.
I'm trying to create something like this:
Thanks for helping
You can achieve that by changing your color line with this:
gradient.colors = [
UIColor(white: 1.0, alpha: 0).cgColor,
UIColor(white: 1.0, alpha: 1).cgColor,
UIColor(white: 1.0, alpha: 0).cgColor
]
Or if you want to have even more control over your gradient, you can also use the code below and play around with the location and/or color alpha values:
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [
UIColor(white: 1, alpha: 0).cgColor,
UIColor(white: 1, alpha: 1).cgColor,
UIColor(white: 1, alpha: 1).cgColor,
UIColor(white: 1, alpha: 0).cgColor
]
gradient.locations = [0, 0.4, 0.6, 1]
view.layer.mask = gradient
Reason of this from the documentation;
An optional layer whose alpha channel is used to mask the layer’s content.
The layer’s alpha channel determines how much of the layer’s content and background shows through. Fully or partially opaque pixels allow the underlying content to show through but fully transparent pixels block that content.

How to guarantee that an NSColor has enough saturation?

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)")
}

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.