@umituz/react-native-image 1.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 +22 -0
- package/README.md +250 -0
- package/package.json +53 -0
- package/src/domain/entities/Image.ts +273 -0
- package/src/index.ts +55 -0
- package/src/infrastructure/services/ImageService.ts +316 -0
- package/src/infrastructure/services/ImageViewerService.ts +89 -0
- package/src/presentation/hooks/useImage.ts +393 -0
- package/src/presentation/hooks/useImageGallery.ts +149 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ümit UZ
|
|
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.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# @umituz/react-native-image
|
|
2
|
+
|
|
3
|
+
Image manipulation and viewing for React Native apps - resize, crop, rotate, flip, compress, gallery viewer.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Image Manipulation** - Resize, crop, rotate, flip, compress
|
|
8
|
+
- ✅ **Format Conversion** - JPEG, PNG, WEBP
|
|
9
|
+
- ✅ **Thumbnail Generation** - Create small preview images
|
|
10
|
+
- ✅ **Full-Screen Image Viewer** - Gallery viewer with zoom/swipe (react-native-image-viewing)
|
|
11
|
+
- ✅ **Image Gallery Management** - Multiple images with navigation
|
|
12
|
+
- ✅ **Filesystem Integration** - Save images to device using @umituz/react-native-filesystem
|
|
13
|
+
- ✅ **Type-Safe** - Full TypeScript support
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @umituz/react-native-image
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Peer Dependencies
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install expo-image-manipulator react-native-image-viewing @umituz/react-native-filesystem
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Image Manipulation
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useImage } from '@umituz/react-native-image';
|
|
33
|
+
|
|
34
|
+
const MyScreen = () => {
|
|
35
|
+
const { resize, crop, rotate, compress, isProcessing } = useImage();
|
|
36
|
+
|
|
37
|
+
const handleResize = async () => {
|
|
38
|
+
const resized = await resize(imageUri, 800, 600);
|
|
39
|
+
if (resized) {
|
|
40
|
+
console.log('Resized:', resized.uri);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleCrop = async () => {
|
|
45
|
+
const cropped = await crop(imageUri, {
|
|
46
|
+
originX: 0,
|
|
47
|
+
originY: 0,
|
|
48
|
+
width: 500,
|
|
49
|
+
height: 500,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleCompress = async () => {
|
|
54
|
+
const compressed = await compress(imageUri, 0.7);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View>
|
|
59
|
+
<Button onPress={handleResize} disabled={isProcessing}>
|
|
60
|
+
Resize Image
|
|
61
|
+
</Button>
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Image Gallery Viewer
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { useImageGallery } from '@umituz/react-native-image';
|
|
71
|
+
import ImageViewing from 'react-native-image-viewing';
|
|
72
|
+
|
|
73
|
+
const MyScreen = () => {
|
|
74
|
+
const { visible, currentIndex, images, open, close, setIndex } = useImageGallery();
|
|
75
|
+
|
|
76
|
+
const handleOpenGallery = () => {
|
|
77
|
+
open(['uri1', 'uri2', 'uri3'], 0);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<View>
|
|
82
|
+
<Button onPress={handleOpenGallery}>Open Gallery</Button>
|
|
83
|
+
|
|
84
|
+
<ImageViewing
|
|
85
|
+
images={images}
|
|
86
|
+
imageIndex={currentIndex}
|
|
87
|
+
visible={visible}
|
|
88
|
+
onRequestClose={close}
|
|
89
|
+
onIndexChange={setIndex}
|
|
90
|
+
/>
|
|
91
|
+
</View>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Image Service (Direct Usage)
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { ImageService } from '@umituz/react-native-image';
|
|
100
|
+
|
|
101
|
+
// Resize image
|
|
102
|
+
const resized = await ImageService.resize(uri, 800, 600);
|
|
103
|
+
|
|
104
|
+
// Crop image
|
|
105
|
+
const cropped = await ImageService.crop(uri, {
|
|
106
|
+
originX: 0,
|
|
107
|
+
originY: 0,
|
|
108
|
+
width: 500,
|
|
109
|
+
height: 500,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Create thumbnail
|
|
113
|
+
const thumbnail = await ImageService.createThumbnail(uri, 200);
|
|
114
|
+
|
|
115
|
+
// Save image
|
|
116
|
+
const savedUri = await ImageService.saveImage(uri, 'my-image.jpg');
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Image Utilities
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { ImageUtils, ImageFormat } from '@umituz/react-native-image';
|
|
123
|
+
|
|
124
|
+
// Get orientation
|
|
125
|
+
const orientation = ImageUtils.getOrientation(1920, 1080);
|
|
126
|
+
// Returns: ImageOrientation.LANDSCAPE
|
|
127
|
+
|
|
128
|
+
// Calculate aspect ratio
|
|
129
|
+
const ratio = ImageUtils.getAspectRatio(1920, 1080);
|
|
130
|
+
// Returns: 1.777...
|
|
131
|
+
|
|
132
|
+
// Fit to size
|
|
133
|
+
const dimensions = ImageUtils.fitToSize(1920, 1080, 800, 600);
|
|
134
|
+
// Returns: { width: 800, height: 450 }
|
|
135
|
+
|
|
136
|
+
// Get format from URI
|
|
137
|
+
const format = ImageUtils.getFormatFromUri('image.jpg');
|
|
138
|
+
// Returns: ImageFormat.JPEG
|
|
139
|
+
|
|
140
|
+
// Format file size
|
|
141
|
+
const size = ImageUtils.formatFileSize(1024000);
|
|
142
|
+
// Returns: "1000.0 KB"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## API Reference
|
|
146
|
+
|
|
147
|
+
### `useImage()`
|
|
148
|
+
|
|
149
|
+
React hook for image manipulation operations.
|
|
150
|
+
|
|
151
|
+
**Returns:**
|
|
152
|
+
- `resize(uri, width?, height?, options?)` - Resize image
|
|
153
|
+
- `crop(uri, cropArea, options?)` - Crop image
|
|
154
|
+
- `rotate(uri, degrees, options?)` - Rotate image
|
|
155
|
+
- `flip(uri, flipOptions, options?)` - Flip image
|
|
156
|
+
- `manipulate(uri, action, options?)` - Perform multiple manipulations
|
|
157
|
+
- `compress(uri, quality?)` - Compress image
|
|
158
|
+
- `resizeToFit(uri, maxWidth, maxHeight, options?)` - Resize to fit within max dimensions
|
|
159
|
+
- `createThumbnail(uri, size?, options?)` - Create thumbnail
|
|
160
|
+
- `cropToSquare(uri, width, height, options?)` - Crop to square (centered)
|
|
161
|
+
- `convertFormat(uri, format, quality?)` - Convert image format
|
|
162
|
+
- `saveImage(uri, filename?)` - Save image to device
|
|
163
|
+
- `isProcessing` - Processing state
|
|
164
|
+
- `error` - Error message
|
|
165
|
+
|
|
166
|
+
### `useImageGallery()`
|
|
167
|
+
|
|
168
|
+
React hook for image gallery and viewer.
|
|
169
|
+
|
|
170
|
+
**Returns:**
|
|
171
|
+
- `visible` - Gallery visibility state
|
|
172
|
+
- `currentIndex` - Current image index
|
|
173
|
+
- `images` - Image viewer items
|
|
174
|
+
- `open(images, startIndex?, options?)` - Open gallery
|
|
175
|
+
- `close()` - Close gallery
|
|
176
|
+
- `setIndex(index)` - Set current image index
|
|
177
|
+
- `options` - Gallery options
|
|
178
|
+
|
|
179
|
+
### `ImageService`
|
|
180
|
+
|
|
181
|
+
Service class for image manipulation.
|
|
182
|
+
|
|
183
|
+
**Methods:**
|
|
184
|
+
- `resize(uri, width?, height?, options?)` - Resize image
|
|
185
|
+
- `crop(uri, cropArea, options?)` - Crop image
|
|
186
|
+
- `rotate(uri, degrees, options?)` - Rotate image
|
|
187
|
+
- `flip(uri, flip, options?)` - Flip image
|
|
188
|
+
- `manipulate(uri, action, options?)` - Perform multiple manipulations
|
|
189
|
+
- `compress(uri, quality?)` - Compress image
|
|
190
|
+
- `resizeToFit(uri, maxWidth, maxHeight, options?)` - Resize to fit
|
|
191
|
+
- `createThumbnail(uri, size?, options?)` - Create thumbnail
|
|
192
|
+
- `cropToSquare(uri, width, height, options?)` - Crop to square
|
|
193
|
+
- `convertFormat(uri, format, quality?)` - Convert format
|
|
194
|
+
- `saveImage(uri, filename?)` - Save image
|
|
195
|
+
|
|
196
|
+
### `ImageViewerService`
|
|
197
|
+
|
|
198
|
+
Service class for image viewer configuration.
|
|
199
|
+
|
|
200
|
+
**Methods:**
|
|
201
|
+
- `prepareImages(uris)` - Prepare images for viewer
|
|
202
|
+
- `prepareImagesWithMetadata(items)` - Prepare images with metadata
|
|
203
|
+
- `createViewerConfig(images, startIndex?, onDismiss?, options?)` - Create viewer config
|
|
204
|
+
- `getDefaultOptions()` - Get default gallery options
|
|
205
|
+
|
|
206
|
+
### `ImageUtils`
|
|
207
|
+
|
|
208
|
+
Utility class for image operations.
|
|
209
|
+
|
|
210
|
+
**Methods:**
|
|
211
|
+
- `getOrientation(width, height)` - Get image orientation
|
|
212
|
+
- `getAspectRatio(width, height)` - Calculate aspect ratio
|
|
213
|
+
- `fitToSize(width, height, maxWidth, maxHeight)` - Fit to size
|
|
214
|
+
- `getThumbnailSize(width, height, thumbnailSize?)` - Get thumbnail size
|
|
215
|
+
- `isValidImageUri(uri)` - Validate image URI
|
|
216
|
+
- `getFormatFromUri(uri)` - Get format from URI
|
|
217
|
+
- `getExtensionFromFormat(format)` - Get extension from format
|
|
218
|
+
- `getSquareCrop(width, height)` - Get square crop dimensions
|
|
219
|
+
- `formatFileSize(bytes)` - Format file size
|
|
220
|
+
- `needsCompression(bytes, maxSizeMB?)` - Check if needs compression
|
|
221
|
+
|
|
222
|
+
## Types
|
|
223
|
+
|
|
224
|
+
- `ImageFormat` - JPEG | PNG | WEBP
|
|
225
|
+
- `ImageOrientation` - PORTRAIT | LANDSCAPE | SQUARE
|
|
226
|
+
- `SaveFormat` - 'jpeg' | 'png' | 'webp'
|
|
227
|
+
- `ImageManipulateAction` - Image manipulation action
|
|
228
|
+
- `ImageSaveOptions` - Image save options
|
|
229
|
+
- `ImageManipulationResult` - Manipulation result
|
|
230
|
+
- `ImageViewerItem` - Image viewer item
|
|
231
|
+
- `ImageGalleryOptions` - Gallery options
|
|
232
|
+
|
|
233
|
+
## Constants
|
|
234
|
+
|
|
235
|
+
- `IMAGE_CONSTANTS` - Image constants (MAX_WIDTH, MAX_HEIGHT, DEFAULT_QUALITY, THUMBNAIL_SIZE, etc.)
|
|
236
|
+
|
|
237
|
+
## Important Notes
|
|
238
|
+
|
|
239
|
+
⚠️ **File Operations**: This package uses `@umituz/react-native-filesystem` for file operations. Make sure to install it as a peer dependency.
|
|
240
|
+
|
|
241
|
+
⚠️ **Image Viewer**: This package provides hooks and services for `react-native-image-viewing`. You need to render the `ImageViewing` component yourself in your app.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT
|
|
246
|
+
|
|
247
|
+
## Author
|
|
248
|
+
|
|
249
|
+
Ümit UZ <umit@umituz.com>
|
|
250
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umituz/react-native-image",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Image manipulation and viewing for React Native apps - resize, crop, rotate, flip, compress, gallery viewer",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"lint": "tsc --noEmit",
|
|
10
|
+
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
11
|
+
"version:major": "npm version major -m 'chore: release v%s'"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react-native",
|
|
15
|
+
"image",
|
|
16
|
+
"manipulation",
|
|
17
|
+
"resize",
|
|
18
|
+
"crop",
|
|
19
|
+
"rotate",
|
|
20
|
+
"flip",
|
|
21
|
+
"compress",
|
|
22
|
+
"gallery",
|
|
23
|
+
"viewer"
|
|
24
|
+
],
|
|
25
|
+
"author": "Ümit UZ <umit@umituz.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/umituz/react-native-image"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=18.2.0",
|
|
33
|
+
"react-native": ">=0.74.0",
|
|
34
|
+
"expo-image-manipulator": ">=11.0.0",
|
|
35
|
+
"react-native-image-viewing": ">=0.2.0",
|
|
36
|
+
"@umituz/react-native-filesystem": "latest"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^18.2.45",
|
|
40
|
+
"@types/react-native": "^0.73.0",
|
|
41
|
+
"react": "^18.2.0",
|
|
42
|
+
"react-native": "^0.74.0",
|
|
43
|
+
"typescript": "^5.3.3"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"src",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Domain - Core Entities
|
|
3
|
+
*
|
|
4
|
+
* This file defines core types and interfaces for image manipulation and viewing.
|
|
5
|
+
* Handles image operations using expo-image-manipulator and react-native-image-viewing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Image format types
|
|
10
|
+
*/
|
|
11
|
+
export enum ImageFormat {
|
|
12
|
+
JPEG = 'jpeg',
|
|
13
|
+
PNG = 'png',
|
|
14
|
+
WEBP = 'webp',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Image save format for expo-image-manipulator
|
|
19
|
+
*/
|
|
20
|
+
export type SaveFormat = 'jpeg' | 'png' | 'webp';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Image manipulation action types
|
|
24
|
+
*/
|
|
25
|
+
export interface ImageManipulateAction {
|
|
26
|
+
resize?: { width?: number; height?: number };
|
|
27
|
+
crop?: { originX: number; originY: number; width: number; height: number };
|
|
28
|
+
rotate?: number; // degrees
|
|
29
|
+
flip?: { vertical?: boolean; horizontal?: boolean };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Image save options
|
|
34
|
+
*/
|
|
35
|
+
export interface ImageSaveOptions {
|
|
36
|
+
format?: SaveFormat;
|
|
37
|
+
compress?: number; // 0-1
|
|
38
|
+
base64?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Image manipulation result
|
|
43
|
+
*/
|
|
44
|
+
export interface ImageManipulationResult {
|
|
45
|
+
uri: string;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
base64?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Image metadata
|
|
53
|
+
*/
|
|
54
|
+
export interface ImageMetadata {
|
|
55
|
+
uri: string;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
format?: ImageFormat;
|
|
59
|
+
size?: number; // bytes
|
|
60
|
+
orientation?: ImageOrientation;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Image orientation
|
|
65
|
+
*/
|
|
66
|
+
export enum ImageOrientation {
|
|
67
|
+
PORTRAIT = 'portrait',
|
|
68
|
+
LANDSCAPE = 'landscape',
|
|
69
|
+
SQUARE = 'square',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Image viewer item
|
|
74
|
+
*/
|
|
75
|
+
export interface ImageViewerItem {
|
|
76
|
+
uri: string;
|
|
77
|
+
title?: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
width?: number;
|
|
80
|
+
height?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Image gallery options
|
|
85
|
+
*/
|
|
86
|
+
export interface ImageGalleryOptions {
|
|
87
|
+
index?: number; // Starting index
|
|
88
|
+
backgroundColor?: string;
|
|
89
|
+
swipeToCloseEnabled?: boolean;
|
|
90
|
+
doubleTapToZoomEnabled?: boolean;
|
|
91
|
+
onDismiss?: () => void;
|
|
92
|
+
onIndexChange?: (index: number) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Image operation result
|
|
97
|
+
*/
|
|
98
|
+
export interface ImageOperationResult {
|
|
99
|
+
success: boolean;
|
|
100
|
+
uri?: string;
|
|
101
|
+
error?: string;
|
|
102
|
+
width?: number;
|
|
103
|
+
height?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Image constants
|
|
108
|
+
*/
|
|
109
|
+
export const IMAGE_CONSTANTS = {
|
|
110
|
+
MAX_WIDTH: 2048,
|
|
111
|
+
MAX_HEIGHT: 2048,
|
|
112
|
+
DEFAULT_QUALITY: 0.8,
|
|
113
|
+
THUMBNAIL_SIZE: 200,
|
|
114
|
+
COMPRESS_QUALITY: {
|
|
115
|
+
LOW: 0.5,
|
|
116
|
+
MEDIUM: 0.7,
|
|
117
|
+
HIGH: 0.9,
|
|
118
|
+
},
|
|
119
|
+
FORMAT: {
|
|
120
|
+
JPEG: 'jpeg' as SaveFormat,
|
|
121
|
+
PNG: 'png' as SaveFormat,
|
|
122
|
+
WEBP: 'webp' as SaveFormat,
|
|
123
|
+
},
|
|
124
|
+
} as const;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Image utilities
|
|
128
|
+
*/
|
|
129
|
+
export class ImageUtils {
|
|
130
|
+
/**
|
|
131
|
+
* Get image orientation from dimensions
|
|
132
|
+
*/
|
|
133
|
+
static getOrientation(width: number, height: number): ImageOrientation {
|
|
134
|
+
if (width > height) return ImageOrientation.LANDSCAPE;
|
|
135
|
+
if (height > width) return ImageOrientation.PORTRAIT;
|
|
136
|
+
return ImageOrientation.SQUARE;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Calculate aspect ratio
|
|
141
|
+
*/
|
|
142
|
+
static getAspectRatio(width: number, height: number): number {
|
|
143
|
+
return width / height;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Calculate dimensions to fit within max size while preserving aspect ratio
|
|
148
|
+
*/
|
|
149
|
+
static fitToSize(
|
|
150
|
+
width: number,
|
|
151
|
+
height: number,
|
|
152
|
+
maxWidth: number,
|
|
153
|
+
maxHeight: number
|
|
154
|
+
): { width: number; height: number } {
|
|
155
|
+
const aspectRatio = ImageUtils.getAspectRatio(width, height);
|
|
156
|
+
|
|
157
|
+
let newWidth = width;
|
|
158
|
+
let newHeight = height;
|
|
159
|
+
|
|
160
|
+
if (width > maxWidth) {
|
|
161
|
+
newWidth = maxWidth;
|
|
162
|
+
newHeight = newWidth / aspectRatio;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (newHeight > maxHeight) {
|
|
166
|
+
newHeight = maxHeight;
|
|
167
|
+
newWidth = newHeight * aspectRatio;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
width: Math.round(newWidth),
|
|
172
|
+
height: Math.round(newHeight),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Calculate thumbnail dimensions (maintains aspect ratio)
|
|
178
|
+
*/
|
|
179
|
+
static getThumbnailSize(
|
|
180
|
+
width: number,
|
|
181
|
+
height: number,
|
|
182
|
+
thumbnailSize: number = IMAGE_CONSTANTS.THUMBNAIL_SIZE
|
|
183
|
+
): { width: number; height: number } {
|
|
184
|
+
return ImageUtils.fitToSize(width, height, thumbnailSize, thumbnailSize);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate image URI
|
|
189
|
+
*/
|
|
190
|
+
static isValidImageUri(uri: string): boolean {
|
|
191
|
+
if (!uri) return false;
|
|
192
|
+
|
|
193
|
+
// Check if it's a valid URI format
|
|
194
|
+
return (
|
|
195
|
+
uri.startsWith('file://') ||
|
|
196
|
+
uri.startsWith('content://') ||
|
|
197
|
+
uri.startsWith('http://') ||
|
|
198
|
+
uri.startsWith('https://') ||
|
|
199
|
+
uri.startsWith('data:image/')
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get image format from URI
|
|
205
|
+
*/
|
|
206
|
+
static getFormatFromUri(uri: string): ImageFormat | null {
|
|
207
|
+
const lowerUri = uri.toLowerCase();
|
|
208
|
+
|
|
209
|
+
if (lowerUri.includes('.jpg') || lowerUri.includes('.jpeg')) {
|
|
210
|
+
return ImageFormat.JPEG;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (lowerUri.includes('.png')) {
|
|
214
|
+
return ImageFormat.PNG;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (lowerUri.includes('.webp')) {
|
|
218
|
+
return ImageFormat.WEBP;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get file extension from format
|
|
226
|
+
*/
|
|
227
|
+
static getExtensionFromFormat(format: ImageFormat): string {
|
|
228
|
+
switch (format) {
|
|
229
|
+
case ImageFormat.JPEG:
|
|
230
|
+
return 'jpg';
|
|
231
|
+
case ImageFormat.PNG:
|
|
232
|
+
return 'png';
|
|
233
|
+
case ImageFormat.WEBP:
|
|
234
|
+
return 'webp';
|
|
235
|
+
default:
|
|
236
|
+
return 'jpg';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Calculate crop dimensions for square crop (centered)
|
|
242
|
+
*/
|
|
243
|
+
static getSquareCrop(width: number, height: number): ImageManipulateAction['crop'] {
|
|
244
|
+
const size = Math.min(width, height);
|
|
245
|
+
const originX = (width - size) / 2;
|
|
246
|
+
const originY = (height - size) / 2;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
originX: Math.round(originX),
|
|
250
|
+
originY: Math.round(originY),
|
|
251
|
+
width: Math.round(size),
|
|
252
|
+
height: Math.round(size),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Format file size to human-readable string
|
|
258
|
+
*/
|
|
259
|
+
static formatFileSize(bytes: number): string {
|
|
260
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
261
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
262
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if image needs compression based on size
|
|
267
|
+
*/
|
|
268
|
+
static needsCompression(bytes: number, maxSizeMB: number = 2): boolean {
|
|
269
|
+
const maxBytes = maxSizeMB * 1024 * 1024;
|
|
270
|
+
return bytes > maxBytes;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-image - Public API
|
|
3
|
+
*
|
|
4
|
+
* Image manipulation and viewing for React Native apps
|
|
5
|
+
* Resize, crop, rotate, flip, compress, gallery viewer
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { useImage, useImageGallery, ImageService, ImageUtils } from '@umituz/react-native-image';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// DOMAIN LAYER - Entities
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
ImageManipulateAction,
|
|
17
|
+
ImageSaveOptions,
|
|
18
|
+
ImageManipulationResult,
|
|
19
|
+
ImageMetadata,
|
|
20
|
+
ImageViewerItem,
|
|
21
|
+
ImageGalleryOptions,
|
|
22
|
+
ImageOperationResult,
|
|
23
|
+
SaveFormat,
|
|
24
|
+
} from './domain/entities/Image';
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
ImageFormat,
|
|
28
|
+
ImageOrientation,
|
|
29
|
+
IMAGE_CONSTANTS,
|
|
30
|
+
ImageUtils,
|
|
31
|
+
} from './domain/entities/Image';
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// INFRASTRUCTURE LAYER - Services
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
export { ImageService } from './infrastructure/services/ImageService';
|
|
38
|
+
export {
|
|
39
|
+
ImageViewerService,
|
|
40
|
+
type ImageViewerConfig,
|
|
41
|
+
} from './infrastructure/services/ImageViewerService';
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// PRESENTATION LAYER - Hooks
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
useImage,
|
|
49
|
+
} from './presentation/hooks/useImage';
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
useImageGallery,
|
|
53
|
+
type UseImageGalleryReturn,
|
|
54
|
+
} from './presentation/hooks/useImageGallery';
|
|
55
|
+
|