diff --git a/Package.resolved b/Package.resolved index 97bd322..e0d27a5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4fcdebfe2c90ba26fcba7ec801dbb697ae67f942fbae6db543f3f15c85ec2747", + "originHash" : "030dd1b5d51c353f6561859605d7a71043caed24528e27ea847be041a4ba12aa", "pins" : [ { "identity" : "darwinprivateframeworks", @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "ba41f455eb1f321921cfa15b8e0c0537faae418b" + "revision" : "0d5aa5bc8533ea6a2607a97c0fe0da54726a5148" } }, { diff --git a/Package.swift b/Package.swift index 91f85b8..79eb034 100644 --- a/Package.swift +++ b/Package.swift @@ -176,6 +176,7 @@ var sharedCxxSettings: [CXXSetting] = [ ] var sharedSwiftSettings: [SwiftSetting] = [ .swiftLanguageMode(.v5), + .enableUpcomingFeature("InternalImportsByDefault"), ] if libraryEvolutionCondition { // NOTE: -enable-library-evolution will cause module verify failure for `swift build`. @@ -245,7 +246,7 @@ let openRenderBoxTestsTarget = Target.testTarget( let openRenderBoxCompatibilityTestTarget = Target.testTarget( name: "OpenRenderBoxCompatibilityTests", dependencies: [ - .product(name: "RealModule", package: "swift-numerics"), + .product(name: "Numerics", package: "swift-numerics"), ], exclude: ["README.md"], cSettings: sharedCSettings + [.define("SWIFT_TESTING")], diff --git a/Sources/OpenRenderBoxCxx/Color/ColorSpace.cpp b/Sources/OpenRenderBoxCxx/Color/ColorSpace.cpp new file mode 100644 index 0000000..dedb13c --- /dev/null +++ b/Sources/OpenRenderBoxCxx/Color/ColorSpace.cpp @@ -0,0 +1,148 @@ +// +// ColorSpace.cpp +// OpenRenderBox + +#include + +#if ORB_TARGET_OS_DARWIN + +#include + +namespace ORB { + +std::optional color_space_from_cg_name(CFStringRef name) { + if (name == nullptr) { + return std::nullopt; + } else if (CFEqual(name, kCGColorSpaceSRGB) || + CFEqual(name, kCGColorSpaceExtendedSRGB)) { + return ColorSpace::SRGB; + } else if (CFEqual(name, kCGColorSpaceLinearSRGB) || + CFEqual(name, kCGColorSpaceExtendedLinearSRGB)) { + return ColorSpace::LinearSRGB; + } else if (CFEqual(name, kCGColorSpaceDisplayP3) || + CFEqual(name, kCGColorSpaceExtendedDisplayP3)) { + return ColorSpace::DisplayP3; + } else if (CFEqual(name, kCGColorSpaceLinearDisplayP3) || + CFEqual(name, kCGColorSpaceExtendedLinearDisplayP3)) { + return ColorSpace::LinearDisplayP3; + } else { + return std::nullopt; + } +} + +std::optional color_space_from_cg(CGColorSpaceRef colorSpace) { + if (colorSpace == nullptr) { + return std::nullopt; + } + CFStringRef name = CGColorSpaceGetName(colorSpace); + return color_space_from_cg_name(name); +} + +CGColorSpaceRef cg_color_space(ColorSpace colorSpace, bool extended) { + switch (colorSpace) { + case ColorSpace::LinearSRGB: + return extended ? extended_linear_srgb_colorspace() : linear_srgb_colorspace(); + case ColorSpace::SRGB: + return extended ? extended_srgb_colorspace() : srgb_colorspace(); + case ColorSpace::LinearDisplayP3: + return extended ? extended_linear_display_p3_colorspace() : linear_display_p3_colorspace(); + case ColorSpace::DisplayP3: + return extended ? extended_display_p3_colorspace() : display_p3_colorspace(); + case ColorSpace::PQ: + return pq_colorspace(); + case ColorSpace::Unknown: + abort(); + } +} + +CGColorSpaceRef linear_srgb_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB); + return colorSpace; +} + +CGColorSpaceRef extended_linear_srgb_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB); + return colorSpace; +} + +CGColorSpaceRef srgb_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + return colorSpace; +} + +CGColorSpaceRef extended_srgb_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB); + return colorSpace; +} + +CGColorSpaceRef linear_display_p3_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceLinearDisplayP3); + return colorSpace; +} + +CGColorSpaceRef extended_linear_display_p3_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3); + return colorSpace; +} + +CGColorSpaceRef display_p3_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3); + return colorSpace; +} + +CGColorSpaceRef extended_display_p3_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedDisplayP3); + return colorSpace; +} + +CGColorSpaceRef pq_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ); + return colorSpace; +} + +CGColorSpaceRef gray_colorspace() { + static CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2); + return colorSpace; +} + +} /* namespace ORB */ + +std::optional orb_color_space(ORBColorSpace orbColorSpace) { + switch (orbColorSpace) { + case ORBColorSpaceDefault: + return std::nullopt; + case ORBColorSpaceSRGB: + return ORB::ColorSpace::SRGB; + case ORBColorSpaceLinearSRGB: + return ORB::ColorSpace::LinearSRGB; + case ORBColorSpaceDisplayP3: + return ORB::ColorSpace::DisplayP3; + case ORBColorSpaceLinearDisplayP3: + return ORB::ColorSpace::LinearDisplayP3; + default: + return std::nullopt; + } +} + +ORBColorSpace orb_color_space(std::optional colorSpace) { + if (!colorSpace.has_value()) { + return ORBColorSpaceDefault; + } + switch (colorSpace.value()) { + case ORB::ColorSpace::LinearSRGB: + return ORBColorSpaceLinearSRGB; + case ORB::ColorSpace::SRGB: + return ORBColorSpaceSRGB; + case ORB::ColorSpace::LinearDisplayP3: + return ORBColorSpaceLinearDisplayP3; + case ORB::ColorSpace::DisplayP3: + return ORBColorSpaceDisplayP3; + case ORB::ColorSpace::Unknown: + case ORB::ColorSpace::PQ: + return ORBColorSpaceDefault; + default: + return ORBColorSpaceLinearSRGB; + } +} + +#endif /* ORB_TARGET_OS_DARWIN */ diff --git a/Sources/OpenRenderBoxCxx/Color/ORBColor.cpp b/Sources/OpenRenderBoxCxx/Color/ORBColor.cpp new file mode 100644 index 0000000..655d9dd --- /dev/null +++ b/Sources/OpenRenderBoxCxx/Color/ORBColor.cpp @@ -0,0 +1,143 @@ +// +// ORBColor.cpp +// OpenRenderBox + +#include +#include +#include + +using namespace ORB; + +// Global color constants +const ORBColor ORBColorClear = { 0.0f, 0.0f, 0.0f, 0.0f }; +const ORBColor ORBColorBlack = { 0.0f, 0.0f, 0.0f, 1.0f }; +const ORBColor ORBColorWhite = { 1.0f, 1.0f, 1.0f, 1.0f }; +const ORBColor ORBColorNull = { ORBColorInvalidComponent, ORBColorInvalidComponent, ORBColorInvalidComponent, ORBColorInvalidComponent }; +const float ORBColorInvalidComponent = -32768.0f; + +// sRGB to linear constants +static const float kSRGBToLinearThreshold = 0.04045f; +static const float kSRGBToLinearScale = 12.92f; +static const float kSRGBToLinearGammaOffset = 0.055f; +static const float kSRGBToLinearGammaScale = 1.055f; +static const float kSRGBToLinearGamma = 2.4f; + +// Linear to sRGB constants +static const float kLinearToSRGBThreshold = 0.0031308f; +static const float kLinearToSRGBScale = 12.92f; +static const float kLinearToSRGBGammaScale = 1.055f; +static const float kLinearToSRGBGammaOffset = 0.055f; +static const float kLinearToSRGBGamma = 1.0f / 2.4f; + +static inline float sRGBToLinear(float value) { + float absValue = value > 0.0f ? value : -value; + float result; + if (absValue <= kSRGBToLinearThreshold) { + result = absValue / kSRGBToLinearScale; + } else { + result = powf((absValue + kSRGBToLinearGammaOffset) / kSRGBToLinearGammaScale, kSRGBToLinearGamma); + } + return value < 0.0f ? -result : result; +} + +static inline float linearToSRGB(float value) { + float absValue = value > 0.0f ? value : -value; + float result; + if (absValue <= kLinearToSRGBThreshold) { + result = absValue * kLinearToSRGBScale; + } else { + result = kLinearToSRGBGammaScale * powf(absValue, kLinearToSRGBGamma) - kLinearToSRGBGammaOffset; + } + return value < 0.0f ? -result : result; +} + +ORBColor ORBColorMake(float red, float green, float blue, float alpha) { + return (ORBColor){ red, green, blue, alpha }; +} + +ORBColor ORBColorMakeLinear(float red, float green, float blue, float alpha) { + return (ORBColor){ + sRGBToLinear(red), + sRGBToLinear(green), + sRGBToLinear(blue), + alpha + }; +} + +ORBColor ORBColorToLinear(ORBColor color) { + return (ORBColor){ + sRGBToLinear(color.red), + sRGBToLinear(color.green), + sRGBToLinear(color.blue), + color.alpha + }; +} + +ORBColor ORBColorFromLinear(ORBColor color) { + return (ORBColor){ + linearToSRGB(color.red), + linearToSRGB(color.green), + linearToSRGB(color.blue), + color.alpha + }; +} + +bool ORBColorEqualToColor(ORBColor lhs, ORBColor rhs) { + return lhs.red == rhs.red && + lhs.green == rhs.green && + lhs.blue == rhs.blue && + lhs.alpha == rhs.alpha; +} + +#if ORB_TARGET_OS_DARWIN + +#include + +ORBColor ORBColorFromComponents(CGColorSpaceRef colorSpace, const CGFloat *components, bool premultiplied) { + size_t componentCount = premultiplied ? 2 : 1; + return ORBColorFromComponents2(colorSpace, components, componentCount); +} + +ORBColor ORBColorFromComponents2(CGColorSpaceRef colorSpace, const CGFloat *components, size_t componentCount) { + // TODO: Implement proper color space conversion + (void)colorSpace; + (void)componentCount; + return (ORBColor){ + static_cast(components[0]), + static_cast(components[1]), + static_cast(components[2]), + static_cast(components[3]) + }; +} + +ORBColor ORBColorFromCGColor(CGColorRef color, bool premultiplied) { + CGColorSpaceRef colorSpace = CGColorGetColorSpace(color); + const CGFloat *components = CGColorGetComponents(color); + size_t componentCount = premultiplied ? 2 : 1; + return ORBColorFromComponents2(colorSpace, components, componentCount); +} + +ORBColor ORBColorFromCGColor2(CGColorRef color, size_t componentCount) { + CGColorSpaceRef colorSpace = CGColorGetColorSpace(color); + const CGFloat *components = CGColorGetComponents(color); + return ORBColorFromComponents2(colorSpace, components, componentCount); +} + +CGColorRef ORBColorCopyCGColor(ORBColor color, ORBColorSpace orbColorSpace) { + CGFloat components[4] = { + static_cast(color.red), + static_cast(color.green), + static_cast(color.blue), + static_cast(color.alpha) + }; + bool isRedOutOfRange = (color.red < 0.0f || color.red > 1.0f); + bool isGreenOutOfRange = (color.green < 0.0f || color.green > 1.0f); + bool isBlueOutOfRange = (color.blue < 0.0f || color.blue > 1.0f); + ORB::ColorSpace colorSpace = orb_color_space(orbColorSpace).value_or(ORB::ColorSpace::LinearSRGB); + bool extended = isRedOutOfRange || isGreenOutOfRange || isBlueOutOfRange; + CGColorSpaceRef cgColorSpace = ORB::cg_color_space(colorSpace, extended); + CGColorRef cgColor = CGColorCreate(cgColorSpace, components); + return cgColor; +} + +#endif /* ORB_TARGET_OS_DARWIN */ diff --git a/Sources/OpenRenderBoxCxx/Color/ORBColorMode.cpp b/Sources/OpenRenderBoxCxx/Color/ORBColorMode.cpp new file mode 100644 index 0000000..c7c3ed5 --- /dev/null +++ b/Sources/OpenRenderBoxCxx/Color/ORBColorMode.cpp @@ -0,0 +1,42 @@ +// +// ORBColorMode.cpp +// OpenRenderBox + +#include + +ORBColorSpace ORBColorModeWorkingColorSpace(ORBColorMode mode) { + switch (mode) { + case ORBColorMode0: + case ORBColorMode3: + case ORBColorMode4: + case ORBColorMode5: + case ORBColorMode9: + case ORBColorMode11: + case ORBColorMode12: + case ORBColorMode14: + return ORBColorSpaceSRGB; + case ORBColorMode1: + case ORBColorMode2: + case ORBColorMode6: + case ORBColorMode7: + case ORBColorMode8: + case ORBColorMode10: + case ORBColorMode13: + case ORBColorMode15: + return ORBColorSpaceLinearSRGB; + default: + return ORBColorSpaceDefault; + } +} + +bool ORBColorModeHasExtendedRange(ORBColorMode mode) { + switch (mode) { + case ORBColorMode2: + case ORBColorMode11: + case ORBColorMode12: + case ORBColorMode13: + return true; + default: + return false; + } +} \ No newline at end of file diff --git a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColor.h b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColor.h index 4683655..557ed27 100644 --- a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColor.h +++ b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColor.h @@ -5,18 +5,48 @@ #pragma once #include +#include +#if ORB_TARGET_OS_DARWIN +#include +#endif ORB_ASSUME_NONNULL_BEGIN ORB_EXTERN_C_BEGIN +typedef float ORBColorComponent; + typedef struct ORBColor { - float red; - float green; - float blue; - float alpha; + ORBColorComponent red; + ORBColorComponent green; + ORBColorComponent blue; + ORBColorComponent alpha; } ORBColor; +ORB_EXPORT const ORBColor ORBColorClear ORB_SWIFT_NAME(ORBColor.clear); +ORB_EXPORT const ORBColor ORBColorBlack ORB_SWIFT_NAME(ORBColor.black); +ORB_EXPORT const ORBColor ORBColorWhite ORB_SWIFT_NAME(ORBColor.white); +ORB_EXPORT const ORBColor ORBColorNull ORB_SWIFT_NAME(ORBColor.null); +ORB_EXPORT const ORBColorComponent ORBColorInvalidComponent ORB_SWIFT_NAME(ORBColor.invalidComponent); + +ORB_EXPORT ORBColor ORBColorMake(float red, float green, float blue, float alpha) ORB_SWIFT_NAME(ORBColor.init(red:green:blue:alpha:)); +ORB_EXPORT ORBColor ORBColorMakeLinear(float red, float green, float blue, float alpha) ORB_SWIFT_NAME(ORBColor.init(linearRed:green:blue:alpha:)); +ORB_EXPORT ORBColor ORBColorToLinear(ORBColor color) ORB_SWIFT_NAME(getter:ORBColor.linear(self:)); +ORB_EXPORT ORBColor ORBColorFromLinear(ORBColor color) ORB_SWIFT_NAME(ORBColor.fromLinear(self:)); +ORB_EXPORT bool ORBColorEqualToColor(ORBColor lhs, ORBColor rhs) ORB_SWIFT_NAME(ORBColor.isEqual(self:to:)); + +#if ORB_TARGET_OS_DARWIN + +// TODO: Verify the interface +ORB_EXPORT ORBColor ORBColorFromComponents(CGColorSpaceRef colorSpace, const CGFloat *components, bool premultiplied) ORB_SWIFT_NAME(ORBColor.init(colorSpace:components:premultiplied:)); +ORB_EXPORT ORBColor ORBColorFromComponents2(CGColorSpaceRef colorSpace, const CGFloat *components, size_t componentCount) ORB_SWIFT_NAME(ORBColor.init(colorSpace:components:componentCount:)); +ORB_EXPORT ORBColor ORBColorFromCGColor(CGColorRef color, bool premultiplied) ORB_SWIFT_NAME(ORBColor.init(_:premultiplied:)); +ORB_EXPORT ORBColor ORBColorFromCGColor2(CGColorRef color, size_t componentCount) ORB_SWIFT_NAME(ORBColor.init(_:componentCount:)); + +ORB_EXPORT CGColorRef _Nullable ORBColorCopyCGColor(ORBColor color, ORBColorSpace orbColorSpace) CF_RETURNS_RETAINED ORB_SWIFT_NAME(ORBColor.cgColor(self:colorSpace:)); + +#endif /* ORB_TARGET_OS_DARWIN */ + ORB_EXTERN_C_END ORB_ASSUME_NONNULL_END diff --git a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorMode.h b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorMode.h new file mode 100644 index 0000000..516d8d2 --- /dev/null +++ b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorMode.h @@ -0,0 +1,40 @@ +// +// ORBColorMode.h +// OpenRenderBox + +#pragma once + +#include +#include + +ORB_ASSUME_NONNULL_BEGIN + +ORB_EXTERN_C_BEGIN + +// TODO: Review those names later +typedef ORB_ENUM(uint32_t, ORBColorMode) { + ORBColorMode0 = 0, + ORBColorMode1 = 1, + ORBColorMode2 = 2, + ORBColorMode3 = 3, + ORBColorMode4 = 4, + ORBColorMode5 = 5, + ORBColorMode6 = 6, + ORBColorMode7 = 7, + ORBColorMode8 = 8, + ORBColorMode9 = 9, + ORBColorMode10 = 10, + ORBColorMode11 = 11, + ORBColorMode12 = 12, + ORBColorMode13 = 13, + ORBColorMode14 = 14, + ORBColorMode15 = 15, +} ORB_SWIFT_NAME(ORBColor.Mode); + +ORB_EXPORT ORBColorSpace ORBColorModeWorkingColorSpace(ORBColorMode mode) ORB_SWIFT_NAME(getter:ORBColorMode.workingColorSpace(self:)); +ORB_EXPORT bool ORBColorModeHasExtendedRange(ORBColorMode mode) ORB_SWIFT_NAME(getter:ORBColorMode.hasExtendedRange(self:)); + +ORB_EXTERN_C_END + +ORB_ASSUME_NONNULL_END + diff --git a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorSpace.h b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorSpace.h new file mode 100644 index 0000000..5382768 --- /dev/null +++ b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/ORBColorSpace.h @@ -0,0 +1,24 @@ +// +// ORBColorSpace.h +// OpenRenderBox + +#pragma once + +#include + +ORB_ASSUME_NONNULL_BEGIN + +ORB_EXTERN_C_BEGIN + +typedef ORB_ENUM(uint32_t, ORBColorSpace) { + ORBColorSpaceDefault = 0, + ORBColorSpaceSRGB = 1, + ORBColorSpaceLinearSRGB = 2, + ORBColorSpaceDisplayP3 = 3, + ORBColorSpaceLinearDisplayP3 = 4, +} ORB_SWIFT_NAME(ORBColor.Space); + +ORB_EXTERN_C_END + +ORB_ASSUME_NONNULL_END + diff --git a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/OpenRenderBox.h b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/OpenRenderBox.h index 58b753f..07d626a 100644 --- a/Sources/OpenRenderBoxCxx/include/OpenRenderBox/OpenRenderBox.h +++ b/Sources/OpenRenderBoxCxx/include/OpenRenderBox/OpenRenderBox.h @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include diff --git a/Sources/OpenRenderBoxCxx/include/OpenRenderBoxCxx/Color/ColorSpace.hpp b/Sources/OpenRenderBoxCxx/include/OpenRenderBoxCxx/Color/ColorSpace.hpp new file mode 100644 index 0000000..241ef7c --- /dev/null +++ b/Sources/OpenRenderBoxCxx/include/OpenRenderBoxCxx/Color/ColorSpace.hpp @@ -0,0 +1,79 @@ +// +// ColorSpace.hpp +// OpenRenderBox + +#pragma once + +#include + +#if ORB_TARGET_OS_DARWIN +#include +#include +#endif /* ORB_TARGET_OS_DARWIN */ +#include + +namespace ORB { + +/// Internal color space representation used by ORB::cg_color_space +enum class ColorSpace : uint32_t { + LinearSRGB = 0, + SRGB = 1, + LinearDisplayP3 = 2, + DisplayP3 = 3, + Unknown = 4, + PQ = 5, +}; + +#if ORB_TARGET_OS_DARWIN +/// Converts a CGColorSpace name (CFStringRef) to internal ColorSpace enum. +/// @param name The color space name from CGColorSpaceGetName. +/// @return The ColorSpace if recognized, or std::nullopt if not. +std::optional color_space_from_cg_name(CFStringRef name); + +/// Converts a CGColorSpaceRef to internal ColorSpace enum. +/// @param colorSpace The CGColorSpace to convert. +/// @return The ColorSpace if recognized, or std::nullopt if not. +std::optional color_space_from_cg(CGColorSpaceRef colorSpace); + +/// Returns a CGColorSpaceRef for the given internal color space. +/// @param colorSpace The internal color space enum value. +/// @param extended If true, returns the extended range variant of the color space. +/// @return A CGColorSpaceRef. The caller does not own this reference. +CGColorSpaceRef cg_color_space(ColorSpace colorSpace, bool extended); + +/// Returns the linear sRGB color space. +CGColorSpaceRef linear_srgb_colorspace(); + +/// Returns the extended linear sRGB color space. +CGColorSpaceRef extended_linear_srgb_colorspace(); + +/// Returns the sRGB color space. +CGColorSpaceRef srgb_colorspace(); + +/// Returns the extended sRGB color space. +CGColorSpaceRef extended_srgb_colorspace(); + +/// Returns the linear Display P3 color space. +CGColorSpaceRef linear_display_p3_colorspace(); + +/// Returns the extended linear Display P3 color space. +CGColorSpaceRef extended_linear_display_p3_colorspace(); + +/// Returns the Display P3 color space. +CGColorSpaceRef display_p3_colorspace(); + +/// Returns the extended Display P3 color space. +CGColorSpaceRef extended_display_p3_colorspace(); + +/// Returns the PQ (HDR) color space. +CGColorSpaceRef pq_colorspace(); + +/// Returns the gray color space. +CGColorSpaceRef gray_colorspace(); +#endif /* ORB_TARGET_OS_DARWIN */ + +} /* namespace ORB */ + +std::optional orb_color_space(ORBColorSpace orbColorSpace); + +ORBColorSpace orb_color_space(std::optional colorSpace); \ No newline at end of file diff --git a/Sources/OpenRenderBoxCxx/include/module.modulemap b/Sources/OpenRenderBoxCxx/include/module.modulemap index 1d90804..f5fac40 100644 --- a/Sources/OpenRenderBoxCxx/include/module.modulemap +++ b/Sources/OpenRenderBoxCxx/include/module.modulemap @@ -7,6 +7,12 @@ module OpenRenderBoxCxx.C { export * } +module OpenRenderBoxCxx.Color { + requires cplusplus + umbrella "OpenRenderBoxCxx/Color" + export * +} + module OpenRenderBoxCxx.Path { requires cplusplus umbrella "OpenRenderBoxCxx/Path" diff --git a/Tests/OpenRenderBoxCompatibilityTests/ColorTests.swift b/Tests/OpenRenderBoxCompatibilityTests/ColorTests.swift new file mode 100644 index 0000000..b3d1685 --- /dev/null +++ b/Tests/OpenRenderBoxCompatibilityTests/ColorTests.swift @@ -0,0 +1,206 @@ +// +// ColorTests.swift +// OpenRenderBoxCompatibilityTests + +import Testing +import Numerics +#if canImport(CoreGraphics) +import CoreGraphics +#endif + +@Suite +struct ColorTests { + // MARK: - ORBColorMake + + @Test("ORBColorMake", arguments: [ + (Float(0.0), Float(0.0), Float(0.0), Float(1.0), ORBColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)), + (Float(1.0), Float(1.0), Float(1.0), Float(1.0), ORBColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)), + (Float(0.5), Float(0.25), Float(0.75), Float(0.8), ORBColor(red: 0.5, green: 0.25, blue: 0.75, alpha: 0.8)), + (Float(0.2), Float(0.4), Float(0.6), Float(0.0), ORBColor(red: 0.2, green: 0.4, blue: 0.6, alpha: 0.0)), + ]) + func colorMake(red: Float, green: Float, blue: Float, alpha: Float, expectColor: ORBColor) { + let color = ORBColor(red: red, green: green, blue: blue, alpha: alpha) + #expect(color.red == expectColor.red) + #expect(color.green == expectColor.green) + #expect(color.blue == expectColor.blue) + #expect(color.alpha == expectColor.alpha) + } + + // MARK: - ORBColorMakeLinear + + @Test("ORBColorMakeLinear", arguments: [ + (Float(0.5), Float(0.25), Float(0.75), Float(1.0), ORBColor(linearRed: 0.5, green: 0.25, blue: 0.75, alpha: 1.0)), + (Float(0.2), Float(0.4), Float(0.6), Float(0.5), ORBColor(linearRed: 0.2, green: 0.4, blue: 0.6, alpha: 0.5)), + ]) + func colorMakeLinear(red: Float, green: Float, blue: Float, alpha: Float, expectColor: ORBColor) { + let color = ORBColor(linearRed: red, green: green, blue: blue, alpha: alpha) + #expect(color.red == expectColor.red) + #expect(color.green == expectColor.green) + #expect(color.blue == expectColor.blue) + #expect(color.alpha == expectColor.alpha) + } + + // MARK: - ORBColorToLinear + + @Test("ORBColorToLinear", arguments: [ + (Float(0.5), Float(0.25), Float(0.75), Float(0.8)), + (Float(0.3), Float(0.6), Float(0.9), Float(1.0)), + ]) + func colorToLinear(red: Float, green: Float, blue: Float, alpha: Float) { + let srgbColor = ORBColor(red: red, green: green, blue: blue, alpha: alpha) + let linearColor = srgbColor.linear + #expect(linearColor.alpha == srgbColor.alpha) + #expect(!linearColor.red.isApproximatelyEqual(to: srgbColor.red)) + #expect(!linearColor.green.isApproximatelyEqual(to: srgbColor.green)) + #expect(!linearColor.blue.isApproximatelyEqual(to: srgbColor.blue)) + } + + // MARK: - ORBColorFromLinear + + @Test("ORBColorFromLinear", arguments: [ + (Float(0.2), Float(0.1), Float(0.5), Float(0.9)), + (Float(0.05), Float(0.15), Float(0.25), Float(1.0)), + ]) + func colorFromLinear(red: Float, green: Float, blue: Float, alpha: Float) { + let linearColor = ORBColor(red: red, green: green, blue: blue, alpha: alpha) + let srgbColor = linearColor.fromLinear() + #expect(srgbColor.alpha == linearColor.alpha) + #expect(!srgbColor.red.isApproximatelyEqual(to: linearColor.red)) + #expect(!srgbColor.green.isApproximatelyEqual(to: linearColor.green)) + #expect(!srgbColor.blue.isApproximatelyEqual(to: linearColor.blue)) + } + + // MARK: - Round Trip (toLinear -> fromLinear) + + @Test("ORBColor Round Trip", arguments: [ + (Float(0.5), Float(0.25), Float(0.75), Float(1.0)), + (Float(0.0), Float(0.0), Float(0.0), Float(1.0)), + (Float(1.0), Float(1.0), Float(1.0), Float(0.5)), + (Float(0.3), Float(0.6), Float(0.9), Float(0.8)), + ]) + func colorRoundTrip(red: Float, green: Float, blue: Float, alpha: Float) { + let originalColor = ORBColor(red: red, green: green, blue: blue, alpha: alpha) + let linearColor = originalColor.linear + let roundTripColor = linearColor.fromLinear() + + #expect(roundTripColor.red.isApproximatelyEqual(to: originalColor.red)) + #expect(roundTripColor.green.isApproximatelyEqual(to: originalColor.green)) + #expect(roundTripColor.blue.isApproximatelyEqual(to: originalColor.blue)) + #expect(roundTripColor.alpha == originalColor.alpha) + } + + // MARK: - ORBColorEqualToColor + + @Test("ORBColorEqualToColor - equal") + func colorEqualToColor_equal() { + let color1 = ORBColor(red: 0.5, green: 0.25, blue: 0.75, alpha: 1.0) + let color2 = ORBColor(red: 0.5, green: 0.25, blue: 0.75, alpha: 1.0) + #expect(color1.isEqual(to: color2)) + } + + @Test("ORBColorEqualToColor - not equal", arguments: [ + (Float(0.6), Float(0.25), Float(0.75), Float(1.0)), + (Float(0.5), Float(0.35), Float(0.75), Float(1.0)), + (Float(0.5), Float(0.25), Float(0.85), Float(1.0)), + (Float(0.5), Float(0.25), Float(0.75), Float(0.5)), + ]) + func colorEqualToColor_notEqual(r2: Float, g2: Float, b2: Float, a2: Float) { + let color1 = ORBColor(red: 0.5, green: 0.25, blue: 0.75, alpha: 1.0) + let color2 = ORBColor(red: r2, green: g2, blue: b2, alpha: a2) + #expect(!color1.isEqual(to: color2)) + } + + // MARK: - Color Constants + + @Test("ORBColor Constant", arguments: [ + (ORBColor.clear, Float(0.0), Float(0.0), Float(0.0), Float(0.0)), + (ORBColor.black, Float(0.0), Float(0.0), Float(0.0), Float(1.0)), + (ORBColor.white, Float(1.0), Float(1.0), Float(1.0), Float(1.0)), + (ORBColor.null, Float(-32768.0), Float(-32768.0), Float(-32768.0), Float(-32768.0)), + ]) + func constant(_ color: ORBColor, expectedRed: Float, expectedGreen: Float, expectedBlue: Float, expectedAlpha: Float) { + #expect(color.red.isApproximatelyEqual(to: expectedRed)) + #expect(color.green.isApproximatelyEqual(to: expectedGreen)) + #expect(color.blue.isApproximatelyEqual(to: expectedBlue)) + #expect(color.alpha.isApproximatelyEqual(to: expectedAlpha)) + } + + @Test("ORBColorInvalidComponent") + func colorInvalidComponent() { + let value = ORBColor.invalidComponent + #expect(value.isApproximatelyEqual(to: -32768.0)) + } + + // MARK: - ORBColorCopyCGColor + + #if canImport(CoreGraphics) + @Test("ORBColorCopyCGColor", arguments: [ + (ORBColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), ORBColor.Space.SRGB), + (ORBColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), ORBColor.Space.linearSRGB), + (ORBColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5), ORBColor.Space.displayP3), + (ORBColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0), ORBColor.Space.linearDisplayP3), + ]) + func colorCopyCGColor(color: ORBColor, colorSpace: ORBColor.Space) throws { + let cgColor = try #require(color.cgColor(colorSpace: colorSpace)) + #expect(cgColor.numberOfComponents == 4) + let components = cgColor.components! + #expect(components[0].isApproximatelyEqual(to: CGFloat(color.red))) + #expect(components[1].isApproximatelyEqual(to: CGFloat(color.green))) + #expect(components[2].isApproximatelyEqual(to: CGFloat(color.blue))) + #expect(components[3].isApproximatelyEqual(to: CGFloat(color.alpha))) + } + #endif + + #if compiler(>=6.2) // old Xcode version swift-testing bug + @Suite + struct ColorModeTests { + // MARK: - ORBColorModeWorkingColorSpace + + @Test("ORBColorModeWorkingColorSpace", arguments: [ + (ORBColor.Mode.mode0, ORBColor.Space.SRGB), + (ORBColor.Mode.mode1, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode2, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode3, ORBColor.Space.SRGB), + (ORBColor.Mode.mode4, ORBColor.Space.SRGB), + (ORBColor.Mode.mode5, ORBColor.Space.SRGB), + (ORBColor.Mode.mode6, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode7, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode8, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode9, ORBColor.Space.SRGB), + (ORBColor.Mode.mode10, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode11, ORBColor.Space.SRGB), + (ORBColor.Mode.mode12, ORBColor.Space.SRGB), + (ORBColor.Mode.mode13, ORBColor.Space.linearSRGB), + (ORBColor.Mode.mode14, ORBColor.Space.SRGB), + (ORBColor.Mode.mode15, ORBColor.Space.linearSRGB), + ]) + func colorModeWorkingColorSpace(mode: ORBColor.Mode, expectedColorSpace: ORBColor.Space) { + #expect(mode.workingColorSpace == expectedColorSpace) + } + + // MARK: - ORBColorModeHasExtendedRange + + @Test("ORBColorModeHasExtendedRange", arguments: [ + (ORBColor.Mode.mode0, false), + (ORBColor.Mode.mode1, false), + (ORBColor.Mode.mode2, true), + (ORBColor.Mode.mode3, false), + (ORBColor.Mode.mode4, false), + (ORBColor.Mode.mode5, false), + (ORBColor.Mode.mode6, false), + (ORBColor.Mode.mode7, false), + (ORBColor.Mode.mode8, false), + (ORBColor.Mode.mode9, false), + (ORBColor.Mode.mode10, false), + (ORBColor.Mode.mode11, true), + (ORBColor.Mode.mode12, true), + (ORBColor.Mode.mode13, true), + (ORBColor.Mode.mode14, false), + (ORBColor.Mode.mode15, false), + ]) + func colorModeHasExtendedRange(mode: ORBColor.Mode, expectedResult: Bool) { + #expect(mode.hasExtendedRange == expectedResult) + } + } + #endif +}