11import Cocoa
22
33// swiftlint:disable identifier_name
4+ // identifier_name is disabled because color science math uses conventional single-letter
5+ // variable names (x, y, z, l, a, b, c, h, L, C, H) that would be misleading if renamed.
6+
7+ private struct XYZComponents { let x , y , z : CGFloat }
8+ struct LabComponents { let l , a , b : CGFloat }
9+ struct OklchComponents { let l , c , h : CGFloat }
410
511extension NSColor {
612 // Shared linearization for sRGB components used by both LAB and OKLCH.
@@ -20,23 +26,25 @@ extension NSColor {
2026 func toOpenGLString( style: CopyFormat = . css) -> String {
2127 let RGB = toRGBAComponents ( )
2228
23- let formatString : NSString
29+ // %.5g strips trailing zeros but drops the decimal entirely for whole numbers,
30+ // so append ".0" when there is no decimal point (e.g. 0 → "0.0", 1 → "1.0").
31+ let red = { let s = String ( format: " %.5g " , RGB . r) ; return s. contains ( " . " ) ? s : " \( s) .0 " } ( )
32+ let green = { let s = String ( format: " %.5g " , RGB . g) ; return s. contains ( " . " ) ? s : " \( s) .0 " } ( )
33+ let blue = { let s = String ( format: " %.5g " , RGB . b) ; return s. contains ( " . " ) ? s : " \( s) .0 " } ( )
34+
2435 switch style {
2536 case . css, . design, . swiftUI:
26- formatString = " rgba(%.5g, %.5g, %.5g , 1.0) "
37+ return " rgba( \( red ) , \( green ) , \( blue ) , 1.0) "
2738 case . unformatted:
28- formatString = " %.5g, %.5g, %.5g , 1.0"
39+ return " \( red ) , \( green ) , \( blue ) , 1.0 "
2940 }
30-
31- let openGLString = NSString ( format: formatString, RGB . r, RGB . g, RGB . b)
32- return openGLString as String
3341 }
3442
3543 /*
3644 * CIE-LAB
3745 */
3846
39- private func toXYZComponents( ) -> ( x : CGFloat , y : CGFloat , z : CGFloat ) {
47+ private func toXYZComponents( ) -> XYZComponents {
4048 let srgb = toRGBAComponents ( in: . sRGB)
4149 let r_lin = linearizeSRGB ( srgb. r)
4250 let g_lin = linearizeSRGB ( srgb. g)
@@ -46,10 +54,10 @@ extension NSColor {
4654 let y = r_lin * 0.2126729 + g_lin * 0.7151522 + b_lin * 0.0721750
4755 let z = r_lin * 0.0193339 + g_lin * 0.1191920 + b_lin * 0.9503041
4856
49- return ( x: x, y: y, z: z)
57+ return XYZComponents ( x: x, y: y, z: z)
5058 }
5159
52- func toLabComponents( ) -> ( l : CGFloat , a : CGFloat , b : CGFloat ) {
60+ func toLabComponents( ) -> LabComponents {
5361 let xyz = toXYZComponents ( )
5462
5563 // D65 Reference White
@@ -70,30 +78,30 @@ extension NSColor {
7078 let a_star = 500.0 * ( f ( xyz. x / Xn) - f( xyz. y / Yn) )
7179 let b_star = 200.0 * ( f ( xyz. y / Yn) - f( xyz. z / Zn) )
7280
73- return ( l: L_star, a: a_star, b: b_star)
81+ return LabComponents ( l: L_star, a: a_star, b: b_star)
7482 }
7583
7684 func toLabString( style: CopyFormat = . css) -> String {
7785 let lab = toLabComponents ( )
78- let l_val = round ( lab. l * 100 ) / 100
79- let a_val = round ( lab. a * 100 ) / 100
80- let b_val = round ( lab. b * 100 ) / 100
86+ let l_str = ( round ( lab. l * 100 ) / 100 ) . strippedDecimalString ( maxDecimalPlaces : 2 )
87+ let a_str = ( round ( lab. a * 100 ) / 100 ) . strippedDecimalString ( maxDecimalPlaces : 2 )
88+ let b_str = ( round ( lab. b * 100 ) / 100 ) . strippedDecimalString ( maxDecimalPlaces : 2 )
8189
8290 switch style {
8391 case . css:
84- return String ( format : " lab(%.2f %.2f %.2f) " , l_val , a_val , b_val )
92+ return " lab( \( l_str ) \( a_str ) \( b_str ) ) "
8593 case . design, . swiftUI:
86- return String ( format : " lab(%.2f, %.2f, %.2f) " , l_val , a_val , b_val )
94+ return " lab( \( l_str ) , \( a_str ) , \( b_str ) ) "
8795 case . unformatted:
88- return String ( format : " %.2f,%.2f,%.2f " , l_val , a_val , b_val )
96+ return " \( l_str ) , \( a_str ) , \( b_str ) "
8997 }
9098 }
9199
92100 /*
93101 * OKLCH
94102 */
95103
96- func toOklchComponents( ) -> ( l : CGFloat , c : CGFloat , h : CGFloat ) {
104+ func toOklchComponents( ) -> OklchComponents {
97105 let srgb = toRGBAComponents ( in: . sRGB)
98106 let r_lin = linearizeSRGB ( srgb. r)
99107 let g_lin = linearizeSRGB ( srgb. g)
@@ -115,22 +123,22 @@ extension NSColor {
115123 var H = atan2 ( b, a) * 180.0 / . pi
116124 if H < 0 { H += 360.0 }
117125
118- return ( l: L, c: C, h: H)
126+ return OklchComponents ( l: L, c: C, h: H)
119127 }
120128
121129 func toOklchString( style: CopyFormat = . css) -> String {
122130 let oklch = toOklchComponents ( )
123- let l_val = round ( oklch. l * 10000 ) / 100
124- let c_val = round ( oklch. c * 10000 ) / 10000
125- let h_val = round ( oklch. h * 100 ) / 100
131+ let l_str = ( round ( oklch. l * 10000 ) / 100 ) . strippedDecimalString ( maxDecimalPlaces : 2 )
132+ let c_str = ( round ( oklch. c * 10000 ) / 10000 ) . strippedDecimalString ( maxDecimalPlaces : 4 )
133+ let h_str = ( round ( oklch. h * 100 ) / 100 ) . strippedDecimalString ( maxDecimalPlaces : 2 )
126134
127135 switch style {
128136 case . css:
129- return String ( format : " oklch(%.2f%% %.4f %.2f) " , l_val , c_val , h_val )
137+ return " oklch( \( l_str ) % \( c_str ) \( h_str ) ) "
130138 case . design, . swiftUI:
131- return String ( format : " oklch(%.2f, %.4f, %.2f) " , l_val , c_val , h_val )
139+ return " oklch( \( l_str ) , \( c_str ) , \( h_str ) ) "
132140 case . unformatted:
133- return String ( format : " %.2f, %.4f, %.2f " , l_val , c_val , h_val )
141+ return " \( l_str ) , \( c_str ) , \( h_str ) "
134142 }
135143 }
136144}
0 commit comments