react-native-parity-blur 0.1.0
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/LICENSE +20 -0
- package/ParityBlur.podspec +20 -0
- package/README.md +198 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/parityblur/AndroidBlurCalibration.kt +82 -0
- package/android/src/main/java/com/parityblur/BlurEngine.kt +132 -0
- package/android/src/main/java/com/parityblur/ParityBlurDebug.kt +24 -0
- package/android/src/main/java/com/parityblur/ParityBlurPackage.kt +17 -0
- package/android/src/main/java/com/parityblur/ParityBlurView.kt +542 -0
- package/android/src/main/java/com/parityblur/ParityBlurViewManager.kt +161 -0
- package/android/src/main/java/com/parityblur/PipelineMath.kt +202 -0
- package/android/src/main/java/com/parityblur/PixelCopySnapshotProvider.kt +108 -0
- package/android/src/main/java/com/parityblur/SoftwareSnapshotProvider.kt +173 -0
- package/android/src/main/java/com/parityblur/WindowBlurContext.kt +116 -0
- package/android/src/test/java/com/parityblur/MiniJson.kt +179 -0
- package/android/src/test/java/com/parityblur/PipelineFixturesTest.kt +222 -0
- package/ios/BlurEngine.swift +106 -0
- package/ios/ColorPipeline.swift +72 -0
- package/ios/MetalPresentationSurface.swift +51 -0
- package/ios/ParityBlurCoreView.swift +382 -0
- package/ios/ParityBlurDebug.swift +18 -0
- package/ios/ParityBlurView.h +14 -0
- package/ios/ParityBlurView.mm +135 -0
- package/ios/PipelineMath.swift +177 -0
- package/ios/SnapshotProvider.swift +128 -0
- package/ios/WindowBlurContext.swift +111 -0
- package/lib/module/BlurView.js +124 -0
- package/lib/module/BlurView.js.map +1 -0
- package/lib/module/ParityBlurViewNativeComponent.ts +82 -0
- package/lib/module/defaults.js +13 -0
- package/lib/module/defaults.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/pipeline/androidCalibration.js +55 -0
- package/lib/module/pipeline/androidCalibration.js.map +1 -0
- package/lib/module/pipeline/captureRect.js +151 -0
- package/lib/module/pipeline/captureRect.js.map +1 -0
- package/lib/module/pipeline/color.js +82 -0
- package/lib/module/pipeline/color.js.map +1 -0
- package/lib/module/pipeline/constants.js +65 -0
- package/lib/module/pipeline/constants.js.map +1 -0
- package/lib/module/pipeline/downsample.js +61 -0
- package/lib/module/pipeline/downsample.js.map +1 -0
- package/lib/module/pipeline/index.js +23 -0
- package/lib/module/pipeline/index.js.map +1 -0
- package/lib/module/pipeline/overlay.js +56 -0
- package/lib/module/pipeline/overlay.js.map +1 -0
- package/lib/module/pipeline/saturation.js +52 -0
- package/lib/module/pipeline/saturation.js.map +1 -0
- package/lib/module/pipeline/types.js +23 -0
- package/lib/module/pipeline/types.js.map +1 -0
- package/lib/module/pipeline/units.js +33 -0
- package/lib/module/pipeline/units.js.map +1 -0
- package/lib/module/presets.js +96 -0
- package/lib/module/presets.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/BlurView.d.ts +16 -0
- package/lib/typescript/src/BlurView.d.ts.map +1 -0
- package/lib/typescript/src/ParityBlurViewNativeComponent.d.ts +63 -0
- package/lib/typescript/src/ParityBlurViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/defaults.d.ts +12 -0
- package/lib/typescript/src/defaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/androidCalibration.d.ts +42 -0
- package/lib/typescript/src/pipeline/androidCalibration.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/captureRect.d.ts +84 -0
- package/lib/typescript/src/pipeline/captureRect.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/color.d.ts +19 -0
- package/lib/typescript/src/pipeline/color.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/constants.d.ts +52 -0
- package/lib/typescript/src/pipeline/constants.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/downsample.d.ts +31 -0
- package/lib/typescript/src/pipeline/downsample.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/index.d.ts +20 -0
- package/lib/typescript/src/pipeline/index.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/overlay.d.ts +31 -0
- package/lib/typescript/src/pipeline/overlay.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/saturation.d.ts +38 -0
- package/lib/typescript/src/pipeline/saturation.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/types.d.ts +32 -0
- package/lib/typescript/src/pipeline/types.d.ts.map +1 -0
- package/lib/typescript/src/pipeline/units.d.ts +23 -0
- package/lib/typescript/src/pipeline/units.d.ts.map +1 -0
- package/lib/typescript/src/presets.d.ts +57 -0
- package/lib/typescript/src/presets.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +116 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +167 -0
- package/src/BlurView.tsx +195 -0
- package/src/ParityBlurViewNativeComponent.ts +82 -0
- package/src/defaults.ts +20 -0
- package/src/index.ts +10 -0
- package/src/pipeline/androidCalibration.ts +64 -0
- package/src/pipeline/captureRect.ts +172 -0
- package/src/pipeline/color.ts +66 -0
- package/src/pipeline/constants.ts +65 -0
- package/src/pipeline/downsample.ts +74 -0
- package/src/pipeline/index.ts +20 -0
- package/src/pipeline/overlay.ts +46 -0
- package/src/pipeline/saturation.ts +65 -0
- package/src/pipeline/types.ts +37 -0
- package/src/pipeline/units.ts +32 -0
- package/src/presets.ts +103 -0
- package/src/types.ts +132 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Swift mirror of the canonical pipeline reference (docs/PIPELINE_SPEC.md; src/pipeline/*.ts).
|
|
4
|
+
/// Pure math, no UIKit/Metal imports — the SPM fixture suite (swift-tests/) compiles this same
|
|
5
|
+
/// file against test/pipeline-fixtures.json, exactly like the Kotlin PipelineMath JVM tests.
|
|
6
|
+
/// When the spec changes, change TS + Kotlin + this file in the same commit.
|
|
7
|
+
public enum PipelineMath {
|
|
8
|
+
|
|
9
|
+
// MARK: - Constants (PIPELINE_SPEC §2, §3, §4, §7 — single home on the iOS side)
|
|
10
|
+
|
|
11
|
+
/// Gaussian support multiplier for capture expansion (spec §3.1).
|
|
12
|
+
public static let captureSupportK = 3.0
|
|
13
|
+
/// Auto-downsample: keep sigmaSnapshot >= this many snapshot px (spec §4).
|
|
14
|
+
public static let minSigmaSnapshot = 1.0
|
|
15
|
+
/// Auto-downsample: captures below this area (device px^2) never exceed 2x (spec §4).
|
|
16
|
+
public static let smallCaptureAreaPx = 256.0 * 256.0
|
|
17
|
+
/// Rec. 709 luma coefficients (spec §7).
|
|
18
|
+
public static let lumaR = 0.2126
|
|
19
|
+
public static let lumaG = 0.7152
|
|
20
|
+
public static let lumaB = 0.0722
|
|
21
|
+
|
|
22
|
+
public struct Rect: Equatable {
|
|
23
|
+
public var x: Double
|
|
24
|
+
public var y: Double
|
|
25
|
+
public var width: Double
|
|
26
|
+
public var height: Double
|
|
27
|
+
public init(x: Double, y: Double, width: Double, height: Double) {
|
|
28
|
+
self.x = x; self.y = y; self.width = width; self.height = height
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public struct RGBA: Equatable {
|
|
33
|
+
public var r: Double
|
|
34
|
+
public var g: Double
|
|
35
|
+
public var b: Double
|
|
36
|
+
public var a: Double
|
|
37
|
+
public init(r: Double, g: Double, b: Double, a: Double) {
|
|
38
|
+
self.r = r; self.g = g; self.b = b; self.a = a
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// MARK: - Units (spec §1)
|
|
43
|
+
|
|
44
|
+
public static func sigmaPxFromDp(_ blurRadiusDp: Double, displayScale: Double) -> Double {
|
|
45
|
+
guard blurRadiusDp.isFinite, blurRadiusDp > 0 else { return 0 }
|
|
46
|
+
guard displayScale.isFinite, displayScale > 0 else { return 0 }
|
|
47
|
+
return blurRadiusDp * displayScale
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static func sigmaSnapshotFromPx(_ sigmaPx: Double, downsample: Int) -> Double {
|
|
51
|
+
guard sigmaPx.isFinite, sigmaPx > 0 else { return 0 }
|
|
52
|
+
return sigmaPx / Double(downsample)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// iOS blur parameter (spec §2): MPSImageGaussianBlur takes the snapshot sigma directly.
|
|
56
|
+
public static func iosSigmaForSigma(_ sigmaSnapshot: Double) -> Double {
|
|
57
|
+
guard sigmaSnapshot.isFinite, sigmaSnapshot > 0 else { return 0 }
|
|
58
|
+
return sigmaSnapshot
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MARK: - Downsample selection (spec §4)
|
|
62
|
+
|
|
63
|
+
public static func autoDownsample(sigmaPx: Double, captureAreaPx: Double, quality: String) -> Int {
|
|
64
|
+
guard sigmaPx.isFinite, sigmaPx > 0 else { return 1 }
|
|
65
|
+
let maxByQuality: Int
|
|
66
|
+
switch quality {
|
|
67
|
+
case "high": maxByQuality = 2
|
|
68
|
+
case "performance": maxByQuality = 8
|
|
69
|
+
default: maxByQuality = 4 // balanced
|
|
70
|
+
}
|
|
71
|
+
let maxBySigma = Int((sigmaPx / minSigmaSnapshot).rounded(.down))
|
|
72
|
+
let area = captureAreaPx.isFinite ? captureAreaPx : 0
|
|
73
|
+
let maxByArea = area < smallCaptureAreaPx ? 2 : 8
|
|
74
|
+
let cap = min(maxByQuality, min(maxBySigma, maxByArea))
|
|
75
|
+
for factor in [8, 4, 2, 1] where factor <= cap {
|
|
76
|
+
return factor
|
|
77
|
+
}
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static func resolveDownsample(
|
|
82
|
+
prop: Int, sigmaPx: Double, captureAreaPx: Double, quality: String
|
|
83
|
+
) -> Int {
|
|
84
|
+
// 0 = the 'auto' sentinel (see ParityBlurViewNativeComponent.ts).
|
|
85
|
+
if prop == 1 || prop == 2 || prop == 4 || prop == 8 { return prop }
|
|
86
|
+
return autoDownsample(sigmaPx: sigmaPx, captureAreaPx: captureAreaPx, quality: quality)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Capture-rect math (spec §3); all rects in target-local DEVICE PX
|
|
90
|
+
|
|
91
|
+
public static func supportMarginPx(_ sigmaPx: Double, k: Double = captureSupportK) -> Double {
|
|
92
|
+
guard sigmaPx.isFinite, sigmaPx > 0 else { return 0 }
|
|
93
|
+
return (k * sigmaPx).rounded(.up)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public static func intersect(_ a: Rect, _ b: Rect) -> Rect {
|
|
97
|
+
let x0 = max(a.x, b.x)
|
|
98
|
+
let y0 = max(a.y, b.y)
|
|
99
|
+
let x1 = min(a.x + a.width, b.x + b.width)
|
|
100
|
+
let y1 = min(a.y + a.height, b.y + b.height)
|
|
101
|
+
let w = max(0, x1 - x0)
|
|
102
|
+
let h = max(0, y1 - y0)
|
|
103
|
+
if w == 0 || h == 0 { return Rect(x: x0, y: y0, width: 0, height: 0) }
|
|
104
|
+
return Rect(x: x0, y: y0, width: w, height: h)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public static func expandCaptureRect(
|
|
108
|
+
visible: Rect, targetBounds: Rect, sigmaPx: Double, k: Double = captureSupportK
|
|
109
|
+
) -> Rect {
|
|
110
|
+
let m = supportMarginPx(sigmaPx, k: k)
|
|
111
|
+
let expanded = Rect(
|
|
112
|
+
x: visible.x - m, y: visible.y - m,
|
|
113
|
+
width: visible.width + 2 * m, height: visible.height + 2 * m
|
|
114
|
+
)
|
|
115
|
+
return intersect(expanded, targetBounds)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public static func rectArea(_ r: Rect) -> Double { r.width * r.height }
|
|
119
|
+
|
|
120
|
+
/// Snapshot rect in INTEGER snapshot px: origin FLOORS, far edge CEILS (locked rule, spec §3.3).
|
|
121
|
+
public static func snapshotRectFor(_ captureRectPx: Rect, downsample: Int) -> Rect {
|
|
122
|
+
let d = Double(downsample)
|
|
123
|
+
let x = (captureRectPx.x / d).rounded(.down)
|
|
124
|
+
let y = (captureRectPx.y / d).rounded(.down)
|
|
125
|
+
let farX = ((captureRectPx.x + captureRectPx.width) / d).rounded(.up)
|
|
126
|
+
let farY = ((captureRectPx.y + captureRectPx.height) / d).rounded(.up)
|
|
127
|
+
return Rect(x: x, y: y, width: max(0, farX - x), height: max(0, farY - y))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Fractional crop rect in SNAPSHOT PX selecting the visible region (spec §3.4).
|
|
131
|
+
public static func cropRectFor(visible: Rect, snapshotRect: Rect, downsample: Int) -> Rect {
|
|
132
|
+
let d = Double(downsample)
|
|
133
|
+
return Rect(
|
|
134
|
+
x: visible.x / d - snapshotRect.x,
|
|
135
|
+
y: visible.y / d - snapshotRect.y,
|
|
136
|
+
width: visible.width / d,
|
|
137
|
+
height: visible.height / d
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// MARK: - Saturation matrix (spec §7): row-major 4x5, Android ColorMatrix layout
|
|
142
|
+
|
|
143
|
+
public static func saturationMatrix(
|
|
144
|
+
_ s: Double, lr: Double = lumaR, lg: Double = lumaG, lb: Double = lumaB
|
|
145
|
+
) -> [Double] {
|
|
146
|
+
let t = 1 - s
|
|
147
|
+
return [
|
|
148
|
+
t * lr + s, t * lg, t * lb, 0, 0,
|
|
149
|
+
t * lr, t * lg + s, t * lb, 0, 0,
|
|
150
|
+
t * lr, t * lg, t * lb + s, 0, 0,
|
|
151
|
+
0, 0, 0, 1, 0,
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public static func applySaturation(
|
|
156
|
+
matrix m: [Double], r: Double, g: Double, b: Double, a: Double
|
|
157
|
+
) -> RGBA {
|
|
158
|
+
func c01(_ v: Double) -> Double { v < 0 ? 0 : (v > 1 ? 1 : v) }
|
|
159
|
+
return RGBA(
|
|
160
|
+
r: c01(m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4]),
|
|
161
|
+
g: c01(m[5] * r + m[6] * g + m[7] * b + m[8] * a + m[9]),
|
|
162
|
+
b: c01(m[10] * r + m[11] * g + m[12] * b + m[13] * a + m[14]),
|
|
163
|
+
a: m[15] * r + m[16] * g + m[17] * b + m[18] * a + m[19]
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Overlay source-over (spec §8), straight alpha
|
|
168
|
+
|
|
169
|
+
public static func sourceOver(src: RGBA, dst: RGBA) -> RGBA {
|
|
170
|
+
let outA = src.a + dst.a * (1 - src.a)
|
|
171
|
+
guard outA > 0 else { return RGBA(r: 0, g: 0, b: 0, a: 0) }
|
|
172
|
+
func blend(_ sc: Double, _ dc: Double) -> Double {
|
|
173
|
+
(sc * src.a + dc * dst.a * (1 - src.a)) / outA
|
|
174
|
+
}
|
|
175
|
+
return RGBA(r: blend(src.r, dst.r), g: blend(src.g, dst.g), b: blend(src.b, dst.b), a: outA)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Exclusion registry (plan §16): every ParityBlur presentation surface registers here and is
|
|
5
|
+
/// hidden at the MODEL-layer level for the duration of a capture pass, restored before the
|
|
6
|
+
/// runloop commits — the render server never sees the hidden state (M0-verified flicker-free;
|
|
7
|
+
/// presentation-layer probe measured 0 leaks).
|
|
8
|
+
final class BlurSurfaceRegistry {
|
|
9
|
+
static let shared = BlurSurfaceRegistry()
|
|
10
|
+
private let table = NSHashTable<UIView>.weakObjects()
|
|
11
|
+
func register(_ v: UIView) { table.add(v) }
|
|
12
|
+
func unregister(_ v: UIView) { table.remove(v) }
|
|
13
|
+
var all: [UIView] { table.allObjects }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Reusable CPU snapshot buffer: BGRA8, premultiplied, sRGB-ENCODED bytes. The bytes are
|
|
17
|
+
/// uploaded to a non-sRGB (.bgra8Unorm) texture so the GPU convolves the encoded values —
|
|
18
|
+
/// gamma-space blur, matching Android/Skia (spec §6; M0 color-domain proof).
|
|
19
|
+
final class SnapshotBuffer {
|
|
20
|
+
private(set) var width = 0
|
|
21
|
+
private(set) var height = 0
|
|
22
|
+
private(set) var bytesPerRow = 0
|
|
23
|
+
private(set) var data: UnsafeMutableRawPointer?
|
|
24
|
+
private(set) var context: CGContext?
|
|
25
|
+
|
|
26
|
+
func ensure(width w: Int, height h: Int) -> Bool {
|
|
27
|
+
guard w > 0, h > 0 else { return false }
|
|
28
|
+
if w == width && h == height, context != nil { return true }
|
|
29
|
+
release()
|
|
30
|
+
bytesPerRow = ((w * 4 + 63) / 64) * 64
|
|
31
|
+
guard let mem = malloc(bytesPerRow * h) else { return false }
|
|
32
|
+
data = mem
|
|
33
|
+
let space = CGColorSpace(name: CGColorSpace.sRGB)!
|
|
34
|
+
let info = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
|
|
35
|
+
guard let ctx = CGContext(
|
|
36
|
+
data: mem, width: w, height: h, bitsPerComponent: 8,
|
|
37
|
+
bytesPerRow: bytesPerRow, space: space, bitmapInfo: info
|
|
38
|
+
) else {
|
|
39
|
+
release()
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
context = ctx
|
|
43
|
+
width = w
|
|
44
|
+
height = h
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func release() {
|
|
49
|
+
free(data)
|
|
50
|
+
data = nil
|
|
51
|
+
context = nil
|
|
52
|
+
width = 0
|
|
53
|
+
height = 0
|
|
54
|
+
bytesPerRow = 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
deinit { free(data) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Capture request: everything is expressed in the SNAPSHOT-PIXEL grid derived by PipelineMath
|
|
61
|
+
/// (spec §3): the buffer's pixel (0,0) is snapshotRect's origin, honoring the locked
|
|
62
|
+
/// floor-origin/ceil-far-edge rounding.
|
|
63
|
+
struct SnapshotRequest {
|
|
64
|
+
let target: UIView
|
|
65
|
+
/// Integer snapshot-pixel rect (PipelineMath.snapshotRectFor output).
|
|
66
|
+
let snapshotRect: PipelineMath.Rect
|
|
67
|
+
/// Downsample factor D.
|
|
68
|
+
let downsample: Int
|
|
69
|
+
/// Device pixels per point (UIScreen scale).
|
|
70
|
+
let displayScale: CGFloat
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Snapshot provider abstraction (plan §15.3). v1 ships the model-state provider as default;
|
|
74
|
+
/// a committed-state drawHierarchy provider can slot in later for structurally-separate targets
|
|
75
|
+
/// (M0: drawHierarchy renders the last committed frame and cannot honor model-layer exclusion).
|
|
76
|
+
protocol SnapshotProvider: AnyObject {
|
|
77
|
+
var buffer: SnapshotBuffer { get }
|
|
78
|
+
@discardableResult
|
|
79
|
+
func capture(_ req: SnapshotRequest, excluding: [UIView]) -> Bool
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Default provider (M0 finding 1): CALayer.render(in:) renders MODEL state, so registry
|
|
83
|
+
/// exclusion works in-tree, and the mandatory clip keeps cost proportional to the capture
|
|
84
|
+
/// region (M0: 4.8x cheaper with the clip).
|
|
85
|
+
final class LayerRenderSnapshotProvider: SnapshotProvider {
|
|
86
|
+
let buffer = SnapshotBuffer()
|
|
87
|
+
|
|
88
|
+
@discardableResult
|
|
89
|
+
func capture(_ req: SnapshotRequest, excluding: [UIView]) -> Bool {
|
|
90
|
+
let w = Int(req.snapshotRect.width)
|
|
91
|
+
let h = Int(req.snapshotRect.height)
|
|
92
|
+
guard buffer.ensure(width: w, height: h), let ctx = buffer.context else { return false }
|
|
93
|
+
|
|
94
|
+
var saved: [(CALayer, Bool)] = []
|
|
95
|
+
for v in excluding {
|
|
96
|
+
saved.append((v.layer, v.layer.isHidden))
|
|
97
|
+
v.layer.isHidden = true
|
|
98
|
+
}
|
|
99
|
+
defer {
|
|
100
|
+
// Restore within the same runloop turn — never committed (plan §16.5/§16.6).
|
|
101
|
+
for (layer, wasHidden) in saved { layer.isHidden = wasHidden }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Points-per-snapshot-pixel and the buffer origin in target-local points.
|
|
105
|
+
let d = Double(req.downsample)
|
|
106
|
+
let scale = Double(req.displayScale)
|
|
107
|
+
let pxPerPoint = scale / d // snapshot px per point
|
|
108
|
+
let originPtX = req.snapshotRect.x * d / scale
|
|
109
|
+
let originPtY = req.snapshotRect.y * d / scale
|
|
110
|
+
let capturePtRect = CGRect(
|
|
111
|
+
x: originPtX, y: originPtY,
|
|
112
|
+
width: Double(w) / pxPerPoint, height: Double(h) / pxPerPoint
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
ctx.saveGState()
|
|
116
|
+
ctx.clear(CGRect(x: 0, y: 0, width: CGFloat(w), height: CGFloat(h)))
|
|
117
|
+
// Flip to UIKit coordinates, then map the capture rect onto the buffer.
|
|
118
|
+
ctx.translateBy(x: 0, y: CGFloat(h))
|
|
119
|
+
ctx.scaleBy(x: 1, y: -1)
|
|
120
|
+
ctx.scaleBy(x: CGFloat(pxPerPoint), y: CGFloat(pxPerPoint))
|
|
121
|
+
ctx.translateBy(x: CGFloat(-originPtX), y: CGFloat(-originPtY))
|
|
122
|
+
// Mandatory clip (M0 finding 2): lets CoreGraphics cull content outside the region.
|
|
123
|
+
ctx.clip(to: capturePtRect)
|
|
124
|
+
req.target.layer.render(in: ctx)
|
|
125
|
+
ctx.restoreGState()
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Per-window active-view registry + capture scheduling (plan §15.2, §20, §21, §39).
|
|
5
|
+
///
|
|
6
|
+
/// STATIC mode: capture requests coalesce to ONE capture per view on the next main-runloop
|
|
7
|
+
/// turn (plan §29, §45.8).
|
|
8
|
+
///
|
|
9
|
+
/// LIVE mode (Milestone 6): ONE shared CADisplayLink per window — never one per view (plan
|
|
10
|
+
/// §45.6) — created when the first live view registers and torn down when the last leaves
|
|
11
|
+
/// (plan §24/§27: zero frame work while no live blur is visible). Each tick filters by the
|
|
12
|
+
/// visibility heuristic and each view's own maxFps throttle + in-flight backpressure
|
|
13
|
+
/// (plan §21/§22: at most 1 in-flight GPU pass per view, latest-wins, stale frames dropped).
|
|
14
|
+
/// The link pauses while the app is inactive (plan §21 app/scene lifecycle pause).
|
|
15
|
+
final class WindowBlurContext {
|
|
16
|
+
|
|
17
|
+
/// Attached ParityBlur core views in this window (weak bookkeeping).
|
|
18
|
+
private let activeViews = NSHashTable<ParityBlurCoreView>.weakObjects()
|
|
19
|
+
|
|
20
|
+
private var pendingCaptures = NSHashTable<ParityBlurCoreView>.weakObjects()
|
|
21
|
+
private var flushScheduled = false
|
|
22
|
+
|
|
23
|
+
// ------------------------------------------------------------------- live (Milestone 6)
|
|
24
|
+
|
|
25
|
+
private let liveViews = NSHashTable<ParityBlurCoreView>.weakObjects()
|
|
26
|
+
private var displayLink: CADisplayLink?
|
|
27
|
+
private var lifecycleObservers: [NSObjectProtocol] = []
|
|
28
|
+
|
|
29
|
+
/// CADisplayLink retains its target; this proxy breaks the cycle so the context (and its
|
|
30
|
+
/// window) can deallocate naturally.
|
|
31
|
+
private final class LinkProxy {
|
|
32
|
+
weak var context: WindowBlurContext?
|
|
33
|
+
init(_ c: WindowBlurContext) { context = c }
|
|
34
|
+
@objc func tick(_ link: CADisplayLink) { context?.liveTick(link) }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
deinit {
|
|
38
|
+
displayLink?.invalidate()
|
|
39
|
+
for o in lifecycleObservers { NotificationCenter.default.removeObserver(o) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func register(_ view: ParityBlurCoreView) {
|
|
43
|
+
activeViews.add(view)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func unregister(_ view: ParityBlurCoreView) {
|
|
47
|
+
activeViews.remove(view)
|
|
48
|
+
pendingCaptures.remove(view)
|
|
49
|
+
unregisterLive(view)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func registerLive(_ view: ParityBlurCoreView) {
|
|
53
|
+
liveViews.add(view)
|
|
54
|
+
ensureDisplayLink()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func unregisterLive(_ view: ParityBlurCoreView) {
|
|
58
|
+
liveViews.remove(view)
|
|
59
|
+
if liveViews.count == 0 {
|
|
60
|
+
displayLink?.invalidate()
|
|
61
|
+
displayLink = nil
|
|
62
|
+
ParityBlurDebug.log("scheduler-uninstall window=\(ObjectIdentifier(self))")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func ensureDisplayLink() {
|
|
67
|
+
guard displayLink == nil else { return }
|
|
68
|
+
let link = CADisplayLink(target: LinkProxy(self), selector: #selector(LinkProxy.tick(_:)))
|
|
69
|
+
// Cap the link at the highest maxFps among live views; per-view throttles refine below it.
|
|
70
|
+
let cap = Float(liveViews.allObjects.map(\.maxFps).max() ?? 30)
|
|
71
|
+
link.preferredFrameRateRange = CAFrameRateRange(minimum: 10, maximum: cap, preferred: cap)
|
|
72
|
+
link.add(to: .main, forMode: .common)
|
|
73
|
+
displayLink = link
|
|
74
|
+
ParityBlurDebug.log("scheduler-install window=\(ObjectIdentifier(self))")
|
|
75
|
+
|
|
76
|
+
if lifecycleObservers.isEmpty {
|
|
77
|
+
let nc = NotificationCenter.default
|
|
78
|
+
lifecycleObservers.append(nc.addObserver(
|
|
79
|
+
forName: UIApplication.willResignActiveNotification, object: nil, queue: .main
|
|
80
|
+
) { [weak self] _ in self?.displayLink?.isPaused = true })
|
|
81
|
+
lifecycleObservers.append(nc.addObserver(
|
|
82
|
+
forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
|
|
83
|
+
) { [weak self] _ in self?.displayLink?.isPaused = false })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func liveTick(_ link: CADisplayLink) {
|
|
88
|
+
let now = link.timestamp
|
|
89
|
+
for view in liveViews.allObjects where view.isLiveEligible(now: now) {
|
|
90
|
+
view.performLiveTick(now: now)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Coalesce a static capture request to the next main-runloop turn (plan §20).
|
|
95
|
+
func scheduleCapture(_ view: ParityBlurCoreView) {
|
|
96
|
+
pendingCaptures.add(view)
|
|
97
|
+
guard !flushScheduled else { return }
|
|
98
|
+
flushScheduled = true
|
|
99
|
+
DispatchQueue.main.async { [weak self] in
|
|
100
|
+
guard let self else { return }
|
|
101
|
+
self.flushScheduled = false
|
|
102
|
+
let batch = self.pendingCaptures.allObjects
|
|
103
|
+
self.pendingCaptures.removeAllObjects()
|
|
104
|
+
for v in batch where v.window != nil {
|
|
105
|
+
v.performScheduledCapture()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var isEmpty: Bool { activeViews.count == 0 }
|
|
111
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import NativeParityBlurView, { Commands as NativeCommands } from './ParityBlurViewNativeComponent';
|
|
5
|
+
import { DEFAULT_BLUR_RADIUS, DEFAULT_DOWNSAMPLE, DEFAULT_MAX_FPS, DEFAULT_MODE, DEFAULT_OVERLAY_COLOR, DEFAULT_QUALITY, DEFAULT_SATURATION, MAX_MAX_FPS, MIN_MAX_FPS } from "./defaults.js";
|
|
6
|
+
import { BlurPresets } from "./presets.js";
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
const LOG_TAG = '[react-native-parity-blur]';
|
|
9
|
+
function clampBlurRadius(value) {
|
|
10
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
11
|
+
return DEFAULT_BLUR_RADIUS;
|
|
12
|
+
}
|
|
13
|
+
return value < 0 ? 0 : value;
|
|
14
|
+
}
|
|
15
|
+
function clampSaturation(value) {
|
|
16
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
17
|
+
return DEFAULT_SATURATION;
|
|
18
|
+
}
|
|
19
|
+
return value < 0 ? 0 : value;
|
|
20
|
+
}
|
|
21
|
+
function clampMaxFps(value) {
|
|
22
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
23
|
+
return DEFAULT_MAX_FPS;
|
|
24
|
+
}
|
|
25
|
+
return Math.min(MAX_MAX_FPS, Math.max(MIN_MAX_FPS, value));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Normalizes the public `downsample` prop (`'auto' | 1 | 2 | 4 | 8`) into the
|
|
30
|
+
* Int32 the native Fabric prop accepts, where `0` is a sentinel for `'auto'`.
|
|
31
|
+
* See the deviation note in ParityBlurViewNativeComponent.ts for why this
|
|
32
|
+
* isn't a string/numeric enum.
|
|
33
|
+
*/
|
|
34
|
+
function normalizeDownsample(value) {
|
|
35
|
+
if (value === undefined || value === 'auto') {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
if (value === 1 || value === 2 || value === 4 || value === 8) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
if (__DEV__) {
|
|
42
|
+
console.warn(`${LOG_TAG} Invalid "downsample" value: ${JSON.stringify(value)}. Expected 'auto' | 1 | 2 | 4 | 8. Falling back to '${DEFAULT_DOWNSAMPLE}'.`);
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Public backdrop blur component (plan §28/§29/§30).
|
|
48
|
+
*
|
|
49
|
+
* Renders the native ParityBlurView host view backed by a real blur on both
|
|
50
|
+
* platforms: Android API 31+ real-time `RenderEffect` Gaussian blur (older
|
|
51
|
+
* Android renders `fallbackColor` instead — real CPU blur is intentionally
|
|
52
|
+
* out of scope, plan §44), iOS `MPSImageGaussianBlur`. `refresh()` is fully
|
|
53
|
+
* wired to a native coalesced recapture (plan §29). See
|
|
54
|
+
* docs/PIPELINE_SPEC.md for the canonical pipeline both backends implement
|
|
55
|
+
* and docs/CALIBRATION_REPORT.md for measured cross-platform parity.
|
|
56
|
+
*/
|
|
57
|
+
export const BlurView = /*#__PURE__*/React.forwardRef(function BlurView(props, ref) {
|
|
58
|
+
const {
|
|
59
|
+
preset,
|
|
60
|
+
blurRadius,
|
|
61
|
+
mode = DEFAULT_MODE,
|
|
62
|
+
overlayColor,
|
|
63
|
+
saturation,
|
|
64
|
+
quality = DEFAULT_QUALITY,
|
|
65
|
+
downsample,
|
|
66
|
+
maxFps,
|
|
67
|
+
fallbackColor,
|
|
68
|
+
// Accepted for public-API shape stability (plan §18); not yet wired
|
|
69
|
+
// to native capture logic. Deferred to v1.1 — see docs/LIMITATIONS.md.
|
|
70
|
+
blurTarget: _blurTarget,
|
|
71
|
+
children,
|
|
72
|
+
style
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
// A preset (plan §11) is pure JS sugar for a base
|
|
76
|
+
// { blurRadius, saturation, overlayColor } bundle — resolved here,
|
|
77
|
+
// client-side, with no native awareness of presets at all. Any of
|
|
78
|
+
// those three props passed explicitly above still wins per-prop.
|
|
79
|
+
const presetProps = preset ? BlurPresets[preset] : undefined;
|
|
80
|
+
const resolvedBlurRadius = blurRadius ?? presetProps?.blurRadius;
|
|
81
|
+
const resolvedSaturation = saturation ?? presetProps?.saturation;
|
|
82
|
+
const resolvedOverlayColor = overlayColor ?? presetProps?.overlayColor ?? DEFAULT_OVERLAY_COLOR;
|
|
83
|
+
const nativeRef = React.useRef(null);
|
|
84
|
+
React.useImperativeHandle(ref, () => ({
|
|
85
|
+
refresh() {
|
|
86
|
+
if (nativeRef.current) {
|
|
87
|
+
NativeCommands.refresh(nativeRef.current);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}), []);
|
|
91
|
+
if (__DEV__) {
|
|
92
|
+
if (preset !== undefined && !(preset in BlurPresets)) {
|
|
93
|
+
console.warn(`${LOG_TAG} Unknown "preset" value: ${JSON.stringify(preset)}. Ignoring.`);
|
|
94
|
+
}
|
|
95
|
+
if (blurRadius !== undefined && (typeof blurRadius !== 'number' || !Number.isFinite(blurRadius) || blurRadius < 0)) {
|
|
96
|
+
console.warn(`${LOG_TAG} "blurRadius" must be a finite number >= 0. Received: ${blurRadius}. Clamping to ${clampBlurRadius(blurRadius)}.`);
|
|
97
|
+
}
|
|
98
|
+
if (saturation !== undefined && (typeof saturation !== 'number' || !Number.isFinite(saturation) || saturation < 0)) {
|
|
99
|
+
console.warn(`${LOG_TAG} "saturation" must be a finite number >= 0. Received: ${saturation}. Clamping to ${clampSaturation(saturation)}.`);
|
|
100
|
+
}
|
|
101
|
+
if (maxFps !== undefined && (typeof maxFps !== 'number' || !Number.isFinite(maxFps) || maxFps < MIN_MAX_FPS || maxFps > MAX_MAX_FPS)) {
|
|
102
|
+
console.warn(`${LOG_TAG} "maxFps" must be a finite number between ${MIN_MAX_FPS} and ${MAX_MAX_FPS}. Received: ${maxFps}. Clamping to ${clampMaxFps(maxFps)}.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const safeBlurRadius = clampBlurRadius(resolvedBlurRadius);
|
|
106
|
+
const safeSaturation = clampSaturation(resolvedSaturation);
|
|
107
|
+
const safeMaxFps = clampMaxFps(maxFps);
|
|
108
|
+
const safeDownsample = normalizeDownsample(downsample);
|
|
109
|
+
return /*#__PURE__*/_jsx(NativeParityBlurView, {
|
|
110
|
+
ref: nativeRef,
|
|
111
|
+
style: style,
|
|
112
|
+
blurRadius: safeBlurRadius,
|
|
113
|
+
mode: mode,
|
|
114
|
+
overlayColor: resolvedOverlayColor,
|
|
115
|
+
saturation: safeSaturation,
|
|
116
|
+
quality: quality,
|
|
117
|
+
downsample: safeDownsample,
|
|
118
|
+
maxFps: safeMaxFps,
|
|
119
|
+
fallbackColor: fallbackColor,
|
|
120
|
+
children: children
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
export default BlurView;
|
|
124
|
+
//# sourceMappingURL=BlurView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","NativeParityBlurView","Commands","NativeCommands","DEFAULT_BLUR_RADIUS","DEFAULT_DOWNSAMPLE","DEFAULT_MAX_FPS","DEFAULT_MODE","DEFAULT_OVERLAY_COLOR","DEFAULT_QUALITY","DEFAULT_SATURATION","MAX_MAX_FPS","MIN_MAX_FPS","BlurPresets","jsx","_jsx","LOG_TAG","clampBlurRadius","value","Number","isFinite","clampSaturation","clampMaxFps","Math","min","max","normalizeDownsample","undefined","__DEV__","console","warn","JSON","stringify","BlurView","forwardRef","props","ref","preset","blurRadius","mode","overlayColor","saturation","quality","downsample","maxFps","fallbackColor","blurTarget","_blurTarget","children","style","presetProps","resolvedBlurRadius","resolvedSaturation","resolvedOverlayColor","nativeRef","useRef","useImperativeHandle","refresh","current","safeBlurRadius","safeSaturation","safeMaxFps","safeDownsample"],"sourceRoot":"../../src","sources":["BlurView.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fabric codegen spec for the ParityBlurView native host view.
|
|
3
|
+
*
|
|
4
|
+
* Every public prop from plan §28 is plumbed through to native, and the
|
|
5
|
+
* `refresh()` Fabric command is wired end-to-end to a real coalesced
|
|
6
|
+
* recapture. Both native backends (Android Kotlin, iOS Swift) implement the
|
|
7
|
+
* real capture/blur/saturate/overlay pipeline defined in
|
|
8
|
+
* docs/PIPELINE_SPEC.md — this file only declares the codegen shape, not
|
|
9
|
+
* the behavior.
|
|
10
|
+
*
|
|
11
|
+
* Deviation from plan §28 (documented in the Milestone 1 report): the public
|
|
12
|
+
* `downsample` prop is `'auto' | 1 | 2 | 4 | 8` (see src/types.ts). Fabric
|
|
13
|
+
* codegen's TypeScript parser rejects a union that mixes string and numeric
|
|
14
|
+
* literals for one prop ("Mixed types are not supported"), and a plain
|
|
15
|
+
* string enum with numeric-looking values ('1', '2', ...) is also unsafe
|
|
16
|
+
* because the iOS/C++ generator turns each enum option into a bare
|
|
17
|
+
* identifier (`ParityBlurViewDownsample::1` is not valid C++). Instead this
|
|
18
|
+
* native prop is a plain Int32 where `0` is a sentinel for `'auto'` and
|
|
19
|
+
* `1 | 2 | 4 | 8` pass through unchanged. `src/BlurView.tsx` performs the
|
|
20
|
+
* 'auto' <-> 0 translation; native code treats 0 as "auto".
|
|
21
|
+
*
|
|
22
|
+
* Note: this file deliberately imports everything from the 'react-native'
|
|
23
|
+
* package root rather than deep 'react-native/Libraries/...' paths. This
|
|
24
|
+
* project's tsconfig opts into the `react-native-strict-api` custom
|
|
25
|
+
* condition, and react-native's package.json `exports` map explicitly
|
|
26
|
+
* blocks deep `Libraries/*` subpath imports under that condition -- so
|
|
27
|
+
* `codegenNativeCommands`, `codegenNativeComponent`, and the CodegenTypes
|
|
28
|
+
* (Double/Int32/WithDefault) must come from the root barrel, which
|
|
29
|
+
* re-exports all three for exactly this reason.
|
|
30
|
+
*
|
|
31
|
+
* Also note: do NOT re-export `CodegenTypes.WithDefault`/`Double`/`Int32` as
|
|
32
|
+
* local generic type aliases (an earlier draft of this file did, to shorten
|
|
33
|
+
* the qualified names). @react-native/codegen's alias resolution does plain
|
|
34
|
+
* textual substitution with no generic instantiation, so a chain like
|
|
35
|
+
* `NativeBlurMode -> WithDefault<'static'|'live','static'> -> (local
|
|
36
|
+
* `WithDefault<Type,Value>` alias) -> CodegenTypes.WithDefault<Type,Value>`
|
|
37
|
+
* loses the real type arguments and fails with "The default value in
|
|
38
|
+
* WithDefault must be string, number, boolean or null." Referencing
|
|
39
|
+
* `CodegenTypes.WithDefault<...>` directly (qualified) avoids the extra
|
|
40
|
+
* alias hop and keeps the real arguments intact.
|
|
41
|
+
*/
|
|
42
|
+
import {
|
|
43
|
+
codegenNativeCommands,
|
|
44
|
+
codegenNativeComponent,
|
|
45
|
+
type CodegenTypes,
|
|
46
|
+
type ColorValue,
|
|
47
|
+
type HostComponent,
|
|
48
|
+
type ViewProps,
|
|
49
|
+
} from 'react-native';
|
|
50
|
+
import type * as React from 'react';
|
|
51
|
+
|
|
52
|
+
type NativeBlurMode = CodegenTypes.WithDefault<'static' | 'live', 'static'>;
|
|
53
|
+
type NativeBlurQuality = CodegenTypes.WithDefault<
|
|
54
|
+
'high' | 'balanced' | 'performance',
|
|
55
|
+
'balanced'
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
export interface NativeProps extends ViewProps {
|
|
59
|
+
blurRadius?: CodegenTypes.WithDefault<CodegenTypes.Double, 0>;
|
|
60
|
+
mode?: NativeBlurMode;
|
|
61
|
+
overlayColor?: ColorValue;
|
|
62
|
+
saturation?: CodegenTypes.WithDefault<CodegenTypes.Double, 1>;
|
|
63
|
+
quality?: NativeBlurQuality;
|
|
64
|
+
// 0 = 'auto' sentinel; see file-level deviation note above.
|
|
65
|
+
downsample?: CodegenTypes.WithDefault<CodegenTypes.Int32, 0>;
|
|
66
|
+
maxFps?: CodegenTypes.WithDefault<CodegenTypes.Int32, 30>;
|
|
67
|
+
fallbackColor?: ColorValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ComponentType = HostComponent<NativeProps>;
|
|
71
|
+
|
|
72
|
+
interface NativeCommands {
|
|
73
|
+
refresh: (viewRef: React.ComponentRef<ComponentType>) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
77
|
+
supportedCommands: ['refresh'],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export default codegenNativeComponent<NativeProps>(
|
|
81
|
+
'ParityBlurView'
|
|
82
|
+
) as ComponentType;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_BLUR_RADIUS = 0;
|
|
4
|
+
export const DEFAULT_MODE = 'static';
|
|
5
|
+
export const DEFAULT_OVERLAY_COLOR = 'transparent';
|
|
6
|
+
export const DEFAULT_SATURATION = 1;
|
|
7
|
+
export const DEFAULT_QUALITY = 'balanced';
|
|
8
|
+
export const DEFAULT_DOWNSAMPLE = 'auto';
|
|
9
|
+
export const DEFAULT_MAX_FPS = 30;
|
|
10
|
+
export const MIN_MAX_FPS = 1;
|
|
11
|
+
export const MAX_MAX_FPS = 120;
|
|
12
|
+
export const ALLOWED_DOWNSAMPLE_VALUES = ['auto', 1, 2, 4, 8];
|
|
13
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DEFAULT_BLUR_RADIUS","DEFAULT_MODE","DEFAULT_OVERLAY_COLOR","DEFAULT_SATURATION","DEFAULT_QUALITY","DEFAULT_DOWNSAMPLE","DEFAULT_MAX_FPS","MIN_MAX_FPS","MAX_MAX_FPS","ALLOWED_DOWNSAMPLE_VALUES"],"sourceRoot":"../../src","sources":["defaults.ts"],"mappings":";;AAEA,OAAO,MAAMA,mBAAmB,GAAG,CAAC;AACpC,OAAO,MAAMC,YAAsB,GAAG,QAAQ;AAC9C,OAAO,MAAMC,qBAAqB,GAAG,aAAa;AAClD,OAAO,MAAMC,kBAAkB,GAAG,CAAC;AACnC,OAAO,MAAMC,eAA4B,GAAG,UAAU;AACtD,OAAO,MAAMC,kBAAkC,GAAG,MAAM;AACxD,OAAO,MAAMC,eAAe,GAAG,EAAE;AAEjC,OAAO,MAAMC,WAAW,GAAG,CAAC;AAC5B,OAAO,MAAMC,WAAW,GAAG,GAAG;AAE9B,OAAO,MAAMC,yBAAwD,GAAG,CACtE,MAAM,EACN,CAAC,EACD,CAAC,EACD,CAAC,EACD,CAAC,CACF","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["BlurView","default","BlurPresets"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,QAAQ,EAAEC,OAAO,QAAQ,eAAY;AAQ9C,SAASC,WAAW,QAAQ,cAAW","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|