react-native-webrtc-kaleidoscope 1.0.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 (301) hide show
  1. package/NOTICE.md +17 -6
  2. package/README.md +67 -19
  3. package/android/build.gradle +6 -5
  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 +23 -51
  27. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/ShadersGenerated.kt +76 -0
  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/app.plugin.js +254 -4
  31. package/dist/backgrounds/dark-office.d.ts +3 -0
  32. package/dist/backgrounds/dark-office.d.ts.map +1 -0
  33. package/dist/backgrounds/dark-office.js +5 -0
  34. package/dist/backgrounds/dark-office.js.map +1 -0
  35. package/dist/backgrounds/dark-office.web.d.ts +3 -0
  36. package/dist/backgrounds/dark-office.web.d.ts.map +1 -0
  37. package/dist/backgrounds/dark-office.web.js +8 -0
  38. package/dist/backgrounds/dark-office.web.js.map +1 -0
  39. package/dist/backgrounds/dark-office.webp +0 -0
  40. package/dist/backgrounds/debug-resolutions.d.ts +3 -0
  41. package/dist/backgrounds/debug-resolutions.d.ts.map +1 -0
  42. package/dist/backgrounds/debug-resolutions.js +6 -0
  43. package/dist/backgrounds/debug-resolutions.js.map +1 -0
  44. package/dist/backgrounds/debug-resolutions.web.d.ts +3 -0
  45. package/dist/backgrounds/debug-resolutions.web.d.ts.map +1 -0
  46. package/dist/backgrounds/debug-resolutions.web.js +8 -0
  47. package/dist/backgrounds/debug-resolutions.web.js.map +1 -0
  48. package/dist/backgrounds/debug-resolutions.webp +0 -0
  49. package/dist/backgrounds/home-dark.d.ts +3 -0
  50. package/dist/backgrounds/home-dark.d.ts.map +1 -0
  51. package/dist/backgrounds/home-dark.js +5 -0
  52. package/dist/backgrounds/home-dark.js.map +1 -0
  53. package/dist/backgrounds/home-dark.web.d.ts +3 -0
  54. package/dist/backgrounds/home-dark.web.d.ts.map +1 -0
  55. package/dist/backgrounds/home-dark.web.js +6 -0
  56. package/dist/backgrounds/home-dark.web.js.map +1 -0
  57. package/dist/backgrounds/home-dark.webp +0 -0
  58. package/dist/backgrounds/home-light.d.ts +3 -0
  59. package/dist/backgrounds/home-light.d.ts.map +1 -0
  60. package/dist/backgrounds/home-light.js +5 -0
  61. package/dist/backgrounds/home-light.js.map +1 -0
  62. package/dist/backgrounds/home-light.web.d.ts +3 -0
  63. package/dist/backgrounds/home-light.web.d.ts.map +1 -0
  64. package/dist/backgrounds/home-light.web.js +6 -0
  65. package/dist/backgrounds/home-light.web.js.map +1 -0
  66. package/dist/backgrounds/home-light.webp +0 -0
  67. package/dist/backgrounds/index.d.ts +3 -0
  68. package/dist/backgrounds/index.d.ts.map +1 -0
  69. package/dist/backgrounds/index.js +6 -0
  70. package/dist/backgrounds/index.js.map +1 -0
  71. package/dist/backgrounds/light-office.d.ts +3 -0
  72. package/dist/backgrounds/light-office.d.ts.map +1 -0
  73. package/dist/backgrounds/light-office.js +5 -0
  74. package/dist/backgrounds/light-office.js.map +1 -0
  75. package/dist/backgrounds/light-office.web.d.ts +3 -0
  76. package/dist/backgrounds/light-office.web.d.ts.map +1 -0
  77. package/dist/backgrounds/light-office.web.js +8 -0
  78. package/dist/backgrounds/light-office.web.js.map +1 -0
  79. package/dist/backgrounds/light-office.webp +0 -0
  80. package/dist/backgrounds/nature-dark.d.ts +3 -0
  81. package/dist/backgrounds/nature-dark.d.ts.map +1 -0
  82. package/dist/backgrounds/nature-dark.js +5 -0
  83. package/dist/backgrounds/nature-dark.js.map +1 -0
  84. package/dist/backgrounds/nature-dark.web.d.ts +3 -0
  85. package/dist/backgrounds/nature-dark.web.d.ts.map +1 -0
  86. package/dist/backgrounds/nature-dark.web.js +6 -0
  87. package/dist/backgrounds/nature-dark.web.js.map +1 -0
  88. package/dist/backgrounds/nature-dark.webp +0 -0
  89. package/dist/backgrounds/nature-light.d.ts +3 -0
  90. package/dist/backgrounds/nature-light.d.ts.map +1 -0
  91. package/dist/backgrounds/nature-light.js +5 -0
  92. package/dist/backgrounds/nature-light.js.map +1 -0
  93. package/dist/backgrounds/nature-light.web.d.ts +3 -0
  94. package/dist/backgrounds/nature-light.web.d.ts.map +1 -0
  95. package/dist/backgrounds/nature-light.web.js +6 -0
  96. package/dist/backgrounds/nature-light.web.js.map +1 -0
  97. package/dist/backgrounds/nature-light.webp +0 -0
  98. package/dist/backgrounds/preset-source.types.d.ts +2 -0
  99. package/dist/backgrounds/preset-source.types.d.ts.map +1 -0
  100. package/dist/backgrounds/preset-source.types.js +2 -0
  101. package/dist/backgrounds/preset-source.types.js.map +1 -0
  102. package/dist/backgrounds/presets.d.ts +3 -0
  103. package/dist/backgrounds/presets.d.ts.map +1 -0
  104. package/dist/backgrounds/presets.js +34 -0
  105. package/dist/backgrounds/presets.js.map +1 -0
  106. package/dist/backgrounds/simiancraft-dark.d.ts +3 -0
  107. package/dist/backgrounds/simiancraft-dark.d.ts.map +1 -0
  108. package/dist/backgrounds/simiancraft-dark.js +5 -0
  109. package/dist/backgrounds/simiancraft-dark.js.map +1 -0
  110. package/dist/backgrounds/simiancraft-dark.web.d.ts +3 -0
  111. package/dist/backgrounds/simiancraft-dark.web.d.ts.map +1 -0
  112. package/dist/backgrounds/simiancraft-dark.web.js +6 -0
  113. package/dist/backgrounds/simiancraft-dark.web.js.map +1 -0
  114. package/dist/backgrounds/simiancraft-dark.webp +0 -0
  115. package/dist/backgrounds/simiancraft-light.d.ts +3 -0
  116. package/dist/backgrounds/simiancraft-light.d.ts.map +1 -0
  117. package/dist/backgrounds/simiancraft-light.js +5 -0
  118. package/dist/backgrounds/simiancraft-light.js.map +1 -0
  119. package/dist/backgrounds/simiancraft-light.web.d.ts +3 -0
  120. package/dist/backgrounds/simiancraft-light.web.d.ts.map +1 -0
  121. package/dist/backgrounds/simiancraft-light.web.js +6 -0
  122. package/dist/backgrounds/simiancraft-light.web.js.map +1 -0
  123. package/dist/backgrounds/simiancraft-light.webp +0 -0
  124. package/dist/backgrounds/stylized-dark.d.ts +3 -0
  125. package/dist/backgrounds/stylized-dark.d.ts.map +1 -0
  126. package/dist/backgrounds/stylized-dark.js +5 -0
  127. package/dist/backgrounds/stylized-dark.js.map +1 -0
  128. package/dist/backgrounds/stylized-dark.web.d.ts +3 -0
  129. package/dist/backgrounds/stylized-dark.web.d.ts.map +1 -0
  130. package/dist/backgrounds/stylized-dark.web.js +6 -0
  131. package/dist/backgrounds/stylized-dark.web.js.map +1 -0
  132. package/dist/backgrounds/stylized-dark.webp +0 -0
  133. package/dist/backgrounds/stylized-light.d.ts +3 -0
  134. package/dist/backgrounds/stylized-light.d.ts.map +1 -0
  135. package/dist/backgrounds/stylized-light.js +5 -0
  136. package/dist/backgrounds/stylized-light.js.map +1 -0
  137. package/dist/backgrounds/stylized-light.web.d.ts +3 -0
  138. package/dist/backgrounds/stylized-light.web.d.ts.map +1 -0
  139. package/dist/backgrounds/stylized-light.web.js +6 -0
  140. package/dist/backgrounds/stylized-light.web.js.map +1 -0
  141. package/dist/backgrounds/stylized-light.webp +0 -0
  142. package/dist/index.d.ts +19 -5
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +71 -22
  145. package/dist/index.js.map +1 -1
  146. package/dist/index.web.d.ts +25 -3
  147. package/dist/index.web.d.ts.map +1 -1
  148. package/dist/index.web.js +55 -16
  149. package/dist/index.web.js.map +1 -1
  150. package/dist/livekit.d.ts +24 -0
  151. package/dist/livekit.d.ts.map +1 -0
  152. package/dist/livekit.js +57 -0
  153. package/dist/livekit.js.map +1 -0
  154. package/dist/types.d.ts +20 -13
  155. package/dist/types.d.ts.map +1 -1
  156. package/dist/types.js +0 -3
  157. package/dist/types.js.map +1 -1
  158. package/dist/web/blur-kernel.d.ts +6 -0
  159. package/dist/web/blur-kernel.d.ts.map +1 -0
  160. package/dist/web/blur-kernel.js +41 -0
  161. package/dist/web/blur-kernel.js.map +1 -0
  162. package/dist/web/effects/background-image.d.ts.map +1 -1
  163. package/dist/web/effects/background-image.js +100 -33
  164. package/dist/web/effects/background-image.js.map +1 -1
  165. package/dist/web/effects/blur.d.ts.map +1 -1
  166. package/dist/web/effects/blur.js +169 -38
  167. package/dist/web/effects/blur.js.map +1 -1
  168. package/dist/web/effects/transform.d.ts +4 -0
  169. package/dist/web/effects/transform.d.ts.map +1 -0
  170. package/dist/web/effects/transform.js +62 -0
  171. package/dist/web/effects/transform.js.map +1 -0
  172. package/dist/web/insertable-streams.d.ts +11 -1
  173. package/dist/web/insertable-streams.d.ts.map +1 -1
  174. package/dist/web/insertable-streams.js +22 -4
  175. package/dist/web/insertable-streams.js.map +1 -1
  176. package/dist/web/segmenter.d.ts +9 -0
  177. package/dist/web/segmenter.d.ts.map +1 -1
  178. package/dist/web/segmenter.js +32 -0
  179. package/dist/web/segmenter.js.map +1 -1
  180. package/dist/web/shaders.d.ts +1 -3
  181. package/dist/web/shaders.d.ts.map +1 -1
  182. package/dist/web/shaders.generated.d.ts +4 -0
  183. package/dist/web/shaders.generated.d.ts.map +1 -0
  184. package/dist/web/shaders.generated.js +54 -0
  185. package/dist/web/shaders.generated.js.map +1 -0
  186. package/dist/web/shaders.js +29 -103
  187. package/dist/web/shaders.js.map +1 -1
  188. package/dist/web/tuning.d.ts +4 -0
  189. package/dist/web/tuning.d.ts.map +1 -1
  190. package/dist/web/tuning.js +17 -5
  191. package/dist/web/tuning.js.map +1 -1
  192. package/ios/Kaleidoscope.podspec +87 -14
  193. package/ios/KaleidoscopeModule/EffectTuning.swift +47 -7
  194. package/ios/KaleidoscopeModule/KaleidoscopeModule.swift +12 -0
  195. package/ios/KaleidoscopeModule/Registration.swift +78 -13
  196. package/ios/KaleidoscopeModule/effects/BackgroundImageProcessor.swift +293 -0
  197. package/ios/KaleidoscopeModule/effects/BlurProcessor.swift +232 -18
  198. package/ios/KaleidoscopeModule/effects/FrameBridge.swift +46 -0
  199. package/ios/KaleidoscopeModule/effects/TransformProcessor.swift +173 -0
  200. package/ios/KaleidoscopeModule/gpu/Ingest.swift +169 -0
  201. package/ios/KaleidoscopeModule/gpu/MetalRenderer.swift +601 -0
  202. package/ios/KaleidoscopeModule/gpu/Orientation.swift +83 -0
  203. package/ios/KaleidoscopeModule/gpu/ShaderLibrary.swift +100 -0
  204. package/ios/KaleidoscopeModule/gpu/TextureBridge.swift +291 -0
  205. package/ios/KaleidoscopeModule/resources/backgrounds/dark-office.webp +0 -0
  206. package/ios/KaleidoscopeModule/resources/backgrounds/debug-resolutions.webp +0 -0
  207. package/ios/KaleidoscopeModule/resources/backgrounds/home-dark.webp +0 -0
  208. package/ios/KaleidoscopeModule/resources/backgrounds/home-light.webp +0 -0
  209. package/ios/KaleidoscopeModule/resources/backgrounds/light-office.webp +0 -0
  210. package/ios/KaleidoscopeModule/resources/backgrounds/nature-dark.webp +0 -0
  211. package/ios/KaleidoscopeModule/resources/backgrounds/nature-light.webp +0 -0
  212. package/ios/KaleidoscopeModule/resources/backgrounds/simiancraft-dark.webp +0 -0
  213. package/ios/KaleidoscopeModule/resources/backgrounds/simiancraft-light.webp +0 -0
  214. package/ios/KaleidoscopeModule/resources/backgrounds/stylized-dark.webp +0 -0
  215. package/ios/KaleidoscopeModule/resources/backgrounds/stylized-light.webp +0 -0
  216. package/ios/KaleidoscopeModule/resources/selfie_segmenter.tflite +0 -0
  217. package/ios/KaleidoscopeModule/segmentation/MaskTuning.swift +23 -0
  218. package/ios/KaleidoscopeModule/segmentation/Segmenter.swift +444 -25
  219. package/ios/KaleidoscopeModule/shaders/SHADERS.txt +6 -0
  220. package/ios/KaleidoscopeModule/shaders/blur.metalsrc +72 -0
  221. package/ios/KaleidoscopeModule/shaders/composite.metalsrc +22 -0
  222. package/ios/KaleidoscopeModule/shaders/nebula.metalsrc +72 -0
  223. package/ios/KaleidoscopeModule/shaders/passthrough.metalsrc +20 -0
  224. package/ios/KaleidoscopeModule/shaders/simianlights.metalsrc +72 -0
  225. package/ios/KaleidoscopeModule/shaders/transform.metalsrc +22 -0
  226. package/package.json +120 -11
  227. package/src/backgrounds/README.md +83 -0
  228. package/src/backgrounds/assets.d.ts +7 -0
  229. package/src/backgrounds/dark-office.ts +6 -0
  230. package/src/backgrounds/dark-office.web.ts +9 -0
  231. package/src/backgrounds/dark-office.webp +0 -0
  232. package/src/backgrounds/debug-resolutions.ts +7 -0
  233. package/src/backgrounds/debug-resolutions.web.ts +9 -0
  234. package/src/backgrounds/debug-resolutions.webp +0 -0
  235. package/src/backgrounds/home-dark.ts +6 -0
  236. package/src/backgrounds/home-dark.web.ts +7 -0
  237. package/src/backgrounds/home-dark.webp +0 -0
  238. package/src/backgrounds/home-light.ts +6 -0
  239. package/src/backgrounds/home-light.web.ts +7 -0
  240. package/src/backgrounds/home-light.webp +0 -0
  241. package/src/backgrounds/index.ts +7 -0
  242. package/src/backgrounds/light-office.ts +6 -0
  243. package/src/backgrounds/light-office.web.ts +9 -0
  244. package/src/backgrounds/light-office.webp +0 -0
  245. package/src/backgrounds/nature-dark.ts +6 -0
  246. package/src/backgrounds/nature-dark.web.ts +7 -0
  247. package/src/backgrounds/nature-dark.webp +0 -0
  248. package/src/backgrounds/nature-light.ts +6 -0
  249. package/src/backgrounds/nature-light.web.ts +7 -0
  250. package/src/backgrounds/nature-light.webp +0 -0
  251. package/src/backgrounds/preset-source.types.ts +5 -0
  252. package/src/backgrounds/presets.ts +36 -0
  253. package/src/backgrounds/simiancraft-dark.ts +6 -0
  254. package/src/backgrounds/simiancraft-dark.web.ts +7 -0
  255. package/src/backgrounds/simiancraft-dark.webp +0 -0
  256. package/src/backgrounds/simiancraft-light.ts +6 -0
  257. package/src/backgrounds/simiancraft-light.web.ts +7 -0
  258. package/src/backgrounds/simiancraft-light.webp +0 -0
  259. package/src/backgrounds/stylized-dark.ts +6 -0
  260. package/src/backgrounds/stylized-dark.web.ts +7 -0
  261. package/src/backgrounds/stylized-dark.webp +0 -0
  262. package/src/backgrounds/stylized-light.ts +6 -0
  263. package/src/backgrounds/stylized-light.web.ts +7 -0
  264. package/src/backgrounds/stylized-light.webp +0 -0
  265. package/src/index.ts +94 -29
  266. package/src/index.web.ts +69 -19
  267. package/src/livekit.ts +65 -0
  268. package/src/types.ts +23 -14
  269. package/src/web/blur-kernel.ts +44 -0
  270. package/src/web/effects/background-image.ts +121 -34
  271. package/src/web/effects/blur.ts +214 -40
  272. package/src/web/effects/transform.ts +69 -0
  273. package/src/web/insertable-streams.ts +33 -5
  274. package/src/web/segmenter.ts +34 -0
  275. package/src/web/shaders.generated.ts +56 -0
  276. package/src/web/shaders.ts +29 -106
  277. package/src/web/tuning.ts +19 -5
  278. package/android/src/main/assets/backgrounds/office-1.png +0 -0
  279. package/android/src/main/assets/backgrounds/office-2.png +0 -0
  280. package/android/src/main/java/com/simiancraft/kaleidoscope/effects/MirrorFactory.kt +0 -57
  281. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectFactory.kt +0 -14
  282. package/android/src/main/java/com/simiancraft/kaleidoscope/gpu/GpuEffectProcessor.kt +0 -198
  283. package/dist/backgrounds.d.ts +0 -3
  284. package/dist/backgrounds.d.ts.map +0 -1
  285. package/dist/backgrounds.js +0 -21
  286. package/dist/backgrounds.js.map +0 -1
  287. package/dist/web/effects/mirror.d.ts +0 -3
  288. package/dist/web/effects/mirror.d.ts.map +0 -1
  289. package/dist/web/effects/mirror.js +0 -31
  290. package/dist/web/effects/mirror.js.map +0 -1
  291. package/dist/web/effects/passthrough.d.ts +0 -3
  292. package/dist/web/effects/passthrough.d.ts.map +0 -1
  293. package/dist/web/effects/passthrough.js +0 -15
  294. package/dist/web/effects/passthrough.js.map +0 -1
  295. package/ios/KaleidoscopeModule/effects/MirrorProcessor.swift +0 -17
  296. package/plugin/build/withKaleidoscope.d.ts +0 -3
  297. package/plugin/build/withKaleidoscope.js +0 -14
  298. package/plugin/build/withKaleidoscope.js.map +0 -1
  299. package/src/backgrounds.ts +0 -23
  300. package/src/web/effects/mirror.ts +0 -37
  301. 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,31 +8,32 @@
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
 
17
- **Active development; not yet production-ready.** Published to npm as `0.1.0-alpha.x` for name reservation and integration testing. The npm presentation, marketing, and release-quality polish will come in a later pass; right now the README's job is to tell the truth about what works.
19
+ **Active development; not yet production-ready.** Published to npm at `1.0.0` (semantic-release cut the first tag at 1.0.0; the number reflects release automation, not a maturity claim). The npm presentation, marketing, and release-quality polish will come in a later pass; right now the README's job is to tell the truth about what works.
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 |
30
- | iOS (≥ 15) | | | | Coming soon; transpilation pipeline in place, host implementation pending |
31
- | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a typed error |
31
+ | Android (API 24+) | ✓ | ✓ | ✓ | OpenGL ES 3.0 + MediaPipe Selfie Segmentation (Tasks) |
32
+ | iOS (≥ 15) | | | | Metal + Vision (person segmentation), verified on device. Older A11 devices (iPhone X) run at a lower frame rate |
33
+ | Safari / Firefox | — | — | — | No Insertable Streams; `applyVideoEffects` throws a clear capability error |
32
34
 
33
35
  ### Coming soon
34
36
 
35
- - **iOS support**, via the canonical GLSL transpiled to Metal Shading Language (`glslangValidator` → `spirv-cross --msl`). The pipeline is in the repo at `scripts/transpile-shaders.ts` and the GLSL canonical source lives in `shaders/`. The Swift host code that loads the metallib and runs the Metal pipeline is the next chunk of work.
36
37
  - **Procedural backgrounds** (animated shaders behind the person, not just still images). Same composite path; the only new piece is each effect's background producer.
37
38
  - A careful pass over the npm presentation, install docs, and demo polish before any "we recommend you use this" framing.
38
39
 
@@ -54,6 +55,24 @@ bun add @livekit/react-native @livekit/react-native-webrtc react-native-webrtc-k
54
55
 
55
56
  Pick one fork. Installing both upstream `react-native-webrtc` and `@livekit/react-native-webrtc` in the same app will cause native class collisions; that's the consumer's problem to resolve.
56
57
 
58
+ **Native wiring.** `@livekit/react-native` hands you a `LocalVideoTrack`; apply effects to its underlying `MediaStreamTrack`:
59
+
60
+ ```ts
61
+ import { applyVideoEffects } from 'react-native-webrtc-kaleidoscope';
62
+
63
+ applyVideoEffects(localCameraTrack.mediaStreamTrack, ['blur']);
64
+ ```
65
+
66
+ **Web wiring.** On web, LiveKit owns the `RTCRtpSender`, so you cannot swap the track yourself; go through LiveKit's processor API instead. The opt-in `/livekit` subpath ships a ready-made processor (it needs `livekit-client`, which a LiveKit app already has):
67
+
68
+ ```ts
69
+ import { KaleidoscopeProcessor } from 'react-native-webrtc-kaleidoscope/livekit';
70
+
71
+ await localVideoTrack.setProcessor(new KaleidoscopeProcessor(['blur']), true);
72
+ ```
73
+
74
+ The second argument shows the processed stream in your local preview. The processor tears down its Insertable-Streams pipeline on camera flip (`restart`) and unpublish (`destroy`), so repeated flips do not leak generators.
75
+
57
76
  ## Configure
58
77
 
59
78
  Add the config plugin to `app.config.ts`:
@@ -84,31 +103,60 @@ import {
84
103
  setMaskHardness,
85
104
  setMaskThreshold,
86
105
  } from 'react-native-webrtc-kaleidoscope';
106
+ import { darkOffice } from 'react-native-webrtc-kaleidoscope/backgrounds/dark-office';
87
107
 
88
108
  const stream = await mediaDevices.getUserMedia({ video: true });
89
109
  const [track] = stream.getVideoTracks();
90
110
 
91
- applyVideoEffects(track, ['mirror']);
111
+ applyVideoEffects(track, ['flip-x']); // also flip-y, rotate-cw, rotate-ccw
92
112
  applyVideoEffects(track, ['blur']);
93
- applyVideoEffects(track, [{ name: 'background-image', source: 'office-1' }]);
113
+ applyVideoEffects(track, [{ name: 'background-image', source: darkOffice }]);
94
114
  applyVideoEffects(track, []); // clear all effects
95
115
 
96
116
  // Runtime tuning (effects pick up the new values on the next frame):
97
- setBlurSigma(25); // Gaussian σ; clamped to [0.5, 64], default 8.
98
- setMaskHardness(0.2); // smoothstep transition width; clamped to [0, 1]. 0 = soft halo, 1 = near-step. Default 0.5.
99
- 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.
100
120
  ```
101
121
 
102
122
  Effects chain in array order.
103
123
 
104
- **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:
105
125
 
106
126
  | Platform | Blur sigma | Mask hardness | Mask threshold |
107
127
  |---|---|---|---|
108
128
  | Web (MediaPipe) | 25 | 0.2 | 0.85 |
109
- | Android (MLKit) | 30 | 0.2 | 0.6 |
129
+ | Android (MediaPipe) | 30 | 0.2 | 0.6 |
130
+
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.
150
+
151
+ ## Web and native differences
152
+
153
+ The API surface is the same across platforms, but the runtimes differ in ways worth knowing before you wire effects in:
110
154
 
111
- 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.
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.
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.
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).
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.
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.
112
160
 
113
161
  ## What this isn't
114
162
 
@@ -123,8 +171,8 @@ The codebase lives across four surfaces:
123
171
 
124
172
  - `src/` — JS facade and shared types. `applyVideoEffects(track, effects)` plus runtime tuning setters.
125
173
  - `src/web/` — WebGL2 pipeline. MediaPipe segmentation + GLSL composite. One shader file per stage in `src/web/shaders.ts`.
126
- - `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.
127
- - `ios/` — Scaffold only; the canonical GLSL in `shaders/` will transpile to Metal Shading Language for the iOS path.
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.
128
176
 
129
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.
130
178
 
@@ -4,7 +4,9 @@ apply plugin: 'com.android.library'
4
4
  apply plugin: 'kotlin-android'
5
5
 
6
6
  group = 'com.simiancraft.kaleidoscope'
7
- version = '0.1.0-alpha.1'
7
+ // Single source of truth for the version is package.json (the iOS podspec reads
8
+ // it the same way), so the three platforms cannot drift apart.
9
+ version = new groovy.json.JsonSlurper().parseText(file("../package.json").text).version
8
10
 
9
11
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
12
  if (expoModulesCorePlugin.exists()) {
@@ -53,10 +55,9 @@ dependencies {
53
55
  }
54
56
  compileOnly project(rnWebrtcProject)
55
57
 
56
- // Person/background mask for the blur effect. The Google artifact ID is
57
- // `segmentation-selfie`, not `selfie-segmentation` (the plan and earlier
58
- // notes had it inverted; that is why the pin would not resolve).
59
- 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'
60
61
  }
61
62
 
62
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
- // Architecture-proof hook from PLAN.md Commit 3. Removed in the cleanup
36
- // 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