From 7725899f980acc80a10895763b1002ad1748fd32 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 25 Oct 2026 20:23:47 +0800 Subject: [PATCH 1/2] feat: add CGColor stub --- Sources/OpenCoreGraphics/CGColor.swift | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Sources/OpenCoreGraphics/CGColor.swift diff --git a/Sources/OpenCoreGraphics/CGColor.swift b/Sources/OpenCoreGraphics/CGColor.swift new file mode 100644 index 0000000..ee7d1d1 --- /dev/null +++ b/Sources/OpenCoreGraphics/CGColor.swift @@ -0,0 +1,7 @@ +// +// CGColor.swift +// OpenCoreGraphics + +public import Foundation + +open class CGColor: NSObject {} From 1fec59d21a43706b239ffa6d93a03225f105de55 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 25 Oct 2026 20:49:27 +0800 Subject: [PATCH 2/2] feat: implement basic CGColor APIs --- Sources/OpenCoreGraphics/CGColor.swift | 82 ++++++++++- .../CGColorTests.swift | 132 ++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 Tests/OpenCoreGraphicsShimsTests/CGColorTests.swift diff --git a/Sources/OpenCoreGraphics/CGColor.swift b/Sources/OpenCoreGraphics/CGColor.swift index ee7d1d1..cf7dab4 100644 --- a/Sources/OpenCoreGraphics/CGColor.swift +++ b/Sources/OpenCoreGraphics/CGColor.swift @@ -4,4 +4,84 @@ public import Foundation -open class CGColor: NSObject {} +open class CGColor: NSObject { + private enum Space: Hashable { + case genericRGB + case sRGB + case genericGray + case genericGrayGamma2_2 + } + + private let space: Space + private let storedComponents: [CGFloat] + + public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + space = .genericRGB + storedComponents = [red, green, blue, alpha].map { min(max($0, 0), 1) } + super.init() + } + + public init(srgbRed red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + space = .sRGB + storedComponents = [red, green, blue, alpha].map { min(max($0, 0), 1) } + super.init() + } + + public init(gray: CGFloat, alpha: CGFloat) { + space = .genericGray + storedComponents = [gray, alpha].map { min(max($0, 0), 1) } + super.init() + } + + public init(genericGrayGamma2_2Gray gray: CGFloat, alpha: CGFloat) { + space = .genericGrayGamma2_2 + storedComponents = [gray, alpha].map { min(max($0, 0), 1) } + super.init() + } + + private init(space: Space, components: [CGFloat]) { + self.space = space + storedComponents = components + super.init() + } + + public class var black: CGColor { + CGColor(genericGrayGamma2_2Gray: 0, alpha: 1) + } + + public class var white: CGColor { + CGColor(genericGrayGamma2_2Gray: 1, alpha: 1) + } + + public class var clear: CGColor { + CGColor(genericGrayGamma2_2Gray: 0, alpha: 0) + } + + public var components: [CGFloat]? { storedComponents } + + public var numberOfComponents: Int { storedComponents.count } + + public var alpha: CGFloat { storedComponents[storedComponents.count - 1] } + + public func copy() -> CGColor? { self } + + public func copy(alpha: CGFloat) -> CGColor? { + var components = storedComponents + // Copying alpha does not apply the initializer's clamping. + components[components.count - 1] = alpha + return CGColor(space: space, components: components) + } + + open override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? CGColor else { return false } + return self === other + || (space == other.space && storedComponents == other.storedComponents) + } + + open override var hash: Int { + var hasher = Hasher() + hasher.combine(space) + hasher.combine(storedComponents) + return hasher.finalize() + } +} diff --git a/Tests/OpenCoreGraphicsShimsTests/CGColorTests.swift b/Tests/OpenCoreGraphicsShimsTests/CGColorTests.swift new file mode 100644 index 0000000..6cb5b23 --- /dev/null +++ b/Tests/OpenCoreGraphicsShimsTests/CGColorTests.swift @@ -0,0 +1,132 @@ +// +// CGColorTests.swift +// OpenCoreGraphicsShimsTests + +import Foundation +import OpenCoreGraphicsShims +import Testing +#if OPENCOREGRAPHICS_COREGRAPHICS +import class CoreGraphics.CGColor +#else +import class OpenCoreGraphics.CGColor +#endif + +@Suite +struct CGColorTests { + @Test + func rgbaComponents() { + let color = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + #expect(color.components == [0.25, 0.5, 0.75, 0.5]) + #expect(color.numberOfComponents == 4) + #expect(color.alpha == 0.5) + } + + @Test + func srgbComponents() { + let color = CGColor(srgbRed: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + #expect(color.components == [0.25, 0.5, 0.75, 0.5]) + #expect(color.numberOfComponents == 4) + #expect(color.alpha == 0.5) + } + + @Test(arguments: [CGFloat(-1), 0, 0.25, 1, 2, .infinity, -.infinity]) + func initializersClampComponents(value: CGFloat) { + let expected = min(max(value, 0), 1) + let rgb = CGColor(red: value, green: value, blue: value, alpha: value) + let srgb = CGColor(srgbRed: value, green: value, blue: value, alpha: value) + let gray = CGColor(gray: value, alpha: value) + let gammaGray = CGColor(genericGrayGamma2_2Gray: value, alpha: value) + #expect(rgb.components == Array(repeating: expected, count: 4)) + #expect(srgb.components == rgb.components) + #expect(gray.components == [expected, expected]) + #expect(gammaGray.components == gray.components) + } + + @Test + func initializersPreserveNaN() throws { + let color = CGColor(red: .nan, green: 0.5, blue: 0.75, alpha: .nan) + let components = try #require(color.components) + #expect(components[0].isNaN) + #expect(color.alpha.isNaN) + } + + @Test + func grayComponents() { + let color = CGColor(gray: 0.25, alpha: 0.5) + #expect(color.components == [0.25, 0.5]) + #expect(color.numberOfComponents == 2) + #expect(color.alpha == 0.5) + } + + @Test + func constantColors() { + #expect(CGColor.black.components == [0, 1]) + #expect(CGColor.white.components == [1, 1]) + #expect(CGColor.clear.components == [0, 0]) + #expect(CGColor.black == CGColor(genericGrayGamma2_2Gray: 0, alpha: 1)) + #expect(CGColor.white == CGColor(genericGrayGamma2_2Gray: 1, alpha: 1)) + #expect(CGColor.clear == CGColor(genericGrayGamma2_2Gray: 0, alpha: 0)) + } + + @Test + func copyPreservesColor() throws { + let color = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + let copy = try #require(color.copy()) + #expect(copy == color) + #expect(copy.components == color.components) + } + + @Test(arguments: [CGFloat(-1), 0, 0.25, 1, 2, .infinity, -.infinity]) + func copyReplacesAlphaWithoutClamping(alpha: CGFloat) throws { + let color = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + let copy = try #require(color.copy(alpha: alpha)) + #expect(copy.components == [0.25, 0.5, 0.75, alpha]) + #expect(copy.alpha == alpha) + #expect(color.alpha == 0.5) + } + + @Test + func copyPreservesGrayAndNaNAlpha() throws { + let color = CGColor(gray: 0.25, alpha: 0.5) + let copy = try #require(color.copy(alpha: .nan)) + #expect(copy.numberOfComponents == 2) + #expect(copy.components?.first == 0.25) + #expect(copy.alpha.isNaN) + #expect(color.alpha == 0.5) + } + + @Test + func copyPreservesColorKind() throws { + let color = CGColor(srgbRed: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + let copy = try #require(color.copy(alpha: 0.75)) + #expect(copy == CGColor(srgbRed: 0.25, green: 0.5, blue: 0.75, alpha: 0.75)) + #expect(copy != CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.75)) + } + + @Test + func returnedComponentsDoNotMutateColor() throws { + let color = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + var components = try #require(color.components) + components[0] = 1 + #expect(color.components == [0.25, 0.5, 0.75, 0.5]) + } + + @Test + func equalityAndHashUseColorValues() { + let color = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + let equal = CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.5) + #expect(color == equal) + #expect(color.hashValue == equal.hashValue) + #expect(Set([color, equal]).count == 1) + #expect(color != CGColor(red: 1, green: 0.5, blue: 0.75, alpha: 0.5)) + #expect(color != CGColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 1)) + } + + @Test + func differentColorKindsAreNotEqual() { + #expect(CGColor(red: 0, green: 0, blue: 0, alpha: 1) != CGColor(gray: 0, alpha: 1)) + #expect(CGColor(red: 0, green: 0, blue: 0, alpha: 1) != CGColor(srgbRed: 0, green: 0, blue: 0, alpha: 1)) + #expect(CGColor(gray: 0, alpha: 1) != CGColor.black) + } + +}