├── .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 | --------------------------------------------------------------------------------