react-native-frame-capture 1.0.1 → 1.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/FrameCapture.podspec +21 -21
- package/LICENSE +20 -20
- package/README.md +159 -158
- package/android/build.gradle +77 -77
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +20 -20
- package/android/src/main/java/com/framecapture/CaptureManager.kt +1013 -831
- package/android/src/main/java/com/framecapture/Constants.kt +205 -196
- package/android/src/main/java/com/framecapture/ErrorHandler.kt +165 -165
- package/android/src/main/java/com/framecapture/FrameCaptureModule.kt +653 -653
- package/android/src/main/java/com/framecapture/OverlayRenderer.kt +423 -423
- package/android/src/main/java/com/framecapture/PermissionHandler.kt +150 -150
- package/android/src/main/java/com/framecapture/ScreenCaptureService.kt +366 -366
- package/android/src/main/java/com/framecapture/StorageManager.kt +221 -221
- package/android/src/main/java/com/framecapture/capture/BitmapProcessor.kt +157 -157
- package/android/src/main/java/com/framecapture/capture/CaptureEventEmitter.kt +150 -120
- package/android/src/main/java/com/framecapture/capture/ChangeDetector.kt +191 -0
- package/android/src/main/java/com/framecapture/models/CaptureModels.kt +343 -302
- package/android/src/main/java/com/framecapture/models/EnumsAndExtensions.kt +67 -60
- package/android/src/main/java/com/framecapture/models/OverlayModels.kt +154 -154
- package/android/src/main/java/com/framecapture/service/CaptureNotificationManager.kt +286 -286
- package/android/src/main/java/com/framecapture/storage/StorageStrategies.kt +317 -317
- package/android/src/main/java/com/framecapture/utils/ValidationUtils.kt +379 -379
- package/ios/FrameCapture.h +5 -5
- package/ios/FrameCapture.mm +21 -21
- package/lib/module/NativeFrameCapture.js.map +1 -1
- package/lib/module/constants.js +45 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/normalize.js +10 -1
- package/lib/module/normalize.js.map +1 -1
- package/lib/module/types.js +9 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/validation.js +86 -9
- package/lib/module/validation.js.map +1 -1
- package/lib/typescript/src/NativeFrameCapture.d.ts +7 -0
- package/lib/typescript/src/NativeFrameCapture.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +33 -0
- package/lib/typescript/src/constants.d.ts.map +1 -1
- package/lib/typescript/src/normalize.d.ts +8 -2
- package/lib/typescript/src/normalize.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +29 -5
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/validation.d.ts.map +1 -1
- package/package.json +199 -196
- package/src/NativeFrameCapture.ts +8 -0
- package/src/constants.ts +45 -0
- package/src/normalize.ts +23 -3
- package/src/types.ts +30 -2
- package/src/validation.ts +132 -13
- package/plugin/build/index.js +0 -48
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
package com.framecapture.capture
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.framecapture.Constants
|
|
6
|
+
import com.framecapture.models.CaptureRegion
|
|
7
|
+
import java.nio.ByteBuffer
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detects changes between consecutive frames using pixel sampling
|
|
11
|
+
*
|
|
12
|
+
* Uses an efficient sampling algorithm that compares a subset of pixels
|
|
13
|
+
* to determine if the screen has changed significantly enough to warrant capture.
|
|
14
|
+
*/
|
|
15
|
+
class ChangeDetector(
|
|
16
|
+
private val threshold: Float = Constants.DEFAULT_CHANGE_THRESHOLD,
|
|
17
|
+
private val sampleRate: Int = Constants.DEFAULT_CHANGE_SAMPLE_RATE,
|
|
18
|
+
private val detectionRegion: CaptureRegion? = null
|
|
19
|
+
) {
|
|
20
|
+
companion object {
|
|
21
|
+
private const val TAG = "ChangeDetector"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Store previous frame's sampled pixel data
|
|
25
|
+
private var previousSamples: IntArray? = null
|
|
26
|
+
private var previousWidth: Int = 0
|
|
27
|
+
private var previousHeight: Int = 0
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detects change between current bitmap and previous frame
|
|
31
|
+
* @return Change percentage (0-100), or 100 if no previous frame exists
|
|
32
|
+
*/
|
|
33
|
+
fun detectChange(currentBitmap: Bitmap): Float {
|
|
34
|
+
val width = currentBitmap.width
|
|
35
|
+
val height = currentBitmap.height
|
|
36
|
+
|
|
37
|
+
// Calculate sampling bounds based on detection region
|
|
38
|
+
val (startX, startY, endX, endY) = calculateBounds(width, height)
|
|
39
|
+
|
|
40
|
+
// Sample current frame
|
|
41
|
+
val currentSamples = samplePixels(currentBitmap, startX, startY, endX, endY)
|
|
42
|
+
|
|
43
|
+
// If no previous frame, treat as 100% change (always capture first frame)
|
|
44
|
+
if (previousSamples == null || previousWidth != width || previousHeight != height) {
|
|
45
|
+
previousSamples = currentSamples
|
|
46
|
+
previousWidth = width
|
|
47
|
+
previousHeight = height
|
|
48
|
+
Log.d(TAG, "No previous frame - returning 100% change")
|
|
49
|
+
return 100f
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Compare samples
|
|
53
|
+
val changedPixels = countChangedPixels(previousSamples!!, currentSamples)
|
|
54
|
+
val totalSamples = currentSamples.size
|
|
55
|
+
val changePercent = if (totalSamples > 0) {
|
|
56
|
+
(changedPixels.toFloat() / totalSamples.toFloat()) * 100f
|
|
57
|
+
} else {
|
|
58
|
+
0f
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Log.d(TAG, "Change detected: $changePercent% ($changedPixels/$totalSamples samples)")
|
|
62
|
+
return changePercent
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Updates the previous frame data for next comparison
|
|
67
|
+
*/
|
|
68
|
+
fun updatePreviousFrame(bitmap: Bitmap) {
|
|
69
|
+
val width = bitmap.width
|
|
70
|
+
val height = bitmap.height
|
|
71
|
+
val (startX, startY, endX, endY) = calculateBounds(width, height)
|
|
72
|
+
|
|
73
|
+
previousSamples = samplePixels(bitmap, startX, startY, endX, endY)
|
|
74
|
+
previousWidth = width
|
|
75
|
+
previousHeight = height
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Clears stored frame data
|
|
80
|
+
*/
|
|
81
|
+
fun clear() {
|
|
82
|
+
previousSamples = null
|
|
83
|
+
previousWidth = 0
|
|
84
|
+
previousHeight = 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Calculates bounds for sampling based on detection region
|
|
89
|
+
*/
|
|
90
|
+
private fun calculateBounds(width: Int, height: Int): BoundsResult {
|
|
91
|
+
return if (detectionRegion != null) {
|
|
92
|
+
when (detectionRegion.unit) {
|
|
93
|
+
Constants.POSITION_UNIT_PERCENTAGE -> {
|
|
94
|
+
val startX = (detectionRegion.x * width).toInt().coerceIn(0, width - 1)
|
|
95
|
+
val startY = (detectionRegion.y * height).toInt().coerceIn(0, height - 1)
|
|
96
|
+
val endX = ((detectionRegion.x + detectionRegion.width) * width).toInt().coerceIn(startX + 1, width)
|
|
97
|
+
val endY = ((detectionRegion.y + detectionRegion.height) * height).toInt().coerceIn(startY + 1, height)
|
|
98
|
+
BoundsResult(startX, startY, endX, endY)
|
|
99
|
+
}
|
|
100
|
+
Constants.POSITION_UNIT_PIXELS -> {
|
|
101
|
+
val startX = detectionRegion.x.toInt().coerceIn(0, width - 1)
|
|
102
|
+
val startY = detectionRegion.y.toInt().coerceIn(0, height - 1)
|
|
103
|
+
val endX = (detectionRegion.x + detectionRegion.width).toInt().coerceIn(startX + 1, width)
|
|
104
|
+
val endY = (detectionRegion.y + detectionRegion.height).toInt().coerceIn(startY + 1, height)
|
|
105
|
+
BoundsResult(startX, startY, endX, endY)
|
|
106
|
+
}
|
|
107
|
+
else -> BoundsResult(0, 0, width, height)
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
BoundsResult(0, 0, width, height)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Samples pixels at regular intervals from the bitmap
|
|
116
|
+
*/
|
|
117
|
+
private fun samplePixels(bitmap: Bitmap, startX: Int, startY: Int, endX: Int, endY: Int): IntArray {
|
|
118
|
+
val regionWidth = endX - startX
|
|
119
|
+
val regionHeight = endY - startY
|
|
120
|
+
|
|
121
|
+
// Calculate sample points
|
|
122
|
+
val samplesX = maxOf(1, regionWidth / sampleRate)
|
|
123
|
+
val samplesY = maxOf(1, regionHeight / sampleRate)
|
|
124
|
+
val totalSamples = samplesX * samplesY
|
|
125
|
+
|
|
126
|
+
val samples = IntArray(totalSamples)
|
|
127
|
+
var index = 0
|
|
128
|
+
|
|
129
|
+
for (sy in 0 until samplesY) {
|
|
130
|
+
val y = startY + (sy * sampleRate).coerceIn(0, regionHeight - 1)
|
|
131
|
+
for (sx in 0 until samplesX) {
|
|
132
|
+
val x = startX + (sx * sampleRate).coerceIn(0, regionWidth - 1)
|
|
133
|
+
if (x < bitmap.width && y < bitmap.height) {
|
|
134
|
+
samples[index] = bitmap.getPixel(x, y)
|
|
135
|
+
}
|
|
136
|
+
index++
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return samples
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Counts pixels that have changed significantly between frames
|
|
145
|
+
*/
|
|
146
|
+
private fun countChangedPixels(previous: IntArray, current: IntArray): Int {
|
|
147
|
+
if (previous.size != current.size) {
|
|
148
|
+
return current.size // All pixels considered changed if sizes don't match
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var changedCount = 0
|
|
152
|
+
val tolerance = Constants.CHANGE_PIXEL_TOLERANCE
|
|
153
|
+
|
|
154
|
+
for (i in previous.indices) {
|
|
155
|
+
if (isPixelChanged(previous[i], current[i], tolerance)) {
|
|
156
|
+
changedCount++
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return changedCount
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Checks if a pixel has changed beyond the tolerance threshold
|
|
165
|
+
*/
|
|
166
|
+
private fun isPixelChanged(pixel1: Int, pixel2: Int, tolerance: Int): Boolean {
|
|
167
|
+
// Extract RGB components (ignore alpha)
|
|
168
|
+
val r1 = (pixel1 shr 16) and 0xFF
|
|
169
|
+
val g1 = (pixel1 shr 8) and 0xFF
|
|
170
|
+
val b1 = pixel1 and 0xFF
|
|
171
|
+
|
|
172
|
+
val r2 = (pixel2 shr 16) and 0xFF
|
|
173
|
+
val g2 = (pixel2 shr 8) and 0xFF
|
|
174
|
+
val b2 = pixel2 and 0xFF
|
|
175
|
+
|
|
176
|
+
// Check if any channel differs beyond tolerance
|
|
177
|
+
return kotlin.math.abs(r1 - r2) > tolerance ||
|
|
178
|
+
kotlin.math.abs(g1 - g2) > tolerance ||
|
|
179
|
+
kotlin.math.abs(b1 - b2) > tolerance
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Simple data class for bounds result
|
|
184
|
+
*/
|
|
185
|
+
private data class BoundsResult(
|
|
186
|
+
val startX: Int,
|
|
187
|
+
val startY: Int,
|
|
188
|
+
val endX: Int,
|
|
189
|
+
val endY: Int
|
|
190
|
+
)
|
|
191
|
+
}
|