react-native-webrtc-kaleidoscope 1.1.0 → 2.0.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.
Files changed (286) hide show
  1. package/NOTICE.md +17 -6
  2. package/README.md +39 -18
  3. package/android/build.gradle +3 -4
  4. package/android/src/main/assets/backgrounds/dark-office.webp +0 -0
  5. package/android/src/main/assets/backgrounds/debug-resolutions.webp +0 -0
  6. package/android/src/main/assets/backgrounds/home-dark.webp +0 -0
  7. package/android/src/main/assets/backgrounds/home-light.webp +0 -0
  8. package/android/src/main/assets/backgrounds/light-office.webp +0 -0
  9. package/android/src/main/assets/backgrounds/nature-dark.webp +0 -0
  10. package/android/src/main/assets/backgrounds/nature-light.webp +0 -0
  11. package/android/src/main/assets/backgrounds/simiancraft-dark.webp +0 -0
  12. package/android/src/main/assets/backgrounds/simiancraft-light.webp +0 -0
  13. package/android/src/main/assets/backgrounds/stylized-dark.webp +0 -0
  14. package/android/src/main/assets/backgrounds/stylized-light.webp +0 -0
  15. package/android/src/main/assets/selfie_segmenter.tflite +0 -0
  16. package/android/src/main/java/com/simiancraft/kaleidoscope/EffectTuning.kt +41 -10
  17. package/android/src/main/java/com/simiancraft/kaleidoscope/KaleidoscopeModule.kt +8 -0
  18. package/android/src/main/java/com/simiancraft/kaleidoscope/Registration.kt +52 -13
  19. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/BackgroundImageFactory.kt +53 -23
  20. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/BlurFactory.kt +114 -50
  21. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/TransformFactory.kt +277 -0
  22. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/FramePipeline.kt +158 -0
  23. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GlProgram.kt +5 -0
  24. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Ingest.kt +123 -0
  25. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Orientation.kt +58 -0
  26. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/Shaders.kt +10 -0
  27. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/ShadersGenerated.kt +15 -3
  28. package/android/src/main/java/com/simiancraft/kaleidoscope/segmentation/Mask.kt +100 -89
  29. package/android/src/main/java/com/simiancraft/kaleidoscope/segmentation/SegmentationEngine.kt +148 -0
  30. package/dist/backgrounds/dark-office.d.ts +3 -0
  31. package/dist/backgrounds/dark-office.d.ts.map +1 -0
  32. package/dist/backgrounds/dark-office.js +5 -0
  33. package/dist/backgrounds/dark-office.js.map +1 -0
  34. package/dist/backgrounds/dark-office.web.d.ts +3 -0
  35. package/dist/backgrounds/dark-office.web.d.ts.map +1 -0
  36. package/dist/backgrounds/{office-1.web.js → dark-office.web.js} +3 -3
  37. package/dist/backgrounds/dark-office.web.js.map +1 -0
  38. package/dist/backgrounds/dark-office.webp +0 -0
  39. package/dist/backgrounds/debug-resolutions.d.ts +3 -0
  40. package/dist/backgrounds/debug-resolutions.d.ts.map +1 -0
  41. package/dist/backgrounds/debug-resolutions.js +6 -0
  42. package/dist/backgrounds/debug-resolutions.js.map +1 -0
  43. package/dist/backgrounds/debug-resolutions.web.d.ts +3 -0
  44. package/dist/backgrounds/debug-resolutions.web.d.ts.map +1 -0
  45. package/dist/backgrounds/debug-resolutions.web.js +8 -0
  46. package/dist/backgrounds/debug-resolutions.web.js.map +1 -0
  47. package/dist/backgrounds/debug-resolutions.webp +0 -0
  48. package/dist/backgrounds/home-dark.d.ts +3 -0
  49. package/dist/backgrounds/home-dark.d.ts.map +1 -0
  50. package/dist/backgrounds/{office-1.js → home-dark.js} +3 -3
  51. package/dist/backgrounds/home-dark.js.map +1 -0
  52. package/dist/backgrounds/home-dark.web.d.ts +3 -0
  53. package/dist/backgrounds/home-dark.web.d.ts.map +1 -0
  54. package/dist/backgrounds/home-dark.web.js +6 -0
  55. package/dist/backgrounds/home-dark.web.js.map +1 -0
  56. package/dist/backgrounds/home-dark.webp +0 -0
  57. package/dist/backgrounds/home-light.d.ts +3 -0
  58. package/dist/backgrounds/home-light.d.ts.map +1 -0
  59. package/dist/backgrounds/{office-2.js → home-light.js} +3 -3
  60. package/dist/backgrounds/home-light.js.map +1 -0
  61. package/dist/backgrounds/home-light.web.d.ts +3 -0
  62. package/dist/backgrounds/home-light.web.d.ts.map +1 -0
  63. package/dist/backgrounds/home-light.web.js +6 -0
  64. package/dist/backgrounds/home-light.web.js.map +1 -0
  65. package/dist/backgrounds/home-light.webp +0 -0
  66. package/dist/backgrounds/index.d.ts.map +1 -1
  67. package/dist/backgrounds/index.js +1 -1
  68. package/dist/backgrounds/index.js.map +1 -1
  69. package/dist/backgrounds/light-office.d.ts +3 -0
  70. package/dist/backgrounds/light-office.d.ts.map +1 -0
  71. package/dist/backgrounds/light-office.js +5 -0
  72. package/dist/backgrounds/light-office.js.map +1 -0
  73. package/dist/backgrounds/light-office.web.d.ts +3 -0
  74. package/dist/backgrounds/light-office.web.d.ts.map +1 -0
  75. package/dist/backgrounds/{office-2.web.js → light-office.web.js} +3 -3
  76. package/dist/backgrounds/light-office.web.js.map +1 -0
  77. package/dist/backgrounds/light-office.webp +0 -0
  78. package/dist/backgrounds/nature-dark.d.ts +3 -0
  79. package/dist/backgrounds/nature-dark.d.ts.map +1 -0
  80. package/dist/backgrounds/nature-dark.js +5 -0
  81. package/dist/backgrounds/nature-dark.js.map +1 -0
  82. package/dist/backgrounds/nature-dark.web.d.ts +3 -0
  83. package/dist/backgrounds/nature-dark.web.d.ts.map +1 -0
  84. package/dist/backgrounds/nature-dark.web.js +6 -0
  85. package/dist/backgrounds/nature-dark.web.js.map +1 -0
  86. package/dist/backgrounds/nature-dark.webp +0 -0
  87. package/dist/backgrounds/nature-light.d.ts +3 -0
  88. package/dist/backgrounds/nature-light.d.ts.map +1 -0
  89. package/dist/backgrounds/nature-light.js +5 -0
  90. package/dist/backgrounds/nature-light.js.map +1 -0
  91. package/dist/backgrounds/nature-light.web.d.ts +3 -0
  92. package/dist/backgrounds/nature-light.web.d.ts.map +1 -0
  93. package/dist/backgrounds/nature-light.web.js +6 -0
  94. package/dist/backgrounds/nature-light.web.js.map +1 -0
  95. package/dist/backgrounds/nature-light.webp +0 -0
  96. package/dist/backgrounds/presets.d.ts +1 -1
  97. package/dist/backgrounds/presets.d.ts.map +1 -1
  98. package/dist/backgrounds/presets.js +22 -9
  99. package/dist/backgrounds/presets.js.map +1 -1
  100. package/dist/backgrounds/simiancraft-dark.d.ts +3 -0
  101. package/dist/backgrounds/simiancraft-dark.d.ts.map +1 -0
  102. package/dist/backgrounds/simiancraft-dark.js +5 -0
  103. package/dist/backgrounds/simiancraft-dark.js.map +1 -0
  104. package/dist/backgrounds/simiancraft-dark.web.d.ts +3 -0
  105. package/dist/backgrounds/simiancraft-dark.web.d.ts.map +1 -0
  106. package/dist/backgrounds/simiancraft-dark.web.js +6 -0
  107. package/dist/backgrounds/simiancraft-dark.web.js.map +1 -0
  108. package/dist/backgrounds/simiancraft-dark.webp +0 -0
  109. package/dist/backgrounds/simiancraft-light.d.ts +3 -0
  110. package/dist/backgrounds/simiancraft-light.d.ts.map +1 -0
  111. package/dist/backgrounds/simiancraft-light.js +5 -0
  112. package/dist/backgrounds/simiancraft-light.js.map +1 -0
  113. package/dist/backgrounds/simiancraft-light.web.d.ts +3 -0
  114. package/dist/backgrounds/simiancraft-light.web.d.ts.map +1 -0
  115. package/dist/backgrounds/simiancraft-light.web.js +6 -0
  116. package/dist/backgrounds/simiancraft-light.web.js.map +1 -0
  117. package/dist/backgrounds/simiancraft-light.webp +0 -0
  118. package/dist/backgrounds/stylized-dark.d.ts +3 -0
  119. package/dist/backgrounds/stylized-dark.d.ts.map +1 -0
  120. package/dist/backgrounds/stylized-dark.js +5 -0
  121. package/dist/backgrounds/stylized-dark.js.map +1 -0
  122. package/dist/backgrounds/stylized-dark.web.d.ts +3 -0
  123. package/dist/backgrounds/stylized-dark.web.d.ts.map +1 -0
  124. package/dist/backgrounds/stylized-dark.web.js +6 -0
  125. package/dist/backgrounds/stylized-dark.web.js.map +1 -0
  126. package/dist/backgrounds/stylized-dark.webp +0 -0
  127. package/dist/backgrounds/stylized-light.d.ts +3 -0
  128. package/dist/backgrounds/stylized-light.d.ts.map +1 -0
  129. package/dist/backgrounds/stylized-light.js +5 -0
  130. package/dist/backgrounds/stylized-light.js.map +1 -0
  131. package/dist/backgrounds/stylized-light.web.d.ts +3 -0
  132. package/dist/backgrounds/stylized-light.web.d.ts.map +1 -0
  133. package/dist/backgrounds/stylized-light.web.js +6 -0
  134. package/dist/backgrounds/stylized-light.web.js.map +1 -0
  135. package/dist/backgrounds/stylized-light.webp +0 -0
  136. package/dist/index.d.ts +19 -5
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +54 -27
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.web.d.ts +14 -2
  141. package/dist/index.web.d.ts.map +1 -1
  142. package/dist/index.web.js +25 -9
  143. package/dist/index.web.js.map +1 -1
  144. package/dist/types.d.ts +20 -13
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/types.js +0 -3
  147. package/dist/types.js.map +1 -1
  148. package/dist/web/blur-kernel.d.ts +6 -0
  149. package/dist/web/blur-kernel.d.ts.map +1 -0
  150. package/dist/web/blur-kernel.js +41 -0
  151. package/dist/web/blur-kernel.js.map +1 -0
  152. package/dist/web/effects/background-image.d.ts.map +1 -1
  153. package/dist/web/effects/background-image.js +100 -33
  154. package/dist/web/effects/background-image.js.map +1 -1
  155. package/dist/web/effects/blur.d.ts.map +1 -1
  156. package/dist/web/effects/blur.js +163 -65
  157. package/dist/web/effects/blur.js.map +1 -1
  158. package/dist/web/effects/transform.d.ts +4 -0
  159. package/dist/web/effects/transform.d.ts.map +1 -0
  160. package/dist/web/effects/transform.js +62 -0
  161. package/dist/web/effects/transform.js.map +1 -0
  162. package/dist/web/segmenter.d.ts +9 -0
  163. package/dist/web/segmenter.d.ts.map +1 -1
  164. package/dist/web/segmenter.js +32 -0
  165. package/dist/web/segmenter.js.map +1 -1
  166. package/dist/web/shaders.d.ts.map +1 -1
  167. package/dist/web/shaders.generated.d.ts +1 -1
  168. package/dist/web/shaders.generated.d.ts.map +1 -1
  169. package/dist/web/shaders.generated.js +3 -3
  170. package/dist/web/shaders.generated.js.map +1 -1
  171. package/dist/web/shaders.js +1 -1
  172. package/dist/web/shaders.js.map +1 -1
  173. package/dist/web/tuning.d.ts +4 -0
  174. package/dist/web/tuning.d.ts.map +1 -1
  175. package/dist/web/tuning.js +17 -5
  176. package/dist/web/tuning.js.map +1 -1
  177. package/ios/Kaleidoscope.podspec +13 -3
  178. package/ios/KaleidoscopeModule/EffectTuning.swift +47 -7
  179. package/ios/KaleidoscopeModule/KaleidoscopeModule.swift +12 -0
  180. package/ios/KaleidoscopeModule/Registration.swift +49 -6
  181. package/ios/KaleidoscopeModule/effects/BackgroundImageProcessor.swift +100 -33
  182. package/ios/KaleidoscopeModule/effects/BlurProcessor.swift +91 -31
  183. package/ios/KaleidoscopeModule/effects/FrameBridge.swift +11 -5
  184. package/ios/KaleidoscopeModule/effects/TransformProcessor.swift +173 -0
  185. package/ios/KaleidoscopeModule/gpu/Ingest.swift +169 -0
  186. package/ios/KaleidoscopeModule/gpu/MetalRenderer.swift +234 -56
  187. package/ios/KaleidoscopeModule/gpu/Orientation.swift +83 -0
  188. package/ios/KaleidoscopeModule/gpu/TextureBridge.swift +183 -12
  189. package/ios/KaleidoscopeModule/resources/backgrounds/dark-office.webp +0 -0
  190. package/ios/KaleidoscopeModule/resources/backgrounds/debug-resolutions.webp +0 -0
  191. package/ios/KaleidoscopeModule/resources/backgrounds/home-dark.webp +0 -0
  192. package/ios/KaleidoscopeModule/resources/backgrounds/home-light.webp +0 -0
  193. package/ios/KaleidoscopeModule/resources/backgrounds/light-office.webp +0 -0
  194. package/ios/KaleidoscopeModule/resources/backgrounds/nature-dark.webp +0 -0
  195. package/ios/KaleidoscopeModule/resources/backgrounds/nature-light.webp +0 -0
  196. package/ios/KaleidoscopeModule/resources/backgrounds/simiancraft-dark.webp +0 -0
  197. package/ios/KaleidoscopeModule/resources/backgrounds/simiancraft-light.webp +0 -0
  198. package/ios/KaleidoscopeModule/resources/backgrounds/stylized-dark.webp +0 -0
  199. package/ios/KaleidoscopeModule/resources/backgrounds/stylized-light.webp +0 -0
  200. package/ios/KaleidoscopeModule/resources/selfie_segmenter.tflite +0 -0
  201. package/ios/KaleidoscopeModule/segmentation/Segmenter.swift +379 -56
  202. package/ios/KaleidoscopeModule/shaders/SHADERS.txt +1 -0
  203. package/ios/KaleidoscopeModule/shaders/blur.metalsrc +2 -2
  204. package/ios/KaleidoscopeModule/shaders/transform.metalsrc +22 -0
  205. package/package.json +93 -19
  206. package/src/backgrounds/README.md +15 -10
  207. package/src/backgrounds/dark-office.ts +6 -0
  208. package/src/backgrounds/{office-2.web.ts → dark-office.web.ts} +2 -2
  209. package/src/backgrounds/dark-office.webp +0 -0
  210. package/src/backgrounds/debug-resolutions.ts +7 -0
  211. package/src/backgrounds/debug-resolutions.web.ts +9 -0
  212. package/src/backgrounds/debug-resolutions.webp +0 -0
  213. package/src/backgrounds/{office-1.ts → home-dark.ts} +2 -2
  214. package/src/backgrounds/home-dark.web.ts +7 -0
  215. package/src/backgrounds/home-dark.webp +0 -0
  216. package/src/backgrounds/{office-2.ts → home-light.ts} +2 -2
  217. package/src/backgrounds/home-light.web.ts +7 -0
  218. package/src/backgrounds/home-light.webp +0 -0
  219. package/src/backgrounds/index.ts +1 -1
  220. package/src/backgrounds/light-office.ts +6 -0
  221. package/src/backgrounds/{office-1.web.ts → light-office.web.ts} +2 -2
  222. package/src/backgrounds/light-office.webp +0 -0
  223. package/src/backgrounds/nature-dark.ts +6 -0
  224. package/src/backgrounds/nature-dark.web.ts +7 -0
  225. package/src/backgrounds/nature-dark.webp +0 -0
  226. package/src/backgrounds/nature-light.ts +6 -0
  227. package/src/backgrounds/nature-light.web.ts +7 -0
  228. package/src/backgrounds/nature-light.webp +0 -0
  229. package/src/backgrounds/presets.ts +22 -9
  230. package/src/backgrounds/simiancraft-dark.ts +6 -0
  231. package/src/backgrounds/simiancraft-dark.web.ts +7 -0
  232. package/src/backgrounds/simiancraft-dark.webp +0 -0
  233. package/src/backgrounds/simiancraft-light.ts +6 -0
  234. package/src/backgrounds/simiancraft-light.web.ts +7 -0
  235. package/src/backgrounds/simiancraft-light.webp +0 -0
  236. package/src/backgrounds/stylized-dark.ts +6 -0
  237. package/src/backgrounds/stylized-dark.web.ts +7 -0
  238. package/src/backgrounds/stylized-dark.webp +0 -0
  239. package/src/backgrounds/stylized-light.ts +6 -0
  240. package/src/backgrounds/stylized-light.web.ts +7 -0
  241. package/src/backgrounds/stylized-light.webp +0 -0
  242. package/src/index.ts +64 -27
  243. package/src/index.web.ts +29 -11
  244. package/src/types.ts +23 -14
  245. package/src/web/blur-kernel.ts +44 -0
  246. package/src/web/effects/background-image.ts +121 -34
  247. package/src/web/effects/blur.ts +206 -67
  248. package/src/web/effects/transform.ts +69 -0
  249. package/src/web/segmenter.ts +34 -0
  250. package/src/web/shaders.generated.ts +3 -3
  251. package/src/web/shaders.ts +1 -1
  252. package/src/web/tuning.ts +19 -5
  253. package/android/src/main/assets/backgrounds/office-1.png +0 -0
  254. package/android/src/main/assets/backgrounds/office-2.png +0 -0
  255. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/MirrorFactory.kt +0 -57
  256. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectFactory.kt +0 -14
  257. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectProcessor.kt +0 -198
  258. package/dist/backgrounds/office-1.d.ts +0 -3
  259. package/dist/backgrounds/office-1.d.ts.map +0 -1
  260. package/dist/backgrounds/office-1.js.map +0 -1
  261. package/dist/backgrounds/office-1.web.d.ts +0 -3
  262. package/dist/backgrounds/office-1.web.d.ts.map +0 -1
  263. package/dist/backgrounds/office-1.web.js.map +0 -1
  264. package/dist/backgrounds/office-1.webp +0 -0
  265. package/dist/backgrounds/office-2.d.ts +0 -3
  266. package/dist/backgrounds/office-2.d.ts.map +0 -1
  267. package/dist/backgrounds/office-2.js.map +0 -1
  268. package/dist/backgrounds/office-2.web.d.ts +0 -3
  269. package/dist/backgrounds/office-2.web.d.ts.map +0 -1
  270. package/dist/backgrounds/office-2.web.js.map +0 -1
  271. package/dist/backgrounds/office-2.webp +0 -0
  272. package/dist/web/effects/mirror.d.ts +0 -3
  273. package/dist/web/effects/mirror.d.ts.map +0 -1
  274. package/dist/web/effects/mirror.js +0 -31
  275. package/dist/web/effects/mirror.js.map +0 -1
  276. package/dist/web/effects/passthrough.d.ts +0 -3
  277. package/dist/web/effects/passthrough.d.ts.map +0 -1
  278. package/dist/web/effects/passthrough.js +0 -15
  279. package/dist/web/effects/passthrough.js.map +0 -1
  280. package/ios/KaleidoscopeModule/effects/MirrorProcessor.swift +0 -136
  281. package/ios/KaleidoscopeModule/resources/backgrounds/office-1.png +0 -0
  282. package/ios/KaleidoscopeModule/resources/backgrounds/office-2.png +0 -0
  283. package/src/backgrounds/office-1.webp +0 -0
  284. package/src/backgrounds/office-2.webp +0 -0
  285. package/src/web/effects/mirror.ts +0 -37
  286. package/src/web/effects/passthrough.ts +0 -17
package/NOTICE.md CHANGED
@@ -6,11 +6,22 @@ react-native-webrtc-kaleidoscope ships under the MIT license (see [LICENSE](./LI
6
6
 
7
7
  - **react-native-webrtc** — MIT license. <https://github.com/react-native-webrtc/react-native-webrtc>. The `track._setVideoEffects(...)` JS surface, the Android `ProcessorProvider` registry, and the iOS `RTCVideoFrameProcessor` protocol all originate upstream. This package is a consumer of, not a fork of, that codebase.
8
8
 
9
- ## Native segmentation backends (used by the `blur` effect)
9
+ ## Segmentation backends (used by the `blur` and `background-image` effects)
10
10
 
11
- - **Apple Vision** (iOS) system framework. `VNGeneratePersonSegmentationRequest`. Subject to the Apple SDK License Agreement.
12
- - **MLKit Selfie Segmentation** (Android) — Apache License 2.0. Distributed by Google as a managed AAR. <https://developers.google.com/ml-kit/vision/selfie-segmentation>.
13
- - **MediaPipe Selfie Segmentation** (web) — Apache License 2.0. Loaded as an `optionalDependency` so native consumers do not bundle the WASM payload. <https://github.com/google-ai-edge/mediapipe>.
11
+ All three platforms run Google's MediaPipe selfie segmentation. The native targets use the MediaPipe Tasks Image Segmenter with a model bundled in this package; the web target uses the legacy MediaPipe Selfie Segmentation Solution loaded at runtime from a CDN.
12
+
13
+ - **MediaPipe Tasks Vision** (Android) — Apache License 2.0. The `com.google.mediapipe:tasks-vision` AAR, `ImageSegmenter` API. <https://github.com/google-ai-edge/mediapipe>.
14
+ - **MediaPipe Tasks Vision** (iOS) — Apache License 2.0. The `MediaPipeTasksVision` CocoaPod, `ImageSegmenter` API. <https://github.com/google-ai-edge/mediapipe>.
15
+ - **MediaPipe Selfie Segmentation** (web) — Apache License 2.0. The `@mediapipe/selfie_segmentation` Solution, loaded at runtime from `cdn.jsdelivr.net` (not bundled in this package). <https://github.com/google-ai-edge/mediapipe>.
16
+
17
+ ### Bundled model
18
+
19
+ The native targets redistribute a TensorFlow Lite model inside the published package; it ships in `android/src/main/assets/selfie_segmenter.tflite` and in the iOS `Kaleidoscope.bundle`.
20
+
21
+ - **MediaPipe SelfieSegmenter** (`selfie_segmenter.tflite`) — distributed by Google as part of MediaPipe Solutions.
22
+ - Source: <https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite>
23
+ - Model card: <https://storage.googleapis.com/mediapipe-assets/Model%20Card%20MediaPipe%20Selfie%20Segmentation.pdf>
24
+ - The MediaPipe project and SDK are published by Google under the Apache License 2.0. The `selfie_segmenter.tflite` weights are redistributed here as published by Google as part of MediaPipe Solutions; Google does not attach a separate license to the hosted model file, so its use is governed by the model card above. The web target does not bundle this file; it fetches its model from the CDN at runtime.
14
25
 
15
26
  ## Algorithmic and architectural references
16
27
 
@@ -19,8 +30,8 @@ react-native-webrtc-kaleidoscope ships under the MIT license (see [LICENSE](./LI
19
30
 
20
31
  ## Trademarks
21
32
 
22
- - "Apple", "Vision", "Core Image", and related Apple marks are trademarks of Apple Inc. Used here as nominative references only.
23
- - "Google", "MLKit", and "MediaPipe" are trademarks of Google LLC. Used here as nominative references only.
33
+ - "Apple", "Core Image", "Metal", and related Apple marks are trademarks of Apple Inc. Used here as nominative references only.
34
+ - "Google", "MediaPipe", and "TensorFlow Lite" are trademarks of Google LLC. Used here as nominative references only.
24
35
  - "WebRTC" is a project of the W3C and IETF.
25
36
 
26
37
  This package is not affiliated with, endorsed by, or certified by Apple, Google, the W3C, or the `react-native-webrtc` project.
package/README.md CHANGED
@@ -8,9 +8,11 @@
8
8
  [![npm version](https://img.shields.io/npm/v/react-native-webrtc-kaleidoscope?color=cb3837&logo=npm)](https://www.npmjs.com/package/react-native-webrtc-kaleidoscope)
9
9
  [![Types: included](https://img.shields.io/npm/types/react-native-webrtc-kaleidoscope?color=3178c6&logo=typescript)](https://www.npmjs.com/package/react-native-webrtc-kaleidoscope)
10
10
  [![CI](https://github.com/simiancraft/react-native-webrtc-kaleidoscope/actions/workflows/ci.yml/badge.svg)](https://github.com/simiancraft/react-native-webrtc-kaleidoscope/actions/workflows/ci.yml)
11
+ [![codecov](https://codecov.io/gh/simiancraft/react-native-webrtc-kaleidoscope/graph/badge.svg)](https://codecov.io/gh/simiancraft/react-native-webrtc-kaleidoscope)
12
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/simiancraft/react-native-webrtc-kaleidoscope/badge)](https://securityscorecards.dev/viewer/?uri=github.com/simiancraft/react-native-webrtc-kaleidoscope)
11
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
12
14
 
13
- > Live video effects for `react-native-webrtc`, packaged as a managed-Expo-friendly Expo Module.
15
+ > Creative, shader-based video effects for React Native video calls: blur or replace a person's background, with more camera effects to come. Works with `react-native-webrtc` and LiveKit, managed-Expo-friendly.
14
16
 
15
17
  ## Status
16
18
 
@@ -18,17 +20,17 @@
18
20
 
19
21
  ### What works today
20
22
 
21
- - **Mirror** (horizontal flip).
23
+ - **Transform** (flip X / flip Y / rotate 90° CW / CCW) — orientation utilities, identical across platforms.
22
24
  - **Blur** (background blur, person stays sharp).
23
- - **Background replacement** (composite a still PNG behind the segmented person; two bundled office presets, library-side asset pipeline).
25
+ - **Background replacement** (composite a still image behind the segmented person; eleven bundled presets, plus arbitrary URLs on web; library-side asset pipeline).
24
26
  - **Runtime tuning** of the GLSL effects; see the [Use](#use) section.
25
27
 
26
- | Platform | Mirror | Blur | Background replacement | Notes |
28
+ | Platform | Transform | Blur | Background replacement | Notes |
27
29
  |---|---|---|---|---|
28
30
  | Web (Chrome / Edge) | ✓ | ✓ | ✓ | MediaStreamTrackProcessor + MediaPipe Selfie Segmentation (WASM, CDN) |
29
- | Android (API 24+) | ✓ | ✓ | ✓ | OpenGL ES 3.0 + MLKit Selfie Segmentation |
31
+ | Android (API 24+) | ✓ | ✓ | ✓ | OpenGL ES 3.0 + MediaPipe Selfie Segmentation (Tasks) |
30
32
  | iOS (≥ 15) | ✓ | ✓ | ✓ | Metal + Vision (person segmentation), verified on device. Older A11 devices (iPhone X) run at a lower frame rate |
31
- | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a typed error |
33
+ | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a clear capability error |
32
34
 
33
35
  ### Coming soon
34
36
 
@@ -101,31 +103,50 @@ import {
101
103
  setMaskHardness,
102
104
  setMaskThreshold,
103
105
  } from 'react-native-webrtc-kaleidoscope';
106
+ import { darkOffice } from 'react-native-webrtc-kaleidoscope/backgrounds/dark-office';
104
107
 
105
108
  const stream = await mediaDevices.getUserMedia({ video: true });
106
109
  const [track] = stream.getVideoTracks();
107
110
 
108
- applyVideoEffects(track, ['mirror']);
111
+ applyVideoEffects(track, ['flip-x']); // also flip-y, rotate-cw, rotate-ccw
109
112
  applyVideoEffects(track, ['blur']);
110
- applyVideoEffects(track, [{ name: 'background-image', source: 'office-1' }]);
113
+ applyVideoEffects(track, [{ name: 'background-image', source: darkOffice }]);
111
114
  applyVideoEffects(track, []); // clear all effects
112
115
 
113
116
  // Runtime tuning (effects pick up the new values on the next frame):
114
- setBlurSigma(25); // Gaussian σ; clamped to [0.5, 64], default 8.
115
- setMaskHardness(0.2); // smoothstep transition width; clamped to [0, 1]. 0 = soft halo, 1 = near-step. Default 0.5.
116
- setMaskThreshold(0.7); // smoothstep center; clamped to [0.05, 0.95]. Higher rejects low-confidence pixels. Default 0.5.
117
+ setBlurSigma(5); // Gaussian σ; clamped to [0.5, 7], default 5.
118
+ setMaskHardness(0.5); // smoothstep transition width; clamped to [0, 1]. 0 = soft halo, 1 = near-step. Default 0.5.
119
+ setMaskThreshold(0.7); // smoothstep center; clamped to [0.05, 0.95]. Higher rejects low-confidence pixels. Default 0.7.
117
120
  ```
118
121
 
119
122
  Effects chain in array order.
120
123
 
121
- **Tuning note:** optimal values are platform-specific because each segmentation model (MediaPipe on web, MLKit on Android, Vision when iOS lands) produces a different confidence distribution. Working defaults on a typical well-lit scene:
124
+ **Tuning note:** all three platforms run MediaPipe selfie segmentation (Tasks Image Segmenter on native, the Selfie Segmentation Solution on web), but optimal values still differ slightly because the input downscale and the API variant differ per platform, producing different confidence distributions. Working defaults on a typical well-lit scene:
122
125
 
123
126
  | Platform | Blur sigma | Mask hardness | Mask threshold |
124
127
  |---|---|---|---|
125
128
  | Web (MediaPipe) | 25 | 0.2 | 0.85 |
126
- | Android (MLKit) | 30 | 0.2 | 0.6 |
129
+ | Android (MediaPipe) | 30 | 0.2 | 0.6 |
127
130
 
128
- The library ships neutral defaults (8, 0.5, 0.5) and consumers tune at runtime via the API above; whether to ship the dialed-in values as platform-specific defaults is an open question waiting on iOS data.
131
+ The library ships defaults (5, 0.5, 0.7) and consumers tune at runtime via the API above; whether to ship the dialed-in per-platform values as defaults is an open question.
132
+
133
+ ## Background presets
134
+
135
+ Eleven backgrounds ship for the `background-image` effect, imported per preset (e.g. `import { darkOffice } from 'react-native-webrtc-kaleidoscope/backgrounds/dark-office'`). On web a preset can also be any image URL or data URI; native resolves bundled preset names only.
136
+
137
+ | Theme | Light | Dark |
138
+ |---|---|---|
139
+ | Office | <img src="src/backgrounds/light-office.webp" width="220" alt="light-office" /> | <img src="src/backgrounds/dark-office.webp" width="220" alt="dark-office" /> |
140
+ | Home | <img src="src/backgrounds/home-light.webp" width="220" alt="home-light" /> | <img src="src/backgrounds/home-dark.webp" width="220" alt="home-dark" /> |
141
+ | Nature | <img src="src/backgrounds/nature-light.webp" width="220" alt="nature-light" /> | <img src="src/backgrounds/nature-dark.webp" width="220" alt="nature-dark" /> |
142
+ | Stylized | <img src="src/backgrounds/stylized-light.webp" width="220" alt="stylized-light" /> | <img src="src/backgrounds/stylized-dark.webp" width="220" alt="stylized-dark" /> |
143
+ | Simiancraft | <img src="src/backgrounds/simiancraft-light.webp" width="220" alt="simiancraft-light" /> | <img src="src/backgrounds/simiancraft-dark.webp" width="220" alt="simiancraft-dark" /> |
144
+
145
+ Plus **`debug-resolutions`**, a viewport/resolution calibration grid for verifying background cover-fit:
146
+
147
+ <img src="src/backgrounds/debug-resolutions.webp" width="220" alt="debug-resolutions" />
148
+
149
+ See [`src/backgrounds/README.md`](./src/backgrounds/README.md) for sizing and how to add a preset.
129
150
 
130
151
  ## Web and native differences
131
152
 
@@ -133,9 +154,9 @@ The API surface is the same across platforms, but the runtimes differ in ways wo
133
154
 
134
155
  - **Effect parameters.** Web reads tuning from the global setters (`setBlurSigma`, `setMaskHardness`, `setMaskThreshold`) on the next frame. Native currently ignores per-call `EffectSpec` parameters such as `{ name: 'blur', sigma: 12 }`; tuning is global through the same setters. Per-call uniforms through the native registry are a follow-up.
135
156
  - **Background source.** `background-image.source` is a bundled preset name on native (the upstream `_setVideoEffects` registry is keyed by flat strings, not URIs), but on web it accepts either a preset name or an arbitrary image URL or data URI.
136
- - **Background presets ship as tree-shakeable files.** Two example backgrounds (`office-1`, `office-2`) are importable per preset: `import { office1 } from 'react-native-webrtc-kaleidoscope/backgrounds/office-1'`. Each preset is its own file behind its own subpath export, and the package sets `sideEffects: false`, so an unused preset is dropped by web bundlers — and, since Metro doesn't tree-shake, simply never imported on native. Web resolves the bundled WebP to a URL; native loads its own bundled copy by name. Web also still accepts an arbitrary image URL or data URI. See [`src/backgrounds/README.md`](./src/backgrounds/README.md).
157
+ - **Background presets ship as tree-shakeable files.** The bundled backgrounds (see [Background presets](#background-presets)) are importable per preset: `import { darkOffice } from 'react-native-webrtc-kaleidoscope/backgrounds/dark-office'`. Each preset is its own file behind its own subpath export, and the package sets `sideEffects: false`, so an unused preset is dropped by web bundlers — and, since Metro doesn't tree-shake, simply never imported on native. Web resolves the bundled WebP to a URL; native loads its own bundled copy by name. Web also still accepts an arbitrary image URL or data URI. See [`src/backgrounds/README.md`](./src/backgrounds/README.md).
137
158
  - **Segmentation model on web.** The web blur and background-image effects load MediaPipe Selfie Segmentation from the jsdelivr CDN (`cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation`) on first use. A strict Content-Security-Policy must allow that origin for `script-src`, `connect-src`, and the WASM fetch, and the effects do not work offline. Mirror needs no model.
138
- - **Browser support on web.** Effects use Insertable Streams (`MediaStreamTrackProcessor` and `MediaStreamTrackGenerator`), which ship in Chromium-based browsers; Safari and Firefox throw a typed capability error.
159
+ - **Browser support on web.** Effects use Insertable Streams (`MediaStreamTrackProcessor` and `MediaStreamTrackGenerator`), which ship in Chromium-based browsers (Chrome, Edge); Safari and Firefox lack the API, so `applyVideoEffects` throws a clear capability error and the demo falls back to the unprocessed track.
139
160
 
140
161
  ## What this isn't
141
162
 
@@ -150,8 +171,8 @@ The codebase lives across four surfaces:
150
171
 
151
172
  - `src/` — JS facade and shared types. `applyVideoEffects(track, effects)` plus runtime tuning setters.
152
173
  - `src/web/` — WebGL2 pipeline. MediaPipe segmentation + GLSL composite. One shader file per stage in `src/web/shaders.ts`.
153
- - `android/` — OpenGL ES 3.0 pipeline. MLKit segmentation (async, worker-thread, last-known-mask cache) + GLSL composite. Shaders inline in `gpu/Shaders.kt` as `const val` strings.
154
- - `ios/` — Metal pipeline (Swift) with Vision person segmentation. The canonical GLSL in `shaders/` transpiles to Metal Shading Language via `scripts/build-shaders.ts`. Implemented and verified on device.
174
+ - `android/` — OpenGL ES 3.0 pipeline. MediaPipe Tasks segmentation (async, worker-thread, last-known-mask cache) + GLSL composite. Shaders inline in `gpu/Shaders.kt` as `const val` strings.
175
+ - `ios/` — Metal pipeline (Swift) with MediaPipe Tasks segmentation (`selfie_segmenter.tflite`, the same model the Android target bundles). The canonical GLSL in `shaders/` transpiles to Metal Shading Language via `scripts/build-shaders.ts`. Implemented and verified on device.
155
176
 
156
177
  The composite shader (`shaders/composite.frag`) is the same GLSL source for every effect category (blur, background-image, future procedural backgrounds). Per-effect difference is upstream of the composite: how the `uBackground` texture gets produced.
157
178
 
@@ -55,10 +55,9 @@ dependencies {
55
55
  }
56
56
  compileOnly project(rnWebrtcProject)
57
57
 
58
- // Person/background mask for the blur effect. The Google artifact ID is
59
- // `segmentation-selfie`, not `selfie-segmentation` (the plan and earlier
60
- // notes had it inverted; that is why the pin would not resolve).
61
- implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta6'
58
+ // Person/background mask: MediaPipe Tasks ImageSegmenter (Mask.kt), the same
59
+ // model family the web and iOS sides run.
60
+ implementation 'com.google.mediapipe:tasks-vision:0.10.14'
62
61
  }
63
62
 
64
63
  // Helper: gracefully read root project ext properties with a default.
@@ -13,14 +13,14 @@ package com.simiancraft.kaleidoscope
13
13
 
14
14
  internal object EffectTuning {
15
15
  /**
16
- * Gaussian sigma for the blur effect; higher = softer blur. Default
17
- * matches the v0.1 hardcoded value. Custom setter clamps to a sane
18
- * range; setting the property is the public mutation API.
16
+ * Gaussian sigma for the blur effect; higher = softer blur. Default 5;
17
+ * clamped to [0.5, 7] (the useful range before the linear-sampled kernel
18
+ * truncates and bands). Setting the property is the public mutation API.
19
19
  */
20
20
  @Volatile
21
- var blurSigma: Float = 8f
21
+ var blurSigma: Float = 5f
22
22
  set(value) {
23
- field = value.coerceIn(0.5f, 64f)
23
+ field = value.coerceIn(0.5f, 7f)
24
24
  }
25
25
 
26
26
  /**
@@ -37,21 +37,52 @@ internal object EffectTuning {
37
37
  /**
38
38
  * Mask smoothstep center for blur and background-image composites,
39
39
  * in [0, 1]; the threshold at which a pixel's raw confidence flips from
40
- * "background" to "person". Default 0.5 reproduces the historical
41
- * smoothstep centered on the confidence midpoint. Higher values reject
40
+ * "background" to "person". Default 0.7 (dialed in post-optimization).
41
+ * Higher values reject
42
42
  * low-confidence edges (chair backs, hair flyaway); lower values are
43
43
  * more inclusive. Clamped to a workable range below to keep the
44
44
  * smoothstep transition non-degenerate.
45
45
  */
46
46
  @Volatile
47
- var maskThreshold: Float = 0.5f
47
+ var maskThreshold: Float = 0.7f
48
48
  set(value) {
49
49
  field = value.coerceIn(0.05f, 0.95f)
50
50
  }
51
51
 
52
+ /**
53
+ * Debug GPU timing. When true, the GLES effect factories log per-frame
54
+ * GPU-completion latency under the "Perf" logcat tag (read a frame late via
55
+ * the frame-pipeline fence). Off by default; toggled from JS via the Expo
56
+ * Module's setDebugTiming so a tester can capture ground-truth numbers on a
57
+ * device build without rebuilding. Mirrors the iOS timing flag being added
58
+ * in parallel.
59
+ */
60
+ @Volatile
61
+ var debugTiming: Boolean = false
62
+ set(value) {
63
+ field = value
64
+ }
65
+
66
+ /**
67
+ * Segmentation input short-side (px). The mask is produced from an input
68
+ * downscaled to this; lower = cheaper segmentation, softer mask edge.
69
+ * Default 384; clamped [128, 1080]. The floor matches iOS and web
70
+ * (EffectTuning.swift, tuning.ts) so the JS setSegmentationTargetShortSide
71
+ * contract is identical across platforms; MediaPipe resizes the input to the
72
+ * model's own 256x256 internally, so a smaller input still segments. Tuned
73
+ * live from JS via setSegmentationTargetShortSide.
74
+ */
75
+ @Volatile
76
+ var targetShortSide: Int = 384
77
+ set(value) {
78
+ field = value.coerceIn(128, 1080)
79
+ }
80
+
52
81
  fun reset() {
53
- blurSigma = 8f
82
+ blurSigma = 5f
54
83
  maskHardness = 0.5f
55
- maskThreshold = 0.5f
84
+ maskThreshold = 0.7f
85
+ debugTiming = false
86
+ targetShortSide = 384
56
87
  }
57
88
  }
@@ -36,6 +36,14 @@ class KaleidoscopeModule : Module() {
36
36
  EffectTuning.maskThreshold = value
37
37
  }
38
38
 
39
+ Function("setDebugTiming") { value: Boolean ->
40
+ EffectTuning.debugTiming = value
41
+ }
42
+
43
+ Function("setSegmentationTargetShortSide") { value: Int ->
44
+ EffectTuning.targetShortSide = value
45
+ }
46
+
39
47
  Function("resetEffectTuning") {
40
48
  EffectTuning.reset()
41
49
  }
@@ -1,8 +1,8 @@
1
1
  // Frame-processor registration for Android. Called from
2
2
  // KaleidoscopeModule.OnCreate at Expo Module init time, before any track
3
3
  // requests an effect by name. The Context is needed by GPU effects so they
4
- // can read PNG assets for background-image and create RenderScript-style
5
- // resources where applicable.
4
+ // can read bundled assets: the background-image WebP presets and the
5
+ // selfie_segmenter.tflite model (loaded via SegmentationEngine).
6
6
 
7
7
  package com.simiancraft.kaleidoscope
8
8
 
@@ -10,30 +10,69 @@ import android.content.Context
10
10
  import com.oney.WebRTCModule.videoEffects.ProcessorProvider
11
11
  import com.simiancraft.kaleidoscope.effects.BackgroundImageFactory
12
12
  import com.simiancraft.kaleidoscope.effects.BlurFactory
13
- import com.simiancraft.kaleidoscope.effects.MirrorFactory
14
- import com.simiancraft.kaleidoscope.gpu.GpuEffectFactory
13
+ import com.simiancraft.kaleidoscope.effects.TransformFactory
14
+ import com.simiancraft.kaleidoscope.gpu.Orientation
15
15
 
16
16
  object Registration {
17
17
  @JvmStatic
18
18
  fun registerAll(context: Context) {
19
- ProcessorProvider.addProcessor("mirror", MirrorFactory())
20
19
  ProcessorProvider.addProcessor("blur", BlurFactory(context))
21
20
 
21
+ // Geometric reorientation effects. flip-x is the corrected screen-horizontal
22
+ // mirror (replaces the old "mirror" CPU effect). The rotation correction
23
+ // lives entirely in Orientation.kt; each registration just names its op.
24
+ ProcessorProvider.addProcessor("flip-x", TransformFactory(Orientation.Op.FLIP_X))
25
+ ProcessorProvider.addProcessor("flip-y", TransformFactory(Orientation.Op.FLIP_Y))
26
+ ProcessorProvider.addProcessor("rotate-cw", TransformFactory(Orientation.Op.ROTATE_CW))
27
+ ProcessorProvider.addProcessor("rotate-ccw", TransformFactory(Orientation.Op.ROTATE_CCW))
28
+
22
29
  // Background-image variants — one factory per source preset. JS side
23
30
  // emits "background-image-{source}" so each preset gets its own
24
31
  // ProcessorProvider entry. Parameterized dispatch via uniforms lands
25
32
  // when we extend the upstream rn-webrtc API surface.
26
33
  ProcessorProvider.addProcessor(
27
- "background-image-office-1",
28
- BackgroundImageFactory(context, "office-1"),
34
+ "background-image-debug-resolutions",
35
+ BackgroundImageFactory(context, "debug-resolutions"),
29
36
  )
30
37
  ProcessorProvider.addProcessor(
31
- "background-image-office-2",
32
- BackgroundImageFactory(context, "office-2"),
38
+ "background-image-dark-office",
39
+ BackgroundImageFactory(context, "dark-office"),
40
+ )
41
+ ProcessorProvider.addProcessor(
42
+ "background-image-light-office",
43
+ BackgroundImageFactory(context, "light-office"),
44
+ )
45
+ ProcessorProvider.addProcessor(
46
+ "background-image-home-light",
47
+ BackgroundImageFactory(context, "home-light"),
48
+ )
49
+ ProcessorProvider.addProcessor(
50
+ "background-image-home-dark",
51
+ BackgroundImageFactory(context, "home-dark"),
52
+ )
53
+ ProcessorProvider.addProcessor(
54
+ "background-image-nature-light",
55
+ BackgroundImageFactory(context, "nature-light"),
56
+ )
57
+ ProcessorProvider.addProcessor(
58
+ "background-image-nature-dark",
59
+ BackgroundImageFactory(context, "nature-dark"),
60
+ )
61
+ ProcessorProvider.addProcessor(
62
+ "background-image-stylized-light",
63
+ BackgroundImageFactory(context, "stylized-light"),
64
+ )
65
+ ProcessorProvider.addProcessor(
66
+ "background-image-stylized-dark",
67
+ BackgroundImageFactory(context, "stylized-dark"),
68
+ )
69
+ ProcessorProvider.addProcessor(
70
+ "background-image-simiancraft-light",
71
+ BackgroundImageFactory(context, "simiancraft-light"),
72
+ )
73
+ ProcessorProvider.addProcessor(
74
+ "background-image-simiancraft-dark",
75
+ BackgroundImageFactory(context, "simiancraft-dark"),
33
76
  )
34
-
35
- // Temporary architecture-proof passthrough hook from the GPU bring-up.
36
- // Removed in the cleanup pass before v0.1 ships.
37
- ProcessorProvider.addProcessor("gpu-passthrough", GpuEffectFactory())
38
77
  }
39
78
  }
@@ -2,16 +2,16 @@
2
2
  //
3
3
  // Per frame:
4
4
  // 1. Render the input OES camera texture into a cached "original 2D" FBO.
5
- // 2. Lazy-load the named PNG asset (e.g. "office-1") from the library's
5
+ // 2. Lazy-load the named WebP asset (e.g. "dark-office") from the library's
6
6
  // android/src/main/assets/backgrounds/ on first frame; upload as a 2D
7
7
  // GL texture; cache for subsequent frames.
8
- // 3. Produce a mask via Mask.produce (downsample, MLKit, upload).
8
+ // 3. Produce a mask via Mask.produce (downsample, MediaPipe, upload).
9
9
  // 4. Composite original + image + mask into a fresh output texture via
10
10
  // COMPOSITE_FRAG.
11
11
  // 5. Wrap the fresh output texture in a TextureBufferImpl and return a
12
12
  // VideoFrame.
13
13
  //
14
- // Each registered name (e.g. "background-image-office-1") gets its own
14
+ // Each registered name (e.g. "background-image-dark-office") gets its own
15
15
  // factory keyed by an asset name; multiple factories can coexist if the
16
16
  // consumer registers multiple variants.
17
17
  //
@@ -31,7 +31,9 @@ import com.oney.WebRTCModule.videoEffects.VideoFrameProcessorFactoryInterface
31
31
  import com.simiancraft.kaleidoscope.EffectTuning
32
32
  import com.simiancraft.kaleidoscope.gpu.Egl
33
33
  import com.simiancraft.kaleidoscope.gpu.Fbo
34
+ import com.simiancraft.kaleidoscope.gpu.FramePipeline
34
35
  import com.simiancraft.kaleidoscope.gpu.GlDebug
36
+ import com.simiancraft.kaleidoscope.gpu.Ingest
35
37
  import com.simiancraft.kaleidoscope.gpu.GlProgram
36
38
  import com.simiancraft.kaleidoscope.gpu.Shaders
37
39
  import com.simiancraft.kaleidoscope.segmentation.Mask
@@ -43,8 +45,9 @@ import org.webrtc.YuvConverter
43
45
 
44
46
  /**
45
47
  * @param context Used to read the PNG asset.
46
- * @param assetName Filename (without `.png`) under `assets/backgrounds/`.
47
- * E.g. "office-1" -> assets/backgrounds/office-1.png.
48
+ * @param assetName Filename (without `.webp`) under `assets/backgrounds/`.
49
+ * E.g. "dark-office" -> assets/backgrounds/dark-office.webp.
50
+ * BitmapFactory decodes WebP natively (supported since API 14).
48
51
  *
49
52
  * maskHardness is read per frame from com.simiancraft.kaleidoscope.EffectTuning
50
53
  * so JS callers can tune the smoothstep edge via the Expo Module's
@@ -62,6 +65,10 @@ private class BackgroundImageProcessor(
62
65
  private val context: Context,
63
66
  private val assetName: String,
64
67
  ) : VideoFrameProcessor {
68
+ // process() is only ever invoked on the single SurfaceTextureHelper capture
69
+ // thread (VideoEffectProcessor.onFrameCaptured), so this never actually
70
+ // contends. Retained as cheap uncontended insurance and as an explicit marker
71
+ // that the GL state below is single-threaded; it is NOT a cross-thread guard.
65
72
  private val lock = Any()
66
73
 
67
74
  private var oesToTwoD: GlProgram? = null
@@ -71,9 +78,13 @@ private class BackgroundImageProcessor(
71
78
  private var cachedWidth = 0
72
79
  private var cachedHeight = 0
73
80
 
74
- private val mask = Mask()
81
+ private val mask = Mask(context)
75
82
  private var yuvConverter: YuvConverter? = null
76
83
 
84
+ // R3: one-frame GPU pipeline (see FramePipeline). Replaces the per-frame
85
+ // glFinish so the capture thread does not block on this frame's GPU work.
86
+ private val pipeline = FramePipeline()
87
+
77
88
  // Background image cached as a 2D GL texture; loaded lazily on the first
78
89
  // successful frame to ensure GL setup is ready. Aspect ratio captured at
79
90
  // load time so the composite shader can center-crop into the output.
@@ -116,13 +127,19 @@ private class BackgroundImageProcessor(
116
127
  )
117
128
  return null
118
129
  }
119
- val width = inputBuffer.width
120
- val height = inputBuffer.height
121
- if (width <= 0 || height <= 0) {
122
- Log.w(TAG, "Degenerate dims ${width}x${height}; forwarding.")
130
+ val bufW = inputBuffer.width
131
+ val bufH = inputBuffer.height
132
+ if (bufW <= 0 || bufH <= 0) {
133
+ Log.w(TAG, "Degenerate dims ${bufW}x${bufH}; forwarding.")
123
134
  return null
124
135
  }
125
136
 
137
+ // Ingest normalization: the OES->2D pass lands a DISPLAY-UPRIGHT frame, so
138
+ // the original FBO and output are sized in DISPLAY dims, the cover-fit runs
139
+ // against the display aspect, and the frame goes out rotation 0.
140
+ val width = Ingest.displayWidth(bufW, bufH, frame.rotation)
141
+ val height = Ingest.displayHeight(bufW, bufH, frame.rotation)
142
+
126
143
  GlDebug.check("bgImage entry")
127
144
  val saved = Egl.save()
128
145
  var outputTextureId = 0
@@ -145,7 +162,8 @@ private class BackgroundImageProcessor(
145
162
  origFbo.bind()
146
163
  oes.use()
147
164
  oes.setInt("uTex", 0)
148
- val texMatrix = Egl.matrixToGl(inputBuffer.transformMatrix)
165
+ // Compose transformMatrix with the display rotation so the FBO lands upright.
166
+ val texMatrix = Ingest.composedTexMatrix(inputBuffer.transformMatrix, frame.rotation)
149
167
  GLES30.glUniformMatrix4fv(oes.uniformLocation("uTexMatrix"), 1, false, texMatrix, 0)
150
168
  GLES30.glDisable(GLES30.GL_DEPTH_TEST)
151
169
  GLES30.glDisable(GLES30.GL_BLEND)
@@ -208,8 +226,6 @@ private class BackgroundImageProcessor(
208
226
  GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
209
227
  GlDebug.check("bgImage composite pass")
210
228
 
211
- GLES30.glFinish()
212
-
213
229
  GLES30.glFramebufferTexture2D(
214
230
  GLES30.GL_FRAMEBUFFER,
215
231
  GLES30.GL_COLOR_ATTACHMENT0,
@@ -222,28 +238,42 @@ private class BackgroundImageProcessor(
222
238
  outputFboHandle = 0
223
239
  GlDebug.check("bgImage output cleanup")
224
240
 
241
+ // R3: fence this frame and hand the previous GPU-complete frame off; the
242
+ // pipeline now owns outputTextureId, so zero the local handle.
243
+ val ready = pipeline.enqueue(
244
+ outputTextureId,
245
+ width,
246
+ height,
247
+ // Pixels are already display-upright after ingest; emit rotation 0.
248
+ 0,
249
+ frame.timestampNs,
250
+ EffectTuning.debugTiming,
251
+ TAG,
252
+ )
253
+ outputTextureId = 0
254
+ ready ?: return null
255
+
225
256
  val yc = yuvConverter ?: run {
226
257
  val c = YuvConverter()
227
258
  yuvConverter = c
228
259
  c
229
260
  }
230
261
 
231
- val capturedTextureId = outputTextureId
262
+ val readyTextureId = ready.textureId
232
263
  val outputBuffer = TextureBufferImpl(
233
- width,
234
- height,
264
+ ready.width,
265
+ ready.height,
235
266
  VideoFrame.TextureBuffer.Type.RGB,
236
- capturedTextureId,
267
+ readyTextureId,
237
268
  Matrix(),
238
269
  textureHelper.handler,
239
270
  yc,
240
271
  Runnable {
241
- GLES30.glDeleteTextures(1, intArrayOf(capturedTextureId), 0)
272
+ GLES30.glDeleteTextures(1, intArrayOf(readyTextureId), 0)
242
273
  },
243
274
  )
244
- outputTextureId = 0
245
275
 
246
- VideoFrame(outputBuffer, frame.rotation, frame.timestampNs)
276
+ VideoFrame(outputBuffer, ready.rotation, ready.timestampNs)
247
277
  } catch (t: Throwable) {
248
278
  if (outputTextureId != 0) {
249
279
  try {
@@ -288,15 +318,15 @@ private class BackgroundImageProcessor(
288
318
  private fun ensureBackgroundTexture() {
289
319
  if (backgroundTextureId != 0) return
290
320
  val bmp = try {
291
- context.assets.open("backgrounds/$assetName.png").use { stream ->
321
+ context.assets.open("backgrounds/$assetName.webp").use { stream ->
292
322
  BitmapFactory.decodeStream(stream)
293
323
  }
294
324
  } catch (t: Throwable) {
295
- Log.e(TAG, "failed to load asset backgrounds/$assetName.png", t)
325
+ Log.e(TAG, "failed to load asset backgrounds/$assetName.webp", t)
296
326
  return
297
327
  }
298
328
  if (bmp == null) {
299
- Log.e(TAG, "BitmapFactory returned null for backgrounds/$assetName.png")
329
+ Log.e(TAG, "BitmapFactory returned null for backgrounds/$assetName.webp")
300
330
  return
301
331
  }
302
332
  // Pre-flip vertically so the texture lands in the shared convention