@shopify/react-native-skia 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +1 -1
- package/android/cpp/rnskia-android/OpenGLWindowContext.h +1 -1
- package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +1 -1
- package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +1 -1
- package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +1 -1
- package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +1 -1
- package/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureViewManager.java +6 -0
- package/apple/MetalContext.h +2 -2
- package/apple/MetalWindowContext.h +2 -2
- package/apple/MetalWindowContext.mm +7 -4
- package/apple/RNSkApplePlatformContext.mm +1 -1
- package/apple/RNSkAppleView.h +7 -1
- package/apple/RNSkMetalCanvasProvider.h +4 -1
- package/apple/RNSkMetalCanvasProvider.mm +9 -4
- package/apple/SkiaPictureView.mm +4 -0
- package/apple/SkiaUIView.h +1 -0
- package/apple/SkiaUIView.mm +9 -0
- package/cpp/api/JsiSkImage.h +1 -1
- package/cpp/api/JsiSkSurface.h +1 -1
- package/cpp/api/JsiSkiaContext.h +1 -1
- package/cpp/api/recorder/Convertor.h +16 -0
- package/cpp/api/recorder/ImageFilters.h +20 -0
- package/cpp/api/recorder/Paint.h +4 -0
- package/cpp/api/recorder/RNRecorder.h +6 -0
- package/cpp/rnskia/{DawnContext.h → RNDawnContext.h} +3 -3
- package/cpp/rnskia/{DawnWindowContext.cpp → RNDawnWindowContext.cpp} +3 -3
- package/cpp/rnskia/{DawnWindowContext.h → RNDawnWindowContext.h} +2 -2
- package/cpp/rnskia/RNSkPlatformContext.h +1 -1
- package/lib/commonjs/dom/types/Drawings.d.ts +4 -1
- package/lib/commonjs/dom/types/Drawings.js.map +1 -1
- package/lib/commonjs/dom/types/NodeType.d.ts +2 -1
- package/lib/commonjs/dom/types/NodeType.js +2 -0
- package/lib/commonjs/dom/types/NodeType.js.map +1 -1
- package/lib/commonjs/renderer/Canvas.d.ts +2 -1
- package/lib/commonjs/renderer/Canvas.js +2 -0
- package/lib/commonjs/renderer/Canvas.js.map +1 -1
- package/lib/commonjs/renderer/__tests__/e2e/ImageFilter.spec.d.ts +1 -0
- package/lib/commonjs/renderer/components/ImageFilter.d.ts +4 -0
- package/lib/commonjs/renderer/components/ImageFilter.js +13 -0
- package/lib/commonjs/renderer/components/ImageFilter.js.map +1 -0
- package/lib/commonjs/renderer/components/index.d.ts +1 -0
- package/lib/commonjs/renderer/components/index.js +11 -0
- package/lib/commonjs/renderer/components/index.js.map +1 -1
- package/lib/commonjs/skia/types/Matrix4.d.ts +4 -0
- package/lib/commonjs/skia/types/Matrix4.js +18 -1
- package/lib/commonjs/skia/types/Matrix4.js.map +1 -1
- package/lib/commonjs/sksg/Elements.d.ts +2 -1
- package/lib/commonjs/sksg/Elements.js.map +1 -1
- package/lib/commonjs/sksg/Node.d.ts +1 -1
- package/lib/commonjs/sksg/Node.js +1 -1
- package/lib/commonjs/sksg/Node.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Player.js +9 -3
- package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js +11 -1
- package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js.map +1 -1
- package/lib/commonjs/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
- package/lib/commonjs/specs/SkiaPictureViewNativeComponent.js.map +1 -1
- package/lib/module/dom/types/Drawings.d.ts +4 -1
- package/lib/module/dom/types/Drawings.js.map +1 -1
- package/lib/module/dom/types/NodeType.d.ts +2 -1
- package/lib/module/dom/types/NodeType.js +2 -0
- package/lib/module/dom/types/NodeType.js.map +1 -1
- package/lib/module/renderer/Canvas.d.ts +2 -1
- package/lib/module/renderer/Canvas.js +2 -0
- package/lib/module/renderer/Canvas.js.map +1 -1
- package/lib/module/renderer/__tests__/e2e/ImageFilter.spec.d.ts +1 -0
- package/lib/module/renderer/components/ImageFilter.d.ts +4 -0
- package/lib/module/renderer/components/ImageFilter.js +5 -0
- package/lib/module/renderer/components/ImageFilter.js.map +1 -0
- package/lib/module/renderer/components/index.d.ts +1 -0
- package/lib/module/renderer/components/index.js +1 -0
- package/lib/module/renderer/components/index.js.map +1 -1
- package/lib/module/skia/types/Matrix4.d.ts +4 -0
- package/lib/module/skia/types/Matrix4.js +16 -0
- package/lib/module/skia/types/Matrix4.js.map +1 -1
- package/lib/module/sksg/Elements.d.ts +2 -1
- package/lib/module/sksg/Elements.js.map +1 -1
- package/lib/module/sksg/Node.d.ts +1 -1
- package/lib/module/sksg/Node.js +1 -1
- package/lib/module/sksg/Node.js.map +1 -1
- package/lib/module/sksg/Recorder/Player.js +9 -3
- package/lib/module/sksg/Recorder/Player.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/ImageFilters.js +11 -1
- package/lib/module/sksg/Recorder/commands/ImageFilters.js.map +1 -1
- package/lib/module/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
- package/lib/module/specs/SkiaPictureViewNativeComponent.js.map +1 -1
- package/lib/typescript/lib/commonjs/renderer/Canvas.d.ts +2 -1
- package/lib/typescript/lib/commonjs/renderer/components/ImageFilter.d.ts +2 -0
- package/lib/typescript/lib/commonjs/skia/types/Matrix4.d.ts +1 -0
- package/lib/typescript/lib/module/mock/index.d.ts +2 -0
- package/lib/typescript/lib/module/renderer/Canvas.d.ts +2 -1
- package/lib/typescript/lib/module/renderer/components/ImageFilter.d.ts +2 -0
- package/lib/typescript/lib/module/renderer/components/index.d.ts +1 -0
- package/lib/typescript/lib/module/skia/types/Matrix4.d.ts +1 -0
- package/lib/typescript/src/dom/types/Drawings.d.ts +4 -1
- package/lib/typescript/src/dom/types/NodeType.d.ts +2 -1
- package/lib/typescript/src/renderer/Canvas.d.ts +2 -1
- package/lib/typescript/src/renderer/__tests__/e2e/ImageFilter.spec.d.ts +1 -0
- package/lib/typescript/src/renderer/components/ImageFilter.d.ts +4 -0
- package/lib/typescript/src/renderer/components/index.d.ts +1 -0
- package/lib/typescript/src/skia/types/Matrix4.d.ts +4 -0
- package/lib/typescript/src/sksg/Elements.d.ts +2 -1
- package/lib/typescript/src/sksg/Node.d.ts +1 -1
- package/lib/typescript/src/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
- package/libs/android/arm64-v8a/libskia.a +0 -0
- package/libs/android/armeabi-v7a/libskia.a +0 -0
- package/libs/android/x86/libskia.a +0 -0
- package/libs/android/x86_64/libskia.a +0 -0
- package/libs/apple/libpathops.xcframework/Info.plist +15 -15
- package/libs/apple/libskia.xcframework/Info.plist +11 -11
- package/libs/apple/libskia.xcframework/ios-arm64_arm64e/libskia.a +0 -0
- package/libs/apple/libskia.xcframework/ios-arm64_arm64e_x86_64-simulator/libskia.a +0 -0
- package/libs/apple/libskia.xcframework/macos-arm64_x86_64/libskia.a +0 -0
- package/libs/apple/libskia.xcframework/tvos-arm64_arm64e/libskia.a +0 -0
- package/libs/apple/libskia.xcframework/tvos-arm64_arm64e_x86_64-simulator/libskia.a +0 -0
- package/libs/apple/libskottie.xcframework/Info.plist +16 -16
- package/libs/apple/libskparagraph.xcframework/Info.plist +8 -8
- package/libs/apple/libsksg.xcframework/Info.plist +6 -6
- package/libs/apple/libskshaper.xcframework/Info.plist +10 -10
- package/libs/apple/libskunicode_core.xcframework/Info.plist +10 -10
- package/libs/apple/libskunicode_libgrapheme.xcframework/Info.plist +14 -14
- package/libs/apple/libsvg.xcframework/Info.plist +15 -15
- package/package.json +1 -1
- package/react-native-skia.podspec +5 -5
- package/src/dom/types/Drawings.ts +5 -0
- package/src/dom/types/NodeType.ts +2 -0
- package/src/renderer/Canvas.tsx +3 -0
- package/src/renderer/__tests__/FitBox.spec.tsx +556 -4
- package/src/renderer/__tests__/e2e/ImageFilter.spec.tsx +99 -0
- package/src/renderer/__tests__/e2e/Paint.spec.tsx +18 -0
- package/src/renderer/__tests__/e2e/Skottie.spec.tsx +24 -1
- package/src/renderer/__tests__/setup.tsx +10 -0
- package/src/renderer/components/ImageFilter.tsx +8 -0
- package/src/renderer/components/index.ts +1 -0
- package/src/skia/types/Matrix4.ts +16 -0
- package/src/sksg/Elements.tsx +2 -0
- package/src/sksg/Node.ts +1 -0
- package/src/sksg/Recorder/Player.ts +7 -7
- package/src/sksg/Recorder/commands/ImageFilters.ts +11 -1
- package/src/specs/SkiaPictureViewNativeComponent.ts +1 -0
- /package/cpp/rnskia/{DawnUtils.h → RNDawnUtils.h} +0 -0
- /package/cpp/rnskia/{ImageProvider.h → RNImageProvider.h} +0 -0
- /package/cpp/rnskia/{WindowContext.h → RNWindowContext.h} +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
import fs from "fs";
|
2
|
-
import
|
2
|
+
import nodePath from "path";
|
3
3
|
|
4
4
|
import React from "react";
|
5
5
|
|
@@ -43,7 +43,7 @@ describe("FitBox", () => {
|
|
43
43
|
const image = Skia.Image.MakeImageFromEncoded(
|
44
44
|
Skia.Data.fromBytes(
|
45
45
|
fs.readFileSync(
|
46
|
-
|
46
|
+
nodePath.resolve(__dirname, "../../skia/__tests__/assets/box.png")
|
47
47
|
)
|
48
48
|
)
|
49
49
|
)!;
|
@@ -71,7 +71,7 @@ describe("FitBox", () => {
|
|
71
71
|
const image = Skia.Image.MakeImageFromEncoded(
|
72
72
|
Skia.Data.fromBytes(
|
73
73
|
fs.readFileSync(
|
74
|
-
|
74
|
+
nodePath.resolve(__dirname, "../../skia/__tests__/assets/box.png")
|
75
75
|
)
|
76
76
|
)
|
77
77
|
)!;
|
@@ -100,7 +100,7 @@ describe("FitBox", () => {
|
|
100
100
|
const image = Skia.Image.MakeImageFromEncoded(
|
101
101
|
Skia.Data.fromBytes(
|
102
102
|
fs.readFileSync(
|
103
|
-
|
103
|
+
nodePath.resolve(__dirname, "../../skia/__tests__/assets/box2.png")
|
104
104
|
)
|
105
105
|
)
|
106
106
|
)!;
|
@@ -123,4 +123,556 @@ describe("FitBox", () => {
|
|
123
123
|
);
|
124
124
|
processResult(surface, "snapshots/drawings/rotated-scaled-image.png");
|
125
125
|
});
|
126
|
+
|
127
|
+
test("transform simple rectangle with fitbox fill", () => {
|
128
|
+
const { Skia, processTransform2d } = importSkia();
|
129
|
+
|
130
|
+
const path = Skia.Path.Make();
|
131
|
+
path.moveTo(0, 0);
|
132
|
+
path.lineTo(10, 0);
|
133
|
+
path.lineTo(10, 10);
|
134
|
+
path.lineTo(0, 10);
|
135
|
+
path.close();
|
136
|
+
|
137
|
+
const src = path.computeTightBounds();
|
138
|
+
const dst = Skia.XYWHRect(0, 0, 20, 20);
|
139
|
+
const transform = fitbox("fill", src, dst);
|
140
|
+
const c = path.copy().transform(processTransform2d(transform));
|
141
|
+
const newBounds = c.computeTightBounds();
|
142
|
+
expect(newBounds.x).toBe(0);
|
143
|
+
expect(newBounds.y).toBe(0);
|
144
|
+
expect(newBounds.width).toBe(20);
|
145
|
+
expect(newBounds.height).toBe(20);
|
146
|
+
});
|
147
|
+
|
148
|
+
test("transform simple rectangle with fitbox contain", () => {
|
149
|
+
const { Skia, processTransform2d } = importSkia();
|
150
|
+
|
151
|
+
const path = Skia.Path.Make();
|
152
|
+
path.moveTo(0, 0);
|
153
|
+
path.lineTo(10, 0);
|
154
|
+
path.lineTo(10, 5);
|
155
|
+
path.lineTo(0, 5);
|
156
|
+
path.close();
|
157
|
+
|
158
|
+
const src = path.computeTightBounds();
|
159
|
+
|
160
|
+
const dst = Skia.XYWHRect(0, 0, 20, 20);
|
161
|
+
const matrix = fitbox("contain", src, dst);
|
162
|
+
|
163
|
+
path.transform(processTransform2d(matrix));
|
164
|
+
const newBounds = path.computeTightBounds();
|
165
|
+
expect(newBounds.x).toBe(0);
|
166
|
+
expect(newBounds.y).toBe(5);
|
167
|
+
expect(newBounds.width).toBe(20);
|
168
|
+
expect(newBounds.height).toBe(10);
|
169
|
+
});
|
170
|
+
|
171
|
+
test("transform line with fitbox scale", () => {
|
172
|
+
const { Skia, processTransform2d } = importSkia();
|
173
|
+
const path = Skia.Path.Make();
|
174
|
+
path.moveTo(0, 0);
|
175
|
+
path.lineTo(5, 5);
|
176
|
+
|
177
|
+
const src = path.computeTightBounds();
|
178
|
+
const dst = Skia.XYWHRect(0, 0, 10, 10);
|
179
|
+
const matrix = fitbox("fill", src, dst);
|
180
|
+
path.transform(processTransform2d(matrix));
|
181
|
+
const newBounds = path.computeTightBounds();
|
182
|
+
expect(newBounds.x).toBe(0);
|
183
|
+
expect(newBounds.y).toBe(0);
|
184
|
+
expect(newBounds.width).toBe(10);
|
185
|
+
expect(newBounds.height).toBe(10);
|
186
|
+
});
|
187
|
+
|
188
|
+
test("transform with offset destination", () => {
|
189
|
+
const { Skia, processTransform2d } = importSkia();
|
190
|
+
const path = Skia.Path.Make();
|
191
|
+
path.moveTo(0, 0);
|
192
|
+
path.lineTo(4, 0);
|
193
|
+
path.lineTo(4, 4);
|
194
|
+
path.lineTo(0, 4);
|
195
|
+
path.close();
|
196
|
+
|
197
|
+
const src = path.computeTightBounds();
|
198
|
+
const dst = Skia.XYWHRect(10, 20, 8, 8);
|
199
|
+
const matrix = fitbox("fill", src, dst);
|
200
|
+
path.transform(processTransform2d(matrix));
|
201
|
+
const newBounds = path.computeTightBounds();
|
202
|
+
expect(newBounds.x).toBe(10);
|
203
|
+
expect(newBounds.y).toBe(20);
|
204
|
+
expect(newBounds.width).toBe(8);
|
205
|
+
expect(newBounds.height).toBe(8);
|
206
|
+
});
|
207
|
+
|
208
|
+
test("transform with cover fit mode", () => {
|
209
|
+
const { Skia, processTransform2d } = importSkia();
|
210
|
+
const path = Skia.Path.Make();
|
211
|
+
path.moveTo(0, 0);
|
212
|
+
path.lineTo(10, 0);
|
213
|
+
path.lineTo(10, 5);
|
214
|
+
path.lineTo(0, 5);
|
215
|
+
path.close();
|
216
|
+
|
217
|
+
const src = path.computeTightBounds();
|
218
|
+
const dst = Skia.XYWHRect(0, 0, 10, 10);
|
219
|
+
const matrix = fitbox("cover", src, dst);
|
220
|
+
path.transform(processTransform2d(matrix));
|
221
|
+
const newBounds = path.computeTightBounds();
|
222
|
+
expect(newBounds.x).toBe(-5);
|
223
|
+
expect(newBounds.y).toBe(0);
|
224
|
+
expect(newBounds.width).toBe(20);
|
225
|
+
expect(newBounds.height).toBe(10);
|
226
|
+
});
|
227
|
+
|
228
|
+
describe("rect2rect", () => {
|
229
|
+
test("basic scaling from unit square to double size", () => {
|
230
|
+
const { Skia, rect2rect } = importSkia();
|
231
|
+
const src = Skia.XYWHRect(0, 0, 1, 1);
|
232
|
+
const dst = Skia.XYWHRect(0, 0, 2, 2);
|
233
|
+
const result = rect2rect(src, dst);
|
234
|
+
|
235
|
+
expect(result).toEqual([
|
236
|
+
{ translateX: 0 },
|
237
|
+
{ translateY: 0 },
|
238
|
+
{ scaleX: 2 },
|
239
|
+
{ scaleY: 2 },
|
240
|
+
]);
|
241
|
+
});
|
242
|
+
|
243
|
+
test("scaling with translation", () => {
|
244
|
+
const { Skia, rect2rect } = importSkia();
|
245
|
+
const src = Skia.XYWHRect(0, 0, 10, 10);
|
246
|
+
const dst = Skia.XYWHRect(5, 5, 20, 20);
|
247
|
+
const result = rect2rect(src, dst);
|
248
|
+
|
249
|
+
expect(result).toEqual([
|
250
|
+
{ translateX: 5 },
|
251
|
+
{ translateY: 5 },
|
252
|
+
{ scaleX: 2 },
|
253
|
+
{ scaleY: 2 },
|
254
|
+
]);
|
255
|
+
});
|
256
|
+
|
257
|
+
test("scaling with non-zero source origin", () => {
|
258
|
+
const { Skia, rect2rect } = importSkia();
|
259
|
+
const src = Skia.XYWHRect(10, 10, 20, 20);
|
260
|
+
const dst = Skia.XYWHRect(0, 0, 40, 40);
|
261
|
+
const result = rect2rect(src, dst);
|
262
|
+
|
263
|
+
expect(result).toEqual([
|
264
|
+
{ translateX: -20 },
|
265
|
+
{ translateY: -20 },
|
266
|
+
{ scaleX: 2 },
|
267
|
+
{ scaleY: 2 },
|
268
|
+
]);
|
269
|
+
});
|
270
|
+
|
271
|
+
test("shrinking transformation", () => {
|
272
|
+
const { Skia, rect2rect } = importSkia();
|
273
|
+
const src = Skia.XYWHRect(0, 0, 100, 100);
|
274
|
+
const dst = Skia.XYWHRect(0, 0, 50, 50);
|
275
|
+
const result = rect2rect(src, dst);
|
276
|
+
|
277
|
+
expect(result).toEqual([
|
278
|
+
{ translateX: 0 },
|
279
|
+
{ translateY: 0 },
|
280
|
+
{ scaleX: 0.5 },
|
281
|
+
{ scaleY: 0.5 },
|
282
|
+
]);
|
283
|
+
});
|
284
|
+
|
285
|
+
test("non-uniform scaling", () => {
|
286
|
+
const { Skia, rect2rect } = importSkia();
|
287
|
+
const src = Skia.XYWHRect(0, 0, 10, 20);
|
288
|
+
const dst = Skia.XYWHRect(0, 0, 30, 40);
|
289
|
+
const result = rect2rect(src, dst);
|
290
|
+
|
291
|
+
expect(result).toEqual([
|
292
|
+
{ translateX: 0 },
|
293
|
+
{ translateY: 0 },
|
294
|
+
{ scaleX: 3 },
|
295
|
+
{ scaleY: 2 },
|
296
|
+
]);
|
297
|
+
});
|
298
|
+
|
299
|
+
test("transformation with offset source and destination", () => {
|
300
|
+
const { Skia, rect2rect } = importSkia();
|
301
|
+
const src = Skia.XYWHRect(5, 10, 15, 20);
|
302
|
+
const dst = Skia.XYWHRect(50, 100, 30, 40);
|
303
|
+
const result = rect2rect(src, dst);
|
304
|
+
|
305
|
+
expect(result).toEqual([
|
306
|
+
{ translateX: 40 },
|
307
|
+
{ translateY: 80 },
|
308
|
+
{ scaleX: 2 },
|
309
|
+
{ scaleY: 2 },
|
310
|
+
]);
|
311
|
+
});
|
312
|
+
|
313
|
+
test("identity transformation", () => {
|
314
|
+
const { Skia, rect2rect } = importSkia();
|
315
|
+
const src = Skia.XYWHRect(10, 20, 30, 40);
|
316
|
+
const dst = Skia.XYWHRect(10, 20, 30, 40);
|
317
|
+
const result = rect2rect(src, dst);
|
318
|
+
|
319
|
+
expect(result).toEqual([
|
320
|
+
{ translateX: 0 },
|
321
|
+
{ translateY: 0 },
|
322
|
+
{ scaleX: 1 },
|
323
|
+
{ scaleY: 1 },
|
324
|
+
]);
|
325
|
+
});
|
326
|
+
|
327
|
+
test("fractional scaling", () => {
|
328
|
+
const { Skia, rect2rect } = importSkia();
|
329
|
+
const src = Skia.XYWHRect(0, 0, 3, 4);
|
330
|
+
const dst = Skia.XYWHRect(0, 0, 1.5, 2);
|
331
|
+
const result = rect2rect(src, dst);
|
332
|
+
|
333
|
+
expect(result).toEqual([
|
334
|
+
{ translateX: 0 },
|
335
|
+
{ translateY: 0 },
|
336
|
+
{ scaleX: 0.5 },
|
337
|
+
{ scaleY: 0.5 },
|
338
|
+
]);
|
339
|
+
});
|
340
|
+
|
341
|
+
test("complex transformation with negative coordinates", () => {
|
342
|
+
const { Skia, rect2rect } = importSkia();
|
343
|
+
const src = Skia.XYWHRect(-10, -20, 20, 30);
|
344
|
+
const dst = Skia.XYWHRect(10, 5, 40, 60);
|
345
|
+
const result = rect2rect(src, dst);
|
346
|
+
|
347
|
+
expect(result).toEqual([
|
348
|
+
{ translateX: 30 },
|
349
|
+
{ translateY: 45 },
|
350
|
+
{ scaleX: 2 },
|
351
|
+
{ scaleY: 2 },
|
352
|
+
]);
|
353
|
+
});
|
354
|
+
|
355
|
+
test("very small dimensions", () => {
|
356
|
+
const { Skia, rect2rect } = importSkia();
|
357
|
+
const src = Skia.XYWHRect(0, 0, 0.1, 0.1);
|
358
|
+
const dst = Skia.XYWHRect(0, 0, 1, 1);
|
359
|
+
const result = rect2rect(src, dst);
|
360
|
+
|
361
|
+
expect(result[0]).toEqual({ translateX: 0 });
|
362
|
+
expect(result[1]).toEqual({ translateY: 0 });
|
363
|
+
expect(result[2].scaleX).toBeCloseTo(10, 5);
|
364
|
+
expect(result[3].scaleY).toBeCloseTo(10, 5);
|
365
|
+
});
|
366
|
+
});
|
367
|
+
|
368
|
+
describe("fitRects", () => {
|
369
|
+
test("fill mode - source fills destination completely", () => {
|
370
|
+
const { Skia, fitRects } = importSkia();
|
371
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
372
|
+
const dstRect = Skia.XYWHRect(10, 20, 200, 100);
|
373
|
+
const result = fitRects("fill", srcRect, dstRect);
|
374
|
+
|
375
|
+
expect(result.src).toEqual({
|
376
|
+
x: 0,
|
377
|
+
y: 0,
|
378
|
+
width: 100,
|
379
|
+
height: 50,
|
380
|
+
});
|
381
|
+
expect(result.dst).toEqual({
|
382
|
+
x: 10,
|
383
|
+
y: 20,
|
384
|
+
width: 200,
|
385
|
+
height: 100,
|
386
|
+
});
|
387
|
+
});
|
388
|
+
|
389
|
+
test("contain mode - source fits inside destination maintaining aspect ratio", () => {
|
390
|
+
const { Skia, fitRects } = importSkia();
|
391
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
392
|
+
const dstRect = Skia.XYWHRect(0, 0, 200, 200);
|
393
|
+
const result = fitRects("contain", srcRect, dstRect);
|
394
|
+
|
395
|
+
expect(result.src).toEqual({
|
396
|
+
x: 0,
|
397
|
+
y: 0,
|
398
|
+
width: 100,
|
399
|
+
height: 50,
|
400
|
+
});
|
401
|
+
// Should maintain aspect ratio 2:1, so width=200, height=100, centered
|
402
|
+
expect(result.dst.width).toBe(200);
|
403
|
+
expect(result.dst.height).toBe(100);
|
404
|
+
expect(result.dst.x).toBe(0);
|
405
|
+
expect(result.dst.y).toBe(50); // Centered vertically
|
406
|
+
});
|
407
|
+
|
408
|
+
test("cover mode - source covers destination completely", () => {
|
409
|
+
const { Skia, fitRects } = importSkia();
|
410
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
411
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 100);
|
412
|
+
const result = fitRects("cover", srcRect, dstRect);
|
413
|
+
|
414
|
+
// Source should be cropped to fit destination aspect ratio
|
415
|
+
expect(result.src.width).toBe(50); // Cropped to maintain 1:1 aspect ratio
|
416
|
+
expect(result.src.height).toBe(50);
|
417
|
+
expect(result.src.x).toBe(25); // Centered horizontally in original rect
|
418
|
+
expect(result.src.y).toBe(0);
|
419
|
+
|
420
|
+
expect(result.dst).toEqual({
|
421
|
+
x: 0,
|
422
|
+
y: 0,
|
423
|
+
width: 100,
|
424
|
+
height: 100,
|
425
|
+
});
|
426
|
+
});
|
427
|
+
|
428
|
+
test("fitWidth mode - fits to width, adjusts height", () => {
|
429
|
+
const { Skia, fitRects } = importSkia();
|
430
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
431
|
+
const dstRect = Skia.XYWHRect(0, 0, 200, 200);
|
432
|
+
const result = fitRects("fitWidth", srcRect, dstRect);
|
433
|
+
|
434
|
+
// Source is scaled to fit the destination width aspect ratio
|
435
|
+
expect(result.src.width).toBe(100);
|
436
|
+
expect(result.src.height).toBe(100);
|
437
|
+
expect(result.src.x).toBe(0);
|
438
|
+
expect(result.src.y).toBe(-25); // Extends beyond original rect to maintain aspect ratio
|
439
|
+
|
440
|
+
// Destination uses the full destination rectangle
|
441
|
+
expect(result.dst.width).toBe(200);
|
442
|
+
expect(result.dst.height).toBe(200);
|
443
|
+
expect(result.dst.x).toBe(0);
|
444
|
+
expect(result.dst.y).toBe(0);
|
445
|
+
});
|
446
|
+
|
447
|
+
test("fitHeight mode - fits to height, adjusts width", () => {
|
448
|
+
const { Skia, fitRects } = importSkia();
|
449
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
450
|
+
const dstRect = Skia.XYWHRect(0, 0, 200, 200);
|
451
|
+
const result = fitRects("fitHeight", srcRect, dstRect);
|
452
|
+
|
453
|
+
// Source is cropped to fit the destination height aspect ratio
|
454
|
+
expect(result.src.width).toBe(50);
|
455
|
+
expect(result.src.height).toBe(50);
|
456
|
+
expect(result.src.x).toBe(25); // Centered horizontally in original rect
|
457
|
+
expect(result.src.y).toBe(0);
|
458
|
+
|
459
|
+
// Destination uses the full destination rectangle
|
460
|
+
expect(result.dst.width).toBe(200);
|
461
|
+
expect(result.dst.height).toBe(200);
|
462
|
+
expect(result.dst.x).toBe(0);
|
463
|
+
expect(result.dst.y).toBe(0);
|
464
|
+
});
|
465
|
+
|
466
|
+
test("none mode - uses minimum dimensions", () => {
|
467
|
+
const { Skia, fitRects } = importSkia();
|
468
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 50);
|
469
|
+
const dstRect = Skia.XYWHRect(0, 0, 80, 120);
|
470
|
+
const result = fitRects("none", srcRect, dstRect);
|
471
|
+
|
472
|
+
// Should use minimum of source and destination dimensions
|
473
|
+
expect(result.src.width).toBe(80); // min(100, 80)
|
474
|
+
expect(result.src.height).toBe(50); // min(50, 120)
|
475
|
+
expect(result.src.x).toBe(10); // Centered in source rect
|
476
|
+
expect(result.src.y).toBe(0);
|
477
|
+
|
478
|
+
expect(result.dst.width).toBe(80);
|
479
|
+
expect(result.dst.height).toBe(50);
|
480
|
+
expect(result.dst.x).toBe(0);
|
481
|
+
expect(result.dst.y).toBe(35); // Centered in destination rect
|
482
|
+
});
|
483
|
+
|
484
|
+
test("scaleDown mode - scales down if needed", () => {
|
485
|
+
const { Skia, fitRects } = importSkia();
|
486
|
+
const srcRect = Skia.XYWHRect(0, 0, 200, 100);
|
487
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 80);
|
488
|
+
const result = fitRects("scaleDown", srcRect, dstRect);
|
489
|
+
|
490
|
+
expect(result.src).toEqual({
|
491
|
+
x: 0,
|
492
|
+
y: 0,
|
493
|
+
width: 200,
|
494
|
+
height: 100,
|
495
|
+
});
|
496
|
+
|
497
|
+
// Should scale down to fit within destination
|
498
|
+
expect(result.dst.width).toBe(100);
|
499
|
+
expect(result.dst.height).toBe(50); // Maintains aspect ratio 2:1
|
500
|
+
expect(result.dst.x).toBe(0);
|
501
|
+
expect(result.dst.y).toBe(15); // Centered vertically
|
502
|
+
});
|
503
|
+
|
504
|
+
test("scaleDown mode - no scaling if source is smaller", () => {
|
505
|
+
const { Skia, fitRects } = importSkia();
|
506
|
+
const srcRect = Skia.XYWHRect(0, 0, 50, 25);
|
507
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 100);
|
508
|
+
const result = fitRects("scaleDown", srcRect, dstRect);
|
509
|
+
|
510
|
+
expect(result.src).toEqual({
|
511
|
+
x: 0,
|
512
|
+
y: 0,
|
513
|
+
width: 50,
|
514
|
+
height: 25,
|
515
|
+
});
|
516
|
+
|
517
|
+
// Should not scale up, keep original size
|
518
|
+
expect(result.dst.width).toBe(50);
|
519
|
+
expect(result.dst.height).toBe(25);
|
520
|
+
expect(result.dst.x).toBe(25); // Centered horizontally
|
521
|
+
expect(result.dst.y).toBe(37.5); // Centered vertically
|
522
|
+
});
|
523
|
+
|
524
|
+
test("with offset source rectangle", () => {
|
525
|
+
const { Skia, fitRects } = importSkia();
|
526
|
+
const srcRect = Skia.XYWHRect(50, 100, 100, 50);
|
527
|
+
const dstRect = Skia.XYWHRect(10, 20, 200, 100);
|
528
|
+
const result = fitRects("fill", srcRect, dstRect);
|
529
|
+
|
530
|
+
expect(result.src).toEqual({
|
531
|
+
x: 50,
|
532
|
+
y: 100,
|
533
|
+
width: 100,
|
534
|
+
height: 50,
|
535
|
+
});
|
536
|
+
expect(result.dst).toEqual({
|
537
|
+
x: 10,
|
538
|
+
y: 20,
|
539
|
+
width: 200,
|
540
|
+
height: 100,
|
541
|
+
});
|
542
|
+
});
|
543
|
+
|
544
|
+
test("with very small source rectangle", () => {
|
545
|
+
const { Skia, fitRects } = importSkia();
|
546
|
+
const srcRect = Skia.XYWHRect(0, 0, 1, 1);
|
547
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 100);
|
548
|
+
const result = fitRects("contain", srcRect, dstRect);
|
549
|
+
|
550
|
+
expect(result.src).toEqual({
|
551
|
+
x: 0,
|
552
|
+
y: 0,
|
553
|
+
width: 1,
|
554
|
+
height: 1,
|
555
|
+
});
|
556
|
+
expect(result.dst).toEqual({
|
557
|
+
x: 0,
|
558
|
+
y: 0,
|
559
|
+
width: 100,
|
560
|
+
height: 100,
|
561
|
+
});
|
562
|
+
});
|
563
|
+
|
564
|
+
test("with zero dimensions - should return empty sizes", () => {
|
565
|
+
const { Skia, fitRects } = importSkia();
|
566
|
+
const srcRect = Skia.XYWHRect(0, 0, 0, 100);
|
567
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 100);
|
568
|
+
const result = fitRects("fill", srcRect, dstRect);
|
569
|
+
|
570
|
+
expect(result.src).toEqual({
|
571
|
+
x: 0,
|
572
|
+
y: 50,
|
573
|
+
width: 0,
|
574
|
+
height: 0,
|
575
|
+
});
|
576
|
+
expect(result.dst).toEqual({
|
577
|
+
x: 50,
|
578
|
+
y: 50,
|
579
|
+
width: 0,
|
580
|
+
height: 0,
|
581
|
+
});
|
582
|
+
});
|
583
|
+
|
584
|
+
test("cover mode with wide source rectangle", () => {
|
585
|
+
const { Skia, fitRects } = importSkia();
|
586
|
+
const srcRect = Skia.XYWHRect(0, 0, 200, 50);
|
587
|
+
const dstRect = Skia.XYWHRect(0, 0, 100, 100);
|
588
|
+
const result = fitRects("cover", srcRect, dstRect);
|
589
|
+
|
590
|
+
// Source should be cropped to square aspect ratio
|
591
|
+
expect(result.src.width).toBe(50); // Cropped width
|
592
|
+
expect(result.src.height).toBe(50);
|
593
|
+
expect(result.src.x).toBe(75); // Centered horizontally
|
594
|
+
expect(result.src.y).toBe(0);
|
595
|
+
|
596
|
+
expect(result.dst).toEqual({
|
597
|
+
x: 0,
|
598
|
+
y: 0,
|
599
|
+
width: 100,
|
600
|
+
height: 100,
|
601
|
+
});
|
602
|
+
});
|
603
|
+
|
604
|
+
test("cover mode with tall destination rectangle", () => {
|
605
|
+
const { Skia, fitRects } = importSkia();
|
606
|
+
const srcRect = Skia.XYWHRect(0, 0, 100, 100);
|
607
|
+
const dstRect = Skia.XYWHRect(0, 0, 50, 200);
|
608
|
+
const result = fitRects("cover", srcRect, dstRect);
|
609
|
+
|
610
|
+
// Source should be cropped to 1:4 aspect ratio
|
611
|
+
expect(result.src.width).toBe(25); // Cropped to match destination aspect ratio
|
612
|
+
expect(result.src.height).toBe(100);
|
613
|
+
expect(result.src.x).toBe(37.5); // Centered horizontally
|
614
|
+
expect(result.src.y).toBe(0);
|
615
|
+
|
616
|
+
expect(result.dst).toEqual({
|
617
|
+
x: 0,
|
618
|
+
y: 0,
|
619
|
+
width: 50,
|
620
|
+
height: 200,
|
621
|
+
});
|
622
|
+
});
|
623
|
+
});
|
624
|
+
describe("Path bounds", () => {
|
625
|
+
test("computes bounds for cubic with extreme control points", () => {
|
626
|
+
const { Skia } = importSkia();
|
627
|
+
const path = Skia.Path.Make();
|
628
|
+
path.moveTo(0, 0);
|
629
|
+
path.cubicTo(-50, 100, 150, -50, 100, 100);
|
630
|
+
const bounds = path.computeTightBounds();
|
631
|
+
// bounds is -8.09475040435791 0 116.1894998550415 100
|
632
|
+
expect(bounds.x).toBeCloseTo(-8.09475, 2);
|
633
|
+
expect(bounds.y).toBeCloseTo(0, 2);
|
634
|
+
expect(bounds.width).toBeCloseTo(116.1895, 2);
|
635
|
+
expect(bounds.height).toBeCloseTo(100, 2);
|
636
|
+
});
|
637
|
+
test("computes bounds for cubic forming loop", () => {
|
638
|
+
const { Skia } = importSkia();
|
639
|
+
const path = Skia.Path.Make();
|
640
|
+
path.moveTo(0, 0);
|
641
|
+
path.cubicTo(100, 0, 100, 100, 0, 50);
|
642
|
+
const bounds = path.computeTightBounds();
|
643
|
+
expect(bounds.x).toBeCloseTo(0, 2);
|
644
|
+
expect(bounds.y).toBeCloseTo(0, 2);
|
645
|
+
expect(bounds.width).toBeCloseTo(75, 2);
|
646
|
+
expect(bounds.height).toBeCloseTo(64, 2);
|
647
|
+
});
|
648
|
+
test("computes bounds for multiple cubics forming complex path", () => {
|
649
|
+
const { Skia } = importSkia();
|
650
|
+
const path = Skia.Path.Make();
|
651
|
+
path.moveTo(0, 50);
|
652
|
+
path.cubicTo(25, 0, 75, 100, 100, 50);
|
653
|
+
path.moveTo(100, 50);
|
654
|
+
path.cubicTo(125, 0, 175, 100, 200, 50);
|
655
|
+
path.moveTo(200, 50);
|
656
|
+
path.cubicTo(225, 100, 275, 0, 300, 50);
|
657
|
+
|
658
|
+
const bounds = path.computeTightBounds();
|
659
|
+
expect(bounds.x).toBeCloseTo(0, 2);
|
660
|
+
expect(bounds.y).toBeCloseTo(35.56624221801758, 2);
|
661
|
+
expect(bounds.width).toBeCloseTo(300, 2);
|
662
|
+
expect(bounds.height).toBeCloseTo(28.867511749267578, 2);
|
663
|
+
});
|
664
|
+
test("draw Skia logo", () => {
|
665
|
+
const skiaLogo =
|
666
|
+
// eslint-disable-next-line max-len
|
667
|
+
"M465.63 273.956C409.11 212.516 348.87 258.846 362.34 310.646C367.09 328.936 381.05 347.906 407.6 363.056C444.3 383.986 460.05 408.286 461.42 430.426C464.57 481.036 392.6 520.356 324 482.376M490 430.426C589.17 299.226 590.11 228.576 568.77 222.366C554.04 218.586 529.13 244.036 518 301.366C505.52 367.526 500.67 405.066 490 494.956C505.58 451.676 514.49 414.746 545.45 389.956C554.589 382.551 565.818 378.197 577.56 377.506C628.71 374.806 621.17 446.096 541.95 430.406M541.95 430.426C575.55 436.726 571.27 458.036 580.75 482.326C582.111 485.913 584.445 489.051 587.49 491.386C607.49 506.386 643.49 476.616 654.36 457.216C671.21 432.636 684.24 404.916 697.36 378.486M697.38 378.486C684.12 411.766 675.597 437.196 671.81 454.776C668.54 481.546 675.24 493.636 686.32 496.256C710.7 502.016 756.23 461.896 763.13 431.256C776.37 372.396 862.18 350.556 881.97 419.656M881.97 419.636C862.18 350.536 776.21 372.376 763.13 431.236C759.37 455.676 766.85 473.336 779.67 483.966C786.621 489.63 794.908 493.417 803.74 494.966C818.132 497.496 832.957 495.178 845.89 488.376C846.69 487.956 847.49 487.516 848.29 487.056C856.441 482.27 863.382 475.672 868.574 467.773C873.765 459.875 877.069 450.887 878.23 441.506C881.09 419.196 888.04 394.656 892.59 378.086M892.59 378.076C885.36 404.426 872.1 449.746 878.77 476.156C880.283 482.292 884.122 487.599 889.475 490.958C894.828 494.316 901.277 495.463 907.46 494.156C925.39 490.256 943.78 472.156 955.09 454.336M714.5 338C714.5 339.933 712.933 341.5 711 341.5C709.067 341.5 707.5 339.933 707.5 338C707.5 336.067 709.067 334.5 711 334.5C712.933 334.5 714.5 336.067 714.5 338Z";
|
668
|
+
const { Skia } = importSkia();
|
669
|
+
const path = Skia.Path.MakeFromSVGString(skiaLogo)!;
|
670
|
+
const bounds = path.computeTightBounds();
|
671
|
+
console.log(bounds.x, bounds.y, bounds.width, bounds.height);
|
672
|
+
expect(bounds.x).toBeCloseTo(324, 2);
|
673
|
+
expect(bounds.y).toBeCloseTo(222, 2);
|
674
|
+
expect(bounds.width).toBeCloseTo(631.09, 2);
|
675
|
+
expect(bounds.height).toBeCloseTo(275.55, 2);
|
676
|
+
});
|
677
|
+
});
|
126
678
|
});
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { checkImage, docPath } from "../../../__tests__/setup";
|
4
|
+
import { importSkia, surface } from "../setup";
|
5
|
+
import { ImageFilter, Circle, Group } from "../../components";
|
6
|
+
import { TileMode } from "../../../skia/types";
|
7
|
+
|
8
|
+
describe("ImageFilter", () => {
|
9
|
+
it("Should render ImageFilter component with blur filter", async () => {
|
10
|
+
const { Skia } = importSkia();
|
11
|
+
const blurFilter = Skia.ImageFilter.MakeBlur(10, 10, TileMode.Clamp, null);
|
12
|
+
// THIS IS FOR INTERNAL TESTING ONLY
|
13
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
14
|
+
// @ts-expect-error
|
15
|
+
blurFilter.source =
|
16
|
+
"Skia.ImageFilter.MakeBlur(10, 10, TileMode.Clamp, null)";
|
17
|
+
// END OF INTERNAL TESTING ONLY
|
18
|
+
const img = await surface.draw(
|
19
|
+
<Group>
|
20
|
+
<ImageFilter filter={blurFilter} />
|
21
|
+
<Circle cx={50} cy={50} r={30} color="red" />
|
22
|
+
</Group>
|
23
|
+
);
|
24
|
+
|
25
|
+
checkImage(img, docPath("image-filter/blur-filter.png"));
|
26
|
+
});
|
27
|
+
|
28
|
+
it("Should render ImageFilter component with offset filter", async () => {
|
29
|
+
const { Skia } = importSkia();
|
30
|
+
const offsetFilter = Skia.ImageFilter.MakeOffset(20, 20, null);
|
31
|
+
// THIS IS FOR INTERNAL TESTING ONLY
|
32
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
33
|
+
// @ts-expect-error
|
34
|
+
offsetFilter.source = "Skia.ImageFilter.MakeOffset(20, 20, null)";
|
35
|
+
// END OF INTERNAL TESTING ONLY
|
36
|
+
|
37
|
+
const img = await surface.draw(
|
38
|
+
<Group>
|
39
|
+
<ImageFilter filter={offsetFilter} />
|
40
|
+
<Circle cx={50} cy={50} r={30} color="blue" />
|
41
|
+
</Group>
|
42
|
+
);
|
43
|
+
|
44
|
+
checkImage(img, docPath("image-filter/offset-filter.png"));
|
45
|
+
});
|
46
|
+
|
47
|
+
it("Should render ImageFilter component with drop shadow filter", async () => {
|
48
|
+
const { Skia } = importSkia();
|
49
|
+
const dropShadowFilter = Skia.ImageFilter.MakeDropShadow(
|
50
|
+
10,
|
51
|
+
10,
|
52
|
+
5,
|
53
|
+
5,
|
54
|
+
Skia.Color("black"),
|
55
|
+
null
|
56
|
+
);
|
57
|
+
// THIS IS FOR INTERNAL TESTING ONLY
|
58
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
59
|
+
// @ts-expect-error
|
60
|
+
dropShadowFilter.source =
|
61
|
+
'Skia.ImageFilter.MakeDropShadow(10, 10, 5, 5, Skia.Color("black"), null)';
|
62
|
+
// END OF INTERNAL TESTING ONLY
|
63
|
+
|
64
|
+
const img = await surface.draw(
|
65
|
+
<Group>
|
66
|
+
<ImageFilter filter={dropShadowFilter} />
|
67
|
+
<Circle cx={50} cy={50} r={30} color="green" />
|
68
|
+
</Group>
|
69
|
+
);
|
70
|
+
|
71
|
+
checkImage(img, docPath("image-filter/drop-shadow-filter.png"));
|
72
|
+
});
|
73
|
+
|
74
|
+
it("Should render ImageFilter component with composed filters", async () => {
|
75
|
+
const { Skia } = importSkia();
|
76
|
+
const blurFilter = Skia.ImageFilter.MakeBlur(5, 5, TileMode.Clamp, null);
|
77
|
+
// THIS IS FOR INTERNAL TESTING ONLY
|
78
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
79
|
+
// @ts-expect-error
|
80
|
+
blurFilter.source = "Skia.ImageFilter.MakeBlur(5, 5, TileMode.Clamp, null)";
|
81
|
+
// END OF INTERNAL TESTING ONLY
|
82
|
+
const offsetFilter = Skia.ImageFilter.MakeOffset(10, 10, blurFilter);
|
83
|
+
// THIS IS FOR INTERNAL TESTING ONLY
|
84
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
85
|
+
// @ts-expect-error
|
86
|
+
offsetFilter.source =
|
87
|
+
"Skia.ImageFilter.MakeOffset(10, 10, Skia.ImageFilter.MakeBlur(5, 5, TileMode.Clamp, null))";
|
88
|
+
// END OF INTERNAL TESTING ONLY
|
89
|
+
|
90
|
+
const img = await surface.draw(
|
91
|
+
<Group>
|
92
|
+
<ImageFilter filter={offsetFilter} />
|
93
|
+
<Circle cx={50} cy={50} r={30} color="purple" />
|
94
|
+
</Group>
|
95
|
+
);
|
96
|
+
|
97
|
+
checkImage(img, docPath("image-filter/composed-filters.png"));
|
98
|
+
});
|
99
|
+
});
|