├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
└── Sources
└── PixelColor
├── PixelColor+Binding.swift
├── PixelColor+Channel.swift
├── PixelColor+Codable.swift
├── PixelColor+Colors.swift
├── PixelColor+DarkMode.swift
├── PixelColor+Decode.swift
├── PixelColor+Funcs.swift
├── PixelColor+HSV.swift
├── PixelColor+Hashable.swift
├── PixelColor+Operators.swift
├── PixelColor+Random.swift
├── PixelColor+Types.swift
├── PixelColor+sRGB.swift
└── PixelColor.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Anton Heestand
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 6.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "PixelColor",
7 | platforms: [
8 | .iOS(.v14),
9 | .tvOS(.v14),
10 | .macOS(.v11),
11 | .visionOS(.v1)
12 | ],
13 | products: [
14 | .library(name: "PixelColor", targets: ["PixelColor"]),
15 | ],
16 | targets: [
17 | .target(name: "PixelColor", dependencies: []),
18 | ]
19 | )
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # PixelColor
3 |
4 | A Swift 6 package for working with colors at the pixel level. The `PixelColor` struct provides utilities for defining, manipulating, and converting colors with `red`, `green`, `blue`, and `opacity` channels (`CGFloat`).
5 |
6 | ---
7 |
8 | ## Features
9 |
10 | - **Color Channels**:
11 | - Work with individual `red`, `green`, `blue`, and `opacity` channels.
12 | - **Color Conversions**:
13 | - Use `hue`, `saturation`, and `brightness` (HSB) representations.
14 | - Get a SwiftUI `Angle` of the hue (`.hueAngle`).
15 | - Initialize colors using `hex` strings with or without opacity.
16 | - **Built-in Colors**:
17 | - System colors like `.red`, `.green`, `.blue`, `.orange`, `.teal`, etc.
18 | - Raw colors for precision: `.rawRed`, `.rawYellow`, `.rawGreen`, etc.
19 | - Clear "white" color for blending gradients (`.clearWhite`).
20 | - Adaptable colors (`.primary`, `.background`) that respect light/dark appearance modes.
21 | - **Operators**:
22 | - Arithmetic (`+`, `-`, `*`, `/`) for blending and scaling colors.
23 | - Prefix operator `!` for inverting colors.
24 | - **Codable, Equatable and Hashable**:
25 | - Serialize, equate and hash colors easily.
26 | - **Sendable**:
27 | - Work with colors concurrently.
28 | - **SwiftUI / UIKit / AppKit Compatibility**:
29 | - Convert `PixelColor` to and from SwiftUI's `Color` or platform-specific `UIColor`/`NSColor`.
30 | - **Utility Methods**:
31 | - Modify hue, saturation, brightness, and opacity.
32 | - Generate random colors (`.random()`) or random fully saturated hues (`.randomHue()`).
33 | - Check and identify pure channel colors (`.isPureChannel`).
34 |
35 | ---
36 |
37 | ## Installation
38 |
39 | ### Swift Package Manager (SPM)
40 |
41 | Add the following dependency to your `Package.swift` file:
42 |
43 | ```swift
44 | dependencies: [
45 | .package(url: "https://github.com/heestand-xyz/PixelColor", from: "3.0.0")
46 | ]
47 | ```
48 |
49 | Then, import `PixelColor` in your code:
50 |
51 | ```swift
52 | import PixelColor
53 | ```
54 |
55 | ---
56 |
57 | ## Usage
58 |
59 | ### 1. Creating Colors
60 |
61 | #### Using RGB Values
62 | ```swift
63 | let color = PixelColor(red: 0.5, green: 0.25, blue: 0.75, opacity: 1.0)
64 | ```
65 |
66 | #### Using Hex Strings
67 | ```swift
68 | let color = PixelColor(hex: "#FF8000") // Orange
69 | let semiTransparentColor = PixelColor(hexWithOpacity: "#FF800080") // 50% transparent orange
70 | ```
71 |
72 | #### Using HSB
73 | ```swift
74 | let color = PixelColor(hue: 0.5, saturation: 1.0, brightness: 1.0, opacity: 1.0)
75 | ```
76 |
77 | ---
78 |
79 | ### 2. Modifying Colors
80 |
81 | #### Adjust Hue
82 | ```swift
83 | let shiftedColor = color.shiftHue(by: .degrees(180))
84 | ```
85 |
86 | #### Adjust Brightness
87 | ```swift
88 | let brighterColor = color.brighten(by: 1.5)
89 | ```
90 |
91 | #### Adjust Opacity
92 | ```swift
93 | let semiTransparentColor = color.withOpacity(of: 0.5)
94 | ```
95 |
96 | ---
97 |
98 | ### 3. Color Conversions
99 |
100 | > Note that `PixelColor` does not manage the color space, these functions are just for convenience.
101 |
102 | #### Convert to Linear Space
103 | ```swift
104 | let linearColor = color.sRGBToLinear()
105 | ```
106 |
107 | #### Convert to sRGB Space
108 | ```swift
109 | let srgbColor = linearColor.linearToSRGB()
110 | ```
111 |
112 | #### Convert to SwiftUI or Platform Colors
113 | ```swift
114 | let swiftUIColor: Color = color.color
115 | let uiColor: UIColor = color.uiColor
116 | /// macOS only
117 | let nsColor: NSColor = color.nsColor
118 | ```
119 |
120 | ---
121 |
122 | ### 4. Operators
123 |
124 | #### Blend Colors
125 | ```swift
126 | let blendedColor = color1 + color2
127 | ```
128 |
129 | #### Invert a Color
130 | ```swift
131 | let invertedColor = !color
132 | ```
133 |
134 | #### Scale Color Channels
135 | ```swift
136 | let scaledColor = color * 0.8
137 | ```
138 |
139 | ---
140 |
141 | ### 5. Utilities
142 |
143 | #### Generate Random Colors
144 | ```swift
145 | let randomColor = PixelColor.random()
146 | let randomHueColor = PixelColor.randomHue()
147 | ```
148 |
149 | #### Check Pure Channels
150 |
151 | > `PixelColor.Channel` is an enum of the 4 channels.
152 |
153 | ```swift
154 | if color.hasPureChannel {
155 | print("Pure channel: \(color.pureChannel!)")
156 | }
157 | ```
158 |
159 | > A color has a pure channel when one channel is at `1.0` and the other channels are at `0.0`.
160 |
161 | ---
162 |
163 | ## Examples
164 |
165 | ### Adaptable Colors
166 | ```swift
167 | let primaryColor = PixelColor.primary // White in dark mode, black in light mode
168 | let backgroundColor = PixelColor.background // Opposite of primary
169 | ```
170 |
171 | ### Hex Conversion
172 | ```swift
173 | let hex = color.hex // "7F3FBF"
174 | let hexWithOpacity = color.hexWithOpacity // "7F3FBFFF"
175 | ```
176 |
177 | ---
178 |
179 | ## Contributing
180 |
181 | Feel free to submit pull requests or open issues for improvements and feature requests.
182 |
183 | ---
184 |
185 | ## License
186 |
187 | This project is licensed under the MIT License. See the LICENSE file for details.
188 |
189 | ---
190 |
191 | ## Acknowledgments
192 |
193 | Developed by [Anton Heestand](http://heestand.xyz)
194 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Binding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelColor+Binding.swift
3 | // PixelColor
4 | //
5 | // Created by a-heestand on 2025/02/15.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension PixelColor {
11 | /// For use with SwiftUI's color picker.
12 | public static func mapBinding(_ color: Binding) -> Binding {
13 | Binding {
14 | color.wrappedValue.linearToSRGB().displayP3
15 | } set: { newColor in
16 | color.wrappedValue = PixelColor(newColor, convertToColorSpace: CGColorSpace(name: CGColorSpace.linearSRGB)!)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Channel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 |
11 | extension PixelColor {
12 |
13 | public enum Channel: Int {
14 | case red
15 | case green
16 | case blue
17 | case alpha
18 | var color: PixelColor {
19 | switch self {
20 | case .red: return PixelColor(red: 1.0, green: 0.0, blue: 0.0, opacity: 0.0)
21 | case .green: return PixelColor(red: 0.0, green: 1.0, blue: 0.0, opacity: 0.0)
22 | case .blue: return PixelColor(red: 0.0, green: 0.0, blue: 1.0, opacity: 0.0)
23 | case .alpha: return PixelColor(red: 0.0, green: 0.0, blue: 0.0, opacity: 1.0)
24 | }
25 | }
26 | }
27 |
28 | @available(*, deprecated, renamed: "hasPureChannel")
29 | public var isPureChannel: Bool {
30 | hasPureChannel
31 | }
32 |
33 | public var hasPureChannel: Bool {
34 | let oneCount: Int = (red == 1.0 ? 1 : 0) + (green == 1.0 ? 1 : 0) + (blue == 1.0 ? 1 : 0) + (opacity == 1.0 ? 1 : 0)
35 | let zeroCount: Int = (red == 0.0 ? 1 : 0) + (green == 0.0 ? 1 : 0) + (blue == 0.0 ? 1 : 0) + (opacity == 0.0 ? 1 : 0)
36 | return oneCount == 1 && zeroCount == 3
37 | }
38 |
39 | public var pureChannel: Channel? {
40 | guard hasPureChannel else { return nil }
41 | if red == 1.0 {
42 | return .red
43 | } else if green == 1.0 {
44 | return .green
45 | } else if blue == 1.0 {
46 | return .blue
47 | } else if opacity == 1.0 {
48 | return .alpha
49 | } else {
50 | return nil
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Codable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 |
11 | extension PixelColor: Codable {
12 |
13 | enum CodingKeys: CodingKey {
14 | case red
15 | case green
16 | case blue
17 | case opacity
18 | case alpha
19 | }
20 |
21 | public init(from decoder: Decoder) throws {
22 | let container = try decoder.container(keyedBy: CodingKeys.self)
23 | red = try container.decode(CGFloat.self, forKey: .red)
24 | green = try container.decode(CGFloat.self, forKey: .green)
25 | blue = try container.decode(CGFloat.self, forKey: .blue)
26 | if container.contains(.alpha) {
27 | opacity = try container.decode(CGFloat.self, forKey: .alpha)
28 | } else {
29 | opacity = try container.decode(CGFloat.self, forKey: .opacity)
30 | }
31 | }
32 |
33 | public func encode(to encoder: Encoder) throws {
34 | var container = encoder.container(keyedBy: CodingKeys.self)
35 | try container.encode(red, forKey: .red)
36 | try container.encode(green, forKey: .green)
37 | try container.encode(blue, forKey: .blue)
38 | try container.encode(opacity, forKey: .opacity)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | #if os(macOS)
9 | import AppKit
10 | #else
11 | import UIKit
12 | #endif
13 | import SwiftUI
14 |
15 | extension PixelColor {
16 |
17 | /// White in dark mode and black in light mode.
18 | @MainActor
19 | public static var primary: PixelColor {
20 | switch Self.appearance {
21 | case .dark: return .white
22 | case .light: return .black
23 | }
24 | }
25 |
26 | /// Black in dark mode and white in light mode.
27 | @MainActor
28 | public static var background: PixelColor {
29 | switch Self.appearance {
30 | case .dark: return .black
31 | case .light: return .white
32 | }
33 | }
34 |
35 | /// Transparent color. `#000000FF`
36 | public static let clear: PixelColor = .init(red: 0.0, green: 0.0, blue: 0.0, opacity: 0.0)
37 | /// Transparent "white" color. `#FFFFFFFF`
38 | ///
39 | /// Useful when blending from white to transparent in a gradient.
40 | public static let clearWhite: PixelColor = .init(red: 1.0, green: 1.0, blue: 1.0, opacity: 0.0)
41 |
42 | /// `#FFFFFF`
43 | public static let white: PixelColor = .init(white: 1.0)
44 | /// `#000000`
45 | public static let black: PixelColor = .init(white: 0.0)
46 |
47 | /// `50%` white.
48 | public static let rawGray: PixelColor = .init(white: 0.5)
49 | /// `#FF0000`
50 | public static let rawRed: PixelColor = .init(red: 1.0, green: 0.0, blue: 0.0)
51 | /// `#FFFF00`
52 | public static let rawYellow: PixelColor = .init(red: 1.0, green: 1.0, blue: 0.0)
53 | /// `#00FF00`
54 | public static let rawGreen: PixelColor = .init(red: 0.0, green: 1.0, blue: 0.0)
55 | /// `#00FFFF`
56 | public static let rawCyan: PixelColor = .init(red: 0.0, green: 1.0, blue: 1.0)
57 | /// `#0000FF`
58 | public static let rawBlue: PixelColor = .init(red: 0.0, green: 0.0, blue: 1.0)
59 | /// `#FF00FF`
60 | public static let rawMagenta: PixelColor = .init(red: 1.0, green: 0.0, blue: 1.0)
61 |
62 | @available(*, deprecated, renamed: "brightness(_:opacity:)")
63 | public static func brightness(_ brightness: CGFloat, alpha: CGFloat) -> PixelColor {
64 | self.brightness(brightness, opacity: alpha)
65 | }
66 |
67 | public static func brightness(_ brightness: CGFloat, opacity: CGFloat = 1.0) -> PixelColor {
68 | PixelColor(hue: 0.0, saturation: 0.0, brightness: brightness, opacity: opacity)
69 | }
70 |
71 | @available(*, deprecated, renamed: "hue(_:opacity:)")
72 | public static func hue(_ hue: CGFloat, alpha: CGFloat) -> PixelColor {
73 | self.hue(hue, opacity: alpha)
74 | }
75 |
76 | public static func hue(_ hue: CGFloat, opacity: CGFloat = 1.0) -> PixelColor {
77 | PixelColor(hue: hue, saturation: 1.0, brightness: 1.0, opacity: opacity)
78 | }
79 | }
80 |
81 | extension PixelColor {
82 |
83 | public static var gray: PixelColor {
84 | #if os(macOS)
85 | PixelColor(NSColor.systemGray)
86 | #else
87 | PixelColor(UIColor.systemGray)
88 | #endif
89 | }
90 |
91 | public static var red: PixelColor{
92 | #if os(macOS)
93 | PixelColor(NSColor.systemRed)
94 | #else
95 | PixelColor(UIColor.systemRed)
96 | #endif
97 | }
98 |
99 | public static var green: PixelColor{
100 | #if os(macOS)
101 | PixelColor(NSColor.systemGreen)
102 | #else
103 | PixelColor(UIColor.systemGreen)
104 | #endif
105 | }
106 |
107 | public static var blue: PixelColor {
108 | #if os(macOS)
109 | PixelColor(NSColor.systemBlue)
110 | #else
111 | PixelColor(UIColor.systemBlue)
112 | #endif
113 | }
114 |
115 | public static var indigo: PixelColor{
116 | #if os(macOS)
117 | PixelColor(NSColor.systemIndigo)
118 | #else
119 | PixelColor(UIColor.systemIndigo)
120 | #endif
121 | }
122 |
123 | public static var orange: PixelColor{
124 | #if os(macOS)
125 | PixelColor(NSColor.systemOrange)
126 | #else
127 | PixelColor(UIColor.systemOrange)
128 | #endif
129 | }
130 |
131 | public static var pink: PixelColor{
132 | #if os(macOS)
133 | PixelColor(NSColor.systemPink)
134 | #else
135 | PixelColor(UIColor.systemPink)
136 | #endif
137 | }
138 |
139 | public static var purple: PixelColor{
140 | #if os(macOS)
141 | PixelColor(NSColor.systemPurple)
142 | #else
143 | PixelColor(UIColor.systemPurple)
144 | #endif
145 | }
146 |
147 | public static var teal: PixelColor{
148 | #if os(macOS)
149 | PixelColor(NSColor.systemTeal)
150 | #else
151 | PixelColor(UIColor.systemTeal)
152 | #endif
153 | }
154 |
155 | public static var yellow: PixelColor{
156 | #if os(macOS)
157 | PixelColor(NSColor.systemYellow)
158 | #else
159 | PixelColor(UIColor.systemYellow)
160 | #endif
161 | }
162 |
163 | public static var brown: PixelColor{
164 | #if os(macOS)
165 | PixelColor(NSColor.systemBrown)
166 | #else
167 | PixelColor(UIColor.systemBrown)
168 | #endif
169 | }
170 |
171 | public static var mint: PixelColor{
172 | #if os(macOS)
173 | PixelColor(NSColor.systemMint)
174 | #else
175 | if #available(iOS 15.0, tvOS 15.0, *) {
176 | PixelColor(UIColor.systemMint)
177 | } else {
178 | .gray
179 | }
180 | #endif
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+DarkMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2021-06-21.
3 | //
4 |
5 | #if canImport(UIKit)
6 | import UIKit
7 | #elseif canImport(AppKit)
8 | import AppKit
9 | #endif
10 |
11 | extension PixelColor {
12 |
13 | public enum Appearance: Hashable {
14 | case light
15 | case dark
16 | }
17 |
18 | @MainActor
19 | public static var appearance: Appearance {
20 | #if os(macOS)
21 | return darkMode() ? .dark : .light
22 | #elseif os(visionOS)
23 | return .dark
24 | #else
25 | return UIScreen.main.traitCollection.userInterfaceStyle == .dark ? .dark : .light
26 | #endif
27 | }
28 |
29 | #if os(macOS)
30 | @MainActor
31 | private static func darkMode() -> Bool {
32 | NSApp?.effectiveAppearance.name == .darkAqua
33 | }
34 | #endif
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Decode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PixelColor+Decode.swift
3 | // PixelColor
4 | //
5 | // Created by Anton on 2024-12-23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension PixelColor {
11 |
12 | static func decode(color: Color) -> PixelColor? {
13 |
14 | func namedColor(_ name: String) -> PixelColor? {
15 | switch name {
16 | case "gray": return .gray
17 | case "red": return .red
18 | case "orange": return .orange
19 | case "yellow": return .yellow
20 | case "green": return .green
21 | case "mint": return .mint
22 | case "teal": return .teal
23 | case "blue": return .blue
24 | case "indigo": return .indigo
25 | case "purple": return .purple
26 | case "pink": return .pink
27 | case "brown": return .brown
28 | default: return nil
29 | }
30 | }
31 |
32 | if let color: PixelColor = namedColor(color.description) {
33 | return color
34 | }
35 |
36 | if color.description.contains("%") {
37 |
38 | let parts = color.description.components(separatedBy: "% ")
39 | guard parts.count == 2 else { return nil }
40 |
41 | let percentage = Int(parts.first!) ?? 100
42 | let opacity = CGFloat(percentage) / 100
43 |
44 | guard let color: PixelColor = namedColor(String(parts.last!)) else { return nil }
45 |
46 | return color.withOpacity(of: opacity)
47 | }
48 |
49 | if color.description.starts(with: "NamedColor") {
50 | let components = color.description.components(separatedBy: "\"")
51 | guard components.count >= 3 else { return nil }
52 | let name: String = components[1]
53 | #if os(macOS)
54 | guard let color = NSColor(named: name) else { return nil }
55 | #else
56 | guard let color = UIColor(named: name) else { return nil }
57 | #endif
58 | return PixelColor(color)
59 | }
60 |
61 | return nil
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Funcs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 | import SwiftUI
11 |
12 | extension PixelColor {
13 |
14 | // MARK: Opacity
15 |
16 | public func opacity(_ opacity: CGFloat) -> PixelColor {
17 | PixelColor(red: red, green: green, blue: blue, opacity: self.opacity * opacity)
18 | }
19 |
20 | @available(*, deprecated, renamed: "withOpacity(of:)")
21 | public func withAlpha(of alpha: CGFloat) -> PixelColor {
22 | return PixelColor(red: red, green: green, blue: blue, opacity: alpha)
23 | }
24 |
25 | public func withOpacity(of opacity: CGFloat) -> PixelColor {
26 | PixelColor(red: red, green: green, blue: blue, opacity: opacity)
27 | }
28 |
29 | // MARK: Hue
30 |
31 | @available(*, deprecated, renamed: "shiftHue(by:)")
32 | public func withShiftedHue(by hueShift: CGFloat) -> PixelColor {
33 | shiftHue(by: hueShift)
34 | }
35 |
36 | /// Replace the hue.
37 | public func withHue(of hue: CGFloat) -> PixelColor {
38 | return PixelColor(hue: hue, saturation: saturation, brightness: brightness)
39 | }
40 |
41 | /// Replace the hue with an `Angle`.
42 | public func withHue(of hue: Angle) -> PixelColor {
43 | return PixelColor(hue: hue.degrees / 360, saturation: saturation, brightness: brightness)
44 | }
45 |
46 | /// Offset the hue.
47 | ///
48 | /// Hue is between `0.0` and `1.0`.
49 | public func shiftHue(by hueShift: CGFloat) -> PixelColor {
50 | return PixelColor(
51 | hue: (hue + hueShift).truncatingRemainder(dividingBy: 1.0),
52 | saturation: saturation,
53 | brightness: brightness
54 | )
55 | }
56 |
57 | /// Offset the hue by an `Angle`.
58 | public func shiftHue(by hueShift: Angle) -> PixelColor {
59 | return PixelColor(
60 | hue: (hue + (hueShift.degrees / 360)).truncatingRemainder(dividingBy: 1.0),
61 | saturation: saturation,
62 | brightness: brightness
63 | )
64 | }
65 |
66 | // MARK: Saturation
67 |
68 | /// Multiply the current saturation.
69 | public func saturate(_ saturation: CGFloat) -> PixelColor {
70 | return PixelColor(hue: hue, saturation: self.saturation * saturation, brightness: brightness)
71 | }
72 |
73 | /// Replace the saturation.
74 | public func withSaturation(of saturation: CGFloat) -> PixelColor {
75 | return PixelColor(hue: hue, saturation: saturation, brightness: brightness)
76 | }
77 |
78 | // MARK: Brightness
79 |
80 | /// Multiply the current brightness.
81 | public func brighten(by brightness: CGFloat) -> PixelColor {
82 | return PixelColor(hue: hue, saturation: saturation, brightness: self.brightness * brightness)
83 | }
84 |
85 | /// Add to the current brightness.
86 | public func expose(by exposure: CGFloat) -> PixelColor {
87 | return PixelColor(hue: hue, saturation: saturation, brightness: self.brightness + exposure)
88 | }
89 |
90 | /// Replace the brightness.
91 | public func withBrightness(of brightness: CGFloat) -> PixelColor {
92 | return PixelColor(hue: hue, saturation: saturation, brightness: brightness)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+HSV.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 | import SwiftUI
11 |
12 | extension PixelColor {
13 |
14 | typealias HSV = (h: CGFloat, s: CGFloat, v: CGFloat)
15 | typealias RGB = (r: CGFloat, g: CGFloat, b: CGFloat)
16 |
17 | public var hueAngle: Angle {
18 | get {
19 | .degrees(Double(hue) * 360)
20 | }
21 | set {
22 | hue = newValue.degrees / 360
23 | }
24 | }
25 |
26 | public var hue: CGFloat {
27 | get { hsv().h }
28 | set {
29 | self = PixelColor(hue: newValue,
30 | saturation: saturation,
31 | brightness: brightness,
32 | opacity: opacity)
33 | }
34 | }
35 |
36 | public var saturation: CGFloat {
37 | get { hsv().s }
38 | set {
39 | self = PixelColor(hue: hue,
40 | saturation: newValue,
41 | brightness: brightness,
42 | opacity: opacity)
43 | }
44 | }
45 |
46 | public var brightness: CGFloat {
47 | get { hsv().v }
48 | set {
49 | self = PixelColor(hue: hue,
50 | saturation: saturation,
51 | brightness: newValue,
52 | opacity: opacity)
53 | }
54 | }
55 |
56 | func hsv() -> (h: CGFloat, s: CGFloat, v: CGFloat) {
57 | let r: CGFloat = red
58 | let g: CGFloat = green
59 | let b: CGFloat = blue
60 | var h, s, v: CGFloat
61 | var mn, mx, d: CGFloat
62 | mn = r < g ? r : g
63 | mn = mn < b ? mn : b
64 | mx = r > g ? r : g
65 | mx = mx > b ? mx : b
66 | v = mx
67 | d = mx - mn
68 | if (d < 0.00001) {
69 | s = 0
70 | h = 0
71 | return (h: h, s: s, v: v)
72 | }
73 | if mx > 0.0 {
74 | s = d / mx
75 | } else {
76 | s = 0.0
77 | h = 0.0
78 | return (h: h, s: s, v: v)
79 | }
80 | if r >= mx {
81 | h = (g - b) / d
82 | } else if g >= mx {
83 | h = 2.0 + (b - r) / d
84 | } else {
85 | h = 4.0 + (r - g) / d
86 | }
87 | h *= 60.0
88 | if h < 0.0 {
89 | h += 360.0
90 | }
91 | h /= 360.0
92 | return (h: h, s: s, v: v)
93 | }
94 |
95 | static func rgb(hsv: HSV) -> RGB {
96 | rgb(h: hsv.h, s: hsv.s, v: hsv.v)
97 | }
98 |
99 | static func rgb(h: CGFloat, s: CGFloat, v: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
100 | let r: CGFloat
101 | let g: CGFloat
102 | let b: CGFloat
103 | var hh, p, q, t, ff: CGFloat
104 | var i: Int
105 | if (s <= 0.0) {
106 | r = v
107 | g = v
108 | b = v
109 | return (r: r, g: g, b: b)
110 | }
111 | hh = (h - floor(h)) * 360
112 | hh = hh / 60.0
113 | i = Int(hh)
114 | ff = hh - CGFloat(i)
115 | p = v * (1.0 - s)
116 | q = v * (1.0 - (s * ff))
117 | t = v * (1.0 - (s * (1.0 - ff)))
118 | switch(i) {
119 | case 0:
120 | r = v
121 | g = t
122 | b = p
123 | case 1:
124 | r = q
125 | g = v
126 | b = p
127 | case 2:
128 | r = p
129 | g = v
130 | b = t
131 | case 3:
132 | r = p
133 | g = q
134 | b = v
135 | case 4:
136 | r = t
137 | g = p
138 | b = v
139 | case 5:
140 | r = v
141 | g = p
142 | b = q
143 | default:
144 | r = v
145 | g = p
146 | b = q
147 | }
148 | return (r: r, g: g, b: b)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Hashable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2023-02-09.
3 | //
4 |
5 | extension PixelColor: Hashable {
6 |
7 | public func hash(into hasher: inout Hasher) {
8 | hasher.combine(red)
9 | hasher.combine(green)
10 | hasher.combine(blue)
11 | hasher.combine(opacity)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Operators.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2022-04-03.
3 | //
4 |
5 | import CoreGraphics
6 |
7 | public extension PixelColor {
8 |
9 | static prefix func ! (color: PixelColor) -> PixelColor {
10 | return PixelColor(
11 | red: 1.0 - color.red,
12 | green: 1.0 - color.green,
13 | blue: 1.0 - color.blue,
14 | opacity: color.opacity
15 | )
16 | }
17 |
18 | static func + (lhs: PixelColor, rhs: PixelColor) -> PixelColor {
19 |
20 | PixelColor(red: lhs.red + rhs.red,
21 | green: lhs.green + rhs.green,
22 | blue: lhs.blue + rhs.blue,
23 | opacity: lhs.opacity + rhs.opacity)
24 | }
25 |
26 | static func - (lhs: PixelColor, rhs: PixelColor) -> PixelColor {
27 |
28 | PixelColor(red: lhs.red - rhs.red,
29 | green: lhs.green - rhs.green,
30 | blue: lhs.blue - rhs.blue,
31 | opacity: lhs.opacity - rhs.opacity)
32 | }
33 |
34 | static func * (lhs: PixelColor, rhs: PixelColor) -> PixelColor {
35 |
36 | PixelColor(red: lhs.red * rhs.red,
37 | green: lhs.green * rhs.green,
38 | blue: lhs.blue * rhs.blue,
39 | opacity: lhs.opacity * rhs.opacity)
40 | }
41 |
42 | static func / (lhs: PixelColor, rhs: PixelColor) -> PixelColor {
43 |
44 | PixelColor(red: (rhs.red > 0.0) ? (lhs.red / rhs.red) : 0.0,
45 | green: (rhs.green > 0.0) ? (lhs.green / rhs.green) : 0.0,
46 | blue: (rhs.blue > 0.0) ? (lhs.blue / rhs.blue) : 0.0,
47 | opacity: (rhs.opacity > 0.0) ? (lhs.opacity / rhs.opacity) : 0.0)
48 | }
49 | }
50 |
51 | public extension PixelColor {
52 |
53 | static func + (lhs: PixelColor, rhs: CGFloat) -> PixelColor {
54 |
55 | PixelColor(red: lhs.red + rhs,
56 | green: lhs.green + rhs,
57 | blue: lhs.blue + rhs,
58 | opacity: lhs.opacity + rhs)
59 | }
60 |
61 | static func - (lhs: PixelColor, rhs: CGFloat) -> PixelColor {
62 |
63 | PixelColor(red: lhs.red - rhs,
64 | green: lhs.green - rhs,
65 | blue: lhs.blue - rhs,
66 | opacity: lhs.opacity - rhs)
67 | }
68 |
69 | static func * (lhs: PixelColor, rhs: CGFloat) -> PixelColor {
70 |
71 | PixelColor(red: lhs.red * rhs,
72 | green: lhs.green * rhs,
73 | blue: lhs.blue * rhs,
74 | opacity: lhs.opacity * rhs)
75 | }
76 |
77 | static func / (lhs: PixelColor, rhs: CGFloat) -> PixelColor {
78 |
79 | PixelColor(red: (rhs > 0.0) ? (lhs.red / rhs) : 0.0,
80 | green: (rhs > 0.0) ? (lhs.green / rhs) : 0.0,
81 | blue: (rhs > 0.0) ? (lhs.blue / rhs) : 0.0,
82 | opacity: (rhs > 0.0) ? (lhs.opacity / rhs) : 0.0)
83 | }
84 | }
85 |
86 | public extension PixelColor {
87 |
88 | static func + (lhs: CGFloat, rhs: PixelColor) -> PixelColor {
89 |
90 | PixelColor(red: lhs + rhs.red,
91 | green: lhs + rhs.green,
92 | blue: lhs + rhs.blue,
93 | opacity: lhs + rhs.opacity)
94 | }
95 |
96 | static func - (lhs: CGFloat, rhs: PixelColor) -> PixelColor {
97 |
98 | PixelColor(red: lhs - rhs.red,
99 | green: lhs - rhs.green,
100 | blue: lhs - rhs.blue,
101 | opacity: lhs - rhs.opacity)
102 | }
103 |
104 | static func * (lhs: CGFloat, rhs: PixelColor) -> PixelColor {
105 |
106 | PixelColor(red: lhs * rhs.red,
107 | green: lhs * rhs.green,
108 | blue: lhs * rhs.blue,
109 | opacity: lhs * rhs.opacity)
110 | }
111 |
112 | static func / (lhs: CGFloat, rhs: PixelColor) -> PixelColor {
113 |
114 | PixelColor(red: (rhs.red > 0.0) ? (lhs / rhs.red) : 0.0,
115 | green: (rhs.green > 0.0) ? (lhs / rhs.green) : 0.0,
116 | blue: (rhs.blue > 0.0) ? (lhs / rhs.blue) : 0.0,
117 | opacity: (rhs.opacity > 0.0) ? (lhs / rhs.opacity) : 0.0)
118 | }
119 | }
120 |
121 | // MARK: - Modify
122 |
123 | public extension PixelColor {
124 |
125 | static func += (lhs: inout PixelColor, rhs: PixelColor) {
126 |
127 | lhs = PixelColor(red: lhs.red + rhs.red,
128 | green: lhs.green + rhs.green,
129 | blue: lhs.blue + rhs.blue,
130 | opacity: lhs.opacity + rhs.opacity)
131 | }
132 |
133 | static func -= (lhs: inout PixelColor, rhs: PixelColor) {
134 |
135 | lhs = PixelColor(red: lhs.red - rhs.red,
136 | green: lhs.green - rhs.green,
137 | blue: lhs.blue - rhs.blue,
138 | opacity: lhs.opacity - rhs.opacity)
139 | }
140 |
141 | static func *= (lhs: inout PixelColor, rhs: PixelColor) {
142 |
143 | lhs = PixelColor(red: lhs.red * rhs.red,
144 | green: lhs.green * rhs.green,
145 | blue: lhs.blue * rhs.blue,
146 | opacity: lhs.opacity * rhs.opacity)
147 | }
148 |
149 | static func /= (lhs: inout PixelColor, rhs: PixelColor) {
150 |
151 | lhs = PixelColor(red: (rhs.red > 0.0) ? (lhs.red / rhs.red) : 0.0,
152 | green: (rhs.green > 0.0) ? (lhs.green / rhs.green) : 0.0,
153 | blue: (rhs.blue > 0.0) ? (lhs.blue / rhs.blue) : 0.0,
154 | opacity: (rhs.opacity > 0.0) ? (lhs.opacity / rhs.opacity) : 0.0)
155 | }
156 | }
157 |
158 | public extension PixelColor {
159 |
160 | static func += (lhs: inout PixelColor, rhs: CGFloat) {
161 |
162 | lhs = PixelColor(red: lhs.red + rhs,
163 | green: lhs.green + rhs,
164 | blue: lhs.blue + rhs,
165 | opacity: lhs.opacity + rhs)
166 | }
167 |
168 | static func -= (lhs: inout PixelColor, rhs: CGFloat) {
169 |
170 | lhs = PixelColor(red: lhs.red - rhs,
171 | green: lhs.green - rhs,
172 | blue: lhs.blue - rhs,
173 | opacity: lhs.opacity - rhs)
174 | }
175 |
176 | static func *= (lhs: inout PixelColor, rhs: CGFloat) {
177 |
178 | lhs = PixelColor(red: lhs.red * rhs,
179 | green: lhs.green * rhs,
180 | blue: lhs.blue * rhs,
181 | opacity: lhs.opacity * rhs)
182 | }
183 |
184 | static func /= (lhs: inout PixelColor, rhs: CGFloat) {
185 |
186 | lhs = PixelColor(red: (rhs > 0.0) ? (lhs.red / rhs) : 0.0,
187 | green: (rhs > 0.0) ? (lhs.green / rhs) : 0.0,
188 | blue: (rhs > 0.0) ? (lhs.blue / rhs) : 0.0,
189 | opacity: (rhs > 0.0) ? (lhs.opacity / rhs) : 0.0)
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Random.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Anton Heestand on 2023-12-07.
3 | //
4 |
5 | extension PixelColor {
6 |
7 | public static func random() -> PixelColor {
8 | PixelColor(red: .random(in: 0.0...1.0),
9 | green: .random(in: 0.0...1.0),
10 | blue: .random(in: 0.0...1.0))
11 | }
12 |
13 | public static func randomHue() -> PixelColor {
14 | PixelColor(hue: .random(in: 0.0...1.0),
15 | saturation: 1.0,
16 | brightness: 1.0)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+Types.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-05-11.
6 | //
7 |
8 | #if os(macOS)
9 | import AppKit
10 | #else
11 | import UIKit
12 | #endif
13 | import SwiftUI
14 |
15 | extension PixelColor {
16 |
17 | /// SwiftUI Color
18 | public var color: Color {
19 | Color(red: red, green: green, blue: blue, opacity: opacity)
20 | }
21 |
22 | /// SwiftUI Color
23 | @available(*, deprecated, renamed: "displayP3")
24 | public var colorP3: Color {
25 | displayP3
26 | }
27 |
28 | /// SwiftUI Color
29 | public var displayP3: Color {
30 | Color(.displayP3, red: red, green: green, blue: blue, opacity: opacity)
31 | }
32 |
33 | #if os(macOS)
34 |
35 | public var nsColor: NSColor {
36 | NSColor(red: red, green: green, blue: blue, alpha: opacity)
37 | }
38 |
39 | public var nsColorP3: NSColor {
40 | NSColor(displayP3Red: red, green: green, blue: blue, alpha: opacity)
41 | }
42 |
43 | #else
44 |
45 | public var uiColor: UIColor {
46 | UIColor(red: red, green: green, blue: blue, alpha: opacity)
47 | }
48 |
49 | public var uiColorP3: UIColor {
50 | UIColor(displayP3Red: red, green: green, blue: blue, alpha: opacity)
51 | }
52 |
53 | #endif
54 |
55 | var colorSpace: CGColorSpace {
56 | if saturation > 0.0 {
57 | return CGColorSpace(name: CGColorSpace.sRGB)!
58 | } else {
59 | return CGColorSpace(name: CGColorSpace.genericGrayGamma2_2)!
60 | }
61 | }
62 |
63 | public var ciColor: CIColor {
64 | if saturation > 0.0 {
65 | return CIColor(red: red, green: green, blue: blue, alpha: opacity, colorSpace: colorSpace) ?? .clear
66 | } else {
67 | return CIColor(cgColor: cgColor)
68 | }
69 | }
70 |
71 | public var cgColor: CGColor {
72 | if saturation > 0.0 {
73 | return CGColor(colorSpace: colorSpace, components: components) ?? CGColor(gray: 0.0, alpha: 0.0)
74 | } else {
75 | return CGColor(colorSpace: CGColorSpace(name: CGColorSpace.genericGrayGamma2_2)!, components: greyComponents) ?? CGColor(gray: 0.0, alpha: 0.0)
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor+sRGB.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Anton Heestand on 2021-11-01.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 |
11 | /// https://gist.github.com/adamgraham/263fac81705221a96e4719cb1ad36009
12 |
13 | extension PixelColor {
14 |
15 | public var sRGB: PixelColor {
16 | linearToSRGB()
17 | }
18 |
19 | func sRGBToLinear() -> PixelColor {
20 |
21 | let red = (red > 0.03928) ? pow((red + 0.055) / 1.055, 2.4) : (red / 12.92)
22 | let green = (green > 0.03928) ? pow((green + 0.055) / 1.055, 2.4) : (green / 12.92)
23 | let blue = (blue > 0.03928) ? pow((blue + 0.055) / 1.055, 2.4) : (blue / 12.92)
24 |
25 | return PixelColor(red: red, green: green, blue: blue, opacity: opacity)
26 | }
27 |
28 | func linearToSRGB() -> PixelColor {
29 |
30 | let k: CGFloat = 1.0 / 2.4
31 |
32 | let red = (red <= 0.00304) ? (12.92 * red) : (1.055 * pow(red, k) - 0.055)
33 | let green = (green <= 0.00304) ? (12.92 * green) : (1.055 * pow(green, k) - 0.055)
34 | let blue = (blue <= 0.00304) ? (12.92 * blue) : (1.055 * pow(blue, k) - 0.055)
35 |
36 | return PixelColor(red: red, green: green, blue: blue, opacity: opacity)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/PixelColor/PixelColor.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import AppKit
3 | #else
4 | import UIKit
5 | #endif
6 | import SwiftUI
7 | import CoreGraphics
8 |
9 | /// Pixel Color with `4` `CGFloat` channels.
10 | ///
11 | /// - Channels: ``red-swift.property``, ``green-swift.property``, ``blue-swift.property`` and ``opacity``.
12 | /// - Color conversions: ``init(hue:saturation:brightness:alpha:)``, ``hue`` and ``saturation``.
13 | /// - Hex conversion: ``init(hex:a:)`` and ``hex``.
14 | public struct PixelColor: Equatable, CustomStringConvertible, Sendable {
15 |
16 | public var red: CGFloat
17 | public var green: CGFloat
18 | public var blue: CGFloat
19 | public var opacity: CGFloat
20 |
21 | /// Opacity
22 | public var alpha: CGFloat {
23 | get {
24 | opacity
25 | }
26 | set {
27 | opacity = newValue
28 | }
29 | }
30 |
31 | public var description: String {
32 | func format(_ value: CGFloat) -> String {
33 | "\(round(value * 1_000) / 1_000)"
34 | }
35 | return "PixelColor(red: \(format(red)), green: \(format(green)), blue: \(format(blue)), opacity: \(format(opacity)))"
36 | }
37 |
38 |
39 | public var components: [CGFloat] {
40 | return [red, green, blue, opacity]
41 | }
42 |
43 | /// Red and alpha.
44 | public var greyComponents: [CGFloat] {
45 | return [red, opacity]
46 | }
47 |
48 | public var monochrome: PixelColor {
49 | PixelColor(red: brightness, green: brightness, blue: brightness, opacity: opacity)
50 | }
51 |
52 | // MARK: Life Cycle
53 |
54 | @available(*, deprecated, renamed: "init(red:green:blue:opacity:)")
55 | public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
56 | self.init(red: red, green: green, blue: blue, opacity: alpha)
57 | }
58 |
59 | public init(red: CGFloat, green: CGFloat, blue: CGFloat, opacity: CGFloat = 1.0) {
60 | self.red = red
61 | self.green = green
62 | self.blue = blue
63 | self.opacity = opacity
64 | }
65 |
66 | @available(*, deprecated, renamed: "init(white:opacity:)")
67 | public init(white: CGFloat, alpha: CGFloat) {
68 | self.init(white: white, opacity: alpha)
69 | }
70 |
71 | public init(white: CGFloat, opacity: CGFloat = 1.0) {
72 | red = white
73 | green = white
74 | blue = white
75 | self.opacity = opacity
76 | }
77 |
78 | @available(*, deprecated, renamed: "init(red255:green255:blue255:opacity255:)")
79 | public init(red255: Int, green255: Int, blue255: Int, alpha255: Int = 255) {
80 | self.init(
81 | red255: red255 < 0 ? 0 : red255 > 255 ? 255 : UInt8(red255),
82 | green255: green255 < 0 ? 0 : green255 > 255 ? 255 : UInt8(green255),
83 | blue255: blue255 < 0 ? 0 : blue255 > 255 ? 255 : UInt8(blue255),
84 | opacity255: alpha255 < 0 ? 0 : alpha255 > 255 ? 255 : UInt8(alpha255)
85 | )
86 | }
87 |
88 | public init(red255: UInt8, green255: UInt8, blue255: UInt8, opacity255: UInt8 = 255) {
89 | red = CGFloat(red255) / 255
90 | green = CGFloat(green255) / 255
91 | blue = CGFloat(blue255) / 255
92 | opacity = CGFloat(opacity255) / 255
93 | }
94 |
95 | #if os(iOS) || os(tvOS) || os(visionOS)
96 | public init(_ uiColor: UIColor) {
97 | let ciColor = CIColor(color: uiColor)
98 | red = ciColor.red
99 | green = ciColor.green
100 | blue = ciColor.blue
101 | opacity = ciColor.alpha
102 | }
103 | #endif
104 |
105 | #if os(macOS)
106 | public init(_ nsColor: NSColor) {
107 | let ciColor = CIColor(color: nsColor)
108 | red = ciColor?.red ?? 0.0
109 | green = ciColor?.green ?? 0.0
110 | blue = ciColor?.blue ?? 0.0
111 | opacity = ciColor?.alpha ?? 0.0
112 | }
113 | #endif
114 |
115 | public init(_ color: Color, convertToColorSpace: CGColorSpace? = nil) {
116 | if #available(iOS 17.0, tvOS 17.0, macOS 14.0, visionOS 1.0, *) {
117 | let cgColor: CGColor = color.resolve(in: .init()).cgColor
118 | self.init(cgColor, convertToColorSpace: convertToColorSpace)
119 | } else {
120 | guard let cgColor: CGColor = color.cgColor else {
121 | guard let pixelColor = Self.decode(color: color) else {
122 | self = .clear
123 | return
124 | }
125 | self = pixelColor
126 | return
127 | }
128 | self.init(cgColor, convertToColorSpace: convertToColorSpace)
129 | }
130 | }
131 |
132 | public init(_ cgColor: CGColor, convertToColorSpace: CGColorSpace? = nil) {
133 |
134 | var cgColor: CGColor = cgColor
135 |
136 | if let convertToColorSpace: CGColorSpace {
137 | if cgColor.colorSpace != convertToColorSpace {
138 | if let convertedCGColor: CGColor = cgColor.converted(to: convertToColorSpace, intent: .defaultIntent, options: nil) {
139 | cgColor = convertedCGColor
140 | }
141 | }
142 | }
143 |
144 | guard let components: [CGFloat] = cgColor.components else {
145 | self = .clear
146 | return
147 | }
148 |
149 | red = components.first!
150 | green = components.count == 4 ? components[1] : components.first!
151 | blue = components.count == 4 ? components[2] : components.first!
152 | opacity = components.last!
153 | }
154 |
155 | public init(_ ciColor: CIColor) {
156 | red = ciColor.red
157 | green = ciColor.green
158 | blue = ciColor.blue
159 | opacity = ciColor.alpha
160 | }
161 |
162 | // MARK: - Hue Saturation Brightness
163 |
164 | @available(*, deprecated, renamed: "init(hue:saturation:brightness:opacity:)")
165 | public init(hue: CGFloat, saturation: CGFloat = 1.0, brightness: CGFloat = 1.0, alpha: CGFloat) {
166 | self.init(hue: hue, saturation: saturation, brightness: brightness, opacity: alpha)
167 | }
168 |
169 | public init(hue: CGFloat, saturation: CGFloat = 1.0, brightness: CGFloat = 1.0, opacity: CGFloat = 1.0) {
170 | let color = PixelColor.rgb(h: hue, s: saturation, v: brightness)
171 | red = color.r
172 | green = color.g
173 | blue = color.b
174 | self.opacity = opacity
175 | }
176 |
177 | // MARK: - Hex
178 |
179 | public var hex: String {
180 | let hexInt: Int = (Int)(CGFloat(red)*255)<<16 | (Int)(CGFloat(green)*255)<<8 | (Int)(CGFloat(blue)*255)<<0
181 | return String(format:"%06x", hexInt).uppercased()
182 | }
183 |
184 | public var hexWithOpacity: String {
185 | let hexInt: Int = (Int)(CGFloat(red)*255)<<24 | (Int)(CGFloat(green)*255)<<16 | (Int)(CGFloat(blue)*255)<<8 | (Int)(CGFloat(opacity)*255)
186 | return String(format:"%08x", hexInt).uppercased()
187 | }
188 |
189 | @available(*, deprecated, renamed: "init(hex:opacity:)")
190 | public init?(hex: String, a: CGFloat) {
191 | self.init(hex: hex, opacity: a)
192 | }
193 |
194 | /// Create a Pixel Color from a hex like orange: `#ff8000`
195 | public init?(hex: String, opacity: CGFloat = 1.0) {
196 | guard hex != "" else {
197 | return nil
198 | }
199 | if hex.lowercased() == "f" {
200 | red = 1.0
201 | green = 1.0
202 | blue = 1.0
203 | self.opacity = opacity
204 | return
205 | } else if hex.lowercased() == "0" {
206 | red = 0.0
207 | green = 0.0
208 | blue = 0.0
209 | self.opacity = opacity
210 | return
211 | }
212 | var hex = hex
213 | func sub(txt: String, range: CountableRange) -> String {
214 | let start: String.Index = txt.index(txt.startIndex, offsetBy: range.lowerBound)
215 | let end: String.Index = txt.index(txt.startIndex, offsetBy: range.upperBound)
216 | return String(txt[start..> 16) / 255.0
242 | green = CGFloat((hexInt & 0x00FF00) >> 8) / 255.0
243 | blue = CGFloat((hexInt & 0x0000FF) >> 0) / 255.0
244 | self.opacity = opacity
245 | }
246 |
247 | /// Create a Pixel Color from a hex like translucent orange: `#ff800080`
248 | public init?(hexWithOpacity: String) {
249 | var hex: String = hexWithOpacity
250 | guard hex != "" else {
251 | return nil
252 | }
253 | if hex.lowercased() == "f" {
254 | red = 1.0
255 | green = 1.0
256 | blue = 1.0
257 | opacity = 1.0
258 | return
259 | } else if hex.lowercased() == "0" {
260 | red = 0.0
261 | green = 0.0
262 | blue = 0.0
263 | opacity = 0.0
264 | return
265 | }
266 | func sub(txt: String, range: CountableRange) -> String {
267 | let start: String.Index = txt.index(txt.startIndex, offsetBy: range.lowerBound)
268 | let end: String.Index = txt.index(txt.startIndex, offsetBy: range.upperBound)
269 | return String(txt[start..> 24) / 255.0
303 | green = CGFloat((hexInt & 0x00FF0000) >> 16) / 255.0
304 | blue = CGFloat((hexInt & 0x0000FF00) >> 8) / 255.0
305 | opacity = CGFloat(hexInt & 0x000000FF) / 255.0
306 | }
307 |
308 | // MARK: - Channel
309 |
310 | public init(channel: Channel) {
311 | red = 0
312 | green = 0
313 | blue = 0
314 | opacity = 0
315 | switch channel {
316 | case .red:
317 | red = 1
318 | case .green:
319 | green = 1
320 | case .blue:
321 | blue = 1
322 | case .alpha:
323 | opacity = 1
324 | }
325 | }
326 | }
327 |
--------------------------------------------------------------------------------