Skip to content

Commit c671885

Browse files
committed
Add ImageDynamicRange
1 parent 8651704 commit c671885

File tree

2 files changed

+185
-1
lines changed

2 files changed

+185
-1
lines changed

Sources/OpenSwiftUICore/View/Image/Image.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ package struct ImageResolutionContext {
113113

114114
// package var symbolRenderingMode: SymbolRenderingMode?
115115

116-
// package var allowedDynamicRange: Image.DynamicRange?
116+
package var allowedDynamicRange: Image.DynamicRange?
117117

118118
package var options: ImageResolutionContext.Options
119119

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// ImageDynamicRange.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
// ID: B0F5FD51133E70141176B7B8AC4E9712 (SwiftUICore)
8+
9+
package import Foundation
10+
11+
// MARK: - Image.DynamicRange
12+
13+
@available(OpenSwiftUI_v5_0, *)
14+
// @_spi_available(watchOS, introduced: 10.0)
15+
extension Image {
16+
public struct DynamicRange: Hashable, Sendable {
17+
package enum Storage: UInt8, Hashable, Comparable {
18+
case standard
19+
case constrainedHigh
20+
case high
21+
22+
package static func < (lhs: Image.DynamicRange.Storage, rhs: Image.DynamicRange.Storage) -> Bool {
23+
lhs.rawValue < rhs.rawValue
24+
}
25+
}
26+
27+
package var storage: Image.DynamicRange.Storage
28+
29+
package init(storage: Image.DynamicRange.Storage) {
30+
self.storage = storage
31+
}
32+
33+
/// Restrict the image content dynamic range to the standard range.
34+
public static let standard: Image.DynamicRange = .init(storage: .standard)
35+
36+
/// Allow image content to use some extended range. This is
37+
/// appropriate for placing HDR content next to SDR content.
38+
public static let constrainedHigh: Image.DynamicRange = .init(storage: .constrainedHigh)
39+
40+
/// Allow image content to use an unrestricted extended range.
41+
public static let high: Image.DynamicRange = .init(storage: .high)
42+
43+
package var maxHeadroom: Image.Headroom {
44+
switch storage {
45+
case .standard: .standard
46+
case .constrainedHigh: .constrainedHigh
47+
case .high: .high
48+
}
49+
}
50+
}
51+
52+
package struct Headroom: RawRepresentable, Comparable {
53+
package let rawValue: CGFloat
54+
55+
package init(rawValue: CGFloat) {
56+
self.rawValue = rawValue
57+
}
58+
59+
package static func < (lhs: Image.Headroom, rhs: Image.Headroom) -> Bool {
60+
lhs.rawValue < rhs.rawValue
61+
}
62+
63+
package static let standard: Image.Headroom = .init(rawValue: 1.0)
64+
65+
package static let constrainedHigh: Image.Headroom = .init(rawValue: 2.0)
66+
67+
package static let highHLG: Image.Headroom = .init(rawValue: 5.0)
68+
69+
package static let high: Image.Headroom = .init(rawValue: 8.0)
70+
}
71+
72+
/// Returns a new image configured with the specified allowed
73+
/// dynamic range.
74+
///
75+
/// The following example enables HDR rendering for a specific
76+
/// image view, assuming that the image has an HDR (ITU-R 2100)
77+
/// color space and the output device supports it:
78+
///
79+
/// Image("hdr-asset").allowedDynamicRange(.high)
80+
///
81+
/// - Parameter range: the requested dynamic range, or nil to
82+
/// restore the default allowed range.
83+
///
84+
/// - Returns: a new image.
85+
public func allowedDynamicRange(_ range: Image.DynamicRange?) -> Image {
86+
Image(
87+
DynamicRangeProvider(
88+
base: self,
89+
allowedDynamicRange: range
90+
)
91+
)
92+
}
93+
}
94+
95+
// MARK: - DynamicRangeProvider
96+
97+
private struct DynamicRangeProvider: ImageProvider {
98+
var base: Image
99+
100+
var allowedDynamicRange: Image.DynamicRange?
101+
102+
func resolve(in context: ImageResolutionContext) -> Image.Resolved {
103+
var context = context
104+
if let allowedDynamicRange {
105+
context.allowedDynamicRange = allowedDynamicRange
106+
}
107+
return base.resolve(in: context)
108+
}
109+
110+
func resolveNamedImage(in context: ImageResolutionContext) -> Image.NamedResolved? {
111+
var context = context
112+
if let allowedDynamicRange {
113+
context.allowedDynamicRange = allowedDynamicRange
114+
}
115+
return base.resolveNamedImage(in: context)
116+
}
117+
}
118+
119+
// MARK: - EnvironmentValues + DynamicRange
120+
121+
private struct AllowedDynamicRangeKey : EnvironmentKey {
122+
static var defaultValue: Image.DynamicRange? { nil }
123+
}
124+
125+
@available(OpenSwiftUI_v5_0, *)
126+
// @_spi_available(watchOS, introduced: 10.0)
127+
extension EnvironmentValues {
128+
/// The allowed dynamic range for the view, or nil.
129+
public var allowedDynamicRange: Image.DynamicRange? {
130+
get { self[AllowedDynamicRangeKey.self] }
131+
set { self[AllowedDynamicRangeKey.self] = newValue }
132+
}
133+
}
134+
135+
struct MaxAllowedDynamicRangeKey: EnvironmentKey {
136+
static var defaultValue: Image.DynamicRange? { nil }
137+
}
138+
139+
@_spi(Private)
140+
@available(OpenSwiftUI_v6_0, *)
141+
extension EnvironmentValues {
142+
public var maxAllowedDynamicRange: Image.DynamicRange? {
143+
get { self[MaxAllowedDynamicRangeKey.self] }
144+
set { self[MaxAllowedDynamicRangeKey.self] = newValue }
145+
}
146+
}
147+
148+
@available(iOS 17.0, macOS 14.0, tvOS 17.0, *)
149+
@_spi_available(watchOS, introduced: 10.0)
150+
extension View {
151+
152+
/// Returns a new view configured with the specified allowed
153+
/// dynamic range.
154+
///
155+
/// The following example enables HDR rendering within a view
156+
/// hierarchy:
157+
///
158+
/// MyView().allowedDynamicRange(.high)
159+
///
160+
/// - Parameter range: the requested dynamic range, or nil to
161+
/// restore the default allowed range.
162+
///
163+
/// - Returns: a new view.
164+
@_alwaysEmitIntoClient
165+
nonisolated
166+
public func allowedDynamicRange(_ range: Image.DynamicRange?) -> some View {
167+
return environment(\.allowedDynamicRange, range)
168+
}
169+
}
170+
171+
// MARK: - DynamicRange + ProtobufEnum
172+
173+
extension Image.DynamicRange: ProtobufEnum {
174+
package var protobufValue: UInt {
175+
UInt(storage.rawValue)
176+
}
177+
178+
package init?(protobufValue value: UInt) {
179+
guard let storage = Image.DynamicRange.Storage(rawValue: UInt8(value)) else {
180+
return nil
181+
}
182+
self.storage = storage
183+
}
184+
}

0 commit comments

Comments
 (0)