@shopify/react-native-skia 2.2.19 → 2.2.20

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 (38) hide show
  1. package/android/CMakeLists.txt +2 -0
  2. package/apple/SkiaCVPixelBufferUtils.mm +8 -4
  3. package/cpp/api/JsiSkCanvas.h +22 -0
  4. package/cpp/api/JsiSkDispatcher.cpp +9 -0
  5. package/cpp/api/JsiSkDispatcher.h +149 -0
  6. package/cpp/api/JsiSkImage.h +27 -9
  7. package/cpp/api/JsiSkPicture.h +22 -1
  8. package/cpp/api/JsiSkSurface.h +15 -10
  9. package/cpp/api/recorder/Drawings.h +43 -9
  10. package/cpp/api/recorder/JsiRecorder.h +3 -1
  11. package/cpp/api/recorder/RNRecorder.h +10 -4
  12. package/lib/commonjs/external/reanimated/textures.js +31 -28
  13. package/lib/commonjs/external/reanimated/textures.js.map +1 -1
  14. package/lib/commonjs/renderer/Offscreen.js +1 -7
  15. package/lib/commonjs/renderer/Offscreen.js.map +1 -1
  16. package/lib/commonjs/skia/core/SVG.web.js +9 -2
  17. package/lib/commonjs/skia/core/SVG.web.js.map +1 -1
  18. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
  19. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +4 -0
  20. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  21. package/lib/module/external/reanimated/textures.js +33 -30
  22. package/lib/module/external/reanimated/textures.js.map +1 -1
  23. package/lib/module/renderer/Offscreen.js +1 -8
  24. package/lib/module/renderer/Offscreen.js.map +1 -1
  25. package/lib/module/skia/core/SVG.web.js +9 -2
  26. package/lib/module/skia/core/SVG.web.js.map +1 -1
  27. package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
  28. package/lib/module/sksg/Recorder/ReanimatedRecorder.js +5 -0
  29. package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  30. package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
  31. package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
  32. package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
  33. package/package.json +6 -1
  34. package/src/external/reanimated/textures.tsx +36 -27
  35. package/src/renderer/Offscreen.tsx +1 -7
  36. package/src/skia/core/SVG.web.ts +12 -8
  37. package/src/sksg/Recorder/ReanimatedRecorder.ts +4 -0
  38. package/cpp/api/JsiSkThreadSafeDeletion.h +0 -105
@@ -126,6 +126,8 @@ add_library(
126
126
  "${PROJECT_SOURCE_DIR}/../cpp/api/third_party/CSSColorParser.cpp"
127
127
  "${PROJECT_SOURCE_DIR}/../cpp/api/third_party/base64.cpp"
128
128
  "${PROJECT_SOURCE_DIR}/../cpp/api/third_party/SkottieUtils.cpp"
129
+
130
+ "${PROJECT_SOURCE_DIR}/../cpp/api/JsiSkDispatcher.cpp"
129
131
  ${BACKEND_SOURCES}
130
132
  )
131
133
 
@@ -30,12 +30,16 @@
30
30
  #include <TargetConditionals.h>
31
31
  #if TARGET_RT_BIG_ENDIAN
32
32
  #define FourCC2Str(fourcc) \
33
- (const char[]){*((char *)&fourcc), *(((char *)&fourcc) + 1), \
34
- *(((char *)&fourcc) + 2), *(((char *)&fourcc) + 3), 0}
33
+ (const char[]) { \
34
+ *((char *)&fourcc), *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 2), \
35
+ *(((char *)&fourcc) + 3), 0 \
36
+ }
35
37
  #else
36
38
  #define FourCC2Str(fourcc) \
37
- (const char[]){*(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \
38
- *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0}
39
+ (const char[]) { \
40
+ *(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \
41
+ *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0 \
42
+ }
39
43
  #endif
40
44
 
41
45
  // pragma MARK: TextureHolder
@@ -34,6 +34,7 @@
34
34
  #include "include/core/SkRegion.h"
35
35
  #include "include/core/SkSurface.h"
36
36
  #include "include/core/SkTypeface.h"
37
+ #include "include/gpu/ganesh/GrDirectContext.h"
37
38
 
38
39
  #pragma clang diagnostic pop
39
40
 
@@ -68,6 +69,7 @@ public:
68
69
 
69
70
  JSI_HOST_FUNCTION(drawImage) {
70
71
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
72
+ validateImageForDrawing(runtime, image);
71
73
  auto x = arguments[1].asNumber();
72
74
  auto y = arguments[2].asNumber();
73
75
  std::shared_ptr<SkPaint> paint;
@@ -80,6 +82,7 @@ public:
80
82
 
81
83
  JSI_HOST_FUNCTION(drawImageRect) {
82
84
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
85
+ validateImageForDrawing(runtime, image);
83
86
  auto src = JsiSkRect::fromValue(runtime, arguments[1]);
84
87
  auto dest = JsiSkRect::fromValue(runtime, arguments[2]);
85
88
  auto paint = JsiSkPaint::fromValue(runtime, arguments[3]);
@@ -92,6 +95,7 @@ public:
92
95
 
93
96
  JSI_HOST_FUNCTION(drawImageCubic) {
94
97
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
98
+ validateImageForDrawing(runtime, image);
95
99
  auto x = arguments[1].asNumber();
96
100
  auto y = arguments[2].asNumber();
97
101
  float B = arguments[3].asNumber();
@@ -108,6 +112,7 @@ public:
108
112
 
109
113
  JSI_HOST_FUNCTION(drawImageOptions) {
110
114
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
115
+ validateImageForDrawing(runtime, image);
111
116
  auto x = arguments[1].asNumber();
112
117
  auto y = arguments[2].asNumber();
113
118
  auto fm = (SkFilterMode)arguments[3].asNumber();
@@ -124,6 +129,7 @@ public:
124
129
 
125
130
  JSI_HOST_FUNCTION(drawImageNine) {
126
131
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
132
+ validateImageForDrawing(runtime, image);
127
133
  auto center = JsiSkRect::fromValue(runtime, arguments[1]);
128
134
  auto dest = JsiSkRect::fromValue(runtime, arguments[2]);
129
135
  auto fm = (SkFilterMode)arguments[3].asNumber();
@@ -140,6 +146,7 @@ public:
140
146
 
141
147
  JSI_HOST_FUNCTION(drawImageRectCubic) {
142
148
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
149
+ validateImageForDrawing(runtime, image);
143
150
  auto src = JsiSkRect::fromValue(runtime, arguments[1]);
144
151
  auto dest = JsiSkRect::fromValue(runtime, arguments[2]);
145
152
  float B = arguments[3].asNumber();
@@ -159,6 +166,7 @@ public:
159
166
 
160
167
  JSI_HOST_FUNCTION(drawImageRectOptions) {
161
168
  auto image = JsiSkImage::fromValue(runtime, arguments[0]);
169
+ validateImageForDrawing(runtime, image);
162
170
  auto src = JsiSkRect::fromValue(runtime, arguments[1]);
163
171
  auto dest = JsiSkRect::fromValue(runtime, arguments[2]);
164
172
  auto filter = (SkFilterMode)arguments[3].asNumber();
@@ -695,6 +703,20 @@ public:
695
703
  SkCanvas *getCanvas() { return _canvas; }
696
704
 
697
705
  private:
706
+ void validateImageForDrawing(jsi::Runtime &runtime,
707
+ const sk_sp<SkImage> image) {
708
+ #if !defined(SK_GRAPHITE)
709
+ auto ctx = getContext()->getDirectContext();
710
+ if (!ctx) {
711
+ throw jsi::JSError(runtime, "No GPU context available");
712
+ }
713
+ if (image && !image->isValid(ctx->asRecorder())) {
714
+ throw jsi::JSError(
715
+ runtime, "image used in drawImage() does not belong to this context");
716
+ }
717
+ #endif
718
+ }
719
+
698
720
  SkCanvas *_canvas;
699
721
  };
700
722
  } // namespace RNSkia
@@ -0,0 +1,9 @@
1
+ #include "JsiSkDispatcher.h"
2
+
3
+ namespace RNSkia {
4
+
5
+ // Thread-local storage definition
6
+ thread_local std::shared_ptr<Dispatcher::DispatcherData>
7
+ Dispatcher::_threadDispatcher;
8
+
9
+ } // namespace RNSkia
@@ -0,0 +1,149 @@
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <memory>
5
+ #include <mutex>
6
+ #include <queue>
7
+ #include <thread>
8
+ #include <unordered_map>
9
+
10
+ namespace RNSkia {
11
+
12
+ /**
13
+ * Thread-local dispatcher for managing deferred operations.
14
+ * Each thread gets its own dispatcher instance for queueing operations
15
+ * to be executed on that specific thread.
16
+ */
17
+ class Dispatcher {
18
+ private:
19
+ using Operation = std::function<void()>;
20
+
21
+ struct DispatcherData {
22
+ std::queue<Operation> operationQueue;
23
+ std::mutex queueMutex;
24
+ };
25
+
26
+ // Thread-local storage for dispatcher data
27
+ static thread_local std::shared_ptr<DispatcherData> _threadDispatcher;
28
+
29
+ // Global registry of all dispatchers by thread ID
30
+ static inline std::mutex _registryMutex;
31
+ static inline std::unordered_map<std::thread::id,
32
+ std::weak_ptr<DispatcherData>>
33
+ _dispatcherRegistry;
34
+
35
+ std::shared_ptr<DispatcherData> _data;
36
+ std::thread::id _threadId;
37
+
38
+ public:
39
+ Dispatcher() : _threadId(std::this_thread::get_id()) {
40
+ // Get or create dispatcher data for current thread
41
+ if (!_threadDispatcher) {
42
+ _threadDispatcher = std::make_shared<DispatcherData>();
43
+
44
+ // Register in global registry
45
+ std::lock_guard<std::mutex> lock(_registryMutex);
46
+ _dispatcherRegistry[_threadId] = _threadDispatcher;
47
+ }
48
+ _data = _threadDispatcher;
49
+ }
50
+
51
+ /**
52
+ * Get the dispatcher for the current thread.
53
+ * Creates one if it doesn't exist.
54
+ */
55
+ static std::shared_ptr<Dispatcher> getDispatcher() {
56
+ return std::make_shared<Dispatcher>();
57
+ }
58
+
59
+ /**
60
+ * Get the dispatcher for a specific thread.
61
+ * Returns nullptr if that thread doesn't have a dispatcher.
62
+ */
63
+ static std::shared_ptr<Dispatcher> getDispatcher(std::thread::id threadId) {
64
+ std::lock_guard<std::mutex> lock(_registryMutex);
65
+ auto it = _dispatcherRegistry.find(threadId);
66
+ if (it != _dispatcherRegistry.end()) {
67
+ if (auto data = it->second.lock()) {
68
+ auto dispatcher = std::make_shared<Dispatcher>();
69
+ dispatcher->_data = data;
70
+ dispatcher->_threadId = threadId;
71
+ return dispatcher;
72
+ }
73
+ }
74
+ return nullptr;
75
+ }
76
+
77
+ /**
78
+ * Queue an operation to be executed on the dispatcher's thread.
79
+ * The operation will be executed when processQueue() is called on that
80
+ * thread.
81
+ */
82
+ void run(Operation op) {
83
+ if (!_data)
84
+ return;
85
+
86
+ std::lock_guard<std::mutex> lock(_data->queueMutex);
87
+ _data->operationQueue.push(std::move(op));
88
+ }
89
+
90
+ /**
91
+ * Process all pending operations for the current thread.
92
+ * Must be called from the thread that owns this dispatcher.
93
+ * Returns the number of operations processed.
94
+ */
95
+ size_t processQueue() {
96
+ if (!_data)
97
+ return 0;
98
+
99
+ // Only process if we're on the correct thread
100
+ if (std::this_thread::get_id() != _threadId) {
101
+ return 0;
102
+ }
103
+
104
+ std::queue<Operation> operations;
105
+ {
106
+ std::lock_guard<std::mutex> lock(_data->queueMutex);
107
+ operations.swap(_data->operationQueue);
108
+ }
109
+
110
+ size_t count = operations.size();
111
+ while (!operations.empty()) {
112
+ auto &op = operations.front();
113
+ op();
114
+ operations.pop();
115
+ }
116
+
117
+ return count;
118
+ }
119
+
120
+ /**
121
+ * Get the number of pending operations.
122
+ */
123
+ size_t getPendingCount() const {
124
+ if (!_data)
125
+ return 0;
126
+
127
+ std::lock_guard<std::mutex> lock(_data->queueMutex);
128
+ return _data->operationQueue.size();
129
+ }
130
+
131
+ /**
132
+ * Clean up dispatcher for a thread that's shutting down.
133
+ */
134
+ static void cleanup() {
135
+ if (_threadDispatcher) {
136
+ // Process any remaining operations
137
+ auto dispatcher = getDispatcher();
138
+ dispatcher->processQueue();
139
+
140
+ // Remove from registry
141
+ std::lock_guard<std::mutex> lock(_registryMutex);
142
+ _dispatcherRegistry.erase(std::this_thread::get_id());
143
+
144
+ _threadDispatcher.reset();
145
+ }
146
+ }
147
+ };
148
+
149
+ } // namespace RNSkia
@@ -4,11 +4,11 @@
4
4
  #include <string>
5
5
  #include <utility>
6
6
 
7
+ #include "JsiSkDispatcher.h"
7
8
  #include "JsiSkHostObjects.h"
8
9
  #include "JsiSkImageInfo.h"
9
10
  #include "JsiSkMatrix.h"
10
11
  #include "JsiSkShader.h"
11
- #include "JsiSkThreadSafeDeletion.h"
12
12
  #include "third_party/base64.h"
13
13
 
14
14
  #include "JsiTextureInfo.h"
@@ -63,7 +63,7 @@ inline SkSamplingOptions SamplingOptionsFromValue(jsi::Runtime &runtime,
63
63
 
64
64
  class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
65
65
  private:
66
- ThreadSafeDeletion<SkImage> _deletionHandler;
66
+ std::shared_ptr<Dispatcher> _dispatcher;
67
67
 
68
68
  public:
69
69
  // TODO-API: Properties?
@@ -258,6 +258,10 @@ public:
258
258
  return JsiTextureInfo::toValue(runtime, texInfo);
259
259
  }
260
260
 
261
+ JSI_HOST_FUNCTION(isTextureBacked) {
262
+ return jsi::Value(getObject()->isTextureBacked());
263
+ }
264
+
261
265
  EXPORT_JSI_API_TYPENAME(JsiSkImage, Image)
262
266
 
263
267
  JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkImage, width),
@@ -270,27 +274,41 @@ public:
270
274
  JSI_EXPORT_FUNC(JsiSkImage, readPixels),
271
275
  JSI_EXPORT_FUNC(JsiSkImage, makeNonTextureImage),
272
276
  JSI_EXPORT_FUNC(JsiSkImage, getNativeTextureUnstable),
277
+ JSI_EXPORT_FUNC(JsiSkImage, isTextureBacked),
273
278
  JSI_EXPORT_FUNC(JsiSkImage, dispose))
274
279
 
275
280
  JsiSkImage(std::shared_ptr<RNSkPlatformContext> context,
276
281
  const sk_sp<SkImage> image)
277
282
  : JsiSkWrappingSkPtrHostObject<SkImage>(std::move(context),
278
283
  std::move(image)) {
279
- // Drain any pending deletions when creating new images
280
- ThreadSafeDeletion<SkImage>::drainDeletionQueue();
284
+ // Get the dispatcher for the current thread
285
+ _dispatcher = Dispatcher::getDispatcher();
286
+ // Process any pending operations
287
+ _dispatcher->processQueue();
281
288
  }
282
289
 
283
290
  ~JsiSkImage() override {
284
- // Handle thread-safe deletion
285
- _deletionHandler.handleDeletion(getObject());
286
- // Always clear the object to prevent base class destructor from deleting it
287
- // handleDeletion takes full responsibility for the object's lifetime
291
+ // Queue deletion on the creation thread if needed
292
+ auto image = getObject();
293
+ if (image && _dispatcher) {
294
+ _dispatcher->run([image]() {
295
+ // Image will be deleted when this lambda is destroyed
296
+ });
297
+ }
298
+ // Clear the object to prevent base class destructor from deleting it
288
299
  setObject(nullptr);
289
300
  }
290
301
 
291
302
  size_t getMemoryPressure() const override {
292
303
  auto image = getObject();
293
- return image ? image->imageInfo().computeMinByteSize() : 0;
304
+ if (image) {
305
+ if (image->isTextureBacked()) {
306
+ return image->textureSize();
307
+ } else {
308
+ return image->imageInfo().computeMinByteSize();
309
+ }
310
+ }
311
+ return 0;
294
312
  }
295
313
  };
296
314
 
@@ -3,6 +3,7 @@
3
3
  #include <memory>
4
4
 
5
5
  #include "JsiSkData.h"
6
+ #include "JsiSkDispatcher.h"
6
7
  #include "JsiSkHostObjects.h"
7
8
  #include "JsiSkMatrix.h"
8
9
  #include "JsiSkRect.h"
@@ -20,10 +21,30 @@ namespace RNSkia {
20
21
  namespace jsi = facebook::jsi;
21
22
 
22
23
  class JsiSkPicture : public JsiSkWrappingSkPtrHostObject<SkPicture> {
24
+ private:
25
+ std::shared_ptr<Dispatcher> _dispatcher;
26
+
23
27
  public:
24
28
  JsiSkPicture(std::shared_ptr<RNSkPlatformContext> context,
25
29
  const sk_sp<SkPicture> picture)
26
- : JsiSkWrappingSkPtrHostObject<SkPicture>(context, picture) {}
30
+ : JsiSkWrappingSkPtrHostObject<SkPicture>(context, picture) {
31
+ // Get the dispatcher for the current thread
32
+ _dispatcher = Dispatcher::getDispatcher();
33
+ // Process any pending operations
34
+ _dispatcher->processQueue();
35
+ }
36
+
37
+ ~JsiSkPicture() override {
38
+ // Queue deletion on the creation thread if needed
39
+ auto picture = getObject();
40
+ if (picture && _dispatcher) {
41
+ _dispatcher->run([picture]() {
42
+ // Picture will be deleted when this lambda is destroyed
43
+ });
44
+ }
45
+ // Clear the object to prevent base class destructor from deleting it
46
+ setObject(nullptr);
47
+ }
27
48
 
28
49
  JSI_HOST_FUNCTION(makeShader) {
29
50
  auto tmx = (SkTileMode)arguments[0].asNumber();
@@ -5,8 +5,8 @@
5
5
 
6
6
  #include <jsi/jsi.h>
7
7
 
8
+ #include "JsiSkDispatcher.h"
8
9
  #include "JsiSkHostObjects.h"
9
- #include "JsiSkThreadSafeDeletion.h"
10
10
  #include "JsiTextureInfo.h"
11
11
 
12
12
  #include "JsiSkCanvas.h"
@@ -30,23 +30,28 @@ namespace jsi = facebook::jsi;
30
30
 
31
31
  class JsiSkSurface : public JsiSkWrappingSkPtrHostObject<SkSurface> {
32
32
  private:
33
- ThreadSafeDeletion<SkSurface> _deletionHandler;
33
+ std::shared_ptr<Dispatcher> _dispatcher;
34
34
 
35
35
  public:
36
36
  JsiSkSurface(std::shared_ptr<RNSkPlatformContext> context,
37
37
  sk_sp<SkSurface> surface)
38
38
  : JsiSkWrappingSkPtrHostObject<SkSurface>(std::move(context),
39
- std::move(surface)),
40
- _deletionHandler() {
41
- // Drain any pending deletions when creating new surfaces
42
- ThreadSafeDeletion<SkSurface>::drainDeletionQueue();
39
+ std::move(surface)) {
40
+ // Get the dispatcher for the current thread
41
+ _dispatcher = Dispatcher::getDispatcher();
42
+ // Process any pending operations
43
+ _dispatcher->processQueue();
43
44
  }
44
45
 
45
46
  ~JsiSkSurface() override {
46
- // Handle thread-safe deletion
47
- _deletionHandler.handleDeletion(getObject());
48
- // Always clear the object to prevent base class destructor from deleting it
49
- // handleDeletion takes full responsibility for the object's lifetime
47
+ // Queue deletion on the creation thread if needed
48
+ auto surface = getObject();
49
+ if (surface && _dispatcher) {
50
+ _dispatcher->run([surface]() {
51
+ // Surface will be deleted when this lambda is destroyed
52
+ });
53
+ }
54
+ // Clear the object to prevent base class destructor from deleting it
50
55
  setObject(nullptr);
51
56
  }
52
57
 
@@ -6,6 +6,7 @@
6
6
  #include "Convertor.h"
7
7
  #include "DrawingCtx.h"
8
8
  #include "ImageFit.h"
9
+ #include "RNSkPlatformContext.h"
9
10
 
10
11
  namespace RNSkia {
11
12
 
@@ -468,10 +469,11 @@ struct ImageCmdProps {
468
469
  class ImageCmd : public Command {
469
470
  public:
470
471
  ImageCmdProps props;
472
+ std::shared_ptr<RNSkPlatformContext> _context;
471
473
 
472
- ImageCmd(jsi::Runtime &runtime, const jsi::Object &object,
473
- Variables &variables)
474
- : Command(CommandType::DrawImage) {
474
+ ImageCmd(std::shared_ptr<RNSkPlatformContext> context, jsi::Runtime &runtime,
475
+ const jsi::Object &object, Variables &variables)
476
+ : Command(CommandType::DrawImage), _context(context) {
475
477
  convertProperty(runtime, object, "rect", props.rect, variables);
476
478
  convertProperty(runtime, object, "image", props.image, variables);
477
479
  convertProperty(runtime, object, "sampling", props.sampling, variables);
@@ -484,6 +486,17 @@ public:
484
486
  convertProperty(runtime, object, "rect", props.rect, variables);
485
487
  }
486
488
 
489
+ ~ImageCmd() {
490
+ if (props.image.has_value()) {
491
+ auto image = props.image.value();
492
+ if (image) {
493
+ _context->runOnMainThread([image]() {
494
+ // Image will be deleted when this lambda is destroyed on main thread
495
+ });
496
+ }
497
+ }
498
+ }
499
+
487
500
  void draw(DrawingCtx *ctx) {
488
501
  auto [x, y, width, height, rect, fit, image, sampling] = props;
489
502
  if (image.has_value()) {
@@ -784,13 +797,24 @@ struct PictureCmdProps {
784
797
  class PictureCmd : public Command {
785
798
  public:
786
799
  PictureCmdProps props;
800
+ std::shared_ptr<RNSkPlatformContext> _context;
787
801
 
788
- PictureCmd(jsi::Runtime &runtime, const jsi::Object &object,
802
+ PictureCmd(std::shared_ptr<RNSkPlatformContext> context,
803
+ jsi::Runtime &runtime, const jsi::Object &object,
789
804
  Variables &variables)
790
- : Command(CommandType::DrawPicture) {
805
+ : Command(CommandType::DrawPicture), _context(context) {
791
806
  convertProperty(runtime, object, "picture", props.picture, variables);
792
807
  }
793
808
 
809
+ ~PictureCmd() {
810
+ auto picture = props.picture;
811
+ if (picture) {
812
+ _context->runOnMainThread([picture]() {
813
+ // Picture will be deleted when this lambda is destroyed on main thread
814
+ });
815
+ }
816
+ }
817
+
794
818
  void draw(DrawingCtx *ctx) { ctx->canvas->drawPicture(props.picture); }
795
819
  };
796
820
 
@@ -914,10 +938,11 @@ struct AtlasCmdProps {
914
938
  class AtlasCmd : public Command {
915
939
  public:
916
940
  AtlasCmdProps props;
941
+ std::shared_ptr<RNSkPlatformContext> _context;
917
942
 
918
- AtlasCmd(jsi::Runtime &runtime, const jsi::Object &object,
919
- Variables &variables)
920
- : Command(CommandType::DrawAtlas) {
943
+ AtlasCmd(std::shared_ptr<RNSkPlatformContext> context, jsi::Runtime &runtime,
944
+ const jsi::Object &object, Variables &variables)
945
+ : Command(CommandType::DrawAtlas), _context(context) {
921
946
  convertProperty(runtime, object, "image", props.image, variables);
922
947
  convertProperty(runtime, object, "sprites", props.sprites, variables);
923
948
  convertProperty(runtime, object, "transforms", props.transforms, variables);
@@ -926,6 +951,15 @@ public:
926
951
  convertProperty(runtime, object, "sampling", props.sampling, variables);
927
952
  }
928
953
 
954
+ ~AtlasCmd() {
955
+ auto image = props.image;
956
+ if (image) {
957
+ _context->runOnMainThread([image]() {
958
+ // Image will be deleted when this lambda is destroyed on main thread
959
+ });
960
+ }
961
+ }
962
+
929
963
  void draw(DrawingCtx *ctx) {
930
964
  if (props.image) {
931
965
  // Validate transforms and sprites have the same size
@@ -956,4 +990,4 @@ public:
956
990
  }
957
991
  };
958
992
 
959
- } // namespace RNSkia
993
+ } // namespace RNSkia
@@ -22,7 +22,9 @@ class JsiRecorder : public JsiSkWrappingSharedPtrHostObject<Recorder> {
22
22
  public:
23
23
  JsiRecorder(std::shared_ptr<RNSkPlatformContext> context)
24
24
  : JsiSkWrappingSharedPtrHostObject(std::move(context),
25
- std::make_shared<Recorder>()) {}
25
+ std::make_shared<Recorder>()) {
26
+ getObject()->_context = getContext();
27
+ }
26
28
 
27
29
  JSI_HOST_FUNCTION(savePaint) {
28
30
  getObject()->savePaint(runtime, arguments[0].asObject(runtime),
@@ -15,6 +15,7 @@
15
15
  #include "ImageFilters.h"
16
16
  #include "Paint.h"
17
17
  #include "PathEffects.h"
18
+ #include "RNSkPlatformContext.h"
18
19
  #include "Shaders.h"
19
20
 
20
21
  namespace RNSkia {
@@ -24,8 +25,10 @@ private:
24
25
  std::vector<std::unique_ptr<Command>> commands;
25
26
 
26
27
  public:
28
+ std::shared_ptr<RNSkPlatformContext> _context;
27
29
  Variables variables;
28
30
 
31
+ Recorder() = default;
29
32
  ~Recorder() = default;
30
33
 
31
34
  void savePaint(jsi::Runtime &runtime, const jsi::Object &props,
@@ -216,7 +219,8 @@ public:
216
219
  }
217
220
 
218
221
  void drawImage(jsi::Runtime &runtime, const jsi::Object &props) {
219
- commands.push_back(std::make_unique<ImageCmd>(runtime, props, variables));
222
+ commands.push_back(
223
+ std::make_unique<ImageCmd>(_context, runtime, props, variables));
220
224
  }
221
225
 
222
226
  void drawPoints(jsi::Runtime &runtime, const jsi::Object &props) {
@@ -255,7 +259,8 @@ public:
255
259
  }
256
260
 
257
261
  void drawPicture(jsi::Runtime &runtime, const jsi::Object &props) {
258
- commands.push_back(std::make_unique<PictureCmd>(runtime, props, variables));
262
+ commands.push_back(
263
+ std::make_unique<PictureCmd>(_context, runtime, props, variables));
259
264
  }
260
265
 
261
266
  void drawImageSVG(jsi::Runtime &runtime, const jsi::Object &props) {
@@ -269,7 +274,8 @@ public:
269
274
  }
270
275
 
271
276
  void drawAtlas(jsi::Runtime &runtime, const jsi::Object &props) {
272
- commands.push_back(std::make_unique<AtlasCmd>(runtime, props, variables));
277
+ commands.push_back(
278
+ std::make_unique<AtlasCmd>(_context, runtime, props, variables));
273
279
  }
274
280
 
275
281
  void drawSkottie(jsi::Runtime &runtime, const jsi::Object &props) {
@@ -646,4 +652,4 @@ public:
646
652
  }
647
653
  };
648
654
 
649
- } // namespace RNSkia
655
+ } // namespace RNSkia