@shopify/react-native-skia 2.0.7 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/android/CMakeLists.txt +20 -1
  2. package/cpp/api/JsiSkApi.h +3 -0
  3. package/cpp/api/JsiSkImageFilterFactory.h +478 -39
  4. package/cpp/api/JsiSkSkottie.h +590 -0
  5. package/cpp/api/JsiSkottieFactory.h +65 -0
  6. package/cpp/api/recorder/Command.h +1 -0
  7. package/cpp/api/recorder/Convertor.h +16 -0
  8. package/cpp/api/recorder/Drawings.h +23 -0
  9. package/cpp/api/recorder/JsiRecorder.h +6 -0
  10. package/cpp/api/recorder/RNRecorder.h +9 -0
  11. package/cpp/api/third_party/SkottieUtils.cpp +333 -0
  12. package/cpp/api/third_party/SkottieUtils.h +172 -0
  13. package/cpp/skia/modules/jsonreader/SkJSONReader.cpp +980 -0
  14. package/cpp/skia/modules/jsonreader/SkJSONReader.h +389 -0
  15. package/cpp/skia/modules/skottie/include/ExternalLayer.h +56 -0
  16. package/cpp/skia/modules/skottie/include/Skottie.h +313 -0
  17. package/cpp/skia/modules/skottie/include/SkottieProperty.h +190 -0
  18. package/cpp/skia/modules/skottie/include/SlotManager.h +113 -0
  19. package/cpp/skia/modules/skottie/include/TextShaper.h +200 -0
  20. package/cpp/skia/modules/skottie/src/SkottieValue.h +56 -0
  21. package/cpp/skia/modules/skottie/src/animator/Animator.h +89 -0
  22. package/cpp/skia/modules/skottie/src/text/Font.h +82 -0
  23. package/cpp/skia/modules/skottie/src/text/TextAdapter.h +155 -0
  24. package/cpp/skia/modules/skottie/src/text/TextAnimator.h +121 -0
  25. package/cpp/skia/modules/skottie/src/text/TextValue.h +28 -0
  26. package/cpp/skia/modules/sksg/include/SkSGClipEffect.h +61 -0
  27. package/cpp/skia/modules/sksg/include/SkSGColorFilter.h +135 -0
  28. package/cpp/skia/modules/sksg/include/SkSGDraw.h +57 -0
  29. package/cpp/skia/modules/sksg/include/SkSGEffectNode.h +50 -0
  30. package/cpp/skia/modules/sksg/include/SkSGGeometryEffect.h +181 -0
  31. package/cpp/skia/modules/sksg/include/SkSGGeometryNode.h +54 -0
  32. package/cpp/skia/modules/sksg/include/SkSGGradient.h +108 -0
  33. package/cpp/skia/modules/sksg/include/SkSGGroup.h +65 -0
  34. package/cpp/skia/modules/sksg/include/SkSGImage.h +59 -0
  35. package/cpp/skia/modules/sksg/include/SkSGInvalidationController.h +46 -0
  36. package/cpp/skia/modules/sksg/include/SkSGMaskEffect.h +65 -0
  37. package/cpp/skia/modules/sksg/include/SkSGMerge.h +74 -0
  38. package/cpp/skia/modules/sksg/include/SkSGNode.h +128 -0
  39. package/cpp/skia/modules/sksg/include/SkSGOpacityEffect.h +54 -0
  40. package/cpp/skia/modules/sksg/include/SkSGPaint.h +112 -0
  41. package/cpp/skia/modules/sksg/include/SkSGPath.h +68 -0
  42. package/cpp/skia/modules/sksg/include/SkSGPlane.h +47 -0
  43. package/cpp/skia/modules/sksg/include/SkSGRect.h +122 -0
  44. package/cpp/skia/modules/sksg/include/SkSGRenderEffect.h +283 -0
  45. package/cpp/skia/modules/sksg/include/SkSGRenderNode.h +157 -0
  46. package/cpp/skia/modules/sksg/include/SkSGScene.h +47 -0
  47. package/cpp/skia/modules/sksg/include/SkSGText.h +82 -0
  48. package/cpp/skia/modules/sksg/include/SkSGTransform.h +127 -0
  49. package/cpp/skia/src/base/SkArenaAlloc.h +371 -0
  50. package/lib/commonjs/dom/nodes/datatypes/Gradient.d.ts +15 -15
  51. package/lib/commonjs/dom/types/Drawings.d.ts +5 -1
  52. package/lib/commonjs/dom/types/Drawings.js.map +1 -1
  53. package/lib/commonjs/dom/types/NodeType.d.ts +2 -1
  54. package/lib/commonjs/dom/types/NodeType.js +2 -0
  55. package/lib/commonjs/dom/types/NodeType.js.map +1 -1
  56. package/lib/commonjs/renderer/__tests__/e2e/AdvancedImageFilters.spec.d.ts +1 -0
  57. package/lib/commonjs/renderer/__tests__/e2e/Camera.spec.d.ts +21 -0
  58. package/lib/commonjs/renderer/__tests__/e2e/LightingImageFilters.spec.d.ts +1 -0
  59. package/lib/commonjs/renderer/__tests__/e2e/Skottie.spec.d.ts +1 -0
  60. package/lib/commonjs/renderer/__tests__/setup.d.ts +5 -0
  61. package/lib/commonjs/renderer/components/Skottie.d.ts +4 -0
  62. package/lib/commonjs/renderer/components/Skottie.js +13 -0
  63. package/lib/commonjs/renderer/components/Skottie.js.map +1 -0
  64. package/lib/commonjs/renderer/components/index.d.ts +1 -0
  65. package/lib/commonjs/renderer/components/index.js +11 -0
  66. package/lib/commonjs/renderer/components/index.js.map +1 -1
  67. package/lib/commonjs/skia/types/ImageFilter/ImageFilterFactory.d.ts +252 -15
  68. package/lib/commonjs/skia/types/ImageFilter/ImageFilterFactory.js.map +1 -1
  69. package/lib/commonjs/skia/types/Matrix4.d.ts +11 -2
  70. package/lib/commonjs/skia/types/Matrix4.js +42 -1
  71. package/lib/commonjs/skia/types/Matrix4.js.map +1 -1
  72. package/lib/commonjs/skia/types/Recorder.d.ts +2 -1
  73. package/lib/commonjs/skia/types/Recorder.js.map +1 -1
  74. package/lib/commonjs/skia/types/Skia.d.ts +2 -0
  75. package/lib/commonjs/skia/types/Skia.js.map +1 -1
  76. package/lib/commonjs/skia/types/Skottie.d.ts +223 -0
  77. package/lib/commonjs/skia/types/Skottie.js +73 -0
  78. package/lib/commonjs/skia/types/Skottie.js.map +1 -0
  79. package/lib/commonjs/skia/types/index.d.ts +1 -0
  80. package/lib/commonjs/skia/types/index.js +11 -0
  81. package/lib/commonjs/skia/types/index.js.map +1 -1
  82. package/lib/commonjs/skia/web/JsiSkImageFilterFactory.d.ts +29 -12
  83. package/lib/commonjs/skia/web/JsiSkImageFilterFactory.js +88 -19
  84. package/lib/commonjs/skia/web/JsiSkImageFilterFactory.js.map +1 -1
  85. package/lib/commonjs/skia/web/JsiSkia.js +2 -0
  86. package/lib/commonjs/skia/web/JsiSkia.js.map +1 -1
  87. package/lib/commonjs/skia/web/JsiSkottieAnimation.d.ts +59 -0
  88. package/lib/commonjs/skia/web/JsiSkottieAnimation.js +243 -0
  89. package/lib/commonjs/skia/web/JsiSkottieAnimation.js.map +1 -0
  90. package/lib/commonjs/skia/web/JsiSkottieFactory.d.ts +9 -0
  91. package/lib/commonjs/skia/web/JsiSkottieFactory.js +26 -0
  92. package/lib/commonjs/skia/web/JsiSkottieFactory.js.map +1 -0
  93. package/lib/commonjs/sksg/Elements.d.ts +2 -1
  94. package/lib/commonjs/sksg/Elements.js.map +1 -1
  95. package/lib/commonjs/sksg/Recorder/Core.d.ts +4 -2
  96. package/lib/commonjs/sksg/Recorder/Core.js +1 -0
  97. package/lib/commonjs/sksg/Recorder/Core.js.map +1 -1
  98. package/lib/commonjs/sksg/Recorder/Player.js +2 -0
  99. package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
  100. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +2 -1
  101. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +4 -0
  102. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  103. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +2 -1
  104. package/lib/commonjs/sksg/Recorder/Recorder.js +6 -0
  105. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  106. package/lib/commonjs/sksg/Recorder/Visitor.js +3 -0
  107. package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
  108. package/lib/commonjs/sksg/Recorder/commands/Drawing.d.ts +2 -2
  109. package/lib/commonjs/sksg/Recorder/commands/Drawing.js +11 -4
  110. package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
  111. package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js +1 -1
  112. package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js.map +1 -1
  113. package/lib/module/dom/nodes/datatypes/Gradient.d.ts +15 -15
  114. package/lib/module/dom/types/Drawings.d.ts +5 -1
  115. package/lib/module/dom/types/Drawings.js.map +1 -1
  116. package/lib/module/dom/types/NodeType.d.ts +2 -1
  117. package/lib/module/dom/types/NodeType.js +2 -0
  118. package/lib/module/dom/types/NodeType.js.map +1 -1
  119. package/lib/module/renderer/__tests__/e2e/AdvancedImageFilters.spec.d.ts +1 -0
  120. package/lib/module/renderer/__tests__/e2e/Camera.spec.d.ts +21 -0
  121. package/lib/module/renderer/__tests__/e2e/LightingImageFilters.spec.d.ts +1 -0
  122. package/lib/module/renderer/__tests__/e2e/Skottie.spec.d.ts +1 -0
  123. package/lib/module/renderer/__tests__/setup.d.ts +5 -0
  124. package/lib/module/renderer/components/Skottie.d.ts +4 -0
  125. package/lib/module/renderer/components/Skottie.js +5 -0
  126. package/lib/module/renderer/components/Skottie.js.map +1 -0
  127. package/lib/module/renderer/components/index.d.ts +1 -0
  128. package/lib/module/renderer/components/index.js +1 -0
  129. package/lib/module/renderer/components/index.js.map +1 -1
  130. package/lib/module/skia/types/ImageFilter/ImageFilterFactory.d.ts +252 -15
  131. package/lib/module/skia/types/ImageFilter/ImageFilterFactory.js.map +1 -1
  132. package/lib/module/skia/types/Matrix4.d.ts +11 -2
  133. package/lib/module/skia/types/Matrix4.js +40 -0
  134. package/lib/module/skia/types/Matrix4.js.map +1 -1
  135. package/lib/module/skia/types/Recorder.d.ts +2 -1
  136. package/lib/module/skia/types/Recorder.js.map +1 -1
  137. package/lib/module/skia/types/Skia.d.ts +2 -0
  138. package/lib/module/skia/types/Skia.js.map +1 -1
  139. package/lib/module/skia/types/Skottie.d.ts +223 -0
  140. package/lib/module/skia/types/Skottie.js +74 -0
  141. package/lib/module/skia/types/Skottie.js.map +1 -0
  142. package/lib/module/skia/types/index.d.ts +1 -0
  143. package/lib/module/skia/types/index.js +1 -0
  144. package/lib/module/skia/types/index.js.map +1 -1
  145. package/lib/module/skia/web/JsiSkImageFilterFactory.d.ts +29 -12
  146. package/lib/module/skia/web/JsiSkImageFilterFactory.js +88 -19
  147. package/lib/module/skia/web/JsiSkImageFilterFactory.js.map +1 -1
  148. package/lib/module/skia/web/JsiSkia.js +2 -0
  149. package/lib/module/skia/web/JsiSkia.js.map +1 -1
  150. package/lib/module/skia/web/JsiSkottieAnimation.d.ts +59 -0
  151. package/lib/module/skia/web/JsiSkottieAnimation.js +236 -0
  152. package/lib/module/skia/web/JsiSkottieAnimation.js.map +1 -0
  153. package/lib/module/skia/web/JsiSkottieFactory.d.ts +9 -0
  154. package/lib/module/skia/web/JsiSkottieFactory.js +19 -0
  155. package/lib/module/skia/web/JsiSkottieFactory.js.map +1 -0
  156. package/lib/module/sksg/Elements.d.ts +2 -1
  157. package/lib/module/sksg/Elements.js.map +1 -1
  158. package/lib/module/sksg/Recorder/Core.d.ts +4 -2
  159. package/lib/module/sksg/Recorder/Core.js +1 -0
  160. package/lib/module/sksg/Recorder/Core.js.map +1 -1
  161. package/lib/module/sksg/Recorder/Player.js +3 -1
  162. package/lib/module/sksg/Recorder/Player.js.map +1 -1
  163. package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +2 -1
  164. package/lib/module/sksg/Recorder/ReanimatedRecorder.js +4 -0
  165. package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  166. package/lib/module/sksg/Recorder/Recorder.d.ts +2 -1
  167. package/lib/module/sksg/Recorder/Recorder.js +6 -0
  168. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  169. package/lib/module/sksg/Recorder/Visitor.js +3 -0
  170. package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
  171. package/lib/module/sksg/Recorder/commands/Drawing.d.ts +2 -2
  172. package/lib/module/sksg/Recorder/commands/Drawing.js +9 -2
  173. package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
  174. package/lib/module/sksg/Recorder/commands/ImageFilters.js +1 -1
  175. package/lib/module/sksg/Recorder/commands/ImageFilters.js.map +1 -1
  176. package/lib/typescript/lib/commonjs/renderer/components/Skottie.d.ts +2 -0
  177. package/lib/typescript/lib/commonjs/skia/types/Matrix4.d.ts +1 -0
  178. package/lib/typescript/lib/commonjs/skia/types/Skottie.d.ts +6 -0
  179. package/lib/typescript/lib/commonjs/skia/web/JsiSkImageFilterFactory.d.ts +22 -5
  180. package/lib/typescript/lib/commonjs/skia/web/JsiSkia.d.ts +2 -0
  181. package/lib/typescript/lib/commonjs/skia/web/JsiSkottieAnimation.d.ts +48 -0
  182. package/lib/typescript/lib/commonjs/skia/web/JsiSkottieFactory.d.ts +6 -0
  183. package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -0
  184. package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -0
  185. package/lib/typescript/lib/commonjs/sksg/Recorder/commands/Drawing.d.ts +1 -1
  186. package/lib/typescript/lib/module/mock/index.d.ts +7 -0
  187. package/lib/typescript/lib/module/renderer/components/Skottie.d.ts +2 -0
  188. package/lib/typescript/lib/module/renderer/components/index.d.ts +1 -0
  189. package/lib/typescript/lib/module/skia/Skia.web.d.ts +1 -0
  190. package/lib/typescript/lib/module/skia/types/Matrix4.d.ts +1 -0
  191. package/lib/typescript/lib/module/skia/types/Skottie.d.ts +5 -0
  192. package/lib/typescript/lib/module/skia/types/index.d.ts +1 -0
  193. package/lib/typescript/lib/module/skia/web/JsiSkImageFilterFactory.d.ts +22 -5
  194. package/lib/typescript/lib/module/skia/web/JsiSkia.d.ts +2 -0
  195. package/lib/typescript/lib/module/skia/web/JsiSkottieAnimation.d.ts +47 -0
  196. package/lib/typescript/lib/module/skia/web/JsiSkottieFactory.d.ts +5 -0
  197. package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -0
  198. package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -0
  199. package/lib/typescript/lib/module/sksg/Recorder/commands/Drawing.d.ts +1 -1
  200. package/lib/typescript/src/dom/nodes/datatypes/Gradient.d.ts +15 -15
  201. package/lib/typescript/src/dom/types/Drawings.d.ts +5 -1
  202. package/lib/typescript/src/dom/types/NodeType.d.ts +2 -1
  203. package/lib/typescript/src/renderer/__tests__/e2e/AdvancedImageFilters.spec.d.ts +1 -0
  204. package/lib/typescript/src/renderer/__tests__/e2e/Camera.spec.d.ts +21 -0
  205. package/lib/typescript/src/renderer/__tests__/e2e/LightingImageFilters.spec.d.ts +1 -0
  206. package/lib/typescript/src/renderer/__tests__/e2e/Skottie.spec.d.ts +1 -0
  207. package/lib/typescript/src/renderer/__tests__/setup.d.ts +5 -0
  208. package/lib/typescript/src/renderer/components/Skottie.d.ts +4 -0
  209. package/lib/typescript/src/renderer/components/index.d.ts +1 -0
  210. package/lib/typescript/src/skia/types/ImageFilter/ImageFilterFactory.d.ts +252 -15
  211. package/lib/typescript/src/skia/types/Matrix4.d.ts +11 -2
  212. package/lib/typescript/src/skia/types/Recorder.d.ts +2 -1
  213. package/lib/typescript/src/skia/types/Skia.d.ts +2 -0
  214. package/lib/typescript/src/skia/types/Skottie.d.ts +223 -0
  215. package/lib/typescript/src/skia/types/index.d.ts +1 -0
  216. package/lib/typescript/src/skia/web/JsiSkImageFilterFactory.d.ts +29 -12
  217. package/lib/typescript/src/skia/web/JsiSkottieAnimation.d.ts +59 -0
  218. package/lib/typescript/src/skia/web/JsiSkottieFactory.d.ts +9 -0
  219. package/lib/typescript/src/sksg/Elements.d.ts +2 -1
  220. package/lib/typescript/src/sksg/Recorder/Core.d.ts +4 -2
  221. package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +2 -1
  222. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +2 -1
  223. package/lib/typescript/src/sksg/Recorder/commands/Drawing.d.ts +2 -2
  224. package/libs/android/arm64-v8a/libjsonreader.a +0 -0
  225. package/libs/android/armeabi-v7a/libjsonreader.a +0 -0
  226. package/libs/android/x86/libjsonreader.a +0 -0
  227. package/libs/android/x86_64/libjsonreader.a +0 -0
  228. package/libs/apple/libpathops.xcframework/Info.plist +8 -8
  229. package/libs/apple/libskia.xcframework/Info.plist +14 -14
  230. package/libs/apple/libskottie.xcframework/Info.plist +14 -14
  231. package/libs/apple/libskparagraph.xcframework/Info.plist +16 -16
  232. package/libs/apple/libsksg.xcframework/Info.plist +5 -5
  233. package/libs/apple/libskshaper.xcframework/Info.plist +14 -14
  234. package/libs/apple/libskunicode_libgrapheme.xcframework/Info.plist +14 -14
  235. package/libs/apple/libsvg.xcframework/Info.plist +14 -14
  236. package/package.json +1 -1
  237. package/react-native-skia.podspec +4 -2
  238. package/src/__tests__/snapshots/matrix4/camera-corner.png +0 -0
  239. package/src/__tests__/snapshots/matrix4/camera-offset.png +0 -0
  240. package/src/__tests__/snapshots/matrix4/camera-top-left-center.png +0 -0
  241. package/src/__tests__/snapshots/matrix4/camera-zoom-out.png +0 -0
  242. package/src/__tests__/snapshots/matrix4/full-rect.png +0 -0
  243. package/src/__tests__/snapshots/matrix4/rect.png +0 -0
  244. package/src/__tests__/snapshots/matrix4/scaled-rect.png +0 -0
  245. package/src/__tests__/snapshots/matrix4/test-perspective.png +0 -0
  246. package/src/__tests__/snapshots/matrix4/test-perspective2.png +0 -0
  247. package/src/dom/types/Drawings.ts +6 -0
  248. package/src/dom/types/NodeType.ts +2 -0
  249. package/src/renderer/__tests__/e2e/AdvancedImageFilters.spec.tsx +492 -0
  250. package/src/renderer/__tests__/e2e/Camera.spec.tsx +475 -0
  251. package/src/renderer/__tests__/e2e/LightingImageFilters.spec.tsx +1478 -0
  252. package/src/renderer/__tests__/e2e/Skottie.spec.tsx +440 -0
  253. package/src/renderer/__tests__/e2e/setup/skottie/basic_slots.json +1118 -0
  254. package/src/renderer/__tests__/e2e/setup/skottie/color-props.json +1 -0
  255. package/src/renderer/__tests__/e2e/setup/skottie/confetti.json +5899 -0
  256. package/src/renderer/__tests__/e2e/setup/skottie/drinks.json +43857 -0
  257. package/src/renderer/__tests__/e2e/setup/skottie/fingerprint.json +1 -0
  258. package/src/renderer/__tests__/e2e/setup/skottie/lego_loader.json +29540 -0
  259. package/src/renderer/__tests__/e2e/setup/skottie/new-drop.json +1 -0
  260. package/src/renderer/__tests__/e2e/setup/skottie/onboarding.json +1 -0
  261. package/src/renderer/__tests__/e2e/setup/skottie/text-layer.json +1 -0
  262. package/src/renderer/__tests__/setup.tsx +23 -0
  263. package/src/renderer/components/Skottie.tsx +8 -0
  264. package/src/renderer/components/index.ts +1 -0
  265. package/src/skia/__tests__/assets/Avenir-Heavy.ttf +0 -0
  266. package/src/skia/types/ImageFilter/ImageFilterFactory.ts +391 -21
  267. package/src/skia/types/Matrix4.ts +108 -2
  268. package/src/skia/types/Recorder.ts +2 -0
  269. package/src/skia/types/Skia.ts +2 -0
  270. package/src/skia/types/Skottie.ts +266 -0
  271. package/src/skia/types/index.ts +1 -0
  272. package/src/skia/web/JsiSkImageFilterFactory.ts +266 -31
  273. package/src/skia/web/JsiSkia.ts +2 -0
  274. package/src/skia/web/JsiSkottieAnimation.ts +259 -0
  275. package/src/skia/web/JsiSkottieFactory.ts +25 -0
  276. package/src/sksg/Elements.tsx +2 -0
  277. package/src/sksg/Recorder/Core.ts +3 -0
  278. package/src/sksg/Recorder/Player.ts +3 -0
  279. package/src/sksg/Recorder/ReanimatedRecorder.ts +6 -0
  280. package/src/sksg/Recorder/Recorder.ts +5 -0
  281. package/src/sksg/Recorder/Visitor.ts +3 -0
  282. package/src/sksg/Recorder/commands/Drawing.ts +7 -3
  283. package/src/sksg/Recorder/commands/ImageFilters.ts +1 -1
@@ -0,0 +1,1478 @@
1
+ import { importSkia, surface, images } from "../setup";
2
+ import type { SkColor } from "../../../skia/types";
3
+ import { BlendMode, ClipOp, PaintStyle, TileMode } from "../../../skia/types";
4
+ import { checkImage, docPath, itRunsE2eOnly } from "../../../__tests__/setup";
5
+
6
+ const checkResult = (base64: string, path: string) => {
7
+ const { Skia } = importSkia();
8
+ const rData = Skia.Data.fromBase64(base64);
9
+ const image = Skia.Image.MakeImageFromEncoded(rData)!;
10
+ expect(rData).toBeDefined();
11
+ checkImage(image, docPath(path));
12
+ };
13
+
14
+ describe("Lighting Image Filters", () => {
15
+ itRunsE2eOnly("DistantLitDiffuse - Dramatic Relief", async () => {
16
+ const { skiaLogoPng } = images;
17
+ const base64 = await surface.eval(
18
+ (Skia, ctx) => {
19
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
20
+ const canvas = sur.getCanvas();
21
+
22
+ // Create a dark background to enhance contrast
23
+ const bgPaint = Skia.Paint();
24
+ bgPaint.setColor(Skia.Color("rgb(20, 20, 30)"));
25
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
26
+
27
+ // Create high-contrast emboss effect
28
+ const paint = Skia.Paint();
29
+
30
+ // Pre-process the image with a threshold filter to make shapes more defined
31
+ // This will help the lighting effect appear more dramatic
32
+ const inputFilter = Skia.ImageFilter.MakeColorFilter(
33
+ Skia.ColorFilter.MakeMatrix([
34
+ 1.5,
35
+ 0,
36
+ 0,
37
+ 0,
38
+ -20, // Increased contrast for red
39
+ 0,
40
+ 1.5,
41
+ 0,
42
+ 0,
43
+ -20, // Increased contrast for green
44
+ 0,
45
+ 0,
46
+ 1.5,
47
+ 0,
48
+ -20, // Increased contrast for blue
49
+ 0,
50
+ 0,
51
+ 0,
52
+ 1.2,
53
+ 0, // Slightly boosted alpha
54
+ ]),
55
+ null
56
+ );
57
+
58
+ // Direction vector for dramatic side lighting
59
+ const direction = { x: -3, y: -0.5, z: 0.5 };
60
+
61
+ // Light color (stark white for contrast)
62
+ const lightColor = Skia.Color("rgb(255, 255, 255)");
63
+
64
+ // Higher surface scale for more dramatic relief
65
+ const surfaceScale = 4.0;
66
+ const kd = 2.5; // Stronger diffuse coefficient
67
+
68
+ // Create the distant light diffuse filter
69
+ const distantLitFilter = Skia.ImageFilter.MakeDistantLitDiffuse(
70
+ direction,
71
+ lightColor,
72
+ surfaceScale,
73
+ kd,
74
+ inputFilter, // Use high-contrast image as input
75
+ null // No crop rect
76
+ );
77
+
78
+ // Set the filter to our paint
79
+ paint.setImageFilter(distantLitFilter);
80
+
81
+ // Add a subtle color tint
82
+ paint.setColorFilter(
83
+ Skia.ColorFilter.MakeMatrix([
84
+ 1.2,
85
+ 0,
86
+ 0,
87
+ 0,
88
+ 10, // Boosted red
89
+ 0,
90
+ 1.0,
91
+ 0,
92
+ 0,
93
+ 0, // Normal green
94
+ 0,
95
+ 0,
96
+ 0.8,
97
+ 0,
98
+ 0, // Reduced blue for warmer tone
99
+ 0,
100
+ 0,
101
+ 0,
102
+ 1,
103
+ 0, // Alpha unchanged
104
+ ])
105
+ );
106
+
107
+ // Draw the image with the dramatic lighting
108
+ const padding = 30;
109
+ canvas.drawImageRect(
110
+ ctx.skiaLogoPng,
111
+ Skia.XYWHRect(
112
+ 0,
113
+ 0,
114
+ ctx.skiaLogoPng.width(),
115
+ ctx.skiaLogoPng.height()
116
+ ),
117
+ Skia.XYWHRect(padding, padding, 768 - padding * 2, 768 - padding * 2),
118
+ paint
119
+ );
120
+
121
+ // Add a subtle vignette effect to enhance depth
122
+ const vignetteRect = Skia.XYWHRect(-100, -100, 968, 968);
123
+ const vignettePaint = Skia.Paint();
124
+ const vignetteShader = Skia.Shader.MakeRadialGradient(
125
+ { x: 384, y: 384 },
126
+ 500,
127
+ [Skia.Color("rgba(0,0,0,0)"), Skia.Color("rgba(0,0,0,0.7)")],
128
+ [0.5, 1.0],
129
+ ctx.TileMode.Clamp
130
+ );
131
+ vignettePaint.setShader(vignetteShader);
132
+ canvas.drawRect(vignetteRect, vignettePaint);
133
+
134
+ sur.flush();
135
+ return sur.makeImageSnapshot().encodeToBase64();
136
+ },
137
+ { skiaLogoPng, TileMode }
138
+ );
139
+ checkResult(base64, "lighting-image-filters/distant-lit-diffuse.png");
140
+ });
141
+
142
+ itRunsE2eOnly("PointLitDiffuse - Glowing Core", async () => {
143
+ const { skiaLogoPng } = images;
144
+ const base64 = await surface.eval(
145
+ (Skia, ctx) => {
146
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
147
+ const canvas = sur.getCanvas();
148
+
149
+ // Create a dark background (not completely black) for better visibility
150
+ const bgPaint = Skia.Paint();
151
+ bgPaint.setColor(Skia.Color("rgb(10, 10, 15)"));
152
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
153
+
154
+ // Create a glowing center effect
155
+ const paint = Skia.Paint();
156
+
157
+ // Position light inside the image for a glowing core effect
158
+ // Moved light closer to surface (z is now positive)
159
+ const location = { x: 384, y: 384, z: 200 };
160
+
161
+ // Light color (intense orange-yellow for a fiery glow)
162
+ const lightColor = Skia.Color("rgb(255, 200, 50)");
163
+
164
+ // Parameters for the filter
165
+ const surfaceScale = 2.0; // Height effect
166
+ const kd = 1.5; // Diffuse reflection strength
167
+
168
+ // Create the point light diffuse filter
169
+ const pointLitFilter = Skia.ImageFilter.MakePointLitDiffuse(
170
+ location,
171
+ lightColor,
172
+ surfaceScale,
173
+ kd,
174
+ null, // Use source bitmap as input
175
+ null // No crop rect
176
+ );
177
+
178
+ // Set the filter to our paint
179
+ paint.setImageFilter(pointLitFilter);
180
+
181
+ // Add a color boost to make the effect more visible
182
+ paint.setColorFilter(
183
+ Skia.ColorFilter.MakeMatrix([
184
+ 1.2,
185
+ 0,
186
+ 0,
187
+ 0,
188
+ 20, // Boosted red
189
+ 0,
190
+ 1.1,
191
+ 0,
192
+ 0,
193
+ 10, // Boosted green
194
+ 0,
195
+ 0,
196
+ 1.0,
197
+ 0,
198
+ 0, // Blue unchanged
199
+ 0,
200
+ 0,
201
+ 0,
202
+ 1,
203
+ 0, // Alpha unchanged
204
+ ])
205
+ );
206
+
207
+ // Draw the image with the glowing core effect
208
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, paint);
209
+
210
+ // Add a second layer of lighting for more intensity
211
+ const accentPaint = Skia.Paint();
212
+ const accentFilter = Skia.ImageFilter.MakePointLitDiffuse(
213
+ { x: 384, y: 384, z: 50 }, // Closer light source
214
+ Skia.Color("rgb(255, 255, 200)"), // Brighter light
215
+ surfaceScale * 0.5,
216
+ kd * 0.8,
217
+ null,
218
+ null
219
+ );
220
+
221
+ accentPaint.setImageFilter(accentFilter);
222
+ accentPaint.setBlendMode(ctx.BlendMode.Plus); // Additive lighting
223
+
224
+ // Draw second layer
225
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, accentPaint);
226
+
227
+ sur.flush();
228
+ return sur.makeImageSnapshot().encodeToBase64();
229
+ },
230
+ { skiaLogoPng, BlendMode }
231
+ );
232
+ checkResult(
233
+ base64,
234
+ `lighting-image-filters/point-lit-diffuse-${surface.OS}.png`
235
+ );
236
+ });
237
+
238
+ itRunsE2eOnly("SpotLitDiffuse - Theatrical Spotlight", async () => {
239
+ const { skiaLogoPng } = images;
240
+ const base64 = await surface.eval(
241
+ (Skia, ctx) => {
242
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
243
+ const canvas = sur.getCanvas();
244
+
245
+ // Fill background with deep black for theatrical effect
246
+ const bgPaint = Skia.Paint();
247
+ bgPaint.setColor(Skia.Color("rgb(0, 0, 0)"));
248
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
249
+
250
+ // Add a very subtle gradient stage background
251
+ const stagePaint = Skia.Paint();
252
+ const stageShader = Skia.Shader.MakeLinearGradient(
253
+ { x: 0, y: 600 },
254
+ { x: 0, y: 768 },
255
+ [
256
+ Skia.Color("rgba(20, 10, 30, 0)"),
257
+ Skia.Color("rgba(40, 20, 60, 0.6)"),
258
+ ],
259
+ null,
260
+ ctx.TileMode.Clamp
261
+ );
262
+ stagePaint.setShader(stageShader);
263
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), stagePaint);
264
+
265
+ // Create multiple spotlight effects for dramatic theatre lighting
266
+ const createSpotlight = (
267
+ location: { x: number; y: number; z: number },
268
+ target: { x: number; y: number; z: number },
269
+ color: SkColor,
270
+ intensity: number,
271
+ angle: number
272
+ ) => {
273
+ const paint = Skia.Paint();
274
+
275
+ // Spotlight parameters
276
+ const falloffExponent = 2.5; // Sharp falloff
277
+ const cutoffAngle = angle; // Narrow spotlight cone
278
+
279
+ // Light color
280
+ const lightColor = color;
281
+
282
+ // Surface parameters
283
+ const surfaceScale = 2.0;
284
+ const kd = intensity; // Strong diffuse coefficient for dramatic effect
285
+
286
+ // Create the spotlight diffuse filter
287
+ const spotLitFilter = Skia.ImageFilter.MakeSpotLitDiffuse(
288
+ location,
289
+ target,
290
+ falloffExponent,
291
+ cutoffAngle,
292
+ lightColor,
293
+ surfaceScale,
294
+ kd,
295
+ null, // Use source bitmap as input
296
+ null // No crop rect
297
+ );
298
+
299
+ // Set the filter and blend mode
300
+ paint.setImageFilter(spotLitFilter);
301
+ paint.setBlendMode(ctx.BlendMode.Plus); // Additive lighting
302
+
303
+ // Draw the image with spotlight effect
304
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, paint);
305
+ };
306
+
307
+ // Main spotlight from top-right
308
+ createSpotlight(
309
+ { x: 600, y: 100, z: 400 },
310
+ { x: 384, y: 384, z: 0 },
311
+ Skia.Color("rgb(255, 220, 180)"), // Warm white
312
+ 2.5,
313
+ 30.0
314
+ );
315
+
316
+ // Accent light from left
317
+ createSpotlight(
318
+ { x: 100, y: 300, z: 300 },
319
+ { x: 300, y: 384, z: 0 },
320
+ Skia.Color("rgb(90, 160, 255)"), // Cool blue
321
+ 1.2,
322
+ 40.0
323
+ );
324
+
325
+ // Subtle rim light from below
326
+ createSpotlight(
327
+ { x: 384, y: 650, z: 150 },
328
+ { x: 384, y: 500, z: 0 },
329
+ Skia.Color("rgb(255, 100, 50)"), // Warm orange/red
330
+ 0.8,
331
+ 60.0
332
+ );
333
+
334
+ // Add volumetric light rays
335
+ const raysPaint = Skia.Paint();
336
+ raysPaint.setColor(Skia.Color("rgba(255, 230, 180, 0.2)"));
337
+
338
+ // Primary light source position
339
+ const lightX = 600;
340
+ const lightY = 100;
341
+
342
+ // Draw 12 light rays from the main spotlight
343
+ for (let i = 0; i < 12; i++) {
344
+ const angle = (i / 12) * Math.PI * 0.5 + Math.PI * 0.75; // Angles for right-top quadrant
345
+ const length = 300 + 0.5 * 200;
346
+ const endX = lightX + Math.cos(angle) * length;
347
+ const endY = lightY + Math.sin(angle) * length;
348
+
349
+ const rayPaint = Skia.Paint();
350
+ rayPaint.setColor(Skia.Color("rgba(255, 230, 180, 0.1)"));
351
+ rayPaint.setStrokeWidth(2 + 0.5 * 4);
352
+ rayPaint.setStyle(ctx.PaintStyle.Stroke);
353
+ rayPaint.setImageFilter(
354
+ Skia.ImageFilter.MakeBlur(3, 3, ctx.TileMode.Decal)
355
+ );
356
+
357
+ canvas.drawLine(lightX, lightY, endX, endY, rayPaint);
358
+ }
359
+
360
+ sur.flush();
361
+ return sur.makeImageSnapshot().encodeToBase64();
362
+ },
363
+ { skiaLogoPng, TileMode, BlendMode, PaintStyle }
364
+ );
365
+ checkResult(
366
+ base64,
367
+ `lighting-image-filters/spot-lit-diffuse-${surface.OS}.png`
368
+ );
369
+ });
370
+
371
+ itRunsE2eOnly("DistantLitSpecular - Metallic Gold", async () => {
372
+ const { skiaLogoPng } = images;
373
+ const base64 = await surface.eval(
374
+ (Skia, ctx) => {
375
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
376
+ const canvas = sur.getCanvas();
377
+
378
+ // Draw a rich dark background with subtle texture
379
+ const bgPaint = Skia.Paint();
380
+ bgPaint.setColor(Skia.Color("rgb(25, 15, 10)"));
381
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
382
+
383
+ // Add subtle noise texture to background
384
+ const noisePaint = Skia.Paint();
385
+
386
+ // Use a fine turbulence noise as a background texture
387
+ for (let y = 0; y < 768; y += 4) {
388
+ for (let x = 0; x < 768; x += 4) {
389
+ // Simple noise function
390
+ const noise = Math.sin(x * 0.1) * Math.cos(y * 0.1) * 0.5 + 0.5;
391
+ const alpha = noise * 0.15; // Very subtle
392
+
393
+ noisePaint.setColor(Skia.Color(`rgba(50, 30, 10, ${alpha})`));
394
+ canvas.drawRect(Skia.XYWHRect(x, y, 4, 4), noisePaint);
395
+ }
396
+ }
397
+
398
+ // Create our metallic gold effect
399
+ const paint = Skia.Paint();
400
+
401
+ // Preprocess the image with a blur and contrast enhancement
402
+ const preprocessFilter = Skia.ImageFilter.MakeColorFilter(
403
+ Skia.ColorFilter.MakeMatrix([
404
+ 2.0,
405
+ 0,
406
+ 0,
407
+ 0,
408
+ -50, // High red contrast
409
+ 0,
410
+ 2.0,
411
+ 0,
412
+ 0,
413
+ -50, // High green contrast
414
+ 0,
415
+ 0,
416
+ 2.0,
417
+ 0,
418
+ -50, // High blue contrast
419
+ 0,
420
+ 0,
421
+ 0,
422
+ 1,
423
+ 0, // Alpha unchanged
424
+ ]),
425
+ Skia.ImageFilter.MakeBlur(1, 1, ctx.TileMode.Decal)
426
+ );
427
+
428
+ // Direction vectors for multiple lights to create more complex highlights
429
+ const direction = { x: 0.5, y: -1, z: 0.3 };
430
+
431
+ // Gold light color with intense highlights
432
+ const lightColor = Skia.Color("rgb(255, 240, 180)");
433
+
434
+ // Parameters for the filter - high shininess for gold
435
+ const surfaceScale = 0.6; // Not too high for gold
436
+ const ks = 0.9; // Strong specular coefficient
437
+ const shininess = 50.0; // Very high shininess for metallic look
438
+
439
+ // Create the distant specular filter
440
+ const specularFilter = Skia.ImageFilter.MakeDistantLitSpecular(
441
+ direction,
442
+ lightColor,
443
+ surfaceScale,
444
+ ks,
445
+ shininess,
446
+ preprocessFilter, // Use enhanced image as input
447
+ null // No crop rect
448
+ );
449
+
450
+ // Set the filter to our paint
451
+ paint.setImageFilter(specularFilter);
452
+
453
+ // Add a gold color tint
454
+ const goldMatrix = [
455
+ 1.2,
456
+ 0.3,
457
+ 0.0,
458
+ 0,
459
+ 30, // Boost red with some green mixed in
460
+ 0.2,
461
+ 1.0,
462
+ 0.0,
463
+ 0,
464
+ 20, // Moderate green
465
+ 0.0,
466
+ 0.1,
467
+ 0.4,
468
+ 0,
469
+ 0, // Minimal blue for gold
470
+ 0,
471
+ 0,
472
+ 0,
473
+ 1,
474
+ 0, // Alpha unchanged
475
+ ];
476
+
477
+ const colorFilter = Skia.ColorFilter.MakeMatrix(goldMatrix);
478
+ paint.setColorFilter(colorFilter);
479
+
480
+ // Add a second light from another angle for more complex highlights
481
+ const secondaryPaint = Skia.Paint();
482
+ const secondDirection = { x: -0.5, y: -0.8, z: 0.2 };
483
+
484
+ const secondaryFilter = Skia.ImageFilter.MakeDistantLitSpecular(
485
+ secondDirection,
486
+ Skia.Color("rgb(255, 255, 255)"), // White highlights
487
+ surfaceScale * 0.5,
488
+ ks * 0.3,
489
+ shininess * 1.5, // Even sharper highlights
490
+ preprocessFilter,
491
+ null
492
+ );
493
+
494
+ secondaryPaint.setImageFilter(secondaryFilter);
495
+ secondaryPaint.setBlendMode(ctx.BlendMode.Plus); // Additive blending
496
+
497
+ // Draw with padding for a framed effect
498
+ const padding = 40;
499
+ const imageRect = Skia.XYWHRect(
500
+ padding,
501
+ padding,
502
+ 768 - padding * 2,
503
+ 768 - padding * 2
504
+ );
505
+
506
+ // Create an ornate frame border
507
+ const framePaint = Skia.Paint();
508
+ framePaint.setStyle(ctx.PaintStyle.Stroke);
509
+ framePaint.setStrokeWidth(10);
510
+ framePaint.setColor(Skia.Color("rgb(80, 50, 10)"));
511
+ canvas.drawRect(
512
+ Skia.XYWHRect(
513
+ padding - 15,
514
+ padding - 15,
515
+ 768 - padding * 2 + 30,
516
+ 768 - padding * 2 + 30
517
+ ),
518
+ framePaint
519
+ );
520
+
521
+ // Add gold leaf effect to frame
522
+ const frameGoldPaint = Skia.Paint();
523
+ frameGoldPaint.setStyle(ctx.PaintStyle.Stroke);
524
+ frameGoldPaint.setStrokeWidth(6);
525
+ frameGoldPaint.setColor(Skia.Color("rgb(200, 170, 40)"));
526
+ canvas.drawRect(
527
+ Skia.XYWHRect(
528
+ padding - 15,
529
+ padding - 15,
530
+ 768 - padding * 2 + 30,
531
+ 768 - padding * 2 + 30
532
+ ),
533
+ frameGoldPaint
534
+ );
535
+
536
+ // Draw the main image
537
+ canvas.drawImageRect(
538
+ ctx.skiaLogoPng,
539
+ Skia.XYWHRect(
540
+ 0,
541
+ 0,
542
+ ctx.skiaLogoPng.width(),
543
+ ctx.skiaLogoPng.height()
544
+ ),
545
+ imageRect,
546
+ paint
547
+ );
548
+
549
+ // Apply secondary highlights
550
+ canvas.drawImageRect(
551
+ ctx.skiaLogoPng,
552
+ Skia.XYWHRect(
553
+ 0,
554
+ 0,
555
+ ctx.skiaLogoPng.width(),
556
+ ctx.skiaLogoPng.height()
557
+ ),
558
+ imageRect,
559
+ secondaryPaint
560
+ );
561
+
562
+ sur.flush();
563
+ return sur.makeImageSnapshot().encodeToBase64();
564
+ },
565
+ { skiaLogoPng, TileMode, BlendMode, PaintStyle }
566
+ );
567
+ checkResult(base64, "lighting-image-filters/distant-lit-specular.png");
568
+ });
569
+
570
+ itRunsE2eOnly("PointLitSpecular - Wet Surface", async () => {
571
+ const { skiaLogoPng } = images;
572
+ const base64 = await surface.eval(
573
+ (Skia, ctx) => {
574
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
575
+ const canvas = sur.getCanvas();
576
+
577
+ // Create a dark wet-looking surface
578
+ const bgPaint = Skia.Paint();
579
+ const bgShader = Skia.Shader.MakeLinearGradient(
580
+ { x: 0, y: 0 },
581
+ { x: 768, y: 768 },
582
+ [Skia.Color("rgb(10, 20, 30)"), Skia.Color("rgb(20, 40, 60)")],
583
+ null,
584
+ ctx.TileMode.Clamp
585
+ );
586
+ bgPaint.setShader(bgShader);
587
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
588
+
589
+ // Add water droplet texture in background
590
+ const dropletPaint = Skia.Paint();
591
+ dropletPaint.setColor(Skia.Color("rgba(255, 255, 255, 0.1)"));
592
+
593
+ // Random water droplets
594
+ const numDroplets = 200;
595
+ for (let i = 0; i < numDroplets; i++) {
596
+ const x = 0.5 * 768;
597
+ const y = 0.5 * 768;
598
+ const size = 1 + 0.5 * 5;
599
+
600
+ // Make some droplets blurry for depth
601
+ if (0.5 > 0.7) {
602
+ dropletPaint.setImageFilter(
603
+ Skia.ImageFilter.MakeBlur(
604
+ 1 + 0.5 * 2,
605
+ 1 + 0.5 * 2,
606
+ ctx.TileMode.Decal
607
+ )
608
+ );
609
+ } else {
610
+ dropletPaint.setImageFilter(null);
611
+ }
612
+
613
+ canvas.drawCircle(x, y, size, dropletPaint);
614
+ }
615
+
616
+ // Create wet surface effect with high specularity
617
+ const mainPaint = Skia.Paint();
618
+
619
+ // Create the point specular light source
620
+ const mainLight = { x: 200, y: 200, z: 300 };
621
+ const specularFilter = Skia.ImageFilter.MakePointLitSpecular(
622
+ mainLight,
623
+ Skia.Color("rgb(240, 250, 255)"), // Slightly blue-white for water highlights
624
+ 1.0, // Moderate surface scale
625
+ 1.0, // Full specular strength
626
+ 80.0, // Very high shininess for wet look
627
+ null,
628
+ null
629
+ );
630
+
631
+ mainPaint.setImageFilter(specularFilter);
632
+
633
+ // Create second light for additional highlights
634
+ const secondaryPaint = Skia.Paint();
635
+ const secondLight = { x: 600, y: 400, z: 400 };
636
+ const secondFilter = Skia.ImageFilter.MakePointLitSpecular(
637
+ secondLight,
638
+ Skia.Color("rgb(200, 220, 255)"), // Cooler light
639
+ 0.8,
640
+ 0.7,
641
+ 100.0, // Even higher shininess
642
+ null,
643
+ null
644
+ );
645
+
646
+ secondaryPaint.setImageFilter(secondFilter);
647
+ secondaryPaint.setBlendMode(ctx.BlendMode.Plus);
648
+
649
+ // Create diffuse lighting for the base image
650
+ const diffusePaint = Skia.Paint();
651
+ const diffuseFilter = Skia.ImageFilter.MakePointLitDiffuse(
652
+ { x: 384, y: 384, z: 300 },
653
+ Skia.Color("rgb(150, 160, 170)"), // Soft gray-blue
654
+ 1.5,
655
+ 1.0,
656
+ null,
657
+ null
658
+ );
659
+
660
+ diffusePaint.setImageFilter(diffuseFilter);
661
+
662
+ // Add a blue-tinted color adjustment for underwater effect
663
+ diffusePaint.setColorFilter(
664
+ Skia.ColorFilter.MakeMatrix([
665
+ 0.8, 0.0, 0.0, 0, 0, 0.0, 0.9, 0.1, 0, 0, 0.1, 0.1, 1.0, 0, 20, 0,
666
+ 0, 0, 1, 0,
667
+ ])
668
+ );
669
+
670
+ // Draw with the base diffuse lighting first
671
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, diffusePaint);
672
+
673
+ // Draw with specular highlights
674
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, mainPaint);
675
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, secondaryPaint);
676
+
677
+ // Add water flowing/dripping effect
678
+ const waterStreakPaint = Skia.Paint();
679
+ waterStreakPaint.setColor(Skia.Color("rgba(220, 230, 255, 0.15)"));
680
+
681
+ // Create several water streaks
682
+ for (let i = 0; i < 8; i++) {
683
+ const startX = 50 + 0.5 * 700;
684
+ const startY = 20 + 0.5 * 100;
685
+ let currentX = startX;
686
+ let currentY = startY;
687
+
688
+ const flowPath = Skia.Path.Make();
689
+ flowPath.moveTo(currentX, currentY);
690
+
691
+ // Create a wavy downward path
692
+ const length = 100 + 0.5 * 600;
693
+ const segments = 10 + Math.floor(length / 30);
694
+
695
+ for (let j = 0; j < segments; j++) {
696
+ // Gravity pulls downward
697
+ currentY += length / segments;
698
+ // Random side-to-side waviness
699
+ currentX += (0.5 - 0.5) * 30;
700
+
701
+ flowPath.lineTo(currentX, currentY);
702
+ }
703
+
704
+ // Make the water streak taper and blur
705
+ const streakPaint = Skia.Paint();
706
+ streakPaint.setStyle(ctx.PaintStyle.Stroke);
707
+ streakPaint.setColor(Skia.Color("rgba(200, 240, 255, 0.2)"));
708
+ streakPaint.setStrokeWidth(1 + 0.5 * 3);
709
+ streakPaint.setImageFilter(
710
+ Skia.ImageFilter.MakeBlur(1, 1, ctx.TileMode.Decal)
711
+ );
712
+
713
+ canvas.drawPath(flowPath, streakPaint);
714
+ }
715
+
716
+ // Add water puddle at bottom
717
+ const puddlePaint = Skia.Paint();
718
+ const puddleShader = Skia.Shader.MakeLinearGradient(
719
+ { x: 0, y: 600 },
720
+ { x: 0, y: 768 },
721
+ [
722
+ Skia.Color("rgba(40, 80, 120, 0)"),
723
+ Skia.Color("rgba(40, 80, 120, 0.4)"),
724
+ ],
725
+ null,
726
+ ctx.TileMode.Clamp
727
+ );
728
+ puddlePaint.setShader(puddleShader);
729
+ canvas.drawRect(Skia.XYWHRect(0, 600, 768, 168), puddlePaint);
730
+
731
+ sur.flush();
732
+ return sur.makeImageSnapshot().encodeToBase64();
733
+ },
734
+ { skiaLogoPng, BlendMode, TileMode, PaintStyle }
735
+ );
736
+ checkResult(
737
+ base64,
738
+ `lighting-image-filters/point-lit-specular-${surface.OS}.png`
739
+ );
740
+ });
741
+ itRunsE2eOnly("SpotLitSpecular - Crystal Prism", async () => {
742
+ const { skiaLogoPng } = images;
743
+ const base64 = await surface.eval(
744
+ (Skia, ctx) => {
745
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
746
+ const canvas = sur.getCanvas();
747
+
748
+ // Create a black background for maximum contrast with crystal effect
749
+ const bgPaint = Skia.Paint();
750
+ bgPaint.setColor(Skia.Color("rgb(0, 0, 0)"));
751
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
752
+
753
+ // Create a "crystal prism" effect with rainbow colors
754
+
755
+ // Draw colored ambient light beams in background
756
+ const beamColors = [
757
+ "rgba(255, 50, 50, 0.2)", // Red
758
+ "rgba(255, 150, 50, 0.2)", // Orange
759
+ "rgba(255, 255, 50, 0.2)", // Yellow
760
+ "rgba(50, 255, 50, 0.2)", // Green
761
+ "rgba(50, 150, 255, 0.2)", // Blue
762
+ "rgba(150, 50, 255, 0.2)", // Purple
763
+ ];
764
+
765
+ // Create rainbow beams
766
+ for (let i = 0; i < beamColors.length; i++) {
767
+ const beamPaint = Skia.Paint();
768
+ beamPaint.setColor(Skia.Color(beamColors[i]));
769
+
770
+ // Calculate angle for each beam
771
+ const angle = (i / beamColors.length) * Math.PI + Math.PI / 2;
772
+
773
+ // Create beam path
774
+ const beamPath = Skia.Path.Make();
775
+ beamPath.moveTo(384, 384);
776
+
777
+ // End coordinates based on angle
778
+ const endX = 384 + Math.cos(angle) * 900;
779
+ const endY = 384 + Math.sin(angle) * 900;
780
+
781
+ beamPath.lineTo(endX, endY);
782
+
783
+ // Draw wide blurred beam
784
+ beamPaint.setStyle(ctx.PaintStyle.Stroke);
785
+ beamPaint.setStrokeWidth(60 + i * 10);
786
+ beamPaint.setImageFilter(
787
+ Skia.ImageFilter.MakeBlur(20, 20, ctx.TileMode.Decal)
788
+ );
789
+
790
+ canvas.drawPath(beamPath, beamPaint);
791
+ }
792
+
793
+ // Apply crystalline effect to the main image
794
+ // First, prepare a base with enhanced contrast
795
+ const basePaint = Skia.Paint();
796
+ basePaint.setColorFilter(
797
+ Skia.ColorFilter.MakeMatrix([
798
+ 1.5,
799
+ 0,
800
+ 0,
801
+ 0,
802
+ -30, // Boosted red
803
+ 0,
804
+ 1.5,
805
+ 0,
806
+ 0,
807
+ -30, // Boosted green
808
+ 0,
809
+ 0,
810
+ 1.5,
811
+ 0,
812
+ -30, // Boosted blue
813
+ 0,
814
+ 0,
815
+ 0,
816
+ 1.2,
817
+ 0, // Slightly boosted alpha
818
+ ])
819
+ );
820
+
821
+ // Draw base layer at reduced size to create crystal border effect
822
+ const padding = 100;
823
+ const imageRect = Skia.XYWHRect(
824
+ padding,
825
+ padding,
826
+ 768 - padding * 2,
827
+ 768 - padding * 2
828
+ );
829
+
830
+ canvas.drawImageRect(
831
+ ctx.skiaLogoPng,
832
+ Skia.XYWHRect(
833
+ 0,
834
+ 0,
835
+ ctx.skiaLogoPng.width(),
836
+ ctx.skiaLogoPng.height()
837
+ ),
838
+ imageRect,
839
+ basePaint
840
+ );
841
+
842
+ // Now create multiple specular highlights from different angles
843
+ // These simulate light refracting through crystal facets
844
+
845
+ // Function to create a colored specular highlight
846
+ const createCrystalFacet = (
847
+ location: { x: number; y: number; z: number },
848
+ target: { x: number; y: number; z: number },
849
+ color: SkColor,
850
+ cutoffAngle: number,
851
+ shininess: number
852
+ ) => {
853
+ const paint = Skia.Paint();
854
+
855
+ // Create the spotlight specular filter
856
+ const spotLitFilter = Skia.ImageFilter.MakeSpotLitSpecular(
857
+ location,
858
+ target,
859
+ 3.0, // Sharp falloff
860
+ cutoffAngle, // Narrow spotlight cone
861
+ color, // Colored light
862
+ 1.0, // Surface scale
863
+ 0.9, // Strong specular coefficient
864
+ shininess, // Very high shininess
865
+ null, // Use source bitmap as input
866
+ null // No crop rect
867
+ );
868
+
869
+ // Set the filter and blend mode
870
+ paint.setImageFilter(spotLitFilter);
871
+ paint.setBlendMode(ctx.BlendMode.Plus); // Additive lighting
872
+
873
+ // Draw the specular highlight
874
+ canvas.drawImageRect(
875
+ ctx.skiaLogoPng,
876
+ Skia.XYWHRect(
877
+ 0,
878
+ 0,
879
+ ctx.skiaLogoPng.width(),
880
+ ctx.skiaLogoPng.height()
881
+ ),
882
+ imageRect,
883
+ paint
884
+ );
885
+ };
886
+
887
+ // Create crystal facets with rainbow colors
888
+ createCrystalFacet(
889
+ { x: 500, y: 200, z: 300 },
890
+ { x: 384, y: 384, z: 0 },
891
+ Skia.Color("rgb(255, 50, 50)"), // Red
892
+ 25.0,
893
+ 100.0
894
+ );
895
+
896
+ createCrystalFacet(
897
+ { x: 200, y: 600, z: 300 },
898
+ { x: 300, y: 384, z: 0 },
899
+ Skia.Color("rgb(255, 150, 50)"), // Orange
900
+ 30.0,
901
+ 120.0
902
+ );
903
+
904
+ createCrystalFacet(
905
+ { x: 600, y: 600, z: 300 },
906
+ { x: 500, y: 400, z: 0 },
907
+ Skia.Color("rgb(255, 255, 50)"), // Yellow
908
+ 20.0,
909
+ 150.0
910
+ );
911
+
912
+ createCrystalFacet(
913
+ { x: 200, y: 200, z: 300 },
914
+ { x: 300, y: 300, z: 0 },
915
+ Skia.Color("rgb(50, 255, 50)"), // Green
916
+ 15.0,
917
+ 200.0
918
+ );
919
+
920
+ createCrystalFacet(
921
+ { x: 400, y: 100, z: 300 },
922
+ { x: 400, y: 300, z: 0 },
923
+ Skia.Color("rgb(50, 150, 255)"), // Blue
924
+ 25.0,
925
+ 180.0
926
+ );
927
+
928
+ createCrystalFacet(
929
+ { x: 700, y: 384, z: 300 },
930
+ { x: 500, y: 384, z: 0 },
931
+ Skia.Color("rgb(150, 50, 255)"), // Purple
932
+ 20.0,
933
+ 160.0
934
+ );
935
+
936
+ // Add intense white highlights for sharp crystal edges
937
+ createCrystalFacet(
938
+ { x: 384, y: 100, z: 400 },
939
+ { x: 384, y: 384, z: 0 },
940
+ Skia.Color("rgb(255, 255, 255)"), // Pure white
941
+ 10.0,
942
+ 250.0
943
+ );
944
+
945
+ // Add crystal border effect
946
+ const borderPaint = Skia.Paint();
947
+
948
+ // Create a crystal-like faceted border
949
+ const borderPath = Skia.Path.Make();
950
+
951
+ // Create an irregular crystal shape around the image
952
+ const centerX = 384;
953
+ const centerY = 384;
954
+ const facets = 12;
955
+
956
+ borderPath.moveTo(
957
+ centerX + (padding - 20) * Math.cos(0),
958
+ centerY + (padding - 20) * Math.sin(0)
959
+ );
960
+
961
+ for (let i = 1; i <= facets; i++) {
962
+ const angle = (i / facets) * Math.PI * 2;
963
+
964
+ // Vary the radius for each facet
965
+ const radiusVariation = 30 + 0.5 * 40;
966
+ const radius = padding - 20 + radiusVariation;
967
+
968
+ borderPath.lineTo(
969
+ centerX + radius * Math.cos(angle),
970
+ centerY + radius * Math.sin(angle)
971
+ );
972
+ }
973
+
974
+ borderPath.close();
975
+
976
+ // Draw crystal border with gradient effect
977
+ const borderShader = Skia.Shader.MakeLinearGradient(
978
+ { x: 0, y: 0 },
979
+ { x: 768, y: 768 },
980
+ [
981
+ Skia.Color("rgba(220, 240, 255, 0.6)"),
982
+ Skia.Color("rgba(180, 220, 255, 0.3)"),
983
+ Skia.Color("rgba(150, 200, 255, 0.6)"),
984
+ ],
985
+ null,
986
+ ctx.TileMode.Clamp
987
+ );
988
+
989
+ borderPaint.setShader(borderShader);
990
+ borderPaint.setStyle(ctx.PaintStyle.Stroke);
991
+ borderPaint.setStrokeWidth(30);
992
+ borderPaint.setImageFilter(
993
+ Skia.ImageFilter.MakeBlur(8, 8, ctx.TileMode.Decal)
994
+ );
995
+
996
+ canvas.drawPath(borderPath, borderPaint);
997
+
998
+ // Add some star-shaped highlights
999
+ const starPaint = Skia.Paint();
1000
+ starPaint.setColor(Skia.Color("rgb(255, 255, 255)"));
1001
+
1002
+ const createStar = (x: number, y: number, size: number) => {
1003
+ const starPath = Skia.Path.Make();
1004
+
1005
+ // Draw 4-point star
1006
+ starPath.moveTo(x, y - size);
1007
+ starPath.lineTo(x + size / 4, y - size / 4);
1008
+ starPath.lineTo(x + size, y);
1009
+ starPath.lineTo(x + size / 4, y + size / 4);
1010
+ starPath.lineTo(x, y + size);
1011
+ starPath.lineTo(x - size / 4, y + size / 4);
1012
+ starPath.lineTo(x - size, y);
1013
+ starPath.lineTo(x - size / 4, y - size / 4);
1014
+ starPath.close();
1015
+
1016
+ // Add blur for glow
1017
+ const glowPaint = Skia.Paint();
1018
+ glowPaint.setColor(Skia.Color("rgba(255, 255, 255, 0.8)"));
1019
+ glowPaint.setImageFilter(
1020
+ Skia.ImageFilter.MakeBlur(size / 4, size / 4, ctx.TileMode.Decal)
1021
+ );
1022
+
1023
+ canvas.drawPath(starPath, glowPaint);
1024
+
1025
+ // Draw sharp center
1026
+ const centerPaint = Skia.Paint();
1027
+ centerPaint.setColor(Skia.Color("rgb(255, 255, 255)"));
1028
+ canvas.drawCircle(x, y, size / 10, centerPaint);
1029
+ };
1030
+
1031
+ // Add several highlight stars
1032
+ createStar(150, 200, 20);
1033
+ createStar(600, 150, 15);
1034
+ createStar(500, 600, 25);
1035
+ createStar(200, 500, 18);
1036
+ createStar(350, 250, 12);
1037
+
1038
+ sur.flush();
1039
+ return sur.makeImageSnapshot().encodeToBase64();
1040
+ },
1041
+ { skiaLogoPng, BlendMode, TileMode, PaintStyle }
1042
+ );
1043
+ checkResult(
1044
+ base64,
1045
+ `lighting-image-filters/spot-lit-specular-${surface.OS}.png`
1046
+ );
1047
+ });
1048
+
1049
+ itRunsE2eOnly("Combined Lighting - Elemental Fire & Ice", async () => {
1050
+ const { skiaLogoPng } = images;
1051
+ const base64 = await surface.eval(
1052
+ (Skia, ctx) => {
1053
+ const sur = Skia.Surface.MakeOffscreen(768, 768)!;
1054
+ const canvas = sur.getCanvas();
1055
+
1056
+ // Fill with dark background
1057
+ const bgPaint = Skia.Paint();
1058
+ bgPaint.setColor(Skia.Color("rgb(5, 10, 20)"));
1059
+ canvas.drawRect(Skia.XYWHRect(0, 0, 768, 768), bgPaint);
1060
+
1061
+ // Create a split-screen effect with fire and ice
1062
+
1063
+ // First, create a mask for each half
1064
+ const leftMask = Skia.Path.Make();
1065
+ leftMask.addRect(Skia.XYWHRect(0, 0, 384, 768));
1066
+
1067
+ const rightMask = Skia.Path.Make();
1068
+ rightMask.addRect(Skia.XYWHRect(384, 0, 384, 768));
1069
+
1070
+ // Prepare the image with enhanced contrast
1071
+ const preprocessFilter = Skia.ImageFilter.MakeColorFilter(
1072
+ Skia.ColorFilter.MakeMatrix([
1073
+ 1.8,
1074
+ 0,
1075
+ 0,
1076
+ 0,
1077
+ -40, // High contrast red
1078
+ 0,
1079
+ 1.8,
1080
+ 0,
1081
+ 0,
1082
+ -40, // High contrast green
1083
+ 0,
1084
+ 0,
1085
+ 1.8,
1086
+ 0,
1087
+ -40, // High contrast blue
1088
+ 0,
1089
+ 0,
1090
+ 0,
1091
+ 1.2,
1092
+ 0, // Slightly boosted alpha
1093
+ ]),
1094
+ Skia.ImageFilter.MakeBlur(1, 1, ctx.TileMode.Decal)
1095
+ );
1096
+
1097
+ // ******** FIRE SIDE (LEFT) ********
1098
+ canvas.save();
1099
+ canvas.clipPath(leftMask, ctx.ClipOp.Intersect, true);
1100
+
1101
+ // Create fiery background
1102
+ const fireBgPaint = Skia.Paint();
1103
+ const fireShader = Skia.Shader.MakeLinearGradient(
1104
+ { x: 0, y: 768 },
1105
+ { x: 384, y: 0 },
1106
+ [
1107
+ Skia.Color("rgb(80, 0, 0)"),
1108
+ Skia.Color("rgb(180, 30, 0)"),
1109
+ Skia.Color("rgb(255, 150, 0)"),
1110
+ ],
1111
+ [0.2, 0.5, 0.9],
1112
+ ctx.TileMode.Clamp
1113
+ );
1114
+ fireBgPaint.setShader(fireShader);
1115
+ canvas.drawRect(Skia.XYWHRect(0, 0, 384, 768), fireBgPaint);
1116
+
1117
+ // Draw stylized flames in background
1118
+ const drawFlame = (
1119
+ x: number,
1120
+ y: number,
1121
+ width: number,
1122
+ height: number
1123
+ ) => {
1124
+ const flamePath = Skia.Path.Make();
1125
+
1126
+ // Start at bottom center
1127
+ flamePath.moveTo(x, y + height);
1128
+
1129
+ // Define control points for bezier curve
1130
+ // Left side of flame
1131
+ flamePath.cubicTo(
1132
+ x - width * 0.5,
1133
+ y + height * 0.7, // Control point 1
1134
+ x - width * 0.2,
1135
+ y + height * 0.3, // Control point 2
1136
+ x,
1137
+ y // End point (top of flame)
1138
+ );
1139
+
1140
+ // Right side of flame
1141
+ flamePath.cubicTo(
1142
+ x + width * 0.2,
1143
+ y + height * 0.3, // Control point 1
1144
+ x + width * 0.5,
1145
+ y + height * 0.7, // Control point 2
1146
+ x,
1147
+ y + height // End point (back to start)
1148
+ );
1149
+
1150
+ // Fill with gradient
1151
+ const flamePaint = Skia.Paint();
1152
+ const flameShader = Skia.Shader.MakeLinearGradient(
1153
+ { x: x, y: y + height },
1154
+ { x: x, y: y },
1155
+ [
1156
+ Skia.Color("rgb(255, 60, 0)"),
1157
+ Skia.Color("rgb(255, 150, 0)"),
1158
+ Skia.Color("rgb(255, 220, 120)"),
1159
+ ],
1160
+ [0.2, 0.6, 0.9],
1161
+ ctx.TileMode.Clamp
1162
+ );
1163
+ flamePaint.setShader(flameShader);
1164
+
1165
+ // Add glow with blur
1166
+ flamePaint.setImageFilter(
1167
+ Skia.ImageFilter.MakeBlur(
1168
+ width * 0.1,
1169
+ width * 0.1,
1170
+ ctx.TileMode.Decal
1171
+ )
1172
+ );
1173
+
1174
+ canvas.drawPath(flamePath, flamePaint);
1175
+ };
1176
+
1177
+ // Draw multiple flames
1178
+ for (let i = 0; i < 8; i++) {
1179
+ const x = 50 + 0.5 * 300;
1180
+ const baseHeight = 200 + 0.5 * 300;
1181
+ drawFlame(x, 768 - baseHeight, 20 + 0.5 * 50, baseHeight);
1182
+ }
1183
+
1184
+ // Create fire lighting with composite of diffuse and specular
1185
+
1186
+ // Create fiery diffuse lighting from below
1187
+ const fireLight = { x: 200, y: 700, z: 100 };
1188
+ const fireColor = Skia.Color("rgb(255, 180, 50)");
1189
+
1190
+ const fireDiffuseFilter = Skia.ImageFilter.MakePointLitDiffuse(
1191
+ fireLight,
1192
+ fireColor,
1193
+ 2.0, // Exaggerated surface scale
1194
+ 2.0, // Strong diffuse
1195
+ preprocessFilter, // Use preprocessed image
1196
+ null
1197
+ );
1198
+
1199
+ // Create specular highlights for embers and sparks
1200
+ const fireSpecularFilter = Skia.ImageFilter.MakePointLitSpecular(
1201
+ { x: 150, y: 550, z: 200 },
1202
+ Skia.Color("rgb(255, 230, 150)"),
1203
+ 1.0,
1204
+ 0.7,
1205
+ 50.0, // High shininess for sparks
1206
+ preprocessFilter,
1207
+ null
1208
+ );
1209
+
1210
+ // Combine diffuse and specular with Plus blending
1211
+ const fireFilter = Skia.ImageFilter.MakeBlend(
1212
+ ctx.BlendMode.Plus,
1213
+ fireDiffuseFilter,
1214
+ fireSpecularFilter
1215
+ );
1216
+
1217
+ // Create the fire paint
1218
+ const firePaint = Skia.Paint();
1219
+ firePaint.setImageFilter(fireFilter);
1220
+
1221
+ // Add fiery color tint
1222
+ firePaint.setColorFilter(
1223
+ Skia.ColorFilter.MakeMatrix([
1224
+ 1.5,
1225
+ 0.3,
1226
+ 0.1,
1227
+ 0,
1228
+ 20, // Strong red
1229
+ 0.2,
1230
+ 1.0,
1231
+ 0.1,
1232
+ 0,
1233
+ 0, // Moderate green
1234
+ 0.0,
1235
+ 0.1,
1236
+ 0.5,
1237
+ 0,
1238
+ -20, // Reduced blue
1239
+ 0,
1240
+ 0,
1241
+ 0,
1242
+ 1,
1243
+ 0, // Alpha unchanged
1244
+ ])
1245
+ );
1246
+
1247
+ // Draw the image with fire effect
1248
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, firePaint);
1249
+
1250
+ // Add ember particles
1251
+ const emberPaint = Skia.Paint();
1252
+
1253
+ for (let i = 0; i < 60; i++) {
1254
+ const x = 0.5 * 384;
1255
+ const y = 300 + 0.5 * 468;
1256
+ const size = 1 + 0.5 * 3;
1257
+
1258
+ const brightness = 150 + 0.5 * 105;
1259
+ emberPaint.setColor(
1260
+ Skia.Color(`rgba(${brightness}, ${brightness * 0.6}, 0, 0.8)`)
1261
+ );
1262
+
1263
+ // Add glow to some embers
1264
+ if (0.5 > 0.6) {
1265
+ emberPaint.setImageFilter(
1266
+ Skia.ImageFilter.MakeBlur(size * 2, size * 2, ctx.TileMode.Decal)
1267
+ );
1268
+ } else {
1269
+ emberPaint.setImageFilter(null);
1270
+ }
1271
+
1272
+ canvas.drawCircle(x, y, size, emberPaint);
1273
+ }
1274
+
1275
+ canvas.restore();
1276
+
1277
+ // ******** ICE SIDE (RIGHT) ********
1278
+ canvas.save();
1279
+ canvas.clipPath(rightMask, ctx.ClipOp.Intersect, true);
1280
+
1281
+ // Create icy background
1282
+ const iceBgPaint = Skia.Paint();
1283
+ const iceShader = Skia.Shader.MakeLinearGradient(
1284
+ { x: 768, y: 768 },
1285
+ { x: 384, y: 0 },
1286
+ [
1287
+ Skia.Color("rgb(0, 20, 50)"),
1288
+ Skia.Color("rgb(30, 70, 120)"),
1289
+ Skia.Color("rgb(180, 220, 255)"),
1290
+ ],
1291
+ [0.2, 0.5, 0.9],
1292
+ ctx.TileMode.Clamp
1293
+ );
1294
+ iceBgPaint.setShader(iceShader);
1295
+ canvas.drawRect(Skia.XYWHRect(384, 0, 384, 768), iceBgPaint);
1296
+
1297
+ // Draw ice crystals in background
1298
+ const drawCrystal = (
1299
+ x: number,
1300
+ y: number,
1301
+ size: number,
1302
+ rotation: number
1303
+ ) => {
1304
+ const crystalPaint = Skia.Paint();
1305
+ crystalPaint.setColor(Skia.Color("rgba(200, 230, 255, 0.4)"));
1306
+
1307
+ // Save canvas state for rotation
1308
+ canvas.save();
1309
+ canvas.translate(x, y);
1310
+ canvas.rotate(rotation, 0, 0);
1311
+
1312
+ // Draw crystal shape
1313
+ const crystalPath = Skia.Path.Make();
1314
+ crystalPath.moveTo(0, -size); // Top
1315
+ crystalPath.lineTo(size / 3, -size / 2); // Upper right
1316
+ crystalPath.lineTo(size / 2, size / 2); // Lower right
1317
+ crystalPath.lineTo(0, size); // Bottom
1318
+ crystalPath.lineTo(-size / 2, size / 2); // Lower left
1319
+ crystalPath.lineTo(-size / 3, -size / 2); // Upper left
1320
+ crystalPath.close();
1321
+
1322
+ // Add light refraction effect
1323
+ crystalPaint.setImageFilter(
1324
+ Skia.ImageFilter.MakeBlur(
1325
+ size * 0.1,
1326
+ size * 0.1,
1327
+ ctx.TileMode.Decal
1328
+ )
1329
+ );
1330
+
1331
+ canvas.drawPath(crystalPath, crystalPaint);
1332
+
1333
+ // Add highlight
1334
+ const highlightPaint = Skia.Paint();
1335
+ highlightPaint.setColor(Skia.Color("rgba(255, 255, 255, 0.7)"));
1336
+
1337
+ const highlightPath = Skia.Path.Make();
1338
+ highlightPath.moveTo(0, -size * 0.8);
1339
+ highlightPath.lineTo(size / 5, -size / 3);
1340
+ highlightPath.lineTo(-size / 5, -size / 3);
1341
+ highlightPath.close();
1342
+
1343
+ canvas.drawPath(highlightPath, highlightPaint);
1344
+
1345
+ // Restore canvas
1346
+ canvas.restore();
1347
+ };
1348
+
1349
+ // Draw multiple crystals
1350
+ for (let i = 0; i < 15; i++) {
1351
+ const x = 384 + 0.5 * 384;
1352
+ const y = 0.5 * 768;
1353
+ const size = 20 + 0.5 * 60;
1354
+ const rotation = 0.5 * 360;
1355
+
1356
+ drawCrystal(x, y, size, rotation);
1357
+ }
1358
+
1359
+ // Create ice lighting with diffuse and specular
1360
+
1361
+ // Create ice diffuse lighting from top
1362
+ const iceLight = { x: 600, y: 100, z: 200 };
1363
+ const iceColor = Skia.Color("rgb(200, 230, 255)");
1364
+
1365
+ const iceDiffuseFilter = Skia.ImageFilter.MakePointLitDiffuse(
1366
+ iceLight,
1367
+ iceColor,
1368
+ 1.5, // Moderate surface scale
1369
+ 1.2, // Strong diffuse
1370
+ preprocessFilter, // Use preprocessed image
1371
+ null
1372
+ );
1373
+
1374
+ // Create specular highlights for ice crystals
1375
+ const iceSpecularFilter = Skia.ImageFilter.MakePointLitSpecular(
1376
+ { x: 620, y: 200, z: 300 },
1377
+ Skia.Color("rgb(240, 250, 255)"),
1378
+ 1.0,
1379
+ 0.8,
1380
+ 80.0, // Very high shininess for ice
1381
+ preprocessFilter,
1382
+ null
1383
+ );
1384
+
1385
+ // Combine diffuse and specular with Screen blending
1386
+ const iceFilter = Skia.ImageFilter.MakeBlend(
1387
+ ctx.BlendMode.Screen,
1388
+ iceDiffuseFilter,
1389
+ iceSpecularFilter
1390
+ );
1391
+
1392
+ // Create the ice paint
1393
+ const icePaint = Skia.Paint();
1394
+ icePaint.setImageFilter(iceFilter);
1395
+
1396
+ // Add icy color tint
1397
+ icePaint.setColorFilter(
1398
+ Skia.ColorFilter.MakeMatrix([
1399
+ 0.7,
1400
+ 0.0,
1401
+ 0.0,
1402
+ 0,
1403
+ -20, // Reduced red
1404
+ 0.0,
1405
+ 0.9,
1406
+ 0.2,
1407
+ 0,
1408
+ 0, // Boosted green slightly
1409
+ 0.2,
1410
+ 0.3,
1411
+ 1.2,
1412
+ 0,
1413
+ 30, // Strong blue
1414
+ 0,
1415
+ 0,
1416
+ 0,
1417
+ 1,
1418
+ 0, // Alpha unchanged
1419
+ ])
1420
+ );
1421
+
1422
+ // Draw the image with ice effect
1423
+ canvas.drawImage(ctx.skiaLogoPng, 0, 0, icePaint);
1424
+
1425
+ // Add snowflake particles
1426
+ const snowPaint = Skia.Paint();
1427
+ snowPaint.setColor(Skia.Color("rgba(255, 255, 255, 0.8)"));
1428
+
1429
+ for (let i = 0; i < 60; i++) {
1430
+ const x = 384 + 0.5 * 384;
1431
+ const y = 0.5 * 768;
1432
+ const size = 1 + 0.5 * 3;
1433
+
1434
+ // Add glow to some snowflakes
1435
+ if (0.5 > 0.7) {
1436
+ snowPaint.setImageFilter(
1437
+ Skia.ImageFilter.MakeBlur(size * 2, size * 2, ctx.TileMode.Decal)
1438
+ );
1439
+ canvas.drawCircle(x, y, size * 1.5, snowPaint);
1440
+ } else {
1441
+ snowPaint.setImageFilter(null);
1442
+ canvas.drawCircle(x, y, size, snowPaint);
1443
+ }
1444
+ }
1445
+
1446
+ canvas.restore();
1447
+
1448
+ // Draw dividing line with glowing effect
1449
+ const dividerPaint = Skia.Paint();
1450
+ const dividerShader = Skia.Shader.MakeLinearGradient(
1451
+ { x: 384 - 20, y: 0 },
1452
+ { x: 384 + 20, y: 0 },
1453
+ [
1454
+ Skia.Color("rgba(255, 120, 0, 0.7)"),
1455
+ Skia.Color("rgba(255, 255, 255, 1.0)"),
1456
+ Skia.Color("rgba(100, 180, 255, 0.7)"),
1457
+ ],
1458
+ [0.0, 0.5, 1.0],
1459
+ ctx.TileMode.Clamp
1460
+ );
1461
+ dividerPaint.setShader(dividerShader);
1462
+ dividerPaint.setImageFilter(
1463
+ Skia.ImageFilter.MakeBlur(10, 10, ctx.TileMode.Decal)
1464
+ );
1465
+
1466
+ canvas.drawRect(Skia.XYWHRect(384 - 5, 0, 10, 768), dividerPaint);
1467
+
1468
+ sur.flush();
1469
+ return sur.makeImageSnapshot().encodeToBase64();
1470
+ },
1471
+ { skiaLogoPng, BlendMode, TileMode, PaintStyle, ClipOp }
1472
+ );
1473
+ checkResult(
1474
+ base64,
1475
+ `lighting-image-filters/combined-lighting-fire-ice-${surface.OS}.png`
1476
+ );
1477
+ });
1478
+ });