diff --git a/.gitignore b/.gitignore index 03e231b..5e48de6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ fastlane/screenshots/**/*.png fastlane/test_output iOSInjectionProject/ *.generated.swift +.env +.vscode/ diff --git a/README.md b/README.md index 84468b1..4e1ed68 100644 --- a/README.md +++ b/README.md @@ -109,4 +109,3 @@ SwiftDevKit is available under the MIT license. See the [LICENSE](LICENSE) file ## Acknowledgments -This project is maintained by [@owdax](https://github.com/owdax) and the SwiftDevKit Contributors. We appreciate all contributions, big and small. \ No newline at end of file diff --git a/Sources/SwiftDevKit/Conversion/Color+Conversion.swift b/Sources/SwiftDevKit/Conversion/Color+Conversion.swift new file mode 100644 index 0000000..23a629b --- /dev/null +++ b/Sources/SwiftDevKit/Conversion/Color+Conversion.swift @@ -0,0 +1,366 @@ +// Color+Conversion.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +/// This file provides comprehensive color conversion utilities for iOS and macOS. +/// It supports conversion between different color spaces including: +/// - RGB (Red, Green, Blue) +/// - HSL (Hue, Saturation, Lightness) +/// - HSV (Hue, Saturation, Value) +/// - CMYK (Cyan, Magenta, Yellow, Key/Black) +/// - Hex string representation +/// +/// All color components are normalized to the range 0-1, except for hue which uses 0-360 degrees. +/// The implementation handles color space conversion automatically and works consistently +/// across UIKit and AppKit. +/// +/// Example usage: +/// ```swift +/// // Create colors from different formats +/// let redFromHex = Color(hex: "#FF0000") +/// let greenFromHSL = Color(hsl: HSL(hue: 120, saturation: 1.0, lightness: 0.5)) +/// let blueFromHSV = Color(hsv: HSV(hue: 240, saturation: 1.0, value: 1.0)) +/// let yellowFromCMYK = Color(cmyk: CMYK(cyan: 0, magenta: 0, yellow: 1.0, key: 0)) +/// +/// // Convert colors to different formats +/// let color = Color.red +/// let hex = color.toHex() // "#FF0000" +/// let hsl = color.toHSL() // HSL(hue: 0, saturation: 1.0, lightness: 0.5) +/// let hsv = color.toHSV() // HSV(hue: 0, saturation: 1.0, value: 1.0) +/// let cmyk = color.toCMYK() // CMYK(cyan: 0, magenta: 1.0, yellow: 1.0, key: 0) +/// ``` + +import Foundation +#if canImport(UIKit) + import UIKit +#elseif canImport(AppKit) + import AppKit +#endif + +/// Represents HSL (Hue, Saturation, Lightness) color values +public struct HSL { + /// Hue value (0-360) + public let hue: CGFloat + /// Saturation value (0-1) + public let saturation: CGFloat + /// Lightness value (0-1) + public let lightness: CGFloat +} + +/// Represents HSV (Hue, Saturation, Value) color values +public struct HSV { + /// Hue value (0-360) + public let hue: CGFloat + /// Saturation value (0-1) + public let saturation: CGFloat + /// Value/Brightness value (0-1) + public let value: CGFloat +} + +/// Represents CMYK (Cyan, Magenta, Yellow, Key/Black) color values +public struct CMYK { + /// Cyan value (0-1) + public let cyan: CGFloat + /// Magenta value (0-1) + public let magenta: CGFloat + /// Yellow value (0-1) + public let yellow: CGFloat + /// Key/Black value (0-1) + public let key: CGFloat +} + +/// Represents RGB (Red, Green, Blue) color values with alpha +public struct RGB { + /// Red value (0-1) + public let red: CGFloat + /// Green value (0-1) + public let green: CGFloat + /// Blue value (0-1) + public let blue: CGFloat + /// Alpha value (0-1) + public let alpha: CGFloat +} + +public extension Color { + /// Helper method to get RGB components with color space conversion if needed + private func rgbComponents() -> RGB { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + #if canImport(AppKit) + guard let rgbColor = usingColorSpace(.sRGB) else { + return RGB(red: 0, green: 0, blue: 0, alpha: 1) + } + rgbColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + #else + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + #endif + + return RGB(red: red, green: green, blue: blue, alpha: alpha) + } + + /// Initialize a color from a hex string. + /// + /// Example: + /// ```swift + /// let color = Color(hex: "#FF0000") // Red color + /// let colorWithoutHash = Color(hex: "00FF00") // Green color + /// ``` + /// + /// - Parameter hex: A hex string in format "#RRGGBB" or "RRGGBB" + convenience init?(hex: String) { + var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines) + hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "") + + guard hexSanitized.count == 6 else { return nil } + + var rgb: UInt64 = 0 + guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil } + + let red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 + let green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 + let blue = CGFloat(rgb & 0x0000FF) / 255.0 + + self.init(red: red, green: green, blue: blue, alpha: 1.0) + } + + /// Convert color to hex string. + /// + /// Example: + /// ```swift + /// let color = Color.red + /// let hex = color.toHex() // Returns "#FF0000" + /// ``` + /// + /// - Parameter includeHash: Whether to include # prefix (default: true) + /// - Returns: Hex string representation of the color + func toHex(includeHash: Bool = true) -> String { + let components = rgbComponents() + let hex = String( + format: "%02X%02X%02X", + Int(components.red * 255), + Int(components.green * 255), + Int(components.blue * 255)) + + return includeHash ? "#\(hex)" : hex + } + + /// Convert RGB color to HSL (Hue, Saturation, Lightness). + /// + /// Example: + /// ```swift + /// let color = Color.red + /// let hsl = color.toHSL() // Returns HSL(hue: 0, saturation: 1.0, lightness: 0.5) + /// ``` + /// + /// - Returns: HSL color values + func toHSL() -> HSL { + let components = rgbComponents() + let red = components.red + let green = components.green + let blue = components.blue + + let maximum = max(red, green, blue) + let minimum = min(red, green, blue) + + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + let lightness = (maximum + minimum) / 2 + + let delta = maximum - minimum + + if delta != 0 { + saturation = lightness > 0.5 ? + delta / (2 - maximum - minimum) : + delta / (maximum + minimum) + + switch maximum { + case red: + hue = (green - blue) / delta + (green < blue ? 6 : 0) + case green: + hue = (blue - red) / delta + 2 + default: + hue = (red - green) / delta + 4 + } + + hue *= 60 + } + + return HSL(hue: hue, saturation: saturation, lightness: lightness) + } + + /// Initialize a color from HSL values. + /// + /// Example: + /// ```swift + /// let color = Color(hsl: HSL(hue: 0, saturation: 1.0, lightness: 0.5)) // Red color + /// ``` + /// + /// - Parameter hsl: HSL color values + convenience init(hsl: HSL) { + func hueToRGB(_ primary: CGFloat, _ secondary: CGFloat, _ third: CGFloat) -> CGFloat { + var hueComponent = third + if hueComponent < 0 { hueComponent += 1 } + if hueComponent > 1 { hueComponent -= 1 } + if hueComponent < 1 / 6 { return primary + (secondary - primary) * 6 * hueComponent } + if hueComponent < 1 / 2 { return secondary } + if hueComponent < 2 / 3 { return primary + (secondary - primary) * (2 / 3 - hueComponent) * 6 } + return primary + } + + let normalizedHue = hsl.hue / 360 + let secondaryComponent = hsl.lightness < 0.5 ? + hsl.lightness * (1 + hsl.saturation) : + hsl.lightness + hsl.saturation - hsl.lightness * hsl.saturation + let primaryComponent = 2 * hsl.lightness - secondaryComponent + + let red = hueToRGB(primaryComponent, secondaryComponent, normalizedHue + 1 / 3) + let green = hueToRGB(primaryComponent, secondaryComponent, normalizedHue) + let blue = hueToRGB(primaryComponent, secondaryComponent, normalizedHue - 1 / 3) + + self.init(red: red, green: green, blue: blue, alpha: 1.0) + } + + /// Convert RGB color to CMYK (Cyan, Magenta, Yellow, Key/Black). + /// + /// Example: + /// ```swift + /// let color = Color.red + /// let cmyk = color.toCMYK() // Returns CMYK(cyan: 0, magenta: 1.0, yellow: 1.0, key: 0) + /// ``` + /// + /// - Returns: CMYK color values + func toCMYK() -> CMYK { + let components = rgbComponents() + let red = components.red + let green = components.green + let blue = components.blue + + let key = 1 - max(red, green, blue) + guard key != 1 else { return CMYK(cyan: 0, magenta: 0, yellow: 0, key: 1) } + + let cyan = (1 - red - key) / (1 - key) + let magenta = (1 - green - key) / (1 - key) + let yellow = (1 - blue - key) / (1 - key) + + return CMYK(cyan: cyan, magenta: magenta, yellow: yellow, key: key) + } + + /// Initialize a color from CMYK values. + /// + /// Example: + /// ```swift + /// let color = Color(cmyk: CMYK(cyan: 0, magenta: 1.0, yellow: 1.0, key: 0)) // Red color + /// ``` + /// + /// - Parameter cmyk: CMYK color values + convenience init(cmyk: CMYK) { + let red = (1 - cmyk.cyan) * (1 - cmyk.key) + let green = (1 - cmyk.magenta) * (1 - cmyk.key) + let blue = (1 - cmyk.yellow) * (1 - cmyk.key) + + self.init(red: red, green: green, blue: blue, alpha: 1.0) + } + + /// Convert RGB color to HSV (Hue, Saturation, Value). + /// + /// Example: + /// ```swift + /// let color = Color.red + /// let hsv = color.toHSV() // Returns HSV(hue: 0, saturation: 1.0, value: 1.0) + /// ``` + /// + /// - Returns: HSV color values + func toHSV() -> HSV { + let components = rgbComponents() + let red = components.red + let green = components.green + let blue = components.blue + + let maximum = max(red, green, blue) + let minimum = min(red, green, blue) + let delta = maximum - minimum + + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + let value = maximum + + if delta != 0 { + saturation = delta / maximum + + switch maximum { + case red: + hue = (green - blue) / delta + (green < blue ? 6 : 0) + case green: + hue = (blue - red) / delta + 2 + default: + hue = (red - green) / delta + 4 + } + + hue *= 60 + } + + return HSV(hue: hue, saturation: saturation, value: value) + } + + /// Initialize a color from HSV values. + /// + /// Example: + /// ```swift + /// let color = Color(hsv: HSV(hue: 0, saturation: 1.0, value: 1.0)) // Red color + /// ``` + /// + /// - Parameter hsv: HSV color values + convenience init(hsv: HSV) { + let normalizedHue = hsv.hue / 60 + let integerPart = floor(normalizedHue) + let fractionalPart = normalizedHue - integerPart + + let minValue = hsv.value * (1 - hsv.saturation) + let decreasing = hsv.value * (1 - hsv.saturation * fractionalPart) + let increasing = hsv.value * (1 - hsv.saturation * (1 - fractionalPart)) + + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + + switch Int(integerPart) % 6 { + case 0: + red = hsv.value + green = increasing + blue = minValue + case 1: + red = decreasing + green = hsv.value + blue = minValue + case 2: + red = minValue + green = hsv.value + blue = increasing + case 3: + red = minValue + green = decreasing + blue = hsv.value + case 4: + red = increasing + green = minValue + blue = hsv.value + default: + red = hsv.value + green = minValue + blue = decreasing + } + + self.init(red: red, green: green, blue: blue, alpha: 1.0) + } +} + +#if canImport(UIKit) + public typealias Color = UIColor +#elseif canImport(AppKit) + public typealias Color = NSColor +#endif diff --git a/Tests/SwiftDevKitTests/Conversion/ColorConversionTests.swift b/Tests/SwiftDevKitTests/Conversion/ColorConversionTests.swift new file mode 100644 index 0000000..00b64d5 --- /dev/null +++ b/Tests/SwiftDevKitTests/Conversion/ColorConversionTests.swift @@ -0,0 +1,220 @@ +// ColorConversionTests.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import XCTest +@testable import SwiftDevKit + +final class ColorConversionTests: XCTestCase { + func testHexInitialization() { + // Test with hash prefix + let redColor = Color(hex: "#FF0000") + XCTAssertNotNil(redColor) + + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + redColor?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 1.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + XCTAssertEqual(alpha, 1.0, accuracy: 0.01) + + // Test without hash prefix + let greenColor = Color(hex: "00FF00") + XCTAssertNotNil(greenColor) + + greenColor?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 1.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + XCTAssertEqual(alpha, 1.0, accuracy: 0.01) + + // Test invalid hex strings + XCTAssertNil(Color(hex: "Invalid")) + XCTAssertNil(Color(hex: "FF00")) + XCTAssertNil(Color(hex: "#FF00")) + } + + func testToHex() { + let redColor = Color.red + XCTAssertEqual(redColor.toHex(), "#FF0000") + XCTAssertEqual(redColor.toHex(includeHash: false), "FF0000") + + let greenColor = Color.green + XCTAssertEqual(greenColor.toHex(), "#00FF00") + XCTAssertEqual(greenColor.toHex(includeHash: false), "00FF00") + + let blueColor = Color.blue + XCTAssertEqual(blueColor.toHex(), "#0000FF") + XCTAssertEqual(blueColor.toHex(includeHash: false), "0000FF") + } + + func testHSLConversion() { + // Test red color HSL values + let redColor = Color.red + let redHSL = redColor.toHSL() + XCTAssertEqual(redHSL.hue, 0.0, accuracy: 0.01) + XCTAssertEqual(redHSL.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(redHSL.lightness, 0.5, accuracy: 0.01) + + // Test green color HSL values + let greenColor = Color.green + let greenHSL = greenColor.toHSL() + XCTAssertEqual(greenHSL.hue, 120.0, accuracy: 0.01) + XCTAssertEqual(greenHSL.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(greenHSL.lightness, 0.5, accuracy: 0.01) + + // Test blue color HSL values + let blueColor = Color.blue + let blueHSL = blueColor.toHSL() + XCTAssertEqual(blueHSL.hue, 240.0, accuracy: 0.01) + XCTAssertEqual(blueHSL.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(blueHSL.lightness, 0.5, accuracy: 0.01) + } + + func testHSLInitialization() { + // Test creating red color from HSL + let redColor = Color(hsl: HSL(hue: 0, saturation: 1.0, lightness: 0.5)) + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + redColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 1.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating green color from HSL + let greenColor = Color(hsl: HSL(hue: 120, saturation: 1.0, lightness: 0.5)) + greenColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 1.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating blue color from HSL + let blueColor = Color(hsl: HSL(hue: 240, saturation: 1.0, lightness: 0.5)) + blueColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 1.0, accuracy: 0.01) + } + + func testCMYKConversion() { + // Test red color CMYK values + let redColor = Color.red + let redCMYK = redColor.toCMYK() + XCTAssertEqual(redCMYK.cyan, 0.0, accuracy: 0.01) + XCTAssertEqual(redCMYK.magenta, 1.0, accuracy: 0.01) + XCTAssertEqual(redCMYK.yellow, 1.0, accuracy: 0.01) + XCTAssertEqual(redCMYK.key, 0.0, accuracy: 0.01) + + // Test green color CMYK values + let greenColor = Color.green + let greenCMYK = greenColor.toCMYK() + XCTAssertEqual(greenCMYK.cyan, 1.0, accuracy: 0.01) + XCTAssertEqual(greenCMYK.magenta, 0.0, accuracy: 0.01) + XCTAssertEqual(greenCMYK.yellow, 1.0, accuracy: 0.01) + XCTAssertEqual(greenCMYK.key, 0.0, accuracy: 0.01) + + // Test blue color CMYK values + let blueColor = Color.blue + let blueCMYK = blueColor.toCMYK() + XCTAssertEqual(blueCMYK.cyan, 1.0, accuracy: 0.01) + XCTAssertEqual(blueCMYK.magenta, 1.0, accuracy: 0.01) + XCTAssertEqual(blueCMYK.yellow, 0.0, accuracy: 0.01) + XCTAssertEqual(blueCMYK.key, 0.0, accuracy: 0.01) + + // Test black color CMYK values + let blackColor = Color.black + let blackCMYK = blackColor.toCMYK() + XCTAssertEqual(blackCMYK.cyan, 0.0, accuracy: 0.01) + XCTAssertEqual(blackCMYK.magenta, 0.0, accuracy: 0.01) + XCTAssertEqual(blackCMYK.yellow, 0.0, accuracy: 0.01) + XCTAssertEqual(blackCMYK.key, 1.0, accuracy: 0.01) + } + + func testCMYKInitialization() { + // Test creating red color from CMYK + let redColor = Color(cmyk: CMYK(cyan: 0, magenta: 1.0, yellow: 1.0, key: 0)) + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + redColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 1.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating green color from CMYK + let greenColor = Color(cmyk: CMYK(cyan: 1.0, magenta: 0, yellow: 1.0, key: 0)) + greenColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 1.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating blue color from CMYK + let blueColor = Color(cmyk: CMYK(cyan: 1.0, magenta: 1.0, yellow: 0, key: 0)) + blueColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 1.0, accuracy: 0.01) + } + + func testHSVConversion() { + // Test red color HSV values + let redColor = Color.red + let redHSV = redColor.toHSV() + XCTAssertEqual(redHSV.hue, 0.0, accuracy: 0.01) + XCTAssertEqual(redHSV.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(redHSV.value, 1.0, accuracy: 0.01) + + // Test green color HSV values + let greenColor = Color.green + let greenHSV = greenColor.toHSV() + XCTAssertEqual(greenHSV.hue, 120.0, accuracy: 0.01) + XCTAssertEqual(greenHSV.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(greenHSV.value, 1.0, accuracy: 0.01) + + // Test blue color HSV values + let blueColor = Color.blue + let blueHSV = blueColor.toHSV() + XCTAssertEqual(blueHSV.hue, 240.0, accuracy: 0.01) + XCTAssertEqual(blueHSV.saturation, 1.0, accuracy: 0.01) + XCTAssertEqual(blueHSV.value, 1.0, accuracy: 0.01) + } + + func testHSVInitialization() { + // Test creating red color from HSV + let redColor = Color(hsv: HSV(hue: 0, saturation: 1.0, value: 1.0)) + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + redColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 1.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating green color from HSV + let greenColor = Color(hsv: HSV(hue: 120, saturation: 1.0, value: 1.0)) + greenColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 1.0, accuracy: 0.01) + XCTAssertEqual(blue, 0.0, accuracy: 0.01) + + // Test creating blue color from HSV + let blueColor = Color(hsv: HSV(hue: 240, saturation: 1.0, value: 1.0)) + blueColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + XCTAssertEqual(red, 0.0, accuracy: 0.01) + XCTAssertEqual(green, 0.0, accuracy: 0.01) + XCTAssertEqual(blue, 1.0, accuracy: 0.01) + } +} diff --git a/docs/Color+Conversion.md b/docs/Color+Conversion.md new file mode 100644 index 0000000..b803b9a --- /dev/null +++ b/docs/Color+Conversion.md @@ -0,0 +1,150 @@ +# Color Conversion Utilities + +SwiftDevKit provides a comprehensive set of color conversion utilities that work seamlessly across iOS and macOS platforms. These utilities enable easy conversion between different color spaces and formats commonly used in digital design and development. + +## Supported Color Spaces + +- RGB (Red, Green, Blue) +- HSL (Hue, Saturation, Lightness) +- HSV (Hue, Saturation, Value) +- CMYK (Cyan, Magenta, Yellow, Key/Black) +- Hex string representation + +## Value Ranges + +All color components are normalized to use consistent value ranges: + +- RGB components (red, green, blue): 0 to 1 +- HSL/HSV hue: 0 to 360 degrees +- HSL/HSV saturation: 0 to 1 +- HSL lightness: 0 to 1 +- HSV value: 0 to 1 +- CMYK components: 0 to 1 +- Alpha: 0 to 1 + +## Usage Examples + +### Creating Colors + +```swift +// From hex string +let redFromHex = Color(hex: "#FF0000") +let blueFromHex = Color(hex: "0000FF") // # is optional + +// From HSL +let greenFromHSL = Color(hsl: HSL( + hue: 120, // Green hue (120 degrees) + saturation: 1.0, // Full saturation + lightness: 0.5 // Medium lightness +)) + +// From HSV +let blueFromHSV = Color(hsv: HSV( + hue: 240, // Blue hue (240 degrees) + saturation: 1.0, // Full saturation + value: 1.0 // Maximum brightness +)) + +// From CMYK +let yellowFromCMYK = Color(cmyk: CMYK( + cyan: 0, // No cyan + magenta: 0, // No magenta + yellow: 1.0, // Full yellow + key: 0 // No black +)) +``` + +### Converting Colors + +```swift +let color = Color.red + +// To hex string +let hex = color.toHex() // "#FF0000" +let hexNoHash = color.toHex(includeHash: false) // "FF0000" + +// To HSL +let hsl = color.toHSL() +// HSL(hue: 0, saturation: 1.0, lightness: 0.5) + +// To HSV +let hsv = color.toHSV() +// HSV(hue: 0, saturation: 1.0, value: 1.0) + +// To CMYK +let cmyk = color.toCMYK() +// CMYK(cyan: 0, magenta: 1.0, yellow: 1.0, key: 0) +``` + +## Color Models + +### RGB Structure +```swift +public struct RGB { + public let red: CGFloat // 0-1 + public let green: CGFloat // 0-1 + public let blue: CGFloat // 0-1 + public let alpha: CGFloat // 0-1 +} +``` + +### HSL Structure +```swift +public struct HSL { + public let hue: CGFloat // 0-360 + public let saturation: CGFloat // 0-1 + public let lightness: CGFloat // 0-1 +} +``` + +### HSV Structure +```swift +public struct HSV { + public let hue: CGFloat // 0-360 + public let saturation: CGFloat // 0-1 + public let value: CGFloat // 0-1 +} +``` + +### CMYK Structure +```swift +public struct CMYK { + public let cyan: CGFloat // 0-1 + public let magenta: CGFloat // 0-1 + public let yellow: CGFloat // 0-1 + public let key: CGFloat // 0-1 +} +``` + +## Platform Compatibility + +The color conversion utilities work consistently across both iOS and macOS platforms: + +- iOS: Uses `UIColor` as the base color type +- macOS: Uses `NSColor` as the base color type + +The code automatically handles the appropriate color type through a type alias: + +```swift +#if canImport(UIKit) + public typealias Color = UIColor +#elseif canImport(AppKit) + public typealias Color = NSColor +#endif +``` + +## Error Handling + +- Hex string initialization returns an optional and fails gracefully if the string format is invalid +- Color space conversions handle edge cases and provide reasonable defaults +- Platform-specific color space conversions are handled automatically + +## Best Practices + +1. Always check if hex string initialization succeeded before using the color +2. Use the appropriate color space for your specific needs: + - HSL: When you need intuitive control over lightness + - HSV: When you need intuitive control over brightness/value + - CMYK: When working with print-related colors + - RGB/Hex: When working with web colors or digital displays +3. Consider color space conversion implications when targeting both iOS and macOS \ No newline at end of file