react-native-blur-vibe 0.1.14 → 0.1.15
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.
|
@@ -7,7 +7,6 @@ import android.graphics.Canvas
|
|
|
7
7
|
import android.graphics.Color
|
|
8
8
|
import android.graphics.LinearGradient
|
|
9
9
|
import android.graphics.Paint
|
|
10
|
-
import android.graphics.PixelFormat
|
|
11
10
|
import android.graphics.PorterDuff
|
|
12
11
|
import android.graphics.PorterDuffXfermode
|
|
13
12
|
import android.graphics.RadialGradient
|
|
@@ -20,8 +19,6 @@ import android.os.HandlerThread
|
|
|
20
19
|
import android.os.Looper
|
|
21
20
|
import android.util.TypedValue
|
|
22
21
|
import android.view.Choreographer
|
|
23
|
-
import android.view.PixelCopy
|
|
24
|
-
import android.view.Surface
|
|
25
22
|
import android.view.View
|
|
26
23
|
import android.view.ViewGroup
|
|
27
24
|
import android.view.ViewOutlineProvider
|
|
@@ -40,8 +37,8 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
40
37
|
|
|
41
38
|
// ── Blur params ────────────────────────────────────────────────────────────
|
|
42
39
|
|
|
43
|
-
private var blurAmount
|
|
44
|
-
private var overlayColor
|
|
40
|
+
private var blurAmount = 10f
|
|
41
|
+
private var overlayColor = Color.TRANSPARENT
|
|
45
42
|
private var cornerRadiusPx = 0f
|
|
46
43
|
|
|
47
44
|
// ── Progressive blur ──────────────────────────────────────────────────────
|
|
@@ -57,14 +54,11 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
57
54
|
private val noisePaint = Paint()
|
|
58
55
|
|
|
59
56
|
// ── Bitmap double-buffer ──────────────────────────────────────────────────
|
|
60
|
-
//
|
|
61
|
-
// pixelCopyBitmap: written by PixelCopy (on its own callback thread)
|
|
62
|
-
// scaledBitmap: written by workerThread after downsampling
|
|
63
|
-
// readyBitmap: @Volatile — RenderThread reads this in onDraw()
|
|
64
57
|
|
|
65
|
-
private var
|
|
66
|
-
private var scaledBitmap:
|
|
67
|
-
@Volatile
|
|
58
|
+
private var captureBitmap: Bitmap? = null // full-size capture (main thread)
|
|
59
|
+
private var scaledBitmap: Bitmap? = null // downsampled + blurred (worker thread)
|
|
60
|
+
@Volatile
|
|
61
|
+
private var readyBitmap: Bitmap? = null // @Volatile pointer — RenderThread reads this
|
|
68
62
|
|
|
69
63
|
private val capturePaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
|
70
64
|
private val bitmapPaint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG)
|
|
@@ -76,20 +70,20 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
76
70
|
private val workerHandler = Handler(workerThread.looper)
|
|
77
71
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
78
72
|
|
|
79
|
-
// ── Root
|
|
73
|
+
// ── Root ──────────────────────────────────────────────────────────────────
|
|
80
74
|
|
|
81
|
-
private var blurRoot:
|
|
75
|
+
private var blurRoot: ViewGroup? = null
|
|
82
76
|
private val myLoc = IntArray(2)
|
|
83
77
|
private val rootLoc = IntArray(2)
|
|
84
78
|
|
|
85
79
|
// ── State ─────────────────────────────────────────────────────────────────
|
|
86
80
|
|
|
87
|
-
|
|
81
|
+
// isCapturing: public read so BlurVibeViewApi31.draw() can check it
|
|
82
|
+
var isCapturing = false
|
|
88
83
|
private set
|
|
89
84
|
private var blurEnabled = true
|
|
90
85
|
private var autoUpdate = true
|
|
91
86
|
private var frameScheduled = false
|
|
92
|
-
private var pixelCopyInFlight = false
|
|
93
87
|
|
|
94
88
|
// ── Choreographer gate ────────────────────────────────────────────────────
|
|
95
89
|
|
|
@@ -117,8 +111,9 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
117
111
|
|
|
118
112
|
init {
|
|
119
113
|
setWillNotDraw(false)
|
|
120
|
-
// outlineProvider = BACKGROUND
|
|
121
|
-
//
|
|
114
|
+
// outlineProvider = BACKGROUND uses ReactViewBackgroundDrawable.getOutline()
|
|
115
|
+
// which correctly handles all RN borderRadius variants automatically.
|
|
116
|
+
// clipToOutline is set to true only when a non-zero radius is applied.
|
|
122
117
|
outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
123
118
|
}
|
|
124
119
|
|
|
@@ -135,13 +130,15 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
135
130
|
override fun onDetachedFromWindow() {
|
|
136
131
|
safeRemovePreDrawListener()
|
|
137
132
|
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
138
|
-
frameScheduled
|
|
139
|
-
isCapturing
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
readyBitmap = null
|
|
133
|
+
frameScheduled = false
|
|
134
|
+
isCapturing = false
|
|
135
|
+
blurRoot = null
|
|
136
|
+
readyBitmap = null
|
|
143
137
|
noiseBitmap?.recycle(); noiseBitmap = null
|
|
144
|
-
workerHandler.post {
|
|
138
|
+
workerHandler.post {
|
|
139
|
+
captureBitmap?.recycle(); captureBitmap = null
|
|
140
|
+
scaledBitmap?.recycle(); scaledBitmap = null
|
|
141
|
+
}
|
|
145
142
|
super.onDetachedFromWindow()
|
|
146
143
|
}
|
|
147
144
|
|
|
@@ -149,7 +146,10 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
149
146
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
150
147
|
if (w > 0 && h > 0) {
|
|
151
148
|
readyBitmap = null
|
|
152
|
-
workerHandler.post {
|
|
149
|
+
workerHandler.post {
|
|
150
|
+
captureBitmap?.recycle(); captureBitmap = null
|
|
151
|
+
scaledBitmap?.recycle(); scaledBitmap = null
|
|
152
|
+
}
|
|
153
153
|
scheduleFrame()
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -162,10 +162,10 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
// ── draw() —
|
|
165
|
+
// ── draw() — skip self during root capture ────────────────────────────────
|
|
166
166
|
|
|
167
167
|
override fun draw(canvas: Canvas) {
|
|
168
|
-
if (isCapturing) return
|
|
168
|
+
if (isCapturing) return // prevents capturing own blur output
|
|
169
169
|
super.draw(canvas)
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -176,26 +176,25 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
176
176
|
val w = width.toFloat(); if (w <= 0f) return
|
|
177
177
|
val h = height.toFloat(); if (h <= 0f) return
|
|
178
178
|
|
|
179
|
+
// Show overlay as placeholder while first blur is loading
|
|
179
180
|
val bmp = readyBitmap?.takeIf { !it.isRecycled } ?: run {
|
|
180
|
-
// No blur ready yet — show overlay color as placeholder
|
|
181
181
|
if (Color.alpha(overlayColor) > 0) {
|
|
182
182
|
overlayPaint.color = overlayColor
|
|
183
183
|
canvas.drawRect(0f, 0f, w, h, overlayPaint)
|
|
184
184
|
}
|
|
185
|
-
// Redraw border on top even when no blur ready
|
|
186
185
|
background?.draw(canvas)
|
|
187
186
|
return
|
|
188
187
|
}
|
|
189
188
|
|
|
190
|
-
// Step 1: progressive mask
|
|
191
|
-
val saveCount = if (progressiveDirection != PROGRESSIVE_NONE)
|
|
189
|
+
// Step 1: save layer for progressive mask compositing
|
|
190
|
+
val saveCount = if (progressiveDirection != PROGRESSIVE_NONE)
|
|
192
191
|
canvas.saveLayer(0f, 0f, w, h, null)
|
|
193
|
-
|
|
192
|
+
else -1
|
|
194
193
|
|
|
195
|
-
// Step 2: blurred bitmap
|
|
194
|
+
// Step 2: blurred bitmap — fills entire view
|
|
196
195
|
canvas.drawBitmap(bmp, null, RectF(0f, 0f, w, h), bitmapPaint)
|
|
197
196
|
|
|
198
|
-
// Step 3: progressive alpha mask
|
|
197
|
+
// Step 3: progressive alpha mask (fades blur across view)
|
|
199
198
|
if (progressiveDirection != PROGRESSIVE_NONE && saveCount >= 0) {
|
|
200
199
|
buildProgressiveShader(w, h)?.let { shader ->
|
|
201
200
|
maskPaint.shader = shader
|
|
@@ -216,197 +215,113 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
216
215
|
noisePaint.shader = BitmapShader(noise, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
|
|
217
216
|
canvas.drawRect(0f, 0f, w, h, noisePaint)
|
|
218
217
|
}
|
|
218
|
+
|
|
219
|
+
// Step 6: redraw ReactViewBackgroundDrawable ON TOP of blur.
|
|
220
|
+
// View.draw() drew the background BEFORE onDraw(), but our bitmap
|
|
221
|
+
// covered it. Redrawing here makes borders/borderColor/borderRadius
|
|
222
|
+
// appear on top of the blur — not hidden underneath it.
|
|
219
223
|
background?.draw(canvas)
|
|
220
224
|
}
|
|
221
225
|
|
|
222
|
-
// ── Capture
|
|
226
|
+
// ── Capture + blur pipeline ────────────────────────────────────────────────
|
|
223
227
|
|
|
224
228
|
private fun captureAndBlur() {
|
|
225
|
-
if (isCapturing
|
|
226
|
-
val root
|
|
227
|
-
val vw
|
|
228
|
-
val vh
|
|
229
|
-
|
|
230
|
-
// Compute this view's screen rect for PixelCopy
|
|
231
|
-
getLocationInWindow(myLoc)
|
|
232
|
-
root.getLocationInWindow(rootLoc)
|
|
233
|
-
|
|
234
|
-
// Screen-space rect of the CONTENT BEHIND this view (use root location
|
|
235
|
-
// as origin since PixelCopy works in window coordinates)
|
|
236
|
-
val srcRect = Rect(
|
|
237
|
-
myLoc[0], myLoc[1],
|
|
238
|
-
myLoc[0] + vw, myLoc[1] + vh
|
|
239
|
-
)
|
|
229
|
+
if (isCapturing) return
|
|
230
|
+
val root = blurRoot ?: return
|
|
231
|
+
val vw = width; if (vw <= 0) return
|
|
232
|
+
val vh = height; if (vh <= 0) return
|
|
240
233
|
|
|
241
234
|
val sw = (vw / DOWNSAMPLE).toInt().coerceAtLeast(1)
|
|
242
235
|
val sh = (vh / DOWNSAMPLE).toInt().coerceAtLeast(1)
|
|
243
236
|
|
|
244
|
-
|
|
245
|
-
.also { pixelCopyBitmap = it }
|
|
246
|
-
|
|
247
|
-
// Hide ourselves during PixelCopy so we capture ONLY content behind us
|
|
248
|
-
isCapturing = true
|
|
249
|
-
pixelCopyInFlight = true
|
|
250
|
-
|
|
251
|
-
val window = (context as? android.app.Activity)?.window
|
|
252
|
-
?: run {
|
|
253
|
-
// Fallback to root.draw() if window not available
|
|
254
|
-
isCapturing = false
|
|
255
|
-
pixelCopyInFlight = false
|
|
256
|
-
captureWithRootDraw()
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
PixelCopy.request(
|
|
261
|
-
window,
|
|
262
|
-
srcRect,
|
|
263
|
-
destBitmap,
|
|
264
|
-
{ result ->
|
|
265
|
-
isCapturing = false
|
|
266
|
-
pixelCopyInFlight = false
|
|
267
|
-
|
|
268
|
-
if (result != PixelCopy.SUCCESS) {
|
|
269
|
-
// PixelCopy failed — fall back to root.draw()
|
|
270
|
-
mainHandler.post { captureWithRootDraw() }
|
|
271
|
-
return@request
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Blur on worker thread
|
|
275
|
-
val captureRef = destBitmap
|
|
276
|
-
workerHandler.post {
|
|
277
|
-
val scaled = reuseBitmap(scaledBitmap, sw, sh).also { scaledBitmap = it }
|
|
278
|
-
|
|
279
|
-
// Downsample
|
|
280
|
-
Canvas(scaled).drawBitmap(
|
|
281
|
-
captureRef,
|
|
282
|
-
Rect(0, 0, captureRef.width, captureRef.height),
|
|
283
|
-
Rect(0, 0, scaled.width, scaled.height),
|
|
284
|
-
capturePaint
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
// Multi-pass software Gaussian blur
|
|
288
|
-
val radius = blurRadiusFromAmount(blurAmount)
|
|
289
|
-
repeat(BLUR_ROUNDS) { stackBlur(scaled, radius.toInt().coerceAtLeast(1)) }
|
|
290
|
-
|
|
291
|
-
readyBitmap = scaled
|
|
292
|
-
mainHandler.post { invalidate() }
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
mainHandler
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── Fallback: root.draw() when PixelCopy unavailable ─────────────────────
|
|
300
|
-
|
|
301
|
-
private fun captureWithRootDraw() {
|
|
302
|
-
if (isCapturing) return
|
|
303
|
-
val root = blurRoot ?: return
|
|
304
|
-
val vw = width; if (vw <= 0) return
|
|
305
|
-
val vh = height; if (vh <= 0) return
|
|
306
|
-
val sw = (vw / DOWNSAMPLE).toInt().coerceAtLeast(1)
|
|
307
|
-
val sh = (vh / DOWNSAMPLE).toInt().coerceAtLeast(1)
|
|
308
|
-
|
|
237
|
+
// Window-relative offset (correct for split-screen, freeform, PiP)
|
|
309
238
|
root.getLocationInWindow(rootLoc)
|
|
310
239
|
getLocationInWindow(myLoc)
|
|
311
240
|
val offsetX = (myLoc[0] - rootLoc[0]).toFloat()
|
|
312
241
|
val offsetY = (myLoc[1] - rootLoc[1]).toFloat()
|
|
313
242
|
|
|
314
|
-
val capture = reuseBitmap(
|
|
315
|
-
val scaled = reuseBitmap(scaledBitmap,
|
|
243
|
+
val capture = reuseBitmap(captureBitmap, vw, vh).also { captureBitmap = it }
|
|
244
|
+
val scaled = reuseBitmap(scaledBitmap, sw, sh).also { scaledBitmap = it }
|
|
316
245
|
|
|
246
|
+
// isCapturing = true → our draw() is a no-op → root.draw() skips us
|
|
247
|
+
// → capture contains ONLY the content behind us (not our own blur output)
|
|
317
248
|
isCapturing = true
|
|
318
249
|
val c = Canvas(capture)
|
|
319
250
|
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
|
320
251
|
c.translate(-offsetX, -offsetY)
|
|
321
|
-
try {
|
|
252
|
+
try {
|
|
253
|
+
root.draw(c)
|
|
254
|
+
} catch (_: Exception) {
|
|
255
|
+
isCapturing = false
|
|
256
|
+
return
|
|
257
|
+
}
|
|
322
258
|
isCapturing = false
|
|
323
259
|
|
|
260
|
+
// Blur on worker thread — never blocks main/RenderThread
|
|
324
261
|
val captureRef = capture
|
|
262
|
+
val radius = blurRadiusFromAmount(blurAmount)
|
|
263
|
+
|
|
325
264
|
workerHandler.post {
|
|
265
|
+
// Downsample
|
|
326
266
|
Canvas(scaled).drawBitmap(
|
|
327
267
|
captureRef,
|
|
328
268
|
Rect(0, 0, captureRef.width, captureRef.height),
|
|
329
269
|
Rect(0, 0, scaled.width, scaled.height),
|
|
330
270
|
capturePaint
|
|
331
271
|
)
|
|
332
|
-
|
|
272
|
+
// Multi-pass StackBlur (pure Kotlin, no deprecated APIs, all API levels)
|
|
333
273
|
repeat(BLUR_ROUNDS) { stackBlur(scaled, radius.toInt().coerceAtLeast(1)) }
|
|
274
|
+
|
|
275
|
+
// Atomic @Volatile swap — RenderThread always reads a complete bitmap
|
|
334
276
|
readyBitmap = scaled
|
|
335
277
|
mainHandler.post { invalidate() }
|
|
336
278
|
}
|
|
337
279
|
}
|
|
338
280
|
|
|
339
|
-
// ──
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// Fast, no RenderScript, works on all API levels, zero deprecation warnings.
|
|
343
|
-
// Used by many production apps including Facebook's Fresco library.
|
|
344
|
-
// radius clamped 1–254 (algorithm limit).
|
|
281
|
+
// ── StackBlur ─────────────────────────────────────────────────────────────
|
|
282
|
+
// Mario Klingemann's algorithm — O(w×h) regardless of radius.
|
|
283
|
+
// No RenderScript, no deprecated APIs. Works on all Android versions.
|
|
345
284
|
|
|
346
285
|
private fun stackBlur(bmp: Bitmap, radius: Int) {
|
|
347
286
|
val r = radius.coerceIn(1, 254)
|
|
348
|
-
val w = bmp.width
|
|
349
|
-
val h = bmp.height
|
|
287
|
+
val w = bmp.width; val h = bmp.height
|
|
350
288
|
val pixels = IntArray(w * h)
|
|
351
289
|
bmp.getPixels(pixels, 0, w, 0, 0, w, h)
|
|
352
|
-
|
|
353
290
|
val div = r + r + 1
|
|
354
|
-
val wm = w - 1
|
|
355
|
-
val
|
|
356
|
-
val wh = w * h
|
|
357
|
-
val divSum = (div + 1) shr 1
|
|
358
|
-
val divSumSq = divSum * divSum
|
|
291
|
+
val wm = w - 1; val hm = h - 1
|
|
292
|
+
val divSumSq = ((div + 1) shr 1).let { it * it }
|
|
359
293
|
val dv = IntArray(256 * divSumSq) { it / divSumSq }
|
|
360
|
-
|
|
361
|
-
var yi = 0
|
|
362
294
|
val vmin = IntArray(maxOf(w, h))
|
|
363
|
-
val
|
|
364
|
-
|
|
365
|
-
val rStack = IntArray(div)
|
|
366
|
-
val gStack = IntArray(div)
|
|
367
|
-
val bStack = IntArray(div)
|
|
368
|
-
|
|
295
|
+
val rStack = IntArray(div); val gStack = IntArray(div); val bStack = IntArray(div)
|
|
296
|
+
var yi = 0
|
|
369
297
|
for (y in 0 until h) {
|
|
370
298
|
var rSum = 0; var gSum = 0; var bSum = 0
|
|
371
299
|
var rOut = 0; var gOut = 0; var bOut = 0
|
|
372
|
-
|
|
373
300
|
var p = pixels[yi]
|
|
374
|
-
var pr = (p shr 16) and 0xFF
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
for (i in 0 until divSum) {
|
|
301
|
+
var pr = (p shr 16) and 0xFF; var pg = (p shr 8) and 0xFF; var pb = p and 0xFF
|
|
302
|
+
val ds = (div + 1) shr 1
|
|
303
|
+
for (i in 0 until ds) {
|
|
379
304
|
rStack[i] = pr; gStack[i] = pg; bStack[i] = pb
|
|
380
305
|
rSum += pr * (i + 1); gSum += pg * (i + 1); bSum += pb * (i + 1)
|
|
381
306
|
rOut += pr; gOut += pg; bOut += pb
|
|
382
307
|
}
|
|
383
|
-
for (i in 1 until
|
|
384
|
-
val
|
|
385
|
-
p
|
|
386
|
-
pr = (p shr 16) and 0xFF; pg = (p shr 8) and 0xFF; pb = p and 0xFF
|
|
308
|
+
for (i in 1 until ds) {
|
|
309
|
+
val xi = if (i <= wm) i else wm
|
|
310
|
+
p = pixels[yi + xi]; pr = (p shr 16) and 0xFF; pg = (p shr 8) and 0xFF; pb = p and 0xFF
|
|
387
311
|
rStack[i + r] = pr; gStack[i + r] = pg; bStack[i + r] = pb
|
|
388
|
-
rSum += pr * (
|
|
389
|
-
gSum += pg * (divSum - i)
|
|
390
|
-
bSum += pb * (divSum - i)
|
|
312
|
+
rSum += pr * (ds - i); gSum += pg * (ds - i); bSum += pb * (ds - i)
|
|
391
313
|
}
|
|
392
|
-
|
|
393
314
|
var si = r
|
|
394
315
|
for (x in 0 until w) {
|
|
395
316
|
pixels[yi + x] = (-0x1000000 or (dv[rSum] shl 16) or (dv[gSum] shl 8) or dv[bSum])
|
|
396
317
|
rSum -= rOut; gSum -= gOut; bSum -= bOut
|
|
397
318
|
rOut -= rStack[si]; gOut -= gStack[si]; bOut -= bStack[si]
|
|
398
|
-
var sip = si +
|
|
399
|
-
if (sip >= div) sip -= div
|
|
319
|
+
var sip = si + ds; if (sip >= div) sip -= div
|
|
400
320
|
pr = rStack[sip]; pg = gStack[sip]; pb = bStack[sip]
|
|
401
|
-
rOut += pr; gOut += pg; bOut += pb
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
val sp = pixels[yi + vmin[x]]
|
|
406
|
-
val vp = pixels[yi + vmax[x]]
|
|
407
|
-
rStack[sip] = (sp shr 16) and 0xFF
|
|
408
|
-
gStack[sip] = (sp shr 8) and 0xFF
|
|
409
|
-
bStack[sip] = sp and 0xFF
|
|
321
|
+
rOut += pr; gOut += pg; bOut += pb; rSum += rOut; gSum += gOut; bSum += bOut
|
|
322
|
+
vmin[x] = if (x + r < wm) x + r + 1 else wm
|
|
323
|
+
val sp = pixels[yi + vmin[x]]; val vp = pixels[yi + (if (x > r) x - r else 0)]
|
|
324
|
+
rStack[sip] = (sp shr 16) and 0xFF; gStack[sip] = (sp shr 8) and 0xFF; bStack[sip] = sp and 0xFF
|
|
410
325
|
rOut += rStack[sip] - ((vp shr 16) and 0xFF)
|
|
411
326
|
gOut += gStack[sip] - ((vp shr 8) and 0xFF)
|
|
412
327
|
bOut += bStack[sip] - (vp and 0xFF)
|
|
@@ -414,50 +329,38 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
414
329
|
}
|
|
415
330
|
yi += w
|
|
416
331
|
}
|
|
417
|
-
|
|
418
|
-
var xi = 0
|
|
419
332
|
for (x in 0 until w) {
|
|
420
333
|
var rSum = 0; var gSum = 0; var bSum = 0
|
|
421
334
|
var rOut = 0; var gOut = 0; var bOut = 0
|
|
422
|
-
|
|
423
|
-
var p
|
|
424
|
-
|
|
425
|
-
var pg = (p shr 8) and 0xFF
|
|
426
|
-
var pb = p and 0xFF
|
|
427
|
-
for (i in 0 until divSum) {
|
|
335
|
+
val ds = (div + 1) shr 1
|
|
336
|
+
var p = pixels[x]; var pr = (p shr 16) and 0xFF; var pg = (p shr 8) and 0xFF; var pb = p and 0xFF
|
|
337
|
+
for (i in 0 until ds) {
|
|
428
338
|
rStack[i] = pr; gStack[i] = pg; bStack[i] = pb
|
|
429
339
|
rSum += pr * (i + 1); gSum += pg * (i + 1); bSum += pb * (i + 1)
|
|
430
340
|
rOut += pr; gOut += pg; bOut += pb
|
|
431
341
|
}
|
|
432
|
-
for (i in 1
|
|
433
|
-
if (i <= hm)
|
|
434
|
-
p
|
|
435
|
-
pr = (p shr 16) and 0xFF; pg = (p shr 8) and 0xFF; pb = p and 0xFF
|
|
342
|
+
for (i in 1 until ds) {
|
|
343
|
+
val yi2 = if (i <= hm) i * w else hm * w
|
|
344
|
+
p = pixels[x + yi2]; pr = (p shr 16) and 0xFF; pg = (p shr 8) and 0xFF; pb = p and 0xFF
|
|
436
345
|
rStack[i + r] = pr; gStack[i + r] = pg; bStack[i + r] = pb
|
|
437
|
-
rSum += pr * (
|
|
346
|
+
rSum += pr * (ds - i); gSum += pg * (ds - i); bSum += pb * (ds - i)
|
|
438
347
|
}
|
|
439
348
|
var si = r
|
|
440
349
|
for (y in 0 until h) {
|
|
441
|
-
pixels[
|
|
350
|
+
pixels[x + y * w] = (-0x1000000 or (dv[rSum] shl 16) or (dv[gSum] shl 8) or dv[bSum])
|
|
442
351
|
rSum -= rOut; gSum -= gOut; bSum -= bOut
|
|
443
352
|
rOut -= rStack[si]; gOut -= gStack[si]; bOut -= bStack[si]
|
|
444
|
-
var sip = si +
|
|
353
|
+
var sip = si + ds; if (sip >= div) sip -= div
|
|
445
354
|
pr = rStack[sip]; pg = gStack[sip]; pb = bStack[sip]
|
|
446
|
-
rOut += pr; gOut += pg; bOut += pb
|
|
447
|
-
rSum += rOut; gSum += gOut; bSum += bOut
|
|
355
|
+
rOut += pr; gOut += pg; bOut += pb; rSum += rOut; gSum += gOut; bSum += bOut
|
|
448
356
|
vmin[y] = if (y + r < hm) (y + r + 1) * w else hm * w
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
val vp = pixels[xi + vmax[y]]
|
|
452
|
-
rStack[sip] = (sp shr 16) and 0xFF
|
|
453
|
-
gStack[sip] = (sp shr 8) and 0xFF
|
|
454
|
-
bStack[sip] = sp and 0xFF
|
|
357
|
+
val sp = pixels[x + vmin[y]]; val vp = pixels[x + (if (y > r) (y - r) * w else 0)]
|
|
358
|
+
rStack[sip] = (sp shr 16) and 0xFF; gStack[sip] = (sp shr 8) and 0xFF; bStack[sip] = sp and 0xFF
|
|
455
359
|
rOut += rStack[sip] - ((vp shr 16) and 0xFF)
|
|
456
360
|
gOut += gStack[sip] - ((vp shr 8) and 0xFF)
|
|
457
361
|
bOut += bStack[sip] - (vp and 0xFF)
|
|
458
362
|
if (++si >= div) si = 0
|
|
459
363
|
}
|
|
460
|
-
xi++
|
|
461
364
|
}
|
|
462
365
|
bmp.setPixels(pixels, 0, w, 0, 0, w, h)
|
|
463
366
|
}
|
|
@@ -472,7 +375,7 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
472
375
|
PROGRESSIVE_BOTTOM_TO_TOP -> LinearGradient(0f,h,0f,0f,sc,ec,Shader.TileMode.CLAMP)
|
|
473
376
|
PROGRESSIVE_LEFT_TO_RIGHT -> LinearGradient(0f,0f,w,0f,sc,ec,Shader.TileMode.CLAMP)
|
|
474
377
|
PROGRESSIVE_RIGHT_TO_LEFT -> LinearGradient(w,0f,0f,0f,sc,ec,Shader.TileMode.CLAMP)
|
|
475
|
-
PROGRESSIVE_RADIAL
|
|
378
|
+
PROGRESSIVE_RADIAL -> RadialGradient(w/2f,h/2f,min(w,h)/2f,sc,ec,Shader.TileMode.CLAMP)
|
|
476
379
|
else -> null
|
|
477
380
|
}
|
|
478
381
|
}
|
|
@@ -564,22 +467,19 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
564
467
|
private fun findBlurRoot(): ViewGroup? {
|
|
565
468
|
var p = parent
|
|
566
469
|
while (p != null) {
|
|
567
|
-
if ((p as? View)?.javaClass?.name == "com.swmansion.rnscreens.Screen")
|
|
470
|
+
if ((p as? View)?.javaClass?.name == "com.swmansion.rnscreens.Screen")
|
|
471
|
+
return p as? ViewGroup
|
|
568
472
|
p = (p as? View)?.parent
|
|
569
473
|
}
|
|
570
474
|
p = parent
|
|
571
475
|
while (p != null) {
|
|
572
|
-
if ((p as? View)?.javaClass?.name == "com.facebook.react.ReactRootView")
|
|
476
|
+
if ((p as? View)?.javaClass?.name == "com.facebook.react.ReactRootView")
|
|
477
|
+
return p as? ViewGroup
|
|
573
478
|
p = (p as? View)?.parent
|
|
574
479
|
}
|
|
575
480
|
return rootView as? ViewGroup
|
|
576
481
|
}
|
|
577
482
|
|
|
578
|
-
private fun releaseBitmapPool() {
|
|
579
|
-
pixelCopyBitmap?.recycle(); pixelCopyBitmap = null
|
|
580
|
-
scaledBitmap?.recycle(); scaledBitmap = null
|
|
581
|
-
}
|
|
582
|
-
|
|
583
483
|
private fun reuseBitmap(existing: Bitmap?, w: Int, h: Int): Bitmap {
|
|
584
484
|
if (existing != null && !existing.isRecycled
|
|
585
485
|
&& existing.width == w && existing.height == h) return existing
|
|
@@ -589,7 +489,7 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
589
489
|
|
|
590
490
|
private fun blurRadiusFromAmount(amount: Float): Float {
|
|
591
491
|
val t = amount.coerceIn(0f, 100f) / 100f
|
|
592
|
-
return
|
|
492
|
+
return 2f + t * 22f // 2–24, with BLUR_ROUNDS=4 effective spread ≈ 4–48px
|
|
593
493
|
}
|
|
594
494
|
|
|
595
495
|
private fun parseHexColor(s: String): Int? {
|
|
@@ -617,8 +517,8 @@ class BlurVibeViewApi31(context: Context) : ReactViewGroup(context) {
|
|
|
617
517
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
|
|
618
518
|
|
|
619
519
|
companion object {
|
|
620
|
-
private const val DOWNSAMPLE = 2f
|
|
621
|
-
private const val BLUR_ROUNDS = 3
|
|
520
|
+
private const val DOWNSAMPLE = 2f // 1/4 pixels — higher quality than legacy
|
|
521
|
+
private const val BLUR_ROUNDS = 4 // 4 passes — wider spread than legacy's 3
|
|
622
522
|
const val PROGRESSIVE_NONE = 0
|
|
623
523
|
const val PROGRESSIVE_TOP_TO_BOTTOM = 1
|
|
624
524
|
const val PROGRESSIVE_BOTTOM_TO_TOP = 2
|
package/package.json
CHANGED