Converting from UIExtendedSRGBColorSpace to Hex - ios

I'm using a framework that outputs colour in the UIExtendedSRGBColorSpace format. I need to display a six character hex value for this output colour to the user and for a backend application that only accepts six-character hex codes. I've tried numerous conversion methods but they all seem to result in odd hex values. For example, getting an output UIExtendedSRGBColorSpace 0.276088 -0.0525 1.05 1 and using an extension like this (which i've used successfully many times before) results in 46FFFFFFFFFFFFFFF310C. There are no issues with simply applying the UIExtendedSRGBColorSpace output as a background colour on a button/view, but its getting a hex value of the colour that is the problem.
It would be fine if any solution to this leads to some form of loss in colour accuracy, as long as i'm able to get a vaguely similar hex code. I'm very new to programming and have spent quite some time trying to understand this with no luck, so appreciate any help!

You can convert your UIColor to CGColor and convert from eRGB to sRGB. Note that once you convert it to standard RGB you can't convert back to extended RGB:
extension CGColorSpace {
static let sRGB = CGColorSpace(name: CGColorSpace.sRGB)!
static let extendedSRGB = CGColorSpace(name: CGColorSpace.extendedSRGB)!
}
extension CGColor {
var color: UIColor { .init(cgColor: self) }
}
extension UIColor {
var extendedSRGB2sRGB: UIColor? {
guard let components = cgColor.components else { return nil }
return CGColor(colorSpace: .extendedSRGB, components: components)?
.converted(to: .sRGB, intent: .relativeColorimetric, options: nil)?.color
}
}
Playground testing:
let extendedSRGB = UIColor(red: 0.276088, green: -0.0525, blue: 1.05, alpha: 1)
let sRGB = extendedSRGB.extendedSRGB2sRGB // r 0.276 g 0.0 b 1.0 a 1.0

Related

Init Generic RGB in Swift

I am attempting to convert a string containing a color in the Generic RGB color space into UIColor in Swift. For example, a typical string would look like this:
0.121569 0.129412 0.156863 1
Using the color picker in macOS, I discovered that these values are using the Generic RGB color space.
However, when I attempt to convert these values into UIColor, it uses the sRGB color space.
let red = CGFloat((components[0] as NSString).doubleValue)
let green = CGFloat((components[1] as NSString).doubleValue)
let blue = CGFloat((components[2] as NSString).doubleValue)
let alpha = CGFloat((components[3] as NSString).doubleValue)
print(UIColor(red: red, green: green, blue: blue, alpha: alpha))
// Log Result: NSCustomColorSpace sRGB IEC61966-2.1 colorspace 0.121569 0.129412 0.156863 1
Hence, a different color is displayed in my application. I confirmed this by changing the color space in Color Picker to IEC61966-2.1 and it indeed displayed different values:
Any idea how I would convert the Generic RGB values into the correct UIColor values?
EDIT For clarification, I am unable to change the color values in the string into another scheme as I am reading the colors from an external source in an XML file
Color conversion by way of color space is performed at the level of CGColor. Example:
let sp = CGColorSpace(name:CGColorSpace.genericRGBLinear)!
let comps : [CGFloat] = [0.121569, 0.129412, 0.156863, 1]
let c = CGColor(colorSpace: sp, components: comps)!
let sp2 = CGColorSpace(name:CGColorSpace.sRGB)!
let c2 = c.converted(to: sp2, intent: .relativeColorimetric, options: nil)!
let color = UIColor(cgColor: c2)
EDIT I think the premise of your original problem is erroneous. You are trying, it turns out, to use the numbers in an Xcode FontAndColorThemes file. Those numbers are sRGB, not generic RGB.
To prove it, I ran this code:
let sp = CGColorSpace(name:CGColorSpace.sRGB)!
let comps : [CGFloat] = [0.0, 0.456, 0.0, 1]
let c = CGColor(colorSpace: sp, components: comps)!
let v1 = UIView(frame:CGRect(x: 50, y: 50, width: 50, height: 50))
v1.backgroundColor = UIColor(cgColor:c)
self.view.addSubview(v1)
That color is taken from the Default color theme's Comment color. Well, the result is identical to the Comment color, as this screen shot demonstrates:
I get the same answer when I use the "eyedropper" tool as when I simply open the color swatch to read the inspector. And I get the same answer when I use the "eyedropper" tool on Xcode's swatch and on my iOS swatch. This seems to me to prove that these colors were always sRGB.

How do I compare 2 UIColor objects which are kCGColorSpaceModelRGB and UIExtendedSRGBColorSpace instances in logs?

Now I'm really confused.
Heres how the variable gets instantiated :
Utils.redColor = UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue)/255.0, alpha: alpha)
And here I enumerate a Attribute text's attributes to skip the color if it's equals to Utils.redColor :
text.enumerateAttributes(in: NSRange(0..<text.length), options: []) { (attributes, range, _) -> Void in
for (attribute, object) in attributes {
if let textColor = object as? UIColor{
NSLog("textColor = \(textColor) red = \(Utils.redColor!)")
if (!textColor.isEqual(Utils.redColor!)){
//I need to repaint any textColor other than red
text.setAttributes(textAttributes , range: range)
}
}
So, as you see in this code textColor is a UIColor object as well, but the log says:
textColor = kCGColorSpaceModelRGB 0.666667 0.172549 0.172549 1 red = UIExtendedSRGBColorSpace 0.666667 0.172549 0.172549 1
Which are two exact colors , but being instances of two different classes. This is totally confusing for both of them are UIColor class's objects!
This comparison never triggers although it worked well in Swift2
How do I fix it and why this problem ever occurs??
Welcome to the wild and wooly world of wide color and color management.
Your two colors aren't equal, per isEqual (or Swift ==, which runs through isEqual for ObjC classes that have it), because they have different color spaces. (They aren't different classes; the first item in UIColor.description is an identifier for the color space, or where the color space doesn't have a name, the model for the color space — that is, whether it's RGB-based, CMYK-based, grayscale, etc.)
Without a color space to define them as a color, the four component values of a color have no reliable meaning, so isEqual uses both the component values and the color space to test for equality.
Aside on color spaces (skip down for solutions)
Your color created with UIColor init(red:green:blue:alpha:) uses the "Extended sRGB" color space. This color space is designed to support wide color displays (like the P3 color display in iPhone 7, iPad Pro 9.7", iMac late-2015, MacBook Pro late-2016, and probably whatever else comes next), but be component-value compatible with the sRGB color space used on other devices.
For example, sRGB 1.0, 0.0, 0.0 is the "red" you're probably most used to... but if you create a color in the P3 color space with RGB values 1.0, 0.0, 0.0 you get much much redder. If you have an app where you need to support both sRGB and P3 displays and work directly with color components, this can get confusing. So the Extended sRGB space lets the same component values mean the same thing, but also allows colors outside the sRGB gamut to be specified using values outside the 0.0-1.0 range. For example, the reddest that Display P3 can get is expressed in Extended sRGB as (roughly) 1.093, -0.227, -0.15.
As [the docs for that initializer note, for apps linked against the iOS 10 or later SDK, init(red:green:blue:alpha:) creates a color in the Extended sRGB color space, but for older apps (even if they're running on iOS 10) it creates a color in a device-specific RGB space (which you can generally treat as equivalent to sRGB).
Dealing with different color spaces
So, either your color-replacing code or whatever code is creating the colors in your attributed string need to be aware of color spaces. There are a few possible ways to deal with this; pick the one that works best for you:
Make sure both your string-creation code and your color-replacement code are using the same device-independent color space. UIColor doesn't provide a lot of utilities for working with color spaces, so you can either use Display P3 (on iOS 10 and up), or drop down to CGColor:
let sRGB = CGColorSpace(name: CGColorSpace.sRGB)!
let cgDarkRed = CGColor(colorSpace: sRGB, components: [0.666667, 0.172549, 0.172549, 1])!
let darkRed = UIColor(cgColor: cgDarkRed)
// example creating attributed string...
let attrString = NSAttributedString(string: "red", attributes: [NSForegroundColorAttributeName : darkRed])
// example processing text...
let redAttributes = [NSForegroundColorAttributeName: darkRed]
text.enumerateAttributes(in: NSRange(0..<attrString.length)) { (attributes, range, stop) in
for (_, textColor) in attributes where (textColor as? UIColor) != darkRed {
text.setAttributes(redAttributes , range: range)
}
}
If you can't control the input colors, convert them to the same color space before comparing. Here's a UIColor extension to do that:
extension UIColor {
func isEqualWithConversion(_ color: UIColor) -> Bool {
guard let space = self.cgColor.colorSpace
else { return false }
guard let converted = color.cgColor.converted(to: space, intent: .absoluteColorimetric, options: nil)
else { return false }
return self.cgColor == converted
}
}
(Then you can just use this function in place of == or isEqual in your text processing.)
Just get at the raw component values of the colors and compare them directly, based on the assumption that you know the color spaces for both are compatible. Sort of fragile, so I recommend against this option.

What is the best way to introduce a custom UIColor to a Swift project? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I've thought of two types of implementation; which one of them is better in your opinion in terms of performance, readability and maintainability?
Create an extension of UIColor like this
extension UIColor {
class func myColor() -> UIColor {
return UIColor(red: 128/255, green: 102/255, blue: 0, alpha: 1)
}
}
Create a structure:
struct Colors {
static let myColor = UIColor(red: 255/255, green: 102/255, blue: 0, alpha: 1)
}
Answer: Extension, in my professional opinion.
Think about it; you are, in philosophy, 'extending' the range of colors offered by UIColor. Provided that your color name is distinct and the new function follows Apple's method naming protocol (i.e. <color name>Color), extending UIColor seems neater. One or two new colors (in my opinion) don't warrant an entire dedicated struct.
Bonus answer:
Where would a struct (or enum!) be a good fit?
If your app forces replacements for the standard colors (i.e. custom
'primary' colors)
If your app is specifically designed to be themed / customized, it might be good have an enum, to serve as a concrete list for available options.
If you can't think of standard names for the colors (sharkBlueColor, anyone?).
If your app is specifically for drawing/painting (in which case a 'palette' construct might be good idea).
... the list goes on. You must learn to discern and decide for yourself as you mature as a Swift developer!
I use enums for this
enum AppColor: UInt32 {
case DarkBlue = 0x00437C
case LightBlue = 0x7CCEF0
var color: UIColor {
return UIColor(hex: rawValue)
}
}
This way it is easy to reuse same color in xib/storyboard because I have hex values ready for copy/paste. And also less code needed for defining new color
For creating colors from hex value I used UIColor extension:
extension UIColor {
public convenience init(hex: UInt32) {
let mask = 0x000000FF
let r = Int(hex >> 16) & mask
let g = Int(hex >> 8) & mask
let b = Int(hex) & mask
let red = CGFloat(r) / 255
let green = CGFloat(g) / 255
let blue = CGFloat(b) / 255
self.init(red:red, green:green, blue:blue, alpha:1)
}
}

Calculate opaque color from alpha + background?

I want to "lighten" a certain color by applying an alpha of 0.3 to it. This is shown on a white background.
I would like to determine the opaque color which corresponds to this semi transparent color shown on a white background. The reason is that I use this as view controllers's background color, and when this background is semitransparent the transitions look bad.
I have not tried anything because have no idea about a reasonable way to approach this except maybe taking a snapshot and get the color of it but this seems a bit of an overkill. Can't find any infos, search results are cluttered with "how to make a background semitransparent" etc
Edit: Putting together imataptool's answer parts (and porting to Swift), this is what I came up with:
extension UIColor {
static func opaqueColorByDisplayingTransparentColorOnBackground(transparentColor: UIColor, backgroundColor: UIColor) -> UIColor {
let bgView = UIView(frame: CGRectMake(0, 0, 1, 1))
bgView.backgroundColor = backgroundColor
let overlayView = UIView(frame: CGRectMake(0, 0, 1, 1))
overlayView.backgroundColor = transparentColor
bgView.addSubview(overlayView)
let image = UIView.imageWithView(bgView)
let provider = CGImageGetDataProvider(image.CGImage)
let pixelData = CGDataProviderCopyData(provider)
let data = CFDataGetBytePtr(pixelData)
return UIColor(
red: CGFloat(data[0]) / 255.0,
green: CGFloat(data[1]) / 255.0,
blue: CGFloat(data[2]) / 255.0,
alpha: 1
)
}
}
extension UIView {
// src http://stackoverflow.com/a/32042439/930450
class func imageWithView(view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
}
Edit 1: This isn't to say this is the best way to achieve your specific goal, but it does solve the problem of getting the RGB of a mix of two colors with a particular opacity. You haven't provided enough information about what exactly you are doing for me to give a more specific answer. However, if this solution does what you need with acceptable performance, excellent, run with it.
Edit 2: Refer to this Q&A for new (ish) methods for rendering UIView as bitmaps as of iOS 7 (so if you are supporting iOS 6 you can ignore this, but that's unlikely). The gist of the article is that you can now use the UIView method -drawViewHierarchyInRect:(CGRect)afterScreenUpdates:(BOOL)
I can't say whether or not there exists a mathematical way to calculate the exact answer, but one way of going about it would be:
Add the two views to a parent view (with the opaque view below the transparent view)
Convert the parent view (and all of its subviews) to a UIImage
Sample the UIImage for the color it is composed of
Doing 1 is trivial. You can do 2 with
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage* img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
Now img contains an image with the color you want. Now you just need to figure out what color it is composed of. I believe you can do that with the code provided in this answer. I'll copy it here for convenience.
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8* data = CFDataGetBytePtr(pixelData);
int pixelInfo = ((image.size.width * y) + x ) * 4; // The image is png
UInt8 red = data[pixelInfo]; // If you need this info, enable it
UInt8 green = data[(pixelInfo + 1)]; // If you need this info, enable it
UInt8 blue = data[pixelInfo + 2]; // If you need this info, enable itgame
CFRelease(pixelData);
red, green, and blue now contain the RGB values of the color at whatever point you pick in your view. You might as well just go to the middle of it. Also, to improve the performance of this process, you might choose to only image a small subsection of the view in question (maybe just one pixel).
If somebody will be looking for more straightforward solution, this post about combining colors applying specific alpha might be helpful.
TLDR
extension UIColor {
func combining(with color: UIColor, fraction f: CGFloat) -> UIColor {
let source = components()
let target = color.components()
return UIColor(
red: interpolate(from: source.r, to: target.r, fraction: f),
green: interpolate(from: source.g, to: target.g, fraction: f),
blue: interpolate(from: source.b, to: target.b, fraction: f),
alpha: 1
)
}
private typealias Components = (
r: CGFloat,
g: CGFloat,
b: CGFloat,
a: CGFloat
)
private func components() -> Components {
var result: Components = (0, 0, 0, 0)
getRed(&result.r, green: &result.g, blue: &result.b, alpha: &result.a)
return result
}
}
func interpolate(from a: CGFloat, to b: CGFloat, fraction: CGFloat) -> CGFloat {
(1 - fraction) * a + fraction * b
}

With swift, is it possible to access the invert colors function that’s in accessibility?

The function that apple has already put in the phone that is in general>accessibility>invert colors, can I somehow use that in my program so for say when the user touches the screen the colors invert?
I don't know of a way to do this automatically, but you could invert colors yourself using an extension on UIColor and accessing the subviews?
extension UIColor {
var inverted: UIColor {
var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0
self.getRed(&r, green: &g, blue: &b, alpha: &a)
return UIColor(red: (1 - r), green: (1 - g), blue: (1 - b), alpha: a) // Assuming you want the same alpha value.
}
}
And then if you want to update specific properties of the views you could do something like this:
view.subviews.map { $0.backgroundColor = $0.backgroundColor.invertedColor }
// And so on to change things like the tint color, text color, etc.
Sorry, I don't know a way to do this directly but till then this is better than nothing I guess.
I know of no such API call, and frankly I'd be surprised if it was available. Generally, Apple does not provide system-wide settings to individual applications. However, you could implement this yourself—but only for your own app.
Now this can be done with a buid-in method from SwiftUI; colorInvert().
The colorInvert() modifier inverts all of the colors in a view so that each color displays as its complementary color. For example, blue converts to yellow, and white converts to black.
See: https://developer.apple.com/documentation/swiftui/view/colorinvert()

Resources