@airisu-dev/react-native-live2d-skia 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -1,40 +1,56 @@
1
1
  # react-native-live2d-skia
2
2
 
3
- react-native-live2d-skia is a react native package built with Nitro
3
+ Live2D Cubism models rendered natively in React Native with Skia. No WebView, a hooks API in the style of `pixi-live2d-display`, and one shared C++ codebase for iOS and Android.
4
4
 
5
5
  [![Version](https://img.shields.io/npm/v/@airisu-dev/react-native-live2d-skia.svg)](https://www.npmjs.com/package/@airisu-dev/react-native-live2d-skia)
6
6
  [![Downloads](https://img.shields.io/npm/dm/@airisu-dev/react-native-live2d-skia.svg)](https://www.npmjs.com/package/@airisu-dev/react-native-live2d-skia)
7
7
  [![License](https://img.shields.io/npm/l/@airisu-dev/react-native-live2d-skia.svg)](https://github.com/airisu-dev/react-native-live2d-skia/blob/main/LICENSE)
8
8
 
9
+ Cubism Core and the Cubism Native Framework only run the model (motions, physics, expressions). Each drawable is drawn with Skia's `drawVertices`, so Skia picks the GPU backend: Metal on iOS, Vulkan or OpenGL on Android.
10
+
9
11
  ## Requirements
10
12
 
11
- - React Native v0.76.0 or higher
12
- - Node 18.0.0 or higher
13
+ - Expo with development builds (`expo prebuild`). The React Native CLI isn't supported.
14
+ - React Native 0.78 or higher (Nitro Views), Node 18 or higher.
15
+ - Skia, either version:
13
16
 
14
- > [!IMPORTANT]
15
- > To Support `Nitro Views` you need to install React Native version v0.78.0 or higher.
17
+ | Skia | Package | Renderer |
18
+ | ---- | ------------------------------------ | ------------------------ |
19
+ | v3 | `react-native-skia` (3.3+) | Graphite (Vulkan, Metal) |
20
+ | v2 | `@shopify/react-native-skia` (2.10+) | Ganesh (OpenGL, Metal) |
16
21
 
17
22
  ## Installation
18
23
 
24
+ ### 1. Install the packages
25
+
19
26
  ```bash
20
27
  npx expo install @airisu-dev/react-native-live2d-skia react-native-nitro-modules react-native-skia react-native-reanimated react-native-worklets react-native-gesture-handler expo-asset expo-file-system
21
28
  ```
22
29
 
23
- Expo apps only (development builds with prebuild); the React Native CLI isn't supported.
30
+ Install one Skia package, not both. For v2, install `@shopify/react-native-skia` instead of `react-native-skia`, at 2.10 or later: `expo install` can pick an older version (2.6.2 for SDK 57), which hands Skia objects to C++ differently. Pin a newer one, e.g. `npx expo install @shopify/react-native-skia@2.14.0`. The config plugin reads which one the app depends on.
24
31
 
25
- Skia: either version works; install one, not both.
32
+ The app needs a `GestureHandlerRootView` at its root.
26
33
 
27
- | Skia | Package | Renderer |
28
- | ---- | ------------------------------------ | ------------------------ |
29
- | v3 | `react-native-skia` (3.3+) | Graphite (Vulkan, Metal) |
30
- | v2 | `@shopify/react-native-skia` (2.10+) | Ganesh (OpenGL, Metal) |
34
+ ### 2. Configure Metro
35
+
36
+ Models are bundled as assets. Add `moc3` to Metro's `assetExts` in `metro.config.js` (create it with `npx expo customize metro.config.js` if the app has none):
37
+
38
+ ```js
39
+ const { getDefaultConfig } = require('expo/metro-config')
40
+
41
+ const config = getDefaultConfig(__dirname)
31
42
 
32
- For v2, install `@shopify/react-native-skia` instead of `react-native-skia` above, at 2.10 or later: `expo install` picks 2.6.2 for SDK 57, which hands Skia objects to C++ differently (JSI host objects, not native state), so pin a newer one, e.g. `npx expo install @shopify/react-native-skia@2.14.0`. The config plugin reads which one the app depends on, and models are drawn in C++ with it.
43
+ // Bundle Live2D model data as assets.
44
+ config.resolver.assetExts.push('moc3')
33
45
 
34
- ### Cubism SDK for Native
46
+ module.exports = config
47
+ ```
48
+
49
+ The model's JSON files (`model3.json`, motions, physics, expressions) and textures bundle with Expo's defaults. Models loaded from a URL don't need this.
35
50
 
36
- The package doesn't include Live2D's Cubism SDK: its Core is proprietary, and
37
- each app downloads it from Live2D, accepting
51
+ ### 3. Get the Cubism SDK for Native
52
+
53
+ The package doesn't include Live2D's Cubism SDK: its Core is proprietary, and each app downloads it from Live2D, accepting
38
54
  [Live2D's license terms](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html).
39
55
  1. Download it (it's checked against a SHA-256):
40
56
 
@@ -42,8 +58,7 @@ each app downloads it from Live2D, accepting
42
58
  npx @airisu-dev/react-native-live2d-skia fetch-sdk --accept-license # or bunx, yarn, pnpm
43
59
  ```
44
60
 
45
- It extracts to `./vendor/CubismSdkForNative` (change it with `--out`). Add `/vendor/` to `.gitignore`, so it's never in a public repo. On EAS Build,
46
- run the same command in an `eas-build-post-install` script.
61
+ It extracts to `./vendor/CubismSdkForNative` (change it with `--out`). Add `/vendor/` to `.gitignore`, so it's never in a public repo. On EAS Build, run the same command in an `eas-build-post-install` script.
47
62
 
48
63
  2. Point the config plugin at it, in `app.json`:
49
64
 
@@ -51,15 +66,194 @@ each app downloads it from Live2D, accepting
51
66
  "plugins": [["@airisu-dev/react-native-live2d-skia", { "cubismSdkPath": "./vendor/CubismSdkForNative" }]]
52
67
  ```
53
68
 
54
- 3. `npx expo prebuild`, then build. A missing or unsupported SDK stops the build with the path it looked at.
69
+ 3. Run `npx expo prebuild`, then build. A missing or unsupported SDK stops the build with the path it looked at.
55
70
 
56
71
  Publishing an app built on the SDK may also need Live2D's
57
72
  [Cubism SDK Release License](https://www.live2d.com/en/sdk/license/) (businesses above 10M JPY a year).
58
73
 
59
- ## Credits
74
+ ## Usage
60
75
 
61
- Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
76
+ ```tsx
77
+ import { Canvas } from '@shopify/react-native-skia' // or 'react-native-skia'
78
+ import { GestureDetector } from 'react-native-gesture-handler'
79
+ import { Live2D, useLive2DGesture, useLive2DModel } from '@airisu-dev/react-native-live2d-skia'
80
+
81
+ export function Mao() {
82
+ // A require.context of the model's folder: the files its model3.json lists are loaded from it.
83
+ const { model, error, loading, retry } = useLive2DModel(
84
+ require.context('./Mao', true, /\.(json|moc3|png)$/),
85
+ )
86
+ const gesture = useLive2DGesture(model, { x: 0, y: 0, scale: 0.4 })
87
+
88
+ return (
89
+ <GestureDetector gesture={gesture}>
90
+ <Canvas style={{ flex: 1 }}>
91
+ <Live2D model={model} x={0} y={0} scale={0.4} />
92
+ </Canvas>
93
+ </GestureDetector>
94
+ )
95
+ }
96
+ ```
97
+
98
+ `useLive2DModel` loads once on mount and doesn't throw. The source is read once, so remount the component (e.g. change its `key`) to switch models. The canvas is an ordinary view: size it with styles. Models are drawn at the `x`, `y` and `scale` they're given and clipped to the canvas, so fit them to the measured size.
99
+
100
+ A tap runs the model's reaction (a `Tap<area>` motion, or an expression for `Head`), and a drag makes it look at the finger.
101
+
102
+ ### Loading from a URL
103
+
104
+ ```tsx
105
+ const { model, error, retry } = useLive2DModel(
106
+ 'https://example.com/haru/haru_greeter_t03.model3.json',
107
+ )
108
+ ```
109
+
110
+ A string is the URL of a `model3.json`. The files it lists are resolved relative to it, and spaces in file names are escaped.
111
+ - Every file (JSON, moc, textures) is fetched in parallel with `expo/fetch`. The only caching is the platform's HTTP cache, so a persistent offline copy is up to the app.
112
+ - Motion sounds are ignored.
113
+ - A failed fetch ends up in `error` as `Live2D: failed to fetch <url> (<status>)`, and network errors (e.g. offline) come through the same way. `retry()` tries again. URLs in error messages never include their query, so tokens don't leak into the UI or logs.
114
+ - Cubism 2 models (`model.json`, `.moc`) can't be loaded: Cubism Core 5 only reads `.moc3`.
115
+
116
+ #### Protected models
117
+
118
+ ```tsx
119
+ // 1. A token in the query: reused for every file on the same host.
120
+ useLive2DModel('https://cdn.example.com/haru/haru.model3.json?token=abc')
121
+
122
+ // 2. Headers: sent with every request.
123
+ useLive2DModel(url, { headers: { Authorization: `Bearer ${token}` } })
124
+
125
+ // 3. A signature per file (e.g. S3 presigned URLs): sign each one yourself.
126
+ useLive2DModel(signedModel3Url, {
127
+ resolveUrl: (url, file) => api.sign(url), // may return a promise
128
+ })
129
+ ```
130
+
131
+ Options are read once, like the source, and `retry()` reuses them. To load with a fresh token, remount. `resolveUrl` runs on every load, so it can fetch fresh signatures.
132
+
133
+ ### Loading from files
134
+
135
+ Pass the files yourself as a `Live2DModelSource`:
136
+
137
+ ```tsx
138
+ useLive2DModel({
139
+ settings: require('./Mao/Mao.model3.json'),
140
+ moc: require('./Mao/Mao.moc3'),
141
+ textures: [require('./Mao/Mao.2048/texture_00.png')],
142
+ physics: require('./Mao/Mao.physics3.json'),
143
+ motions: { Idle: [require('./Mao/motions/idle.motion3.json')] },
144
+ })
145
+ ```
146
+
147
+ ### Several models in one canvas
148
+
149
+ ```tsx
150
+ const gesture = useLive2DGestures([
151
+ { model: hiyori, ...hiyoriPlace, onMove: (x, y) => moveHiyori(x, y) },
152
+ { model: mao, ...maoPlace, onMove: (x, y) => moveMao(x, y) },
153
+ ])
154
+
155
+ <GestureDetector gesture={gesture}>
156
+ <Canvas style={{ flex: 1 }}>
157
+ <Rect … /> {/* behind the models */}
158
+ <Live2D model={hiyori} {...hiyoriPlace} />
159
+ <Live2D model={mao} {...maoPlace} />
160
+ <RoundedRect … /> {/* over the models */}
161
+ </Canvas>
162
+ </GestureDetector>
163
+ ```
164
+
165
+ Inside a `<Canvas>`, JSX order is drawing order: a `<Live2D>` is one layer among ordinary Skia shapes. One animation loop updates and draws every shown model each frame, and two `<Live2D>`s showing the same model share its frames.
166
+
167
+ ### Controlling the model
168
+
169
+ Points are in model pixels: the model's canvas at its native size (`model.width` x `model.height`), origin top-left.
170
+
171
+ ```ts
172
+ model.motion('TapBody') // random motion of a group
173
+ model.motion('TapBody', 0, 'force') // given motion and priority (idle / normal / force)
174
+ model.expression('exp_01') // expression; random without a name
175
+ model.resetExpression()
176
+ model.setParameter('ParamAngleX', 30) // held over motions until resetParameter
177
+ model.resetParameter('ParamAngleX')
178
+ model.focus(x, y) // look at a point, easing
179
+ model.resetFocus()
180
+ model.hitTest(x, y) // → ['Head']
181
+ ```
182
+
183
+ Methods can be called from the JS thread while the model animates on its background thread: the native model is locked.
184
+
185
+ ## API
186
+
187
+ ### `useLive2DModel(source, options?)`
188
+
189
+ `source` is a `require.context` of the model's folder, a `Live2DModelSource`, or the URL of a `model3.json`. It returns:
190
+
191
+ | Field | Description |
192
+ | --------- | ---------------------------------------------------------------- |
193
+ | `model` | The loaded model, `null` while loading or after an error |
194
+ | `error` | Why the last load failed (e.g. a network error for a URL source) |
195
+ | `loading` | `true` while a load is in progress |
196
+ | `retry()` | Loads again |
197
+
198
+ | Option | Description |
199
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
200
+ | `headers` | Sent with every request for a remote file |
201
+ | `resolveUrl` | `(url, file) => string \| Promise<string>`: the URL to request for each file the `model3.json` lists. `url` is resolved next to it without a query, `file` is the path as listed. Replaces the default, which reuses the `model3.json` URL's query |
202
+
203
+ ### `<Live2D />`
204
+
205
+ | Prop | Description |
206
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
207
+ | `model` | `model` from `useLive2DModel` (renders nothing while `null`) |
208
+ | `x`, `y` | Top-left position on the canvas. Default `0` |
209
+ | `scale` | Uniform scale of the model's native canvas size. Default `1` |
210
+ | `paused` | Stops animating, keeps the current frame. Default `false` |
211
+ | `maxFps` | Caps the frame rate (e.g. `30` to save battery). Default: the display's refresh rate |
212
+ | `onFrameStats(stats)` | About once a second: `fps`, `targetFps`, per-frame times (`workMs`, `updateMs`, `drawablesMs`, `drawMs`, `meshMs`), `meshesBuilt`, `layers` (mask layers), `models` and `loopWorkMs` |
213
+
214
+ ### `useLive2DGestures(targets, options?)`
215
+
216
+ Returns one gesture for a `<GestureDetector>` around the `<Canvas>`. Gestures need a native view, so this can't be a prop of `<Live2D>`, which is drawn inside the canvas. List targets in drawing order.
217
+ - A drag makes every model look at the finger.
218
+ - A tap goes to the topmost model with a hit area under it.
219
+ - A long press (350 ms) on a model's hit area picks it up, if it has `onMove`.
220
+
221
+ | Target field | Description |
222
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
223
+ | `model` | `model` from `useLive2DModel` |
224
+ | `x`, `y`, `scale` | Same as its `<Live2D>` props, to map touches to the model |
225
+ | `onHit(hitAreas)` | Called on a tap on its hit areas. Return `true` to skip the default: a random `Tap<Area>` motion (e.g. `TapBody`), else a random expression for `Head`, else a random `Tap` motion (models with one tap group) |
226
+ | `onMove(x, y)` | Makes it draggable. Called with its new top-left position as the finger moves, keeping the point where it was grabbed; the app moves it |
227
+
228
+ | Option | Description |
229
+ | ------- | -------------------------------------------------------- |
230
+ | `focus` | Models follow the finger while it's down. Default `true` |
231
+
232
+ The gesture is built once; targets are read at touch time, so they can change on every render.
233
+
234
+ ### `useLive2DGesture(model, options?)`
235
+
236
+ The same for one model: `options` is a target (without `model`) plus `focus`.
237
+
238
+ ### `Live2DModel`
239
+
240
+ | Member | Description |
241
+ | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
242
+ | `motion(group, index?, priority?)` | Plays a motion; `priority` is `'idle'`, `'normal'` or `'force'`. `false` if there is none or a higher priority one is playing |
243
+ | `expression(name?)` | Fades to an expression, random without a name. `false` if there is none |
244
+ | `resetExpression()` | Removes the current expression |
245
+ | `setParameter(id, value)` / `resetParameter(id)` | Holds a parameter over motions until reset |
246
+ | `focus(x, y)` / `resetFocus()` | Looks at a point, easing / looks straight ahead |
247
+ | `hitTest(x, y)` | Names of the `model3.json` hit areas at a point |
248
+ | `motionGroups`, `expressionNames` | What the model has |
249
+ | `width`, `height` | The model's native canvas size |
62
250
 
63
251
  ## Contributing
64
252
 
65
- Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
253
+ Pull requests are welcome. For major changes, please open an issue first. To build and run the repo, see [DEVELOPMENT.md](https://github.com/airisu-dev/react-native-live2d-skia/blob/main/DEVELOPMENT.md).
254
+
255
+ ## License
256
+
257
+ MIT. The Cubism SDK is not part of this package; it's under [Live2D's license](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html).
258
+
259
+ Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airisu-dev/react-native-live2d-skia",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "react-native-live2d-skia is a react native package built with Nitro",
5
5
  "main": "src/index",
6
6
  "types": "src/index",
@@ -17,7 +17,7 @@
17
17
  "build": "bun run typecheck && bob build && tsc -b plugin/tsconfig.build.json cli/tsconfig.build.json",
18
18
  "codegen": "nitrogen --logLevel=debug",
19
19
  "build:node": "tsc -b plugin/tsconfig.build.json cli/tsconfig.build.json",
20
- "prepack": "tsc -b plugin/tsconfig.build.json cli/tsconfig.build.json"
20
+ "prepack": "bash ../../scripts/copy-readme.sh && tsc -b plugin/tsconfig.build.json cli/tsconfig.build.json"
21
21
  },
22
22
  "keywords": [
23
23
  "react-native",
@@ -47,7 +47,10 @@
47
47
  "*.podspec",
48
48
  "README.md"
49
49
  ],
50
- "repository": "https://github.com/airisu-dev/react-native-live2d-skia.git",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/airisu-dev/react-native-live2d-skia.git"
53
+ },
51
54
  "author": "airisu-dev",
52
55
  "license": "MIT",
53
56
  "bugs": "https://github.com/airisu-dev/react-native-live2d-skia/issues",
@@ -1,6 +1,7 @@
1
1
  import { Asset } from 'expo-asset'
2
2
  import { File } from 'expo-file-system'
3
3
  import { Live2dSkia } from '../native'
4
+ import { decodeImage } from '../rendering/decodeImage'
4
5
  import {
5
6
  type CanvasInfo,
6
7
  createDrawer,
@@ -102,7 +103,7 @@ async function readAsset(source: AssetSource, options: Live2DLoadOptions): Promi
102
103
 
103
104
  async function loadTexture(source: AssetSource, options: Live2DLoadOptions): Promise<SkImage> {
104
105
  const bytes = new Uint8Array(await readAsset(source, options))
105
- const image = Skia.Image.MakeImageFromEncoded(Skia.Data.fromBytes(bytes))
106
+ const image = decodeImage(Skia.Image, Skia.Data.fromBytes(bytes))
106
107
  if (!image) {
107
108
  throw new Error(`Live2D: failed to decode texture ${describe(source)}`)
108
109
  }
@@ -0,0 +1,12 @@
1
+ import type { SkData, SkImage, SkImageFactory } from './skiaTypes'
2
+
3
+ /** Null when the data can't be decoded. Throws when this Skia has neither factory method. */
4
+ export function decodeImage(factory: SkImageFactory, data: SkData): SkImage | null {
5
+ if ('MakeFromEncoded' in factory && factory.MakeFromEncoded) return factory.MakeFromEncoded(data)
6
+ if ('MakeImageFromEncoded' in factory && factory.MakeImageFromEncoded) {
7
+ return factory.MakeImageFromEncoded(data)
8
+ }
9
+ throw new Error(
10
+ 'Live2D: this version of react-native-skia has neither Image.MakeFromEncoded nor Image.MakeImageFromEncoded',
11
+ )
12
+ }
@@ -1,97 +1,9 @@
1
1
  // The Skia package in use: react-native-skia (v3) or @shopify/react-native-skia (v2).
2
2
  // Types are declared structurally rather than imported: the app type-checks these
3
3
  // sources and may have only one of the two packages.
4
- import type { ComponentType, ReactNode } from 'react'
4
+ import type { SkiaModule } from './skiaTypes'
5
5
 
6
- export interface SkPoint {
7
- x: number
8
- y: number
9
- }
10
-
11
- export interface SkRect {
12
- x: number
13
- y: number
14
- width: number
15
- height: number
16
- }
17
-
18
- export interface SkShader {
19
- dispose(): void
20
- }
21
-
22
- export interface SkPicture {
23
- dispose(): void
24
- }
25
-
26
- export interface SkVertices {
27
- dispose(): void
28
- }
29
-
30
- export interface SkImage {
31
- width(): number
32
- height(): number
33
- makeShaderOptions(tx: number, ty: number, fm: number, mm: number): SkShader
34
- dispose(): void
35
- }
36
-
37
- export interface SkPaint {
38
- setAntiAlias(antiAlias: boolean): void
39
- setAlphaf(alpha: number): void
40
- setBlendMode(blendMode: number): void
41
- setShader(shader: SkShader | null): void
42
- }
43
-
44
- export interface SkCanvas {
45
- saveLayer(paint?: SkPaint, bounds?: SkRect | null): number
46
- restore(): void
47
- drawVertices(vertices: SkVertices, mode: number, paint: SkPaint): void
48
- }
49
-
50
- interface SkPictureRecorder {
51
- beginRecording(bounds?: SkRect): SkCanvas
52
- finishRecordingAsPicture(): SkPicture
53
- }
54
-
55
- interface SkData {
56
- dispose(): void
57
- }
58
-
59
- interface SkiaApi {
60
- XYWHRect(x: number, y: number, width: number, height: number): SkRect
61
- Paint(): SkPaint
62
- PictureRecorder(): SkPictureRecorder
63
- MakeVertices(
64
- mode: number,
65
- positions: SkPoint[],
66
- textures?: SkPoint[] | null,
67
- colors?: undefined,
68
- indices?: number[] | null,
69
- ): SkVertices
70
- Image: { MakeImageFromEncoded(data: SkData): SkImage | null }
71
- Data: { fromBytes(bytes: Uint8Array): SkData }
72
- }
73
-
74
- interface SkiaModule {
75
- Skia: SkiaApi
76
- BlendMode: {
77
- SrcOver: number
78
- Dst: number
79
- DstIn: number
80
- DstOut: number
81
- Plus: number
82
- Multiply: number
83
- }
84
- VertexMode: { Triangles: number }
85
- TileMode: { Clamp: number }
86
- FilterMode: { Linear: number }
87
- MipmapMode: { Linear: number }
88
- Group: ComponentType<{
89
- transform?: Record<string, number>[]
90
- children?: ReactNode
91
- }>
92
- // A picture or a shared value of one (Reanimated).
93
- Picture: ComponentType<{ picture: unknown }>
94
- }
6
+ export type * from './skiaTypes'
95
7
 
96
8
  // Guarded requires: Metro (Expo enables optional dependencies) bundles the
97
9
  // missing one as a require that throws. v3 first: in a monorepo both can be
@@ -0,0 +1,99 @@
1
+ // The part of react-native-skia that this package uses, as structural types, with no import of
2
+ // Skia itself. rendering/skia.ts loads the real thing, and scripts/skiaCompat.ts checks every
3
+ // supported Skia version against these types.
4
+ import type { ComponentType, ReactNode } from 'react'
5
+
6
+ export interface SkPoint {
7
+ x: number
8
+ y: number
9
+ }
10
+
11
+ export interface SkRect {
12
+ x: number
13
+ y: number
14
+ width: number
15
+ height: number
16
+ }
17
+
18
+ export interface SkShader {
19
+ dispose(): void
20
+ }
21
+
22
+ export interface SkPicture {
23
+ dispose(): void
24
+ }
25
+
26
+ export interface SkVertices {
27
+ dispose(): void
28
+ }
29
+
30
+ export interface SkImage {
31
+ width(): number
32
+ height(): number
33
+ makeShaderOptions(tx: number, ty: number, fm: number, mm: number): SkShader
34
+ dispose(): void
35
+ }
36
+
37
+ export interface SkPaint {
38
+ setAntiAlias(antiAlias: boolean): void
39
+ setAlphaf(alpha: number): void
40
+ setBlendMode(blendMode: number): void
41
+ setShader(shader: SkShader | null): void
42
+ }
43
+
44
+ export interface SkCanvas {
45
+ saveLayer(paint?: SkPaint | null, bounds?: SkRect | null): number
46
+ restore(): void
47
+ drawVertices(vertices: SkVertices, mode: number, paint: SkPaint): void
48
+ }
49
+
50
+ export interface SkPictureRecorder {
51
+ beginRecording(bounds?: SkRect): SkCanvas
52
+ finishRecordingAsPicture(): SkPicture
53
+ }
54
+
55
+ export interface SkData {
56
+ dispose(): void
57
+ }
58
+
59
+ // Skia 3.8 renamed MakeImageFromEncoded to MakeFromEncoded; v2 and older v3 only have the old name.
60
+ export type SkImageFactory =
61
+ | { MakeFromEncoded(data: SkData): SkImage | null }
62
+ | { MakeImageFromEncoded(data: SkData): SkImage | null }
63
+
64
+ export interface SkiaApi {
65
+ XYWHRect(x: number, y: number, width: number, height: number): SkRect
66
+ Paint(): SkPaint
67
+ PictureRecorder(): SkPictureRecorder
68
+ MakeVertices(
69
+ mode: number,
70
+ positions: SkPoint[],
71
+ textures?: SkPoint[] | null,
72
+ colors?: undefined,
73
+ indices?: number[] | null,
74
+ ): SkVertices
75
+ Image: SkImageFactory
76
+ Data: { fromBytes(bytes: Uint8Array): SkData }
77
+ }
78
+
79
+ export interface SkiaModule {
80
+ Skia: SkiaApi
81
+ BlendMode: {
82
+ SrcOver: number
83
+ Dst: number
84
+ DstIn: number
85
+ DstOut: number
86
+ Plus: number
87
+ Multiply: number
88
+ }
89
+ VertexMode: { Triangles: number }
90
+ TileMode: { Clamp: number }
91
+ FilterMode: { Linear: number }
92
+ MipmapMode: { Linear: number }
93
+ Group: ComponentType<{
94
+ transform?: Record<string, number>[]
95
+ children?: ReactNode
96
+ }>
97
+ // A picture or a shared value of one (Reanimated).
98
+ Picture: ComponentType<{ picture: unknown }>
99
+ }