From bcab3751274b85da0ca4f4698fa462d8852c3c14 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Thu, 6 Jun 2024 11:43:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(=F0=9F=93=B9):=20Better=20support=20for=20r?= =?UTF-8?q?otated=20videos=20(#2461)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs/video.md | 48 +++++++++- .../cpp/rnskia-android/RNSkAndroidVideo.cpp | 26 +++++ .../cpp/rnskia-android/RNSkAndroidVideo.h | 1 + .../shopify/reactnative/skia/RNSkVideo.java | 12 ++- package/cpp/api/JsiVideo.h | 14 ++- package/cpp/rnskia/RNSkVideo.h | 1 + package/ios/RNSkia-iOS/RNSkiOSVideo.h | 4 + package/ios/RNSkia-iOS/RNSkiOSVideo.mm | 20 ++-- .../snapshots/drawings/rotated-image.png | Bin 0 -> 2970 bytes .../drawings/rotated-scaled-image.png | Bin 0 -> 3531 bytes .../snapshots/drawings/scaled-image.png | Bin 0 -> 4061 bytes .../snapshots/drawings/scaled-image2.png | Bin 0 -> 3544 bytes package/src/dom/nodes/datatypes/Fitting.ts | 49 ++++++---- package/src/external/reanimated/useVideo.ts | 15 ++- .../src/renderer/__tests__/FitBox.spec.tsx | 90 +++++++++++++++++- package/src/renderer/__tests__/Video.spec.tsx | 3 +- .../src/renderer/__tests__/e2e/Video.spec.tsx | 9 +- .../src/renderer/components/shapes/FitBox.tsx | 42 +++++++- package/src/skia/__tests__/assets/box.png | Bin 0 -> 904 bytes package/src/skia/__tests__/assets/box2.png | Bin 0 -> 3531 bytes package/src/skia/core/Matrix.ts | 6 +- package/src/skia/types/Matrix.ts | 1 + package/src/skia/types/Video/Video.ts | 5 +- 23 files changed, 296 insertions(+), 50 deletions(-) create mode 100644 package/src/__tests__/snapshots/drawings/rotated-image.png create mode 100644 package/src/__tests__/snapshots/drawings/rotated-scaled-image.png create mode 100644 package/src/__tests__/snapshots/drawings/scaled-image.png create mode 100644 package/src/__tests__/snapshots/drawings/scaled-image2.png create mode 100644 package/src/skia/__tests__/assets/box.png create mode 100644 package/src/skia/__tests__/assets/box2.png diff --git a/docs/docs/video.md b/docs/docs/video.md index 8890c70215..8ee1bd447c 100644 --- a/docs/docs/video.md +++ b/docs/docs/video.md @@ -96,7 +96,53 @@ export const useVideoFromAsset = ( ## Returned Values -The `useVideo` hook returns `currentFrame` which contains the current video frame, as well as `currentTime`, and `rotationInDegrees`. +The `useVideo` hook returns `currentFrame` which contains the current video frame, as well as `currentTime`, `rotation`, and `size`. + +## Rotated Video + +`rotation` can either be `0`, `90`, `180`, or `270`. +We provide a `fitbox` function that can help rotating and scaling the video. + +```tsx twoslash +import React from "react"; +import { + Canvas, + Image, + useVideo, + fitbox, + rect +} from "@shopify/react-native-skia"; +import { Pressable, useWindowDimensions } from "react-native"; +import { useSharedValue } from "react-native-reanimated"; + +interface VideoExampleProps { + localVideoFile: string; +} + +// The URL needs to be a local path; we usually use expo-asset for that. +export const VideoExample = ({ localVideoFile }: VideoExampleProps) => { + const paused = useSharedValue(false); + const { width, height } = useWindowDimensions(); + const { currentFrame, rotation, size } = useVideo(require(localVideoFile)); + const src = rect(0, 0, size.width, size.height); + const dst = rect(0, 0, width, height) + const transform = fitbox("cover", src, dst, rotation); + return ( + + + + ); +}; +``` + ## Playback Options diff --git a/package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp b/package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp index f561097771..c73bc4d79c 100644 --- a/package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp +++ b/package/android/cpp/rnskia-android/RNSkAndroidVideo.cpp @@ -9,6 +9,7 @@ #pragma clang diagnostic ignored "-Wdocumentation" #include "include/core/SkImage.h" +#include "include/core/SkSize.h" #pragma clang diagnostic pop @@ -102,4 +103,29 @@ float RNSkAndroidVideo::getRotationInDegrees() { return static_cast(rotation); } +SkISize RNSkAndroidVideo::getSize() { + JNIEnv *env = facebook::jni::Environment::current(); + jclass cls = env->GetObjectClass(_jniVideo.get()); + jmethodID mid = + env->GetMethodID(cls, "getSize", "()Landroid/graphics/Point;"); + if (!mid) { + RNSkLogger::logToConsole("getSize method not found"); + return SkISize::Make(0, 0); + } + jobject jPoint = env->CallObjectMethod(_jniVideo.get(), mid); + jclass pointCls = env->GetObjectClass(jPoint); + + jfieldID xFid = env->GetFieldID(pointCls, "x", "I"); + jfieldID yFid = env->GetFieldID(pointCls, "y", "I"); + if (!xFid || !yFid) { + RNSkLogger::logToConsole("Point class fields not found"); + return SkISize::Make(0, 0); + } + + jint width = env->GetIntField(jPoint, xFid); + jint height = env->GetIntField(jPoint, yFid); + + return SkISize::Make(width, height); +} + } // namespace RNSkia diff --git a/package/android/cpp/rnskia-android/RNSkAndroidVideo.h b/package/android/cpp/rnskia-android/RNSkAndroidVideo.h index 3d08728c6a..0d18c47b74 100644 --- a/package/android/cpp/rnskia-android/RNSkAndroidVideo.h +++ b/package/android/cpp/rnskia-android/RNSkAndroidVideo.h @@ -32,6 +32,7 @@ class RNSkAndroidVideo : public RNSkVideo { double framerate() override; void seek(double timestamp) override; float getRotationInDegrees() override; + SkISize getSize() override; }; } // namespace RNSkia diff --git a/package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java b/package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java index 7646818f03..63233123f2 100644 --- a/package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java +++ b/package/android/src/main/java/com/shopify/reactnative/skia/RNSkVideo.java @@ -11,6 +11,7 @@ import android.net.Uri; import android.os.Build; import android.view.Surface; +import android.graphics.Point; import androidx.annotation.RequiresApi; @@ -30,6 +31,8 @@ public class RNSkVideo { private double durationMs; private double frameRate; private int rotationDegrees = 0; + private int width = 0; + private int height = 0; RNSkVideo(Context context, String localUri) { this.uri = Uri.parse(localUri); @@ -57,8 +60,8 @@ private void initializeReader() { if (format.containsKey(MediaFormat.KEY_ROTATION)) { rotationDegrees = format.getInteger(MediaFormat.KEY_ROTATION); } - int width = format.getInteger(MediaFormat.KEY_WIDTH); - int height = format.getInteger(MediaFormat.KEY_HEIGHT); + width = format.getInteger(MediaFormat.KEY_WIDTH); + height = format.getInteger(MediaFormat.KEY_HEIGHT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { imageReader = ImageReader.newInstance( width, @@ -125,6 +128,11 @@ public void seek(long timestamp) { } } + @DoNotStrip + public Point getSize() { + return new Point(width, height); + } + private int selectVideoTrack(MediaExtractor extractor) { int numTracks = extractor.getTrackCount(); for (int i = 0; i < numTracks; i++) { diff --git a/package/cpp/api/JsiVideo.h b/package/cpp/api/JsiVideo.h index 8b6e9bac26..da0dcad8c5 100644 --- a/package/cpp/api/JsiVideo.h +++ b/package/cpp/api/JsiVideo.h @@ -53,17 +53,27 @@ class JsiVideo : public JsiSkWrappingSharedPtrHostObject { return jsi::Value::undefined(); } - JSI_HOST_FUNCTION(getRotationInDegrees) { + JSI_HOST_FUNCTION(rotation) { auto context = getContext(); auto rot = getObject()->getRotationInDegrees(); return jsi::Value(static_cast(rot)); } + JSI_HOST_FUNCTION(size) { + auto context = getContext(); + auto size = getObject()->getSize(); + auto result = jsi::Object(runtime); + result.setProperty(runtime, "width", static_cast(size.width())); + result.setProperty(runtime, "height", static_cast(size.height())); + return result; + } + JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiVideo, nextImage), JSI_EXPORT_FUNC(JsiVideo, duration), JSI_EXPORT_FUNC(JsiVideo, framerate), JSI_EXPORT_FUNC(JsiVideo, seek), - JSI_EXPORT_FUNC(JsiVideo, getRotationInDegrees), + JSI_EXPORT_FUNC(JsiVideo, rotation), + JSI_EXPORT_FUNC(JsiVideo, size), JSI_EXPORT_FUNC(JsiVideo, dispose)) JsiVideo(std::shared_ptr context, diff --git a/package/cpp/rnskia/RNSkVideo.h b/package/cpp/rnskia/RNSkVideo.h index fdb4e84285..d02d76359e 100644 --- a/package/cpp/rnskia/RNSkVideo.h +++ b/package/cpp/rnskia/RNSkVideo.h @@ -19,6 +19,7 @@ class RNSkVideo { virtual double framerate() = 0; virtual void seek(double timestamp) = 0; virtual float getRotationInDegrees() = 0; + virtual SkISize getSize() = 0; }; } // namespace RNSkia diff --git a/package/ios/RNSkia-iOS/RNSkiOSVideo.h b/package/ios/RNSkia-iOS/RNSkiOSVideo.h index 23b02caadb..4344c63c86 100644 --- a/package/ios/RNSkia-iOS/RNSkiOSVideo.h +++ b/package/ios/RNSkia-iOS/RNSkiOSVideo.h @@ -9,6 +9,7 @@ #pragma clang diagnostic ignored "-Wdocumentation" #include "include/core/SkImage.h" +#include "include/core/SkSize.h" #pragma clang diagnostic pop @@ -25,6 +26,8 @@ class RNSkiOSVideo : public RNSkVideo { RNSkPlatformContext *_context; double _duration = 0; double _framerate = 0; + float _videoWidth = 0; + float _videoHeight = 0; void setupReader(CMTimeRange timeRange); NSDictionary *getOutputSettings(); CGAffineTransform _preferredTransform; @@ -37,6 +40,7 @@ class RNSkiOSVideo : public RNSkVideo { double framerate() override; void seek(double timestamp) override; float getRotationInDegrees() override; + SkISize getSize() override; }; } // namespace RNSkia diff --git a/package/ios/RNSkia-iOS/RNSkiOSVideo.mm b/package/ios/RNSkia-iOS/RNSkiOSVideo.mm index 86c6c8616f..b18f199566 100644 --- a/package/ios/RNSkia-iOS/RNSkiOSVideo.mm +++ b/package/ios/RNSkia-iOS/RNSkiOSVideo.mm @@ -45,7 +45,9 @@ [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; _framerate = videoTrack.nominalFrameRate; _preferredTransform = videoTrack.preferredTransform; - + CGSize videoSize = videoTrack.naturalSize; + _videoWidth = videoSize.width; + _videoHeight = videoSize.height; NSDictionary *outputSettings = getOutputSettings(); AVAssetReaderTrackOutput *trackOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack @@ -104,19 +106,15 @@ // Determine the rotation angle in radians if (transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0) { - rotationAngle = M_PI_2; // 90 degrees + rotationAngle = 90; } else if (transform.a == 0 && transform.b == -1 && transform.c == 1 && transform.d == 0) { - rotationAngle = -M_PI_2; // -90 degrees + rotationAngle = 270; } else if (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) { - rotationAngle = M_PI; // 180 degrees - } else if (transform.a == 1 && transform.b == 0 && transform.c == 0 && - transform.d == 1) { - rotationAngle = 0.0; // 0 degrees + rotationAngle = 180; } - // Convert the rotation angle from radians to degrees - return rotationAngle * 180 / M_PI; + return rotationAngle; } void RNSkiOSVideo::seek(double timeInMilliseconds) { @@ -136,4 +134,8 @@ double RNSkiOSVideo::framerate() { return _framerate; } +SkISize RNSkiOSVideo::getSize() { + return SkISize::Make(_videoWidth, _videoHeight); +} + } // namespace RNSkia diff --git a/package/src/__tests__/snapshots/drawings/rotated-image.png b/package/src/__tests__/snapshots/drawings/rotated-image.png new file mode 100644 index 0000000000000000000000000000000000000000..07ad737909c634f5484c43e2dbf9cdc20cb15106 GIT binary patch literal 2970 zcmeAS@N?(olHy`uVBq!ia0y~yU*#r>mdK II;Vst0E_{RLjV8( literal 0 HcmV?d00001 diff --git a/package/src/__tests__/snapshots/drawings/rotated-scaled-image.png b/package/src/__tests__/snapshots/drawings/rotated-scaled-image.png new file mode 100644 index 0000000000000000000000000000000000000000..589a32482b99a7b9a29492c1412e876509772d86 GIT binary patch literal 3531 zcmeAS@N?(olHy`uVBq!ia0y~yU? l6%BY*1dM`FFkm6@Y+pnVr_*8GIZHrJ@O1TaS?83{1OP&2^6&rv literal 0 HcmV?d00001 diff --git a/package/src/__tests__/snapshots/drawings/scaled-image.png b/package/src/__tests__/snapshots/drawings/scaled-image.png new file mode 100644 index 0000000000000000000000000000000000000000..9586774cd7924f30d9b84446c22c8540124e6abb GIT binary patch literal 4061 zcmeAS@N?(olHy`uVBq!ia0y~yUU#5|I_B{{diEO9ZWp+ z_~Bn&#jyFDReBY3@w@Z3wu}-*wd-GU^4!Ya&3HKOGBCCqo^t@*#vuqK1eAb;f(wvv z0LCptg8(CtR0PGJ6AO@N8C5tMJfn$XG%Jjj6r<(iXstl!O7s0oMur2Qzs-z0u)@~1 x!P5>{A&=JAz(5$SkOsIy0>;587^o01c*h<-lbcykm0Jzu1W#8#mvv4FO#pB{^`HO% literal 0 HcmV?d00001 diff --git a/package/src/dom/nodes/datatypes/Fitting.ts b/package/src/dom/nodes/datatypes/Fitting.ts index 8d8ec86224..bf084d65c0 100644 --- a/package/src/dom/nodes/datatypes/Fitting.ts +++ b/package/src/dom/nodes/datatypes/Fitting.ts @@ -7,7 +7,10 @@ export interface Size { height: number; } -export const size = (width = 0, height = 0) => ({ width, height }); +export const size = (width = 0, height = 0) => { + "worklet"; + return { width, height }; +}; export const rect2rect = ( src: SkRect, @@ -18,6 +21,7 @@ export const rect2rect = ( { scaleX: number }, { scaleY: number } ] => { + "worklet"; const scaleX = dst.width / src.width; const scaleY = dst.height / src.height; const translateX = dst.x - src.x * scaleX; @@ -25,30 +29,11 @@ export const rect2rect = ( return [{ translateX }, { translateY }, { scaleX }, { scaleY }]; }; -export const fitRects = ( - fit: Fit, - rect: SkRect, - { x, y, width, height }: SkRect -) => { - const sizes = applyBoxFit( - fit, - { width: rect.width, height: rect.height }, - { width, height } - ); - const src = inscribe(sizes.src, rect); - const dst = inscribe(sizes.dst, { - x, - y, - width, - height, - }); - return { src, dst }; -}; - const inscribe = ( { width, height }: Size, rect: { x: number; y: number; width: number; height: number } ) => { + "worklet"; const halfWidthDelta = (rect.width - width) / 2.0; const halfHeightDelta = (rect.height - height) / 2.0; return { @@ -60,6 +45,7 @@ const inscribe = ( }; const applyBoxFit = (fit: Fit, input: Size, output: Size) => { + "worklet"; let src = size(), dst = size(); if ( @@ -122,3 +108,24 @@ const applyBoxFit = (fit: Fit, input: Size, output: Size) => { } return { src, dst }; }; + +export const fitRects = ( + fit: Fit, + rect: SkRect, + { x, y, width, height }: SkRect +) => { + "worklet"; + const sizes = applyBoxFit( + fit, + { width: rect.width, height: rect.height }, + { width, height } + ); + const src = inscribe(sizes.src, rect); + const dst = inscribe(sizes.dst, { + x, + y, + width, + height, + }); + return { src, dst }; +}; diff --git a/package/src/external/reanimated/useVideo.ts b/package/src/external/reanimated/useVideo.ts index b2971cb3a4..71aac12040 100644 --- a/package/src/external/reanimated/useVideo.ts +++ b/package/src/external/reanimated/useVideo.ts @@ -49,10 +49,8 @@ export const useVideo = ( const lastTimestamp = Rea.useSharedValue(-1); const duration = useMemo(() => video?.duration() ?? 0, [video]); const framerate = useMemo(() => video?.framerate() ?? 0, [video]); - const rotationInDegrees = useMemo( - () => video?.getRotationInDegrees() ?? 0, - [video] - ); + const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]); + const rotation = useMemo(() => video?.rotation() ?? 0, [video]); Rea.useFrameCallback((frameInfo: FrameInfo) => { processVideoState( video, @@ -78,5 +76,12 @@ export const useVideo = ( }; }, [video]); - return { currentFrame, currentTime, duration, framerate, rotationInDegrees }; + return { + currentFrame, + currentTime, + duration, + framerate, + rotation, + size, + }; }; diff --git a/package/src/renderer/__tests__/FitBox.spec.tsx b/package/src/renderer/__tests__/FitBox.spec.tsx index 39a8b76369..569a8dfdb9 100644 --- a/package/src/renderer/__tests__/FitBox.spec.tsx +++ b/package/src/renderer/__tests__/FitBox.spec.tsx @@ -1,7 +1,10 @@ +import fs from "fs"; +import path from "path"; + import React from "react"; import { processResult } from "../../__tests__/setup"; -import { Circle, FitBox, Rect } from "../components"; +import { Circle, FitBox, Rect, Image, Group, fitbox } from "../components"; import { drawOnNode, width, height, importSkia } from "./setup"; @@ -35,4 +38,89 @@ describe("FitBox", () => { ); processResult(surface, "snapshots/drawings/lightblue-quarter-circle.png"); }); + it("Should scale the image (1)", () => { + const { Skia, rect } = importSkia(); + const image = Skia.Image.MakeImageFromEncoded( + Skia.Data.fromBytes( + fs.readFileSync( + path.resolve(__dirname, "../../skia/__tests__/assets/box.png") + ) + ) + )!; + const surface = drawOnNode( + + + + ); + processResult(surface, "snapshots/drawings/scaled-image.png"); + }); + it("Should scale the image (2)", () => { + const { Skia, rect } = importSkia(); + const image = Skia.Image.MakeImageFromEncoded( + Skia.Data.fromBytes( + fs.readFileSync( + path.resolve(__dirname, "../../skia/__tests__/assets/box.png") + ) + ) + )!; + const screen = rect(256, 128, 256, 512); + const surface = drawOnNode( + + + + + ); + processResult(surface, "snapshots/drawings/scaled-image2.png"); + }); + it("Should rotate and scale the image", () => { + const { Skia, rect } = importSkia(); + const image = Skia.Image.MakeImageFromEncoded( + Skia.Data.fromBytes( + fs.readFileSync( + path.resolve(__dirname, "../../skia/__tests__/assets/box2.png") + ) + ) + )!; + const screen = rect(256, 128, 256, 512); + const imageRect = rect(0, 0, image.width(), image.height()); + const transform = fitbox("contain", imageRect, screen, 270); + const surface = drawOnNode( + + + + + ); + processResult(surface, "snapshots/drawings/rotated-scaled-image.png"); + }); }); diff --git a/package/src/renderer/__tests__/Video.spec.tsx b/package/src/renderer/__tests__/Video.spec.tsx index eccab935e3..ff2b086d1f 100644 --- a/package/src/renderer/__tests__/Video.spec.tsx +++ b/package/src/renderer/__tests__/Video.spec.tsx @@ -34,7 +34,8 @@ describe("Video Player", () => { duration: jest.fn().mockReturnValue(duration), seek: jest.fn(), nextImage: jest.fn().mockReturnValue({} as SkImage), - getRotationInDegrees: jest.fn().mockReturnValue(0), + rotation: jest.fn().mockReturnValue(0), + size: jest.fn().mockReturnValue({ width: 0, height: 0 }), }; options = { playbackSpeed: 1, diff --git a/package/src/renderer/__tests__/e2e/Video.spec.tsx b/package/src/renderer/__tests__/e2e/Video.spec.tsx index ea16167c86..f65764ba6c 100644 --- a/package/src/renderer/__tests__/e2e/Video.spec.tsx +++ b/package/src/renderer/__tests__/e2e/Video.spec.tsx @@ -8,9 +8,16 @@ describe("Videos", () => { return { duration: video.duration(), framerate: video.framerate(), + width: video.size().width, + height: video.size().height, }; }); - expect(result).toEqual({ duration: 5280, framerate: 25 }); + expect(result).toEqual({ + duration: 5280, + framerate: 25, + height: 720, + width: 1280, + }); }); // TODO: We need to reanable these tests once we can run them on the UI thread // itRunsE2eOnly("get frame", async () => { diff --git a/package/src/renderer/components/shapes/FitBox.tsx b/package/src/renderer/components/shapes/FitBox.tsx index bcd78ea647..42a1ac39d2 100644 --- a/package/src/renderer/components/shapes/FitBox.tsx +++ b/package/src/renderer/components/shapes/FitBox.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from "react"; import type { Fit } from "../../../dom/nodes"; import { fitRects, rect2rect } from "../../../dom/nodes"; -import type { SkRect } from "../../../skia/types"; +import type { SkRect, Transforms3d } from "../../../skia/types"; import { Group } from "../Group"; interface FitProps { @@ -13,9 +13,43 @@ interface FitProps { children: ReactNode | ReactNode[]; } -export const fitbox = (fit: Fit, src: SkRect, dst: SkRect) => { - const rects = fitRects(fit, src, dst); - return rect2rect(rects.src, rects.dst); +export const fitbox = ( + fit: Fit, + src: SkRect, + dst: SkRect, + rotation: 0 | 90 | 180 | 270 = 0 +) => { + "worklet"; + const rects = fitRects( + fit, + rotation === 90 || rotation === 270 + ? { x: 0, y: 0, width: src.height, height: src.width } + : src, + dst + ); + const result = rect2rect(rects.src, rects.dst); + if (rotation === 90) { + return [ + ...result, + { translate: [src.height, 0] }, + { rotate: Math.PI / 2 }, + ] as Transforms3d; + } + if (rotation === 180) { + return [ + ...result, + { translate: [src.width, src.height] }, + { rotate: Math.PI }, + ] as Transforms3d; + } + if (rotation === 270) { + return [ + ...result, + { translate: [0, src.width] }, + { rotate: -Math.PI / 2 }, + ] as Transforms3d; + } + return result; }; export const FitBox = ({ fit = "contain", src, dst, children }: FitProps) => { diff --git a/package/src/skia/__tests__/assets/box.png b/package/src/skia/__tests__/assets/box.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea1d64c0c88e9980850a6599bd6c4d8d5b6a2f9 GIT binary patch literal 904 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6%U;1OBOz`!jG!i)^F=172) z6bHFGF|0c$^AgBmNq6*hWMJ6X&;2Knm4Sg-*3-o?q=ND7HAY?r1)c*NoIm9MN4#x!l9TCuhDu&!8JvAf#{N|z)in%cxRHHK1Bp_;T!YtZ~4*^<ftgL<(3ph^%#@F4XjJ*5ZHgNS%4YN6u6D(W8~LVYtyQ~eKm*ja}6=I!^s@69m# zV6O%Hx^31bD*)L1K5q!Xgf0P#8O_U^Z^zN_E7l6Hqqv-~?%|_#x6- z#$Y8^Bo2GPzt#c&`f z97|l>o{a8{!cNbow6uy)n#?am1A>Rcb@sY1y8%z z@AdTO$IC;ng95uu zgf1-BMp%5`)xt1ly}1PT5Kag$yk_7cq&emYtr6gR8tx$(Z)t=iW#uh=RjnTRCf2h= zu4qGg9i|F8sXH(<%H$&Of=LN=+ZC98e7% - processTransform(Skia.Matrix(), transforms); +export const processTransform2d = (transforms: Transforms3d) => { + "worklet"; + return processTransform(Skia.Matrix(), transforms); +}; diff --git a/package/src/skia/types/Matrix.ts b/package/src/skia/types/Matrix.ts index 37e75d9c28..353c3894ec 100644 --- a/package/src/skia/types/Matrix.ts +++ b/package/src/skia/types/Matrix.ts @@ -30,6 +30,7 @@ export const processTransform = ( m: T, transforms: Transforms3d ) => { + "worklet"; const m3 = processTransform3d(transforms); m.concat(m3); return m; diff --git a/package/src/skia/types/Video/Video.ts b/package/src/skia/types/Video/Video.ts index fbe8ab8527..7175de8cd8 100644 --- a/package/src/skia/types/Video/Video.ts +++ b/package/src/skia/types/Video/Video.ts @@ -1,10 +1,13 @@ import type { SkImage } from "../Image"; import type { SkJSIInstance } from "../JsiInstance"; +export type VideoRotation = 0 | 90 | 180 | 270; + export interface Video extends SkJSIInstance<"Video"> { duration(): number; framerate(): number; nextImage(): SkImage | null; seek(time: number): void; - getRotationInDegrees(): number; + rotation(): VideoRotation; + size(): { width: number; height: number }; }