@symbiote-native/image-manipulator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +2 -0
- package/build/angular/inject-image-manipulator.d.ts +3 -0
- package/build/angular/inject-image-manipulator.js +8 -0
- package/build/core/image-manipulator.d.ts +10 -0
- package/build/core/image-manipulator.js +34 -0
- package/build/core/index.d.ts +4 -0
- package/build/core/index.js +2 -0
- package/build/core/manipulator-context-controller.d.ts +9 -0
- package/build/core/manipulator-context-controller.js +13 -0
- package/build/core/manipulator-resource-controller.d.ts +6 -0
- package/build/core/manipulator-resource-controller.js +17 -0
- package/build/core/native-module.d.ts +27 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +47 -0
- package/build/core/types.js +11 -0
- package/build/core/validators.d.ts +5 -0
- package/build/core/validators.js +92 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +2 -0
- package/build/react/use-image-manipulator.d.ts +3 -0
- package/build/react/use-image-manipulator.js +7 -0
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +2 -0
- package/build/solid/use-image-manipulator.d.ts +4 -0
- package/build/solid/use-image-manipulator.js +7 -0
- package/build/svelte/index.d.ts +2 -0
- package/build/svelte/index.js +2 -0
- package/build/svelte/use-image-manipulator.svelte.d.ts +5 -0
- package/build/svelte/use-image-manipulator.svelte.js +7 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +2 -0
- package/build/vue/use-image-manipulator.d.ts +4 -0
- package/build/vue/use-image-manipulator.js +8 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/inject-image-manipulator.d.ts +3 -0
- package/build-ngc/angular/inject-image-manipulator.js +9 -0
- package/build-ngc/angular/inject-image-manipulator.js.map +1 -0
- package/build-ngc/core/image-manipulator.d.ts +10 -0
- package/build-ngc/core/image-manipulator.js +35 -0
- package/build-ngc/core/image-manipulator.js.map +1 -0
- package/build-ngc/core/index.d.ts +4 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/manipulator-context-controller.d.ts +9 -0
- package/build-ngc/core/manipulator-context-controller.js +14 -0
- package/build-ngc/core/manipulator-context-controller.js.map +1 -0
- package/build-ngc/core/manipulator-resource-controller.d.ts +6 -0
- package/build-ngc/core/manipulator-resource-controller.js +18 -0
- package/build-ngc/core/manipulator-resource-controller.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +27 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +47 -0
- package/build-ngc/core/types.js +12 -0
- package/build-ngc/core/types.js.map +1 -0
- package/build-ngc/core/validators.d.ts +5 -0
- package/build-ngc/core/validators.js +93 -0
- package/build-ngc/core/validators.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +151 -0
- package/src/angular/index.ts +2 -0
- package/src/angular/inject-image-manipulator.ts +17 -0
- package/src/core/image-manipulator.ts +51 -0
- package/src/core/index.ts +12 -0
- package/src/core/manipulator-context-controller.ts +34 -0
- package/src/core/manipulator-resource-controller.ts +40 -0
- package/src/core/native-module.ts +40 -0
- package/src/core/types.ts +47 -0
- package/src/core/validators.ts +128 -0
- package/src/react/index.ts +2 -0
- package/src/react/use-image-manipulator.ts +14 -0
- package/src/solid/index.ts +2 -0
- package/src/solid/use-image-manipulator.ts +15 -0
- package/src/svelte/index.ts +2 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-image-manipulator.svelte.ts +19 -0
- package/src/vue/index.ts +2 -0
- package/src/vue/use-image-manipulator.ts +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @symbiote-native/image-manipulator
|
|
2
|
+
|
|
3
|
+
Shrink a photo before uploading it, rotate a scan, crop an avatar: build a chain of edits, render
|
|
4
|
+
once, save to a new file. One API for every [SymbioteNative](../../README.md) adapter (React, Vue,
|
|
5
|
+
Svelte, Solid and Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`expo-image-manipulator`](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator)
|
|
8
|
+
the same way [`@symbiote-native/print`](../print) wraps its upstream: `expo-modules-core` is a
|
|
9
|
+
direct dependency, never the `expo` meta-package, and the upstream JS is hand-ported into `core/`.
|
|
10
|
+
The mechanics live in the `symbiote-expo-native-module` project skill.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
**New app:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @symbiote-native/cli new my-app --image-manipulator
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Existing SymbioteNative app:**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @symbiote-native/cli add --image-manipulator
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Either way: installs `@symbiote-native/image-manipulator` and wires the native autolinking
|
|
27
|
+
automatically - see [`@symbiote-native/cli`](../cli).
|
|
28
|
+
|
|
29
|
+
<details>
|
|
30
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @symbiote-native/image-manipulator
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`expo-image-manipulator` and `expo-modules-core` come along as regular, pinned dependencies -
|
|
37
|
+
never install either yourself, and never add the `expo` meta-package to this project.
|
|
38
|
+
|
|
39
|
+
### Required one-time step: native autolinking wiring
|
|
40
|
+
|
|
41
|
+
Same one-time app wiring every `expo-modules-core` package shares - see
|
|
42
|
+
[`@symbiote-native/print`'s README](../print/README.md#required-one-time-step-native-autolinking-wiring)
|
|
43
|
+
for the full table; nothing package-specific here.
|
|
44
|
+
|
|
45
|
+
### No permissions, no config plugin
|
|
46
|
+
|
|
47
|
+
- **No runtime permission on either platform.** The module only ever touches a URI/file the
|
|
48
|
+
caller already has access to - no camera, no photo library.
|
|
49
|
+
- **No manifest edit.** `expo-image-manipulator`'s own `AndroidManifest.xml` declares nothing.
|
|
50
|
+
- **No config plugin.** Upstream ships none.
|
|
51
|
+
|
|
52
|
+
</details>
|
|
53
|
+
|
|
54
|
+
## Shape
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
src/core/ manipulate + manipulateAsync + FlipType + SaveFormat + the shared
|
|
58
|
+
releasing-context controller. native-module.ts resolves
|
|
59
|
+
ExpoImageManipulator through expo-modules-core's requireNativeModule.
|
|
60
|
+
src/react/ useImageManipulator (hook)
|
|
61
|
+
src/vue/ useImageManipulator (composable)
|
|
62
|
+
src/solid/ useImageManipulator (primitive)
|
|
63
|
+
src/svelte/ useImageManipulator (rune)
|
|
64
|
+
src/angular/ injectImageManipulator
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`manipulate`/`manipulateAsync`/`FlipType`/`SaveFormat` stay framework-agnostic free functions,
|
|
68
|
+
re-exported by every adapter's own barrel. Upstream's `useImageManipulator` is ported to
|
|
69
|
+
**every** adapter, not just React: the recreate/release rule lives once in
|
|
70
|
+
`src/core/manipulator-context-controller.ts`, each adapter supplies only its own reactive
|
|
71
|
+
lifecycle, per this project's `components_split_logic_view_lifecycle` convention.
|
|
72
|
+
|
|
73
|
+
## Use it
|
|
74
|
+
|
|
75
|
+
The current, chainable API (mirrors upstream's `ImageManipulator.manipulate`/`ImageManipulatorContext`):
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { manipulate } from '@symbiote-native/image-manipulator';
|
|
79
|
+
|
|
80
|
+
const context = manipulate('file:///photo.jpg');
|
|
81
|
+
context.resize({ width: 300 }).rotate(90);
|
|
82
|
+
const image = await context.renderAsync();
|
|
83
|
+
const { uri, width, height } = await image.saveAsync({ format: 'jpeg', compress: 0.8 });
|
|
84
|
+
|
|
85
|
+
// Free the native memory once done - no lifecycle wrapper here (framework-agnostic core), each
|
|
86
|
+
// adapter's own hook/composable/service is responsible for calling `.release()` on unmount.
|
|
87
|
+
context.release();
|
|
88
|
+
image.release();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The deprecated, one-shot API (kept for parity - upstream still ships it too):
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { manipulateAsync, SaveFormat } from '@symbiote-native/image-manipulator';
|
|
95
|
+
|
|
96
|
+
const { uri } = await manipulateAsync(
|
|
97
|
+
'file:///photo.jpg',
|
|
98
|
+
[{ resize: { width: 300 } }, { rotate: 90 }],
|
|
99
|
+
{ format: SaveFormat.JPEG, compress: 0.8 },
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
| Export | Signature | Notes |
|
|
106
|
+
| ------------------ | ------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
107
|
+
| `manipulate` | `(source: string \| IImageRef) => IImageManipulatorContext` | Chainable `resize`/`rotate`/`flip`/`crop`/`reset`/`renderAsync`. |
|
|
108
|
+
| `manipulateAsync` | `(uri, actions?, saveOptions?) => Promise<IImageResult>` | @deprecated - replaced by `manipulate`. |
|
|
109
|
+
| `FlipType` | `{ Vertical, Horizontal }` | Enum for the `flip` action. |
|
|
110
|
+
| `SaveFormat` | `{ JPEG, PNG, WEBP }` | Enum for `ISaveOptions.format`. |
|
|
111
|
+
|
|
112
|
+
## Declarative `useImageManipulator`
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
useImageManipulator(source) -> IImageManipulatorContext
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
React takes a plain value, re-invoked per render (matches upstream exactly). Vue/Solid/Svelte
|
|
119
|
+
take a reactive source (`Ref`/getter/`MaybeRefOrGetter`/accessor) so switching it recreates the
|
|
120
|
+
context and releases the stale one. Angular's `injectImageManipulator(source)` takes a `Signal`
|
|
121
|
+
and returns one, `injectX` shape matching `@symbiote-native/navigation`'s
|
|
122
|
+
`injectLinkingIntegration`.
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- **Release what you create outside a component.** `manipulate()` and `renderAsync()` hold native
|
|
127
|
+
memory until `release()`. The adapter bindings release on unmount; the free function does not.
|
|
128
|
+
- **`extent` action is not ported.** It's web-only in upstream - neither the iOS nor the Android
|
|
129
|
+
native module registers an `extent` function (verified by reading both `ImageManipulatorModule`
|
|
130
|
+
sources). Calling `.extent()` on a native context is a type error here, matching runtime reality.
|
|
131
|
+
- **`ImageManipulator.Image` (the hidden native class property) is not exposed.** Upstream itself
|
|
132
|
+
marks it `@hidden` - only reachable indirectly via `context.renderAsync()`'s resolved `IImageRef`.
|
|
133
|
+
|
|
134
|
+
## Common questions
|
|
135
|
+
|
|
136
|
+
- **Smaller upload.** Resize first (`resize({ width: 1080 })`), then
|
|
137
|
+
`saveAsync({ format: SaveFormat.JPEG, compress: 0.7 })`; `compress` is 0 to 1 and applies to JPEG
|
|
138
|
+
and WebP, PNG is lossless.
|
|
139
|
+
- **Keep the aspect ratio.** Give only one of `width` and `height`.
|
|
140
|
+
- **Memory growth.** `manipulate()` and `renderAsync()` hold native memory until `release()`.
|
|
141
|
+
- **`manipulateAsync`.** Works but deprecated upstream; use `manipulate()`.
|
|
142
|
+
|
|
143
|
+
Sources: [Expo docs: ImageManipulator](https://docs.expo.dev/versions/latest/sdk/imagemanipulator/),
|
|
144
|
+
[expo/expo#2512](https://github.com/expo/expo/issues/2512).
|
|
145
|
+
|
|
146
|
+
## Test it
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm vitest run packages/image-manipulator
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Upstream ships one test suite, `src/__tests__/validators-test.ts` - ported to
|
|
153
|
+
`validators.test.ts` (minus the `extent` cases, see above). `image-manipulator.test.ts` is net-new
|
|
154
|
+
coverage of `manipulate`/`manipulateAsync` against a faked native module.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Angular twin of `useImageManipulator`, `injectX` shape matching
|
|
2
|
+
// `@symbiote-native/navigation`'s `injectLinkingIntegration`
|
|
3
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
4
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
|
|
5
|
+
const injectManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
6
|
+
export function injectImageManipulator(source) {
|
|
7
|
+
return injectManipulatorResource(() => [source()]);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NativeImageManipulatorContext, NativeImageRef } from './native-module';
|
|
2
|
+
import type { IAction, IImageResult, ISaveOptions } from './types';
|
|
3
|
+
export type { NativeImageManipulatorContext as IImageManipulatorContext };
|
|
4
|
+
export type { NativeImageRef as IImageRef };
|
|
5
|
+
/** Loads `source` and starts a new chainable manipulation context. */
|
|
6
|
+
export declare function manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
|
|
9
|
+
*/
|
|
10
|
+
export declare function manipulateAsync(uri: string, actions?: IAction[], saveOptions?: ISaveOptions): Promise<IImageResult>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { expoImageManipulator } from './native-module.js';
|
|
2
|
+
import { SaveFormat } from './types.js';
|
|
3
|
+
import { validateArguments } from './validators.js';
|
|
4
|
+
/** Loads `source` and starts a new chainable manipulation context. */
|
|
5
|
+
export function manipulate(source) {
|
|
6
|
+
return expoImageManipulator.manipulate(source);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
|
|
10
|
+
*/
|
|
11
|
+
export async function manipulateAsync(uri, actions = [], saveOptions = {}) {
|
|
12
|
+
validateArguments(uri, actions, saveOptions);
|
|
13
|
+
const { format = SaveFormat.JPEG, ...rest } = saveOptions;
|
|
14
|
+
const context = expoImageManipulator.manipulate(uri);
|
|
15
|
+
for (const action of actions) {
|
|
16
|
+
if ('resize' in action) {
|
|
17
|
+
context.resize(action.resize);
|
|
18
|
+
}
|
|
19
|
+
else if ('rotate' in action) {
|
|
20
|
+
context.rotate(action.rotate);
|
|
21
|
+
}
|
|
22
|
+
else if ('flip' in action) {
|
|
23
|
+
context.flip(action.flip);
|
|
24
|
+
}
|
|
25
|
+
else if ('crop' in action) {
|
|
26
|
+
context.crop(action.crop);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const image = await context.renderAsync();
|
|
30
|
+
const result = await image.saveAsync({ format, ...rest });
|
|
31
|
+
context.release();
|
|
32
|
+
image.release();
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { manipulate, manipulateAsync } from './image-manipulator';
|
|
2
|
+
export type { IImageManipulatorContext, IImageRef } from './image-manipulator';
|
|
3
|
+
export { FlipType, SaveFormat } from './types';
|
|
4
|
+
export type { IAction, IActionCrop, IActionFlip, IActionResize, IActionRotate, IImageResult, ISaveOptions, } from './types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type IReleasableContext = {
|
|
2
|
+
release: () => void;
|
|
3
|
+
};
|
|
4
|
+
export type IManipulatorContextController<TSource, TContext extends IReleasableContext> = {
|
|
5
|
+
resolve: (source: TSource) => TContext;
|
|
6
|
+
flushRelease: () => void;
|
|
7
|
+
release: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function createManipulatorContextController<TSource, TContext extends IReleasableContext>(manipulate: (source: TSource) => TContext): IManipulatorContextController<TSource, TContext>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Thin binding of `@symbiote-native/engine`'s generic `createResourceController` to this
|
|
2
|
+
// package's own `.release()`-shaped context, kept for its own descriptive type names
|
|
3
|
+
import { createResourceController } from '@symbiote-native/engine';
|
|
4
|
+
export function createManipulatorContextController(manipulate) {
|
|
5
|
+
const controller = createResourceController(manipulate, context => {
|
|
6
|
+
context.release();
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
resolve: controller.resolve,
|
|
10
|
+
flushRelease: controller.flushDispose,
|
|
11
|
+
release: controller.dispose,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Adapts `createManipulatorContextController`'s `.release()`-shaped controller to the
|
|
2
|
+
// `resolve`/`flushDispose`/`dispose` shape every adapter's `createResourceHook` expects
|
|
3
|
+
import { createManipulatorContextController, } from './manipulator-context-controller.js';
|
|
4
|
+
import { manipulate } from './index.js';
|
|
5
|
+
function createManipulatorResourceController(manipulateSource) {
|
|
6
|
+
const controller = createManipulatorContextController(manipulateSource);
|
|
7
|
+
return {
|
|
8
|
+
resolve: controller.resolve,
|
|
9
|
+
flushDispose: controller.flushRelease,
|
|
10
|
+
dispose: controller.release,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// Bakes in this package's own `manipulate`, so every adapter's `createResourceHook` binds a
|
|
14
|
+
// plain zero-arg factory instead of repeating the same arrow wrapper five times
|
|
15
|
+
export function createImageManipulatorResourceController() {
|
|
16
|
+
return createManipulatorResourceController(manipulate);
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SharedObject, SharedRef } from 'expo-modules-core';
|
|
2
|
+
import type { IImageResult, ISaveOptions } from './types';
|
|
3
|
+
export declare class NativeImageRef extends SharedRef<'image'> {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
saveAsync(options?: ISaveOptions): Promise<IImageResult>;
|
|
7
|
+
}
|
|
8
|
+
export declare class NativeImageManipulatorContext extends SharedObject {
|
|
9
|
+
resize(size: {
|
|
10
|
+
width?: number | null;
|
|
11
|
+
height?: number | null;
|
|
12
|
+
}): NativeImageManipulatorContext;
|
|
13
|
+
rotate(degrees: number): NativeImageManipulatorContext;
|
|
14
|
+
flip(flipType: 'vertical' | 'horizontal'): NativeImageManipulatorContext;
|
|
15
|
+
crop(rect: {
|
|
16
|
+
originX: number;
|
|
17
|
+
originY: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}): NativeImageManipulatorContext;
|
|
21
|
+
reset(): NativeImageManipulatorContext;
|
|
22
|
+
renderAsync(): Promise<NativeImageRef>;
|
|
23
|
+
}
|
|
24
|
+
export type INativeImageManipulatorModule = {
|
|
25
|
+
manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
|
|
26
|
+
};
|
|
27
|
+
export declare const expoImageManipulator: INativeImageManipulatorModule;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare enum FlipType {
|
|
2
|
+
Vertical = "vertical",
|
|
3
|
+
Horizontal = "horizontal"
|
|
4
|
+
}
|
|
5
|
+
export declare enum SaveFormat {
|
|
6
|
+
JPEG = "jpeg",
|
|
7
|
+
PNG = "png",
|
|
8
|
+
WEBP = "webp"
|
|
9
|
+
}
|
|
10
|
+
export type IActionResize = {
|
|
11
|
+
/** Provide only one of `width`/`height` to preserve the image's ratio. */
|
|
12
|
+
resize: {
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type IActionRotate = {
|
|
18
|
+
/** Degrees, clockwise when positive, counter-clockwise when negative. */
|
|
19
|
+
rotate: number;
|
|
20
|
+
};
|
|
21
|
+
export type IActionFlip = {
|
|
22
|
+
flip: FlipType;
|
|
23
|
+
};
|
|
24
|
+
export type IActionCrop = {
|
|
25
|
+
crop: {
|
|
26
|
+
originX: number;
|
|
27
|
+
originY: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** @deprecated use `manipulate(source)` instead. */
|
|
33
|
+
export type IAction = IActionResize | IActionRotate | IActionFlip | IActionCrop;
|
|
34
|
+
export type ISaveOptions = {
|
|
35
|
+
base64?: boolean;
|
|
36
|
+
/** `0.0`-`1.0`; `1` is no compression (highest quality). Default `1`. */
|
|
37
|
+
compress?: number;
|
|
38
|
+
/** Default `SaveFormat.JPEG`. */
|
|
39
|
+
format?: SaveFormat;
|
|
40
|
+
};
|
|
41
|
+
export type IImageResult = {
|
|
42
|
+
uri: string;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
/** Set only when `base64` was truthy in `ISaveOptions`. */
|
|
46
|
+
base64?: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export var FlipType;
|
|
2
|
+
(function (FlipType) {
|
|
3
|
+
FlipType["Vertical"] = "vertical";
|
|
4
|
+
FlipType["Horizontal"] = "horizontal";
|
|
5
|
+
})(FlipType || (FlipType = {}));
|
|
6
|
+
export var SaveFormat;
|
|
7
|
+
(function (SaveFormat) {
|
|
8
|
+
SaveFormat["JPEG"] = "jpeg";
|
|
9
|
+
SaveFormat["PNG"] = "png";
|
|
10
|
+
SaveFormat["WEBP"] = "webp";
|
|
11
|
+
})(SaveFormat || (SaveFormat = {}));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IAction, ISaveOptions } from './types';
|
|
2
|
+
export declare function validateArguments(uri: string, actions: IAction[], saveOptions: ISaveOptions): void;
|
|
3
|
+
export declare function validateUri(uri: string): void;
|
|
4
|
+
export declare function validateActions(actions: IAction[]): void;
|
|
5
|
+
export declare function validateSaveOptions({ base64, compress, format, }: ISaveOptions): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { FlipType, SaveFormat } from './types.js';
|
|
2
|
+
const SUPPORTED_ACTION_TYPES = ['crop', 'flip', 'rotate', 'resize'];
|
|
3
|
+
export function validateArguments(uri, actions, saveOptions) {
|
|
4
|
+
validateUri(uri);
|
|
5
|
+
validateActions(actions);
|
|
6
|
+
validateSaveOptions(saveOptions);
|
|
7
|
+
}
|
|
8
|
+
export function validateUri(uri) {
|
|
9
|
+
if (typeof uri !== 'string') {
|
|
10
|
+
throw new TypeError('The "uri" argument must be a string');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function validateActions(actions) {
|
|
14
|
+
if (!Array.isArray(actions)) {
|
|
15
|
+
throw new TypeError('The "actions" argument must be an array');
|
|
16
|
+
}
|
|
17
|
+
for (const action of actions) {
|
|
18
|
+
if (typeof action !== 'object' || action === null) {
|
|
19
|
+
throw new TypeError('Action must be an object');
|
|
20
|
+
}
|
|
21
|
+
const actionKeys = Object.keys(action);
|
|
22
|
+
if (actionKeys.length !== 1) {
|
|
23
|
+
throw new TypeError(`Single action must contain exactly one transformation: ${SUPPORTED_ACTION_TYPES.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
const actionType = actionKeys[0];
|
|
26
|
+
if (!SUPPORTED_ACTION_TYPES.includes(actionType)) {
|
|
27
|
+
throw new TypeError(`Unsupported action type: ${actionType}`);
|
|
28
|
+
}
|
|
29
|
+
if ('crop' in action) {
|
|
30
|
+
validateCropAction(action);
|
|
31
|
+
}
|
|
32
|
+
else if ('flip' in action) {
|
|
33
|
+
validateFlipAction(action);
|
|
34
|
+
}
|
|
35
|
+
else if ('rotate' in action) {
|
|
36
|
+
validateRotateAction(action);
|
|
37
|
+
}
|
|
38
|
+
else if ('resize' in action) {
|
|
39
|
+
validateResizeAction(action);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function validateCropAction(action) {
|
|
44
|
+
const isValid = typeof action.crop === 'object' &&
|
|
45
|
+
action.crop !== null &&
|
|
46
|
+
typeof action.crop.originX === 'number' &&
|
|
47
|
+
typeof action.crop.originY === 'number' &&
|
|
48
|
+
typeof action.crop.width === 'number' &&
|
|
49
|
+
typeof action.crop.height === 'number';
|
|
50
|
+
if (!isValid) {
|
|
51
|
+
throw new TypeError('Crop action must be an object of shape { originX: number; originY: number; width: number; height: number }');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function validateFlipAction(action) {
|
|
55
|
+
if (typeof action.flip !== 'string' ||
|
|
56
|
+
![FlipType.Horizontal, FlipType.Vertical].includes(action.flip)) {
|
|
57
|
+
throw new TypeError(`Unsupported flip type: ${action.flip}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function validateRotateAction(action) {
|
|
61
|
+
if (typeof action.rotate !== 'number') {
|
|
62
|
+
throw new TypeError('Rotation must be a number');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateResizeAction(action) {
|
|
66
|
+
const isValid = typeof action.resize === 'object' &&
|
|
67
|
+
action.resize !== null &&
|
|
68
|
+
(typeof action.resize.width === 'number' ||
|
|
69
|
+
action.resize.width === undefined) &&
|
|
70
|
+
(typeof action.resize.height === 'number' ||
|
|
71
|
+
action.resize.height === undefined);
|
|
72
|
+
if (!isValid) {
|
|
73
|
+
throw new TypeError('Resize action must be an object of shape { width?: number; height?: number }');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function validateSaveOptions({ base64, compress, format, }) {
|
|
77
|
+
if (base64 !== undefined && typeof base64 !== 'boolean') {
|
|
78
|
+
throw new TypeError('The "base64" argument must be a boolean');
|
|
79
|
+
}
|
|
80
|
+
if (compress !== undefined) {
|
|
81
|
+
if (typeof compress !== 'number') {
|
|
82
|
+
throw new TypeError('The "compress" argument must be a number');
|
|
83
|
+
}
|
|
84
|
+
if (compress < 0 || compress > 1) {
|
|
85
|
+
throw new TypeError('The "compress" argument must be a number between 0 and 1');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const allowedFormats = [SaveFormat.JPEG, SaveFormat.PNG, SaveFormat.WEBP];
|
|
89
|
+
if (format !== undefined && !allowedFormats.includes(format)) {
|
|
90
|
+
throw new TypeError(`The "format" argument must be one of: ${allowedFormats.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createResourceHook } from '@symbiote-native/react';
|
|
2
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
|
|
3
|
+
const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
4
|
+
/** React twin of `expo-image-manipulator`'s `useImageManipulator` */
|
|
5
|
+
export function useImageManipulator(source) {
|
|
6
|
+
return useManipulatorResource(source);
|
|
7
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Accessor } from 'solid-js';
|
|
2
|
+
import type { IImageManipulatorContext, IImageRef } from '../core';
|
|
3
|
+
/** Solid twin of `expo-image-manipulator`'s `useImageManipulator` */
|
|
4
|
+
export declare function useImageManipulator(source: Accessor<string | IImageRef>): Accessor<IImageManipulatorContext>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createResourceHook } from '@symbiote-native/solid';
|
|
2
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
|
|
3
|
+
const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
4
|
+
/** Solid twin of `expo-image-manipulator`'s `useImageManipulator` */
|
|
5
|
+
export function useImageManipulator(source) {
|
|
6
|
+
return useManipulatorResource(() => [source()]);
|
|
7
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IImageManipulatorContext, IImageRef } from '../core';
|
|
2
|
+
export type IUseImageManipulatorResult = {
|
|
3
|
+
readonly current: IImageManipulatorContext;
|
|
4
|
+
};
|
|
5
|
+
export declare function useImageManipulator(getSource: () => string | IImageRef): IUseImageManipulatorResult;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Svelte twin of `expo-image-manipulator`'s `useImageManipulator`
|
|
2
|
+
import { createResourceHook } from '@symbiote-native/svelte/runes/create-resource-hook';
|
|
3
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
|
|
4
|
+
const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
5
|
+
export function useImageManipulator(getSource) {
|
|
6
|
+
return useManipulatorResource(() => [getSource()]);
|
|
7
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ComputedRef, type MaybeRefOrGetter } from '@vue/runtime-core';
|
|
2
|
+
import type { IImageManipulatorContext, IImageRef } from '../core';
|
|
3
|
+
/** Vue twin of `expo-image-manipulator`'s `useImageManipulator` */
|
|
4
|
+
export declare function useImageManipulator(source: MaybeRefOrGetter<string | IImageRef>): ComputedRef<IImageManipulatorContext>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { toValue, } from '@vue/runtime-core';
|
|
2
|
+
import { createResourceHook } from '@symbiote-native/vue';
|
|
3
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
|
|
4
|
+
const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
5
|
+
/** Vue twin of `expo-image-manipulator`'s `useImageManipulator` */
|
|
6
|
+
export function useImageManipulator(source) {
|
|
7
|
+
return useManipulatorResource(() => [toValue(source)]);
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Angular twin of `useImageManipulator`, `injectX` shape matching
|
|
2
|
+
// `@symbiote-native/navigation`'s `injectLinkingIntegration`
|
|
3
|
+
import { createResourceHook } from '@symbiote-native/angular';
|
|
4
|
+
import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
|
|
5
|
+
const injectManipulatorResource = createResourceHook(createImageManipulatorResourceController);
|
|
6
|
+
export function injectImageManipulator(source) {
|
|
7
|
+
return injectManipulatorResource(() => [source()]);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=inject-image-manipulator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject-image-manipulator.js","sourceRoot":"","sources":["../../src/angular/inject-image-manipulator.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,6DAA6D;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,wCAAwC,EAAE,MAAM,yCAAyC,CAAC;AAEnG,MAAM,yBAAyB,GAAG,kBAAkB,CAClD,wCAAwC,CACzC,CAAC;AAEF,MAAM,UAAU,sBAAsB,CACpC,MAAgC;IAEhC,OAAO,yBAAyB,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NativeImageManipulatorContext, NativeImageRef } from './native-module';
|
|
2
|
+
import type { IAction, IImageResult, ISaveOptions } from './types';
|
|
3
|
+
export type { NativeImageManipulatorContext as IImageManipulatorContext };
|
|
4
|
+
export type { NativeImageRef as IImageRef };
|
|
5
|
+
/** Loads `source` and starts a new chainable manipulation context. */
|
|
6
|
+
export declare function manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
|
|
9
|
+
*/
|
|
10
|
+
export declare function manipulateAsync(uri: string, actions?: IAction[], saveOptions?: ISaveOptions): Promise<IImageResult>;
|