@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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +2 -0
  5. package/build/angular/inject-image-manipulator.d.ts +3 -0
  6. package/build/angular/inject-image-manipulator.js +8 -0
  7. package/build/core/image-manipulator.d.ts +10 -0
  8. package/build/core/image-manipulator.js +34 -0
  9. package/build/core/index.d.ts +4 -0
  10. package/build/core/index.js +2 -0
  11. package/build/core/manipulator-context-controller.d.ts +9 -0
  12. package/build/core/manipulator-context-controller.js +13 -0
  13. package/build/core/manipulator-resource-controller.d.ts +6 -0
  14. package/build/core/manipulator-resource-controller.js +17 -0
  15. package/build/core/native-module.d.ts +27 -0
  16. package/build/core/native-module.js +3 -0
  17. package/build/core/types.d.ts +47 -0
  18. package/build/core/types.js +11 -0
  19. package/build/core/validators.d.ts +5 -0
  20. package/build/core/validators.js +92 -0
  21. package/build/react/index.d.ts +2 -0
  22. package/build/react/index.js +2 -0
  23. package/build/react/use-image-manipulator.d.ts +3 -0
  24. package/build/react/use-image-manipulator.js +7 -0
  25. package/build/solid/index.d.ts +2 -0
  26. package/build/solid/index.js +2 -0
  27. package/build/solid/use-image-manipulator.d.ts +4 -0
  28. package/build/solid/use-image-manipulator.js +7 -0
  29. package/build/svelte/index.d.ts +2 -0
  30. package/build/svelte/index.js +2 -0
  31. package/build/svelte/use-image-manipulator.svelte.d.ts +5 -0
  32. package/build/svelte/use-image-manipulator.svelte.js +7 -0
  33. package/build/vue/index.d.ts +2 -0
  34. package/build/vue/index.js +2 -0
  35. package/build/vue/use-image-manipulator.d.ts +4 -0
  36. package/build/vue/use-image-manipulator.js +8 -0
  37. package/build-ngc/angular/index.d.ts +2 -0
  38. package/build-ngc/angular/index.js +3 -0
  39. package/build-ngc/angular/index.js.map +1 -0
  40. package/build-ngc/angular/inject-image-manipulator.d.ts +3 -0
  41. package/build-ngc/angular/inject-image-manipulator.js +9 -0
  42. package/build-ngc/angular/inject-image-manipulator.js.map +1 -0
  43. package/build-ngc/core/image-manipulator.d.ts +10 -0
  44. package/build-ngc/core/image-manipulator.js +35 -0
  45. package/build-ngc/core/image-manipulator.js.map +1 -0
  46. package/build-ngc/core/index.d.ts +4 -0
  47. package/build-ngc/core/index.js +3 -0
  48. package/build-ngc/core/index.js.map +1 -0
  49. package/build-ngc/core/manipulator-context-controller.d.ts +9 -0
  50. package/build-ngc/core/manipulator-context-controller.js +14 -0
  51. package/build-ngc/core/manipulator-context-controller.js.map +1 -0
  52. package/build-ngc/core/manipulator-resource-controller.d.ts +6 -0
  53. package/build-ngc/core/manipulator-resource-controller.js +18 -0
  54. package/build-ngc/core/manipulator-resource-controller.js.map +1 -0
  55. package/build-ngc/core/native-module.d.ts +27 -0
  56. package/build-ngc/core/native-module.js +4 -0
  57. package/build-ngc/core/native-module.js.map +1 -0
  58. package/build-ngc/core/types.d.ts +47 -0
  59. package/build-ngc/core/types.js +12 -0
  60. package/build-ngc/core/types.js.map +1 -0
  61. package/build-ngc/core/validators.d.ts +5 -0
  62. package/build-ngc/core/validators.js +93 -0
  63. package/build-ngc/core/validators.js.map +1 -0
  64. package/native-link.json +12 -0
  65. package/package.json +151 -0
  66. package/src/angular/index.ts +2 -0
  67. package/src/angular/inject-image-manipulator.ts +17 -0
  68. package/src/core/image-manipulator.ts +51 -0
  69. package/src/core/index.ts +12 -0
  70. package/src/core/manipulator-context-controller.ts +34 -0
  71. package/src/core/manipulator-resource-controller.ts +40 -0
  72. package/src/core/native-module.ts +40 -0
  73. package/src/core/types.ts +47 -0
  74. package/src/core/validators.ts +128 -0
  75. package/src/react/index.ts +2 -0
  76. package/src/react/use-image-manipulator.ts +14 -0
  77. package/src/solid/index.ts +2 -0
  78. package/src/solid/use-image-manipulator.ts +15 -0
  79. package/src/svelte/index.ts +2 -0
  80. package/src/svelte/svelte-ambient.d.ts +3 -0
  81. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  82. package/src/svelte/use-image-manipulator.svelte.ts +19 -0
  83. package/src/vue/index.ts +2 -0
  84. package/src/vue/use-image-manipulator.ts +19 -0
@@ -0,0 +1,40 @@
1
+ import {
2
+ requireNativeModule,
3
+ SharedObject,
4
+ SharedRef,
5
+ } from 'expo-modules-core';
6
+ import type { IImageResult, ISaveOptions } from './types';
7
+
8
+ const EXPO_IMAGE_MANIPULATOR_MODULE_NAME = 'ExpoImageManipulator';
9
+
10
+ export declare class NativeImageRef extends SharedRef<'image'> {
11
+ width: number;
12
+ height: number;
13
+ saveAsync(options?: ISaveOptions): Promise<IImageResult>;
14
+ }
15
+
16
+ export declare class NativeImageManipulatorContext extends SharedObject {
17
+ resize(size: {
18
+ width?: number | null;
19
+ height?: number | null;
20
+ }): NativeImageManipulatorContext;
21
+ rotate(degrees: number): NativeImageManipulatorContext;
22
+ flip(flipType: 'vertical' | 'horizontal'): NativeImageManipulatorContext;
23
+ crop(rect: {
24
+ originX: number;
25
+ originY: number;
26
+ width: number;
27
+ height: number;
28
+ }): NativeImageManipulatorContext;
29
+ reset(): NativeImageManipulatorContext;
30
+ renderAsync(): Promise<NativeImageRef>;
31
+ }
32
+
33
+ export type INativeImageManipulatorModule = {
34
+ manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
35
+ };
36
+
37
+ export const expoImageManipulator =
38
+ requireNativeModule<INativeImageManipulatorModule>(
39
+ EXPO_IMAGE_MANIPULATOR_MODULE_NAME,
40
+ );
@@ -0,0 +1,47 @@
1
+ export enum FlipType {
2
+ Vertical = 'vertical',
3
+ Horizontal = 'horizontal',
4
+ }
5
+
6
+ export enum SaveFormat {
7
+ JPEG = 'jpeg',
8
+ PNG = 'png',
9
+ WEBP = 'webp',
10
+ }
11
+
12
+ export type IActionResize = {
13
+ /** Provide only one of `width`/`height` to preserve the image's ratio. */
14
+ resize: { width?: number; height?: number };
15
+ };
16
+
17
+ export type IActionRotate = {
18
+ /** Degrees, clockwise when positive, counter-clockwise when negative. */
19
+ rotate: number;
20
+ };
21
+
22
+ export type IActionFlip = {
23
+ flip: FlipType;
24
+ };
25
+
26
+ export type IActionCrop = {
27
+ crop: { originX: number; originY: number; width: number; height: number };
28
+ };
29
+
30
+ /** @deprecated use `manipulate(source)` instead. */
31
+ export type IAction = IActionResize | IActionRotate | IActionFlip | IActionCrop;
32
+
33
+ export type ISaveOptions = {
34
+ base64?: boolean;
35
+ /** `0.0`-`1.0`; `1` is no compression (highest quality). Default `1`. */
36
+ compress?: number;
37
+ /** Default `SaveFormat.JPEG`. */
38
+ format?: SaveFormat;
39
+ };
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,128 @@
1
+ import { FlipType, SaveFormat } from './types';
2
+ import type {
3
+ IAction,
4
+ IActionCrop,
5
+ IActionFlip,
6
+ IActionResize,
7
+ IActionRotate,
8
+ ISaveOptions,
9
+ } from './types';
10
+
11
+ const SUPPORTED_ACTION_TYPES = ['crop', 'flip', 'rotate', 'resize'];
12
+
13
+ export function validateArguments(
14
+ uri: string,
15
+ actions: IAction[],
16
+ saveOptions: ISaveOptions,
17
+ ): void {
18
+ validateUri(uri);
19
+ validateActions(actions);
20
+ validateSaveOptions(saveOptions);
21
+ }
22
+
23
+ export function validateUri(uri: string): void {
24
+ if (typeof uri !== 'string') {
25
+ throw new TypeError('The "uri" argument must be a string');
26
+ }
27
+ }
28
+
29
+ export function validateActions(actions: IAction[]): void {
30
+ if (!Array.isArray(actions)) {
31
+ throw new TypeError('The "actions" argument must be an array');
32
+ }
33
+ for (const action of actions) {
34
+ if (typeof action !== 'object' || action === null) {
35
+ throw new TypeError('Action must be an object');
36
+ }
37
+ const actionKeys = Object.keys(action);
38
+ if (actionKeys.length !== 1) {
39
+ throw new TypeError(
40
+ `Single action must contain exactly one transformation: ${SUPPORTED_ACTION_TYPES.join(', ')}`,
41
+ );
42
+ }
43
+ const actionType = actionKeys[0];
44
+ if (!SUPPORTED_ACTION_TYPES.includes(actionType)) {
45
+ throw new TypeError(`Unsupported action type: ${actionType}`);
46
+ }
47
+ if ('crop' in action) {
48
+ validateCropAction(action);
49
+ } else if ('flip' in action) {
50
+ validateFlipAction(action);
51
+ } else if ('rotate' in action) {
52
+ validateRotateAction(action);
53
+ } else if ('resize' in action) {
54
+ validateResizeAction(action);
55
+ }
56
+ }
57
+ }
58
+
59
+ function validateCropAction(action: IActionCrop): void {
60
+ const isValid =
61
+ typeof action.crop === 'object' &&
62
+ action.crop !== null &&
63
+ typeof action.crop.originX === 'number' &&
64
+ typeof action.crop.originY === 'number' &&
65
+ typeof action.crop.width === 'number' &&
66
+ typeof action.crop.height === 'number';
67
+ if (!isValid) {
68
+ throw new TypeError(
69
+ 'Crop action must be an object of shape { originX: number; originY: number; width: number; height: number }',
70
+ );
71
+ }
72
+ }
73
+
74
+ function validateFlipAction(action: IActionFlip): void {
75
+ if (
76
+ typeof action.flip !== 'string' ||
77
+ ![FlipType.Horizontal, FlipType.Vertical].includes(action.flip)
78
+ ) {
79
+ throw new TypeError(`Unsupported flip type: ${action.flip}`);
80
+ }
81
+ }
82
+
83
+ function validateRotateAction(action: IActionRotate): void {
84
+ if (typeof action.rotate !== 'number') {
85
+ throw new TypeError('Rotation must be a number');
86
+ }
87
+ }
88
+
89
+ function validateResizeAction(action: IActionResize): void {
90
+ const isValid =
91
+ typeof action.resize === 'object' &&
92
+ action.resize !== null &&
93
+ (typeof action.resize.width === 'number' ||
94
+ action.resize.width === undefined) &&
95
+ (typeof action.resize.height === 'number' ||
96
+ action.resize.height === undefined);
97
+ if (!isValid) {
98
+ throw new TypeError(
99
+ 'Resize action must be an object of shape { width?: number; height?: number }',
100
+ );
101
+ }
102
+ }
103
+
104
+ export function validateSaveOptions({
105
+ base64,
106
+ compress,
107
+ format,
108
+ }: ISaveOptions): void {
109
+ if (base64 !== undefined && typeof base64 !== 'boolean') {
110
+ throw new TypeError('The "base64" argument must be a boolean');
111
+ }
112
+ if (compress !== undefined) {
113
+ if (typeof compress !== 'number') {
114
+ throw new TypeError('The "compress" argument must be a number');
115
+ }
116
+ if (compress < 0 || compress > 1) {
117
+ throw new TypeError(
118
+ 'The "compress" argument must be a number between 0 and 1',
119
+ );
120
+ }
121
+ }
122
+ const allowedFormats = [SaveFormat.JPEG, SaveFormat.PNG, SaveFormat.WEBP];
123
+ if (format !== undefined && !allowedFormats.includes(format)) {
124
+ throw new TypeError(
125
+ `The "format" argument must be one of: ${allowedFormats.join(', ')}`,
126
+ );
127
+ }
128
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,14 @@
1
+ import { createResourceHook } from '@symbiote-native/react';
2
+ import type { IImageManipulatorContext, IImageRef } from '../core';
3
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
4
+
5
+ const useManipulatorResource = createResourceHook(
6
+ createImageManipulatorResourceController,
7
+ );
8
+
9
+ /** React twin of `expo-image-manipulator`'s `useImageManipulator` */
10
+ export function useImageManipulator(
11
+ source: string | IImageRef,
12
+ ): IImageManipulatorContext {
13
+ return useManipulatorResource(source);
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,15 @@
1
+ import type { Accessor } from 'solid-js';
2
+ import { createResourceHook } from '@symbiote-native/solid';
3
+ import type { IImageManipulatorContext, IImageRef } from '../core';
4
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
5
+
6
+ const useManipulatorResource = createResourceHook(
7
+ createImageManipulatorResourceController,
8
+ );
9
+
10
+ /** Solid twin of `expo-image-manipulator`'s `useImageManipulator` */
11
+ export function useImageManipulator(
12
+ source: Accessor<string | IImageRef>,
13
+ ): Accessor<IImageManipulatorContext> {
14
+ return useManipulatorResource(() => [source()]);
15
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator.svelte';
@@ -0,0 +1,3 @@
1
+ // Pulls svelte's ambient globals ($state/$effect/...) into this package's tsc program,
2
+ // same fix as `packages/navigation/src/svelte/svelte-ambient.d.ts`
3
+ /// <reference types="svelte" />
@@ -0,0 +1,7 @@
1
+ // Re-exports the existing harness instead of a third copy (packages/sqlite and
2
+ // packages/navigation already carry one, not part of either package's public exports)
3
+ export {
4
+ createSvelteHarness,
5
+ loadComponent,
6
+ type ISvelteHarness,
7
+ } from '../../../sqlite/src/svelte/svelte-compile.test-helper';
@@ -0,0 +1,19 @@
1
+ // Svelte twin of `expo-image-manipulator`'s `useImageManipulator`
2
+
3
+ import { createResourceHook } from '@symbiote-native/svelte/runes/create-resource-hook';
4
+ import type { IImageManipulatorContext, IImageRef } from '../core';
5
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
6
+
7
+ export type IUseImageManipulatorResult = {
8
+ readonly current: IImageManipulatorContext;
9
+ };
10
+
11
+ const useManipulatorResource = createResourceHook(
12
+ createImageManipulatorResourceController,
13
+ );
14
+
15
+ export function useImageManipulator(
16
+ getSource: () => string | IImageRef,
17
+ ): IUseImageManipulatorResult {
18
+ return useManipulatorResource(() => [getSource()]);
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,19 @@
1
+ import {
2
+ toValue,
3
+ type ComputedRef,
4
+ type MaybeRefOrGetter,
5
+ } from '@vue/runtime-core';
6
+ import { createResourceHook } from '@symbiote-native/vue';
7
+ import type { IImageManipulatorContext, IImageRef } from '../core';
8
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
9
+
10
+ const useManipulatorResource = createResourceHook(
11
+ createImageManipulatorResourceController,
12
+ );
13
+
14
+ /** Vue twin of `expo-image-manipulator`'s `useImageManipulator` */
15
+ export function useImageManipulator(
16
+ source: MaybeRefOrGetter<string | IImageRef>,
17
+ ): ComputedRef<IImageManipulatorContext> {
18
+ return useManipulatorResource(() => [toValue(source)]);
19
+ }