This article is the 2rd part of this series. The following content will focus on the Color Space in the Color-Specific APIs in the Apple Framework, while the first part of this series mainly focuses on the definition of a Color. If you haven’t read the first part, I recommend you read it first:
Color Space in Color-Specific APIs
Now, we all recognize the significance of Color Space in defining a Color. How do we deal with it properly when writing code for Apple platforms?
Fortunately, Apple handles all kinds of stuff for us, and normally we should only be careful not to revert the work that Apple has done for us. But you may still wonder: I handle Colors and Images in my app all the time, and how does Apple handle things for us? What place does Color Space hide in those related APIs? The following content will unveil the details insights into the Color Spaces behind those APIs.
Color Spaces in Xcode Assets
The first thing to address is the Color Space when you define a Color in the Xcode’s Assets.xcassets
folder. When you select a color and open the attributes inspector, you can set the attributes of the Color itself.
To customize your own color, like setting it from a Hex form or a color picker, you should choose the Color Space from the Custom Colors section in the Content Menu, or you can just use the system-provided colors, which are predefined colors that match the system and the current traits collection. When you click to open the Content menu, there are a few options under the Custom Colors section:
Display P3
Extended Range sRGB
Extended Range Linear sRGB
sRGB
Those are the Color Spaces for your custom color. After choosing one Color Space, then you can select a method to represent the color, like using the 8-bit Hexadecimal, 8-bit (0-255), or just float point.
Therefore, before you copy-paste the hex string or the 8-bit RGB components’ integer value from any other apps or your designer, make sure to find out which color spaces those colors are in:
If your Mac is using a Display P3 Color Profile, then normally colors being read from the screen by some screen-capture apps are in Display P3 Color Space.
If you copy colors from Photoshop, Sketch, or any other apps, please make sure to check which Color Profiles the documents are in.
If your designer just sends the hex string to you, like “#FF1259”, you may have to ask which color space it is in.
Then, when you initialize UIColor, Color, or NSColor from the ColorResource, it knows how to load and tag the color properly.
Color Spaces in Color specific APIs
If you are building an app that mostly uses UIKit, SwiftUI, or AppKit to draw your content, the UIColor, Color, and NSColor APIs are the most frequently used. Note that those “Color” aren’t always representations of valid and static colors for you; they are wrappers for all sorts of color-related stuff, like wrapping the ColorResources for you to resolve later. That means:
Getting the RGB component values from the Color APIs may not always work: for NSColor initialized with a ColorResource, calling redComponent from it will simply throw an exception.
UIColor, Color, and NSColor can be dynamic colors, which means the concrete color you get will be based on the trait collection of the current environment. UIColor and Color provide resolve methods for us to get the underlying static color from a specific trait, and NSColor relies on the current NSAppearance to get the underlying static color.
When you initialize those Color APIs with ColorResources defined in XCAssets, you don’t worry about the Color Spaces tagging. But if you choose to construct those Colors yourself using RGB components, you have to keep your eyes on those APIs.
For UIColor
:
UIColor(red:green:blue:alpha:) will create a color object in an extended range sRGB color space when linked for iOS 10 or later.
UIColor(displayP3Red:green:blue:alpha:) will create create a color object in an extended range Display P3.
The RGB obtained from getRed(_:green:blue:alpha:) API will always be converted to the sRGB Color Space, no matter how the
UIColor
object is originally created.
For SwiftUI Color
:
Color(_:red:green:blue:opacity:) will default to use sRGB Color Space for the first parameter. You can change it to other predefined Color.RGBColorSpace like displayP3 .
You use the resolve(in:) method to “resolve” the Color.Resolved based on the
EnvironmentValues
you pass in. For example, you can resolve the Color struct with a Light Color Scheme to get the components of the Light color even the app is currently in Dark mode.
The Color.Resolved represents a concrete color, with some properties like blue in sRGB color space and linearBlue, as the name suggested, in the linear sRGB color space.
For NSColor
:
NSColor(colorSpace:components:count:) will create a color object of the
NSColorSpace
specified by you.
You can use usingColorSpace(_:) to convert a NSColor to another one with a different Color Space.
The colorSpace, redComponent, greenComponent and blueComponent properties only valid for some initializers when creating the
NSColor
. And those RGB components will be in the same Color Space when initializing theNSColor
object. Thus, unlikeUIColor
, no conversion is done for you. If you access those properties in an invalidNSColor
, it will just throw an exception. So be careful withNSColor
.
Aside from those high-level APIs, of course, there are some counterpart low-level APIs. UIColor, Color.Resolved, and NSColor all have a property to access the underlying CGColor in the low-level Core Graphics framework. The CGColor represents a concrete color that has its components and the corresponding Color Space. Additionally, there is a CIColor in the Core Image framework, which represents a Color object in the Core Image framework. The components and Color Space attributes in CIColor and CGColor are directly accessible to you.
For CIColor
:
Use CIColor(red:green:blue:alpha:) to create a
CIColor
object in the sRGB Color Space.
Use CIColor(red:green:blue:colorSpace:) to create a
CIColor
object in the specified Color Space.
You can access the colorSpace property of a
CIColor
, and the red, green and blue components when initializing this object.
There are some predefined
CIColor
instances that you can use directly, like black and green, which are in sRGB Color Space.
For CGColor
:
Use CGColor(srgbRed:green:blue:alpha:) to create a
CGColor
in a sRGB Color Space.
Use CGColor(red:green:blue:alpha:) to create a
CGColor
in a Generic RGB color space. Note that the Generic Color Space (kCGColorSpaceGenericRGB) seems to be a deprecated Color Space, and according to Apple we should use sRGB instead.
You can access the colorSpace property of a
CGColor
, and the red, green and blue components when initializing this object.
CGColorSpace and ColorSyncProfile
There is a CGColorSpace API for us to use in the Core Graphics, Core Image and other frameworks. Typically we obtain a CGColorSpace
using:
Retrieve the
colorSpace
property from the Color and Image-related instances.
Create a predefined one using the CGColorSpace(name:) initializer, which takes a
CFString
representing the name of a Color Space and can be found in the static properties inCGColorSpace
, like CGColorSpace.displayP3.
Create from an ICC Profile data using CGColorSpace(iccData:) initializer.
Though CGColorSpace
provides some convenient method and properties for use to examine, like:
Use model property to check the color model like RGB model.
Use CGColorSpaceCreateLinearized(_:) to create a linearized Color Space based on the current non-linear Color Space.
Use
CGColorSpaceIsHLGBased
to check whether current it’s HLG based color space.
there are still some missing properties we mentioned earlier like the Color Gamut, which can be represented as Color Primaries in XYZ Color Space.
We can examine the ICC Profile itself using the following methods:
Get the ICC Profile Data using CGColorSpace.copyICCData() method.
Use ColorSyncProfileCreate from ColorSync framework to create a
ColorSyncProfile
instance.
Use ColorSyncProfileCopyHeader to copy the header as
CFData
, then examine the data based on the documentation.
Use ColorSyncProfileCopyTag to copy a specified tag as
CFData
, then examine the data based on the ICC Profile Format Specification.
Note that those APIs in ColorSyncProfile framework are parts of the Core Foundation. When used in Swift, the instances you get will sometimes be wrapped with Unmanaged API, and it’s recommended to read this Working with Core Foundation Types documentation before fiddling with those unmanaged instances.
This is the end of the second part of this series. In the next article, we will cover the Color Space in Image-Specific APIs and finally how Color Management works across Apple frameworks.