@onekeyfe/react-native-image 6.7.0-alpha.414 → 6.7.0-alpha.418
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/android/build.gradle +3 -1
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyIcon.kt +93 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +2 -1
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyNamedSVG.kt +217 -0
- package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeySVG.kt +31 -0
- package/ios/OneKeyIcon.swift +90 -0
- package/ios/OneKeyImageCoderBridge.h +4 -0
- package/ios/OneKeyImageCoderBridge.m +9 -0
- package/ios/OneKeyNamedSVG.swift +282 -0
- package/ios/tests/OneKeyIconTests.swift +484 -0
- package/lib/module/OneKeyIcon.nitro.js +4 -0
- package/lib/module/icon/assetUrls.js +13 -0
- package/lib/module/icon/dom.js +21 -0
- package/lib/module/icon/index.js +29 -0
- package/lib/module/icon/index.web.js +32 -0
- package/lib/module/icon/props.js +22 -0
- package/lib/module/package.json +6 -1
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.cpp +67 -0
- package/lib/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.hpp +66 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.cpp +79 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.hpp +54 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyIconSpec.kt +65 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconManager.kt +96 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconStateUpdater.kt +23 -0
- package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +4 -0
- package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +18 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +17 -0
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +32 -15
- package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +5 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +8 -0
- package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +12 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.hpp +89 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyIconComponent.mm +150 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyIconSpec.swift +56 -0
- package/lib/nitrogen/generated/ios/swift/HybridOneKeyIconSpec_cxx.swift +193 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.cpp +24 -0
- package/lib/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.hpp +66 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.cpp +36 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.hpp +88 -0
- package/lib/nitrogen/generated/shared/json/OneKeyIconConfig.json +11 -0
- package/lib/typescript/src/OneKeyIcon.nitro.d.ts +7 -0
- package/lib/typescript/src/icon/assetUrls.d.ts +2 -0
- package/lib/typescript/src/icon/dom.d.ts +3 -0
- package/lib/typescript/src/icon/index.d.ts +140 -0
- package/lib/typescript/src/icon/index.web.d.ts +134 -0
- package/lib/typescript/src/icon/props.d.ts +8 -0
- package/nitro.json +10 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.cpp +67 -0
- package/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.hpp +66 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.cpp +79 -0
- package/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.hpp +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyIconSpec.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconManager.kt +96 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconStateUpdater.kt +23 -0
- package/nitrogen/generated/android/onekeyimage+autolinking.cmake +4 -0
- package/nitrogen/generated/android/onekeyimageOnLoad.cpp +18 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +32 -15
- package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +8 -0
- package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +12 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.hpp +89 -0
- package/nitrogen/generated/ios/c++/views/HybridOneKeyIconComponent.mm +150 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyIconSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridOneKeyIconSpec_cxx.swift +193 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.cpp +36 -0
- package/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.hpp +88 -0
- package/nitrogen/generated/shared/json/OneKeyIconConfig.json +11 -0
- package/package.json +40 -7
- package/scripts/configure-icon-imports.mjs +7 -0
- package/scripts/icon-fixtures.test.mjs +398 -0
- package/src/OneKeyIcon.nitro.ts +8 -0
- package/src/icon/assetUrls.ts +13 -0
- package/src/icon/dom.ts +21 -0
- package/src/icon/index.tsx +38 -0
- package/src/icon/index.web.tsx +36 -0
- package/src/icon/props.ts +38 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
private final class OneKeyIconBundleToken: NSObject {}
|
|
5
|
+
|
|
6
|
+
@objc(OneKeyNamedSVGBridge)
|
|
7
|
+
internal final class OneKeyNamedSVGBridge: NSObject {
|
|
8
|
+
@objc(cancelWithOwner:)
|
|
9
|
+
static func cancel(owner: AnyObject) {
|
|
10
|
+
OneKeyNamedSVG.shared.cancel(owner: owner)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@objc(requestWithOwner:name:color:width:height:scale:complete:)
|
|
14
|
+
static func request(
|
|
15
|
+
owner: AnyObject,
|
|
16
|
+
name: String,
|
|
17
|
+
color: String,
|
|
18
|
+
width: Int,
|
|
19
|
+
height: Int,
|
|
20
|
+
scale: Double,
|
|
21
|
+
complete: @escaping (UIImage?) -> Void
|
|
22
|
+
) {
|
|
23
|
+
OneKeyNamedSVG.shared.request(
|
|
24
|
+
owner: owner,
|
|
25
|
+
name: name,
|
|
26
|
+
color: color,
|
|
27
|
+
width: width,
|
|
28
|
+
height: height,
|
|
29
|
+
scale: CGFloat(scale),
|
|
30
|
+
complete: complete
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Process-owned resource metadata and immutable raster cache, independent of JS runtimes.
|
|
36
|
+
public final class OneKeyNamedSVG {
|
|
37
|
+
public static let shared = OneKeyNamedSVG()
|
|
38
|
+
private init() {}
|
|
39
|
+
public enum RecolorMode: String { case fixed, single, multiple, raster }
|
|
40
|
+
public struct IconMetadata {
|
|
41
|
+
public let width: Double
|
|
42
|
+
public let height: Double
|
|
43
|
+
public let recolorMode: RecolorMode
|
|
44
|
+
|
|
45
|
+
public init(width: Double, height: Double, recolorMode: RecolorMode) {
|
|
46
|
+
self.width = width
|
|
47
|
+
self.height = height
|
|
48
|
+
self.recolorMode = recolorMode
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
public struct RenderResult {
|
|
52
|
+
public let image: UIImage
|
|
53
|
+
public let mode: RecolorMode
|
|
54
|
+
public let sourceIdentity: String
|
|
55
|
+
public let maskAlpha: CGFloat
|
|
56
|
+
|
|
57
|
+
public func canTint(_ color: String) -> Bool {
|
|
58
|
+
guard let value = OneKeyNamedSVG.channels(color) else { return false }
|
|
59
|
+
return mode == .fixed || mode == .single || (mode == .multiple && value[3] == maskAlpha)
|
|
60
|
+
}
|
|
61
|
+
public func tintColor(_ color: String) -> UIColor? {
|
|
62
|
+
guard mode == .single || mode == .multiple, let value = OneKeyNamedSVG.channels(color) else { return nil }
|
|
63
|
+
return UIColor(red: value[0], green: value[1], blue: value[2], alpha: mode == .single ? value[3] : 1)
|
|
64
|
+
}
|
|
65
|
+
fileprivate func finalImage(_ color: String) -> UIImage {
|
|
66
|
+
guard let tint = tintColor(color) else { return image }
|
|
67
|
+
if OneKeyNamedSVG.channels(color) == [0, 0, 0, 1] { return image }
|
|
68
|
+
// UIImage tint wrappers can retain the original black cgImage. Legacy callers
|
|
69
|
+
// receive actual final-color pixels, so bake the wrapper on the worker.
|
|
70
|
+
let format = UIGraphicsImageRendererFormat(); format.scale = 1; format.preferredRange = .standard
|
|
71
|
+
return UIGraphicsImageRenderer(size: image.size, format: format).image { _ in
|
|
72
|
+
image.withTintColor(tint, renderingMode: .alwaysOriginal).draw(in: CGRect(origin: .zero, size: image.size))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private static func channels(_ color: String) -> [CGFloat]? {
|
|
78
|
+
guard color.range(of: #"^rgba\(\d{1,3},\d{1,3},\d{1,3},(?:0(?:\.\d+)?|1(?:\.0+)?)\)$"#, options: .regularExpression) != nil else { return nil }
|
|
79
|
+
let parts = color.dropFirst(5).dropLast().split(separator: ",").compactMap { Double($0) }
|
|
80
|
+
guard parts.count == 4, parts.prefix(3).allSatisfy({ (0...255).contains($0) }), (0...1).contains(parts[3]) else { return nil }
|
|
81
|
+
return parts.enumerated().map { CGFloat($0.offset == 3 ? $0.element : $0.element / 255) }
|
|
82
|
+
}
|
|
83
|
+
public static func isValidColor(_ color: String) -> Bool { channels(color) != nil }
|
|
84
|
+
|
|
85
|
+
private struct Request {
|
|
86
|
+
let name: String
|
|
87
|
+
let color: String
|
|
88
|
+
let width: Int
|
|
89
|
+
let height: Int
|
|
90
|
+
let scale: CGFloat
|
|
91
|
+
let finalColor: Bool
|
|
92
|
+
let complete: (RenderResult?) -> Void
|
|
93
|
+
}
|
|
94
|
+
private let lock = NSLock()
|
|
95
|
+
private let metadataLock = NSLock()
|
|
96
|
+
private var pending: [ObjectIdentifier: Request] = [:]
|
|
97
|
+
private var order: [ObjectIdentifier] = []
|
|
98
|
+
private var workers = 0
|
|
99
|
+
private var metadata: [String: [String: Any]]?
|
|
100
|
+
private var resourceBundle: Bundle?
|
|
101
|
+
private var cache: [String: RenderResult] = [:]
|
|
102
|
+
private var recency: [String] = []
|
|
103
|
+
private var bytes = 0
|
|
104
|
+
|
|
105
|
+
public func cancel(owner: AnyObject) {
|
|
106
|
+
lock.lock(); defer { lock.unlock() }
|
|
107
|
+
let key = ObjectIdentifier(owner)
|
|
108
|
+
pending.removeValue(forKey: key)
|
|
109
|
+
order.removeAll { $0 == key }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private func loadManifestIfNeeded() {
|
|
113
|
+
guard metadata == nil else { return }
|
|
114
|
+
let owner = Bundle(for: OneKeyIconBundleToken.self)
|
|
115
|
+
let url = owner.url(forResource: "OneKeyIcons", withExtension: "bundle")
|
|
116
|
+
?? Bundle.main.url(forResource: "OneKeyIcons", withExtension: "bundle")
|
|
117
|
+
resourceBundle = url.flatMap(Bundle.init(url:))
|
|
118
|
+
if let file = resourceBundle?.url(forResource: "manifest", withExtension: "json"),
|
|
119
|
+
let data = try? Data(contentsOf: file) {
|
|
120
|
+
metadata = (try? JSONSerialization.jsonObject(with: data)) as? [String: [String: Any]]
|
|
121
|
+
}
|
|
122
|
+
if metadata == nil {
|
|
123
|
+
// Hosts own the resources; a missing bundle means every named icon stays blank.
|
|
124
|
+
NSLog("[OneKeyIcon] OneKeyIcons.bundle/manifest.json is missing or invalid; named icons will not render")
|
|
125
|
+
metadata = [:]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static func renderKey(name: String, digest: String, width: Int, height: Int,
|
|
130
|
+
scale: CGFloat, mode: RecolorMode, color: String,
|
|
131
|
+
channels: [CGFloat]) -> (identity: String, key: String, maskAlpha: CGFloat) {
|
|
132
|
+
let identity = "\(name)|\(digest)|\(width)|\(height)|\(scale)"
|
|
133
|
+
let maskAlpha: CGFloat = mode == .multiple ? channels[3] : 1
|
|
134
|
+
let key = "\(identity)|\(mode.rawValue)|\(mode == .raster ? color : mode == .multiple ? String(Double(maskAlpha)) : "")"
|
|
135
|
+
return (identity, key, maskAlpha)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Main-thread fast path: returns an already-decoded result without disk I/O or
|
|
139
|
+
/// manifest loading, so remounted and recycled views paint in the same frame.
|
|
140
|
+
public func cachedRender(name: String, color: String, width: Int, height: Int,
|
|
141
|
+
scale: CGFloat) -> RenderResult? {
|
|
142
|
+
guard let channels = Self.channels(color), metadataLock.try() else { return nil }
|
|
143
|
+
let entry = metadata?[name]
|
|
144
|
+
metadataLock.unlock()
|
|
145
|
+
guard let digest = entry?["digest"] as? String else { return nil }
|
|
146
|
+
let mode = (entry?["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
|
|
147
|
+
let key = Self.renderKey(name: name, digest: digest, width: width, height: height, scale: scale,
|
|
148
|
+
mode: mode, color: color, channels: channels).key
|
|
149
|
+
lock.lock(); defer { lock.unlock() }
|
|
150
|
+
guard let hit = cache[key] else { return nil }
|
|
151
|
+
recency.removeAll { $0 == key }; recency.append(key)
|
|
152
|
+
return hit
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public func iconMetadata(forName name: String) -> IconMetadata? {
|
|
156
|
+
let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
157
|
+
guard !trimmed.isEmpty else { return nil }
|
|
158
|
+
metadataLock.lock()
|
|
159
|
+
loadManifestIfNeeded()
|
|
160
|
+
let entry = metadata?[trimmed]
|
|
161
|
+
metadataLock.unlock()
|
|
162
|
+
guard let entry else { return nil }
|
|
163
|
+
let mode = (entry["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
|
|
164
|
+
let width: Double
|
|
165
|
+
let height: Double
|
|
166
|
+
if let vb = entry["viewBox"] as? [NSNumber], vb.count >= 4 {
|
|
167
|
+
let vbWidth = vb[2].doubleValue
|
|
168
|
+
let vbHeight = vb[3].doubleValue
|
|
169
|
+
width = (vbWidth > 0 && vbHeight > 0) ? vbWidth : 24
|
|
170
|
+
height = (vbWidth > 0 && vbHeight > 0) ? vbHeight : 24
|
|
171
|
+
} else {
|
|
172
|
+
width = 24
|
|
173
|
+
height = 24
|
|
174
|
+
}
|
|
175
|
+
return IconMetadata(width: width, height: height, recolorMode: mode)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public func request(owner: AnyObject, name: String, color: String, width: Int,
|
|
179
|
+
height: Int, scale: CGFloat, complete: @escaping (UIImage?) -> Void) {
|
|
180
|
+
enqueue(owner: owner, name: name, color: color, width: width, height: height, scale: scale,
|
|
181
|
+
finalColor: true) { complete($0?.image) }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public func requestRender(owner: AnyObject, name: String, color: String, width: Int,
|
|
185
|
+
height: Int, scale: CGFloat, complete: @escaping (RenderResult?) -> Void) {
|
|
186
|
+
enqueue(owner: owner, name: name, color: color, width: width, height: height, scale: scale,
|
|
187
|
+
finalColor: false, complete: complete)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func enqueue(owner: AnyObject, name: String, color: String, width: Int,
|
|
191
|
+
height: Int, scale: CGFloat, finalColor: Bool, complete: @escaping (RenderResult?) -> Void) {
|
|
192
|
+
lock.lock(); defer { lock.unlock() }
|
|
193
|
+
let key = ObjectIdentifier(owner)
|
|
194
|
+
if pending[key] == nil { order.append(key) }
|
|
195
|
+
pending[key] = Request(name: name, color: color, width: width, height: height, scale: scale,
|
|
196
|
+
finalColor: finalColor, complete: complete)
|
|
197
|
+
while workers < 2 && workers < pending.count {
|
|
198
|
+
workers += 1
|
|
199
|
+
DispatchQueue.global(qos: .userInitiated).async { self.drain() }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private func drain() {
|
|
204
|
+
while true {
|
|
205
|
+
lock.lock()
|
|
206
|
+
guard !order.isEmpty, let request = pending.removeValue(forKey: order.removeFirst()) else {
|
|
207
|
+
workers -= 1; lock.unlock(); return
|
|
208
|
+
}
|
|
209
|
+
lock.unlock()
|
|
210
|
+
let image = autoreleasepool { () -> RenderResult? in
|
|
211
|
+
guard let result = decode(request) else { return nil }
|
|
212
|
+
if !request.finalColor { return result }
|
|
213
|
+
return RenderResult(image: result.finalImage(request.color), mode: .raster,
|
|
214
|
+
sourceIdentity: result.sourceIdentity, maskAlpha: result.maskAlpha)
|
|
215
|
+
}
|
|
216
|
+
DispatchQueue.main.async { request.complete(image) }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private func decode(_ request: Request) -> RenderResult? {
|
|
221
|
+
guard (1...4096).contains(request.width), (1...4096).contains(request.height),
|
|
222
|
+
request.width * request.height <= 4_194_304, request.scale.isFinite,
|
|
223
|
+
request.scale > 0,
|
|
224
|
+
let channels = Self.channels(request.color) else { return nil }
|
|
225
|
+
metadataLock.lock()
|
|
226
|
+
loadManifestIfNeeded()
|
|
227
|
+
let entry = metadata?[request.name]
|
|
228
|
+
let bundle = resourceBundle
|
|
229
|
+
metadataLock.unlock()
|
|
230
|
+
guard let digest = entry?["digest"] as? String,
|
|
231
|
+
let url = bundle?.url(forResource: request.name, withExtension: "svg") else { return nil }
|
|
232
|
+
let mode = (entry?["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
|
|
233
|
+
let (sourceIdentity, key, maskAlpha) = Self.renderKey(
|
|
234
|
+
name: request.name, digest: digest, width: request.width, height: request.height,
|
|
235
|
+
scale: request.scale, mode: mode, color: request.color, channels: channels)
|
|
236
|
+
let paintColor: String
|
|
237
|
+
switch mode {
|
|
238
|
+
case .fixed, .single: paintColor = "rgba(0,0,0,1)"
|
|
239
|
+
case .multiple: paintColor = "rgba(0,0,0,\(maskAlpha))"
|
|
240
|
+
case .raster: paintColor = request.color
|
|
241
|
+
}
|
|
242
|
+
lock.lock()
|
|
243
|
+
if let image = cache[key] {
|
|
244
|
+
recency.removeAll { $0 == key }; recency.append(key)
|
|
245
|
+
lock.unlock(); return image
|
|
246
|
+
}
|
|
247
|
+
lock.unlock()
|
|
248
|
+
guard let source = try? String(contentsOf: url, encoding: .utf8),
|
|
249
|
+
let pattern = try? NSRegularExpression(pattern: #"((?:fill|stroke)\s*=\s*["'])currentColor(["'])"#) else { return nil }
|
|
250
|
+
// CoreSVG interprets integer rgba alpha as an 8-bit channel; decimals retain CSS endpoints.
|
|
251
|
+
let svgColor = paintColor.replacingOccurrences(of: #",(0|1)\)$"#, with: ",$1.0)", options: .regularExpression)
|
|
252
|
+
let xml = pattern.stringByReplacingMatches(in: source, range: NSRange(source.startIndex..., in: source),
|
|
253
|
+
withTemplate: "$1\(svgColor)$2")
|
|
254
|
+
guard let data = xml.data(using: .utf8),
|
|
255
|
+
let image = OneKeyImageCoderBridge.decodeSVG(data, pixelSize: CGSize(width: request.width, height: request.height)),
|
|
256
|
+
image.size.width > 0, image.size.height > 0 else { return nil }
|
|
257
|
+
let size = CGSize(width: request.width, height: request.height)
|
|
258
|
+
let format = UIGraphicsImageRendererFormat()
|
|
259
|
+
format.scale = 1
|
|
260
|
+
format.preferredRange = .standard
|
|
261
|
+
let output = UIGraphicsImageRenderer(size: size, format: format).image { _ in
|
|
262
|
+
let ratio = min(size.width / image.size.width, size.height / image.size.height)
|
|
263
|
+
let fitted = CGSize(width: image.size.width * ratio, height: image.size.height * ratio)
|
|
264
|
+
image.draw(in: CGRect(x: (size.width - fitted.width) / 2, y: (size.height - fitted.height) / 2,
|
|
265
|
+
width: fitted.width, height: fitted.height))
|
|
266
|
+
}
|
|
267
|
+
let result = RenderResult(image: output, mode: mode, sourceIdentity: sourceIdentity, maskAlpha: maskAlpha)
|
|
268
|
+
let cost = (output.cgImage?.bytesPerRow ?? request.width * 4) * request.height
|
|
269
|
+
if cost <= 8_388_608 {
|
|
270
|
+
lock.lock(); defer { lock.unlock() }
|
|
271
|
+
if let previous = cache[key] { bytes -= (previous.image.cgImage?.bytesPerRow ?? request.width * 4) * request.height }
|
|
272
|
+
cache[key] = result
|
|
273
|
+
recency.removeAll { $0 == key }; recency.append(key)
|
|
274
|
+
bytes += cost
|
|
275
|
+
while cache.count > 128 || bytes > 8_388_608 {
|
|
276
|
+
let oldest = recency.removeFirst()
|
|
277
|
+
if let image = cache.removeValue(forKey: oldest) { bytes -= (image.image.cgImage?.bytesPerRow ?? 0) * (image.image.cgImage?.height ?? 0) }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return result
|
|
281
|
+
}
|
|
282
|
+
}
|