Skip to content

Commit 5954bc7

Browse files
authored
Add ColorMatrix (#162)
* Add ColorMatrix * Update StrongHashTests * Update ColorMatrix * Update GraphicsFilter * Update ColorMatrix
1 parent f18cd5d commit 5954bc7

File tree

5 files changed

+549
-1
lines changed

5 files changed

+549
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
//
2+
// ColorMatrix.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Blocked by Color, GraphicsFilter and ShapeStyle
7+
// ID: 623CA953523AF4C256B3825254A7F058 (SwiftUICore)
8+
9+
#if canImport(Darwin)
10+
import CoreGraphics
11+
#else
12+
import Foundation
13+
#endif
14+
15+
// MARK: - ColorMatrix
16+
17+
/// A matrix to use in an RGBA color transformation.
18+
///
19+
/// The matrix has five columns, each with a red, green, blue, and alpha
20+
/// component. You can use the matrix for tasks like creating a color
21+
/// transformation ``GraphicsContext/Filter`` for a ``GraphicsContext`` using
22+
/// the ``GraphicsContext/Filter/colorMatrix(_:)`` method.
23+
@frozen
24+
public struct ColorMatrix: Equatable {
25+
public var r1: Float = 1, r2: Float = 0, r3: Float = 0, r4: Float = 0, r5: Float = 0
26+
public var g1: Float = 0, g2: Float = 1, g3: Float = 0, g4: Float = 0, g5: Float = 0
27+
public var b1: Float = 0, b2: Float = 0, b3: Float = 1, b4: Float = 0, b5: Float = 0
28+
public var a1: Float = 0, a2: Float = 0, a3: Float = 0, a4: Float = 1, a5: Float = 0
29+
30+
/// Creates the identity matrix.
31+
@inlinable
32+
public init() {}
33+
}
34+
35+
// MARK: - _ColorMatrix
36+
37+
@frozen
38+
public struct _ColorMatrix: Equatable, Codable {
39+
public var m11: Float = 1, m12: Float = 0, m13: Float = 0, m14: Float = 0, m15: Float = 0
40+
public var m21: Float = 0, m22: Float = 1, m23: Float = 0, m24: Float = 0, m25: Float = 0
41+
public var m31: Float = 0, m32: Float = 0, m33: Float = 1, m34: Float = 0, m35: Float = 0
42+
public var m41: Float = 0, m42: Float = 0, m43: Float = 0, m44: Float = 1, m45: Float = 0
43+
44+
@inline(__always)
45+
init(m11: Float = 1, m12: Float = 0, m13: Float = 0, m14: Float = 0, m15: Float = 0,
46+
m21: Float = 0, m22: Float = 1, m23: Float = 0, m24: Float = 0, m25: Float = 0,
47+
m31: Float = 0, m32: Float = 0, m33: Float = 1, m34: Float = 0, m35: Float = 0,
48+
m41: Float = 0, m42: Float = 0, m43: Float = 0, m44: Float = 1, m45: Float = 0) {
49+
self.m11 = m11; self.m12 = m12; self.m13 = m13; self.m14 = m14; self.m15 = m15
50+
self.m21 = m21; self.m22 = m22; self.m23 = m23; self.m24 = m24; self.m25 = m25
51+
self.m31 = m31; self.m32 = m32; self.m33 = m33; self.m34 = m34; self.m35 = m35
52+
self.m41 = m41; self.m42 = m42; self.m43 = m43; self.m44 = m44; self.m45 = m45
53+
}
54+
55+
@inlinable
56+
public init() {}
57+
58+
public init(color: Color, in environment: EnvironmentValues) {
59+
// Blocked by Color
60+
fatalError("TODO")
61+
}
62+
63+
package init(_ m: ColorMatrix) {
64+
m11 = m.r1; m12 = m.r2; m13 = m.r3; m14 = m.r4; m15 = m.r5
65+
m21 = m.g1; m22 = m.g2; m23 = m.g3; m24 = m.g4; m25 = m.g5
66+
m31 = m.b1; m32 = m.b2; m33 = m.b3; m34 = m.b4; m35 = m.b5
67+
m41 = m.a1; m42 = m.a2; m43 = m.a3; m44 = m.a4; m45 = m.a5
68+
}
69+
70+
package var isIdentity: Bool {
71+
self == .identity
72+
}
73+
74+
@inline(__always)
75+
static let identity = _ColorMatrix()
76+
77+
/// The missing fifth row would be (0, 0, 0, 0, 1)
78+
///
79+
/// | R' | | r1 r2 r3 r4 r5 | | R |
80+
/// | G' | | g1 g2 g3 g4 g5 | | G |
81+
/// | B' | = | b1 b2 b3 b4 b5 | * | B |
82+
/// | A' | | a1 a2 a3 a4 a5 | | A |
83+
/// | 1 | | 0 0 0 0 1 | | 1 |
84+
public static func * (a: _ColorMatrix, b: _ColorMatrix) -> _ColorMatrix {
85+
let m11 = a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31 + a.m14 * b.m41
86+
let m12 = a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32 + a.m14 * b.m42
87+
let m13 = a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33 + a.m14 * b.m43
88+
let m14 = a.m11 * b.m14 + a.m12 * b.m24 + a.m13 * b.m34 + a.m14 * b.m44
89+
let m15 = a.m11 * b.m15 + a.m12 * b.m25 + a.m13 * b.m35 + a.m14 * b.m45 + a.m15
90+
91+
let m21 = a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31 + a.m24 * b.m41
92+
let m22 = a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32 + a.m24 * b.m42
93+
let m23 = a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33 + a.m24 * b.m43
94+
let m24 = a.m21 * b.m14 + a.m22 * b.m24 + a.m23 * b.m34 + a.m24 * b.m44
95+
let m25 = a.m21 * b.m15 + a.m22 * b.m25 + a.m23 * b.m35 + a.m24 * b.m45 + a.m25
96+
97+
let m31 = a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31 + a.m34 * b.m41
98+
let m32 = a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32 + a.m34 * b.m42
99+
let m33 = a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33 + a.m34 * b.m43
100+
let m34 = a.m31 * b.m14 + a.m32 * b.m24 + a.m33 * b.m34 + a.m34 * b.m44
101+
let m35 = a.m31 * b.m15 + a.m32 * b.m25 + a.m33 * b.m35 + a.m34 * b.m45 + a.m35
102+
103+
let m41 = a.m41 * b.m11 + a.m42 * b.m21 + a.m43 * b.m31 + a.m44 * b.m41
104+
let m42 = a.m41 * b.m12 + a.m42 * b.m22 + a.m43 * b.m32 + a.m44 * b.m42
105+
let m43 = a.m41 * b.m13 + a.m42 * b.m23 + a.m43 * b.m33 + a.m44 * b.m43
106+
let m44 = a.m41 * b.m14 + a.m42 * b.m24 + a.m43 * b.m34 + a.m44 * b.m44
107+
let m45 = a.m41 * b.m15 + a.m42 * b.m25 + a.m43 * b.m35 + a.m44 * b.m45 + a.m45
108+
109+
return _ColorMatrix(
110+
row1: (m11, m12, m13, m14, m15),
111+
row2: (m21, m22, m23, m24, m25),
112+
row3: (m31, m32, m33, m34, m35),
113+
row4: (m41, m42, m43, m44, m45)
114+
)
115+
}
116+
117+
public func encode(to encoder: any Encoder) throws {
118+
var container = encoder.unkeyedContainer()
119+
try container.encodeRow((m11, m12, m13, m14, m15))
120+
try container.encodeRow((m21, m22, m23, m24, m25))
121+
try container.encodeRow((m31, m32, m33, m34, m35))
122+
try container.encodeRow((m41, m42, m43, m44, m45))
123+
}
124+
125+
public init(from decoder: any Decoder) throws {
126+
var container = try decoder.unkeyedContainer()
127+
let row1 = try container.decodeRow()
128+
let row2 = try container.decodeRow()
129+
let row3 = try container.decodeRow()
130+
let row4 = try container.decodeRow()
131+
self.init(row1: row1, row2: row2, row3: row3, row4: row4)
132+
}
133+
}
134+
135+
extension UnkeyedEncodingContainer {
136+
fileprivate mutating func encodeRow(_ row: (Float, Float, Float, Float, Float)) throws {
137+
try encode(row.0)
138+
try encode(row.1)
139+
try encode(row.2)
140+
try encode(row.3)
141+
try encode(row.4)
142+
}
143+
}
144+
145+
extension UnkeyedDecodingContainer {
146+
fileprivate mutating func decodeRow() throws -> (Float, Float, Float, Float, Float) {
147+
let m11 = try decode(Float.self)
148+
let m12 = try decode(Float.self)
149+
let m13 = try decode(Float.self)
150+
let m14 = try decode(Float.self)
151+
let m15 = try decode(Float.self)
152+
return (m11, m12, m13, m14, m15)
153+
}
154+
}
155+
156+
// MARK: - _ColorMatrix + init [TODO]
157+
158+
extension _ColorMatrix {
159+
@inline(__always)
160+
package init(
161+
row1: (Float, Float, Float, Float, Float),
162+
row2: (Float, Float, Float, Float, Float),
163+
row3: (Float, Float, Float, Float, Float),
164+
row4: (Float, Float, Float, Float, Float)
165+
) {
166+
m11 = row1.0; m12 = row1.1; m13 = row1.2; m14 = row1.3; m15 = row1.4
167+
m21 = row2.0; m22 = row2.1; m23 = row2.2; m24 = row2.3; m25 = row2.4
168+
m31 = row3.0; m32 = row3.1; m33 = row3.2; m34 = row3.3; m35 = row3.4
169+
m41 = row4.0; m42 = row4.1; m43 = row4.2; m44 = row4.3; m45 = row4.4
170+
}
171+
172+
package init?(_ filter: GraphicsFilter, premultiplied: Bool = false) {
173+
fatalError("TODO")
174+
}
175+
176+
package init(colorMultiply c: Color.Resolved, premultiplied: Bool = false) {
177+
let factor: Float = premultiplied ? c.opacity : 1.0
178+
let red = c.red * factor
179+
let green = c.green * factor
180+
let blue = c.blue * factor
181+
let alpha = c.opacity
182+
self.init(
183+
row1: (red, 0, 0, 0, 0),
184+
row2: (0, green, 0, 0, 0),
185+
row3: (0, 0, blue, 0, 0),
186+
row4: (0, 0, 0, alpha, 0)
187+
)
188+
}
189+
190+
package init(hueRotation angle: Angle) {
191+
let cosValue = Float(cos(angle.radians))
192+
let sinValue = Float(sin(angle.radians))
193+
194+
// Matrix coefficients for hue rotation
195+
// Based on color rotation matrix formula
196+
self.init(
197+
row1: (0.2126 + cosValue * 0.7873 - sinValue * 0.2126, 0.2126 - cosValue * 0.2126 + sinValue * 0.1430, 0.2126 - cosValue * 0.2126 - sinValue * 0.7873, 0, 0),
198+
row2: (0.7152 - cosValue * 0.7152 - sinValue * 0.7152, 0.7152 + cosValue * 0.2848 + sinValue * 0.1400, 0.7152 - cosValue * 0.7152 + sinValue * 0.7152, 0, 0),
199+
row3: (0.0722 - cosValue * 0.0722 + sinValue * 0.9278, 0.0722 - cosValue * 0.0722 - sinValue * 0.2830, 0.0722 + cosValue * 0.9278 + sinValue * 0.0722, 0, 0),
200+
row4: (0, 0, 0, 1, 0)
201+
)
202+
}
203+
204+
package init(brightness: Double) {
205+
let b = Float(brightness)
206+
self.init(
207+
row1: (1, 0, 0, 0, b),
208+
row2: (0, 1, 0, 0, b),
209+
row3: (0, 0, 1, 0, b),
210+
row4: (0, 0, 0, 1, 0)
211+
)
212+
}
213+
214+
package init(contrast: Double) {
215+
let c = Float(contrast)
216+
let t = (1.0 - c) / 2.0
217+
self.init(
218+
row1: (c, 0, 0, 0, t),
219+
row2: (0, c, 0, 0, t),
220+
row3: (0, 0, c, 0, t),
221+
row4: (0, 0, 0, 1, 0)
222+
)
223+
}
224+
225+
package init(luminanceToAlpha: Void) {
226+
// Using standard luminance coefficients (Rec. 709)
227+
self.init(
228+
row1: (0, 0, 0, 0, 0),
229+
row2: (0, 0, 0, 0, 0),
230+
row3: (0, 0, 0, 0, 0),
231+
row4: (0.2126, 0.7152, 0.0722, 0, 0) // Luminance coefficients in alpha channel
232+
)
233+
}
234+
235+
package init(colorInvert x: Float) {
236+
self.init(
237+
row1: (-1, 0, 0, 0, x),
238+
row2: (0, -1, 0, 0, x),
239+
row3: (0, 0, -1, 0, x),
240+
row4: (0, 0, 0, 1, 0)
241+
)
242+
}
243+
244+
package init(colorMonochrome c: Color.Resolved, amount: Float = 1, bias: Float = 0) {
245+
let red = c.red
246+
let green = c.green
247+
let blue = c.blue
248+
let opacity = c.opacity
249+
250+
// Standard luminance coefficients (Rec. 709)
251+
let lumR: Float = 0.2126
252+
let lumG: Float = 0.7152
253+
let lumB: Float = 0.0722
254+
255+
// Calculate the inverse of amount for blending
256+
let invAmount = 1.0 - amount
257+
258+
self.init(
259+
row1: (red * lumR * amount + invAmount, red * lumG * amount, red * lumB * amount, 0, red * amount * bias),
260+
row2: (green * lumR * amount, green * lumG * amount + invAmount, green * lumB * amount, 0, green * amount * bias),
261+
row3: (blue * lumR * amount, blue * lumG * amount, blue * lumB * amount + invAmount, 0, blue * amount * bias),
262+
row4: (0, 0, 0, opacity * amount + invAmount, 0)
263+
)
264+
}
265+
266+
package init(floatArray: [Float]) {
267+
m11 = floatArray[0]; m12 = floatArray[1]; m13 = floatArray[2]; m14 = floatArray[3]; m15 = floatArray[4]
268+
m21 = floatArray[5]; m22 = floatArray[6]; m23 = floatArray[7]; m24 = floatArray[8]; m25 = floatArray[9]
269+
m31 = floatArray[10]; m32 = floatArray[11]; m33 = floatArray[12]; m34 = floatArray[13]; m35 = floatArray[14]
270+
m41 = floatArray[15]; m42 = floatArray[16]; m43 = floatArray[17]; m44 = floatArray[18]; m45 = floatArray[19]
271+
}
272+
273+
package var floatArray: [Float] {
274+
[
275+
m11, m12, m13, m14, m15,
276+
m21, m22, m23, m24, m25,
277+
m31, m32, m33, m34, m35,
278+
m41, m42, m43, m44, m45
279+
]
280+
}
281+
}
282+
283+
// MARK: - _ColorMatrix + ShapeStyle [TODO]
284+
285+
@_spi(Private)
286+
extension _ColorMatrix: ShapeStyle {
287+
public func _apply(to shape: inout _ShapeStyle_Shape) {
288+
// TODO
289+
}
290+
291+
public typealias Resolved = Never
292+
}
293+
294+
// MARK: - _ColorMatrix + ProtobufMessage
295+
296+
extension _ColorMatrix: ProtobufMessage {
297+
package func encode(to encoder: inout ProtobufEncoder) {
298+
encoder.floatField(1, m11, defaultValue: 1.0)
299+
encoder.floatField(2, m12, defaultValue: 0.0)
300+
encoder.floatField(3, m13, defaultValue: 0.0)
301+
encoder.floatField(4, m14, defaultValue: 0.0)
302+
encoder.floatField(5, m15, defaultValue: 0.0)
303+
encoder.floatField(6, m21, defaultValue: 0.0)
304+
encoder.floatField(7, m22, defaultValue: 1.0)
305+
encoder.floatField(8, m23, defaultValue: 0.0)
306+
encoder.floatField(9, m24, defaultValue: 0.0)
307+
encoder.floatField(10, m25, defaultValue: 0.0)
308+
encoder.floatField(11, m31, defaultValue: 0.0)
309+
encoder.floatField(12, m32, defaultValue: 0.0)
310+
encoder.floatField(13, m33, defaultValue: 1.0)
311+
encoder.floatField(14, m34, defaultValue: 0.0)
312+
encoder.floatField(15, m35, defaultValue: 0.0)
313+
encoder.floatField(16, m41, defaultValue: 0.0)
314+
encoder.floatField(17, m42, defaultValue: 0.0)
315+
encoder.floatField(18, m43, defaultValue: 0.0)
316+
encoder.floatField(19, m44, defaultValue: 1.0)
317+
encoder.floatField(20, m45, defaultValue: 0.0)
318+
}
319+
320+
package init(from decoder: inout ProtobufDecoder) throws {
321+
self = _ColorMatrix()
322+
while let field = try decoder.nextField() {
323+
switch field.tag {
324+
case 1: m11 = try decoder.floatField(field)
325+
case 2: m12 = try decoder.floatField(field)
326+
case 3: m13 = try decoder.floatField(field)
327+
case 4: m14 = try decoder.floatField(field)
328+
case 5: m15 = try decoder.floatField(field)
329+
case 6: m21 = try decoder.floatField(field)
330+
case 7: m22 = try decoder.floatField(field)
331+
case 8: m23 = try decoder.floatField(field)
332+
case 9: m24 = try decoder.floatField(field)
333+
case 10: m25 = try decoder.floatField(field)
334+
case 11: m31 = try decoder.floatField(field)
335+
case 12: m32 = try decoder.floatField(field)
336+
case 13: m33 = try decoder.floatField(field)
337+
case 14: m34 = try decoder.floatField(field)
338+
case 15: m35 = try decoder.floatField(field)
339+
case 16: m41 = try decoder.floatField(field)
340+
case 17: m42 = try decoder.floatField(field)
341+
case 18: m43 = try decoder.floatField(field)
342+
case 19: m44 = try decoder.floatField(field)
343+
case 20: m45 = try decoder.floatField(field)
344+
default:
345+
try decoder.skipField(field)
346+
}
347+
}
348+
}
349+
}
350+
351+
// MARK: - _ColorMatrix + Calculations
352+
353+
extension _ColorMatrix {
354+
mutating func add(_ other: _ColorMatrix) {
355+
m11 += other.m11; m12 += other.m12; m13 += other.m13; m14 += other.m14; m15 += other.m15
356+
m21 += other.m21; m22 += other.m22; m23 += other.m23; m24 += other.m24; m25 += other.m25
357+
m31 += other.m31; m32 += other.m32; m33 += other.m33; m34 += other.m34; m35 += other.m35
358+
m41 += other.m41; m42 += other.m42; m43 += other.m43; m44 += other.m44; m45 += other.m45
359+
}
360+
361+
mutating func subtract(_ other: _ColorMatrix) {
362+
m11 -= other.m11; m12 -= other.m12; m13 -= other.m13; m14 -= other.m14; m15 -= other.m15
363+
m21 -= other.m21; m22 -= other.m22; m23 -= other.m23; m24 -= other.m24; m25 -= other.m25
364+
m31 -= other.m31; m32 -= other.m32; m33 -= other.m33; m34 -= other.m34; m35 -= other.m35
365+
m41 -= other.m41; m42 -= other.m42; m43 -= other.m43; m44 -= other.m44; m45 -= other.m45
366+
}
367+
368+
mutating func negate() {
369+
m11 = -m11; m12 = -m12; m13 = -m13; m14 = -m14; m15 = -m15
370+
m21 = -m21; m22 = -m22; m23 = -m23; m24 = -m24; m25 = -m25
371+
m31 = -m31; m32 = -m32; m33 = -m33; m34 = -m34; m35 = -m35
372+
m41 = -m41; m42 = -m42; m43 = -m43; m44 = -m44; m45 = -m45
373+
}
374+
375+
mutating func scale(by scalar: Double) {
376+
let scalar = Float(scalar)
377+
m11 *= scalar; m12 *= scalar; m13 *= scalar; m14 *= scalar; m15 *= scalar
378+
m21 *= scalar; m22 *= scalar; m23 *= scalar; m24 *= scalar; m25 *= scalar
379+
m31 *= scalar; m32 *= scalar; m33 *= scalar; m34 *= scalar; m35 *= scalar
380+
m41 *= scalar; m42 *= scalar; m43 *= scalar; m44 *= scalar; m45 *= scalar
381+
}
382+
383+
var magnitudeSquared: Double {
384+
floatArray.reduce(0.0) { $0 + Double($1 * $1) }
385+
}
386+
}

0 commit comments

Comments
 (0)