@umituz/react-native-image 1.1.0 → 1.1.2
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/package.json +10 -4
- package/src/domain/entities/ImageConstants.ts +21 -0
- package/src/domain/entities/ImageTypes.ts +71 -0
- package/src/domain/utils/ImageUtils.ts +103 -0
- package/src/index.ts +16 -10
- package/src/infrastructure/services/ImageConversionService.ts +66 -0
- package/src/infrastructure/services/ImageStorageService.ts +22 -0
- package/src/infrastructure/services/ImageTransformService.ts +172 -0
- package/src/infrastructure/services/ImageViewerService.ts +1 -1
- package/src/presentation/components/ImageGallery.tsx +54 -0
- package/src/presentation/hooks/useImage.ts +13 -384
- package/src/presentation/hooks/useImageConversion.ts +26 -0
- package/src/presentation/hooks/useImageGallery.ts +1 -1
- package/src/presentation/hooks/useImageOperation.ts +33 -0
- package/src/presentation/hooks/useImageTransform.ts +37 -0
- package/src/domain/entities/Image.ts +0 -273
- package/src/infrastructure/services/ImageService.ts +0 -316
|
@@ -1,273 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Domain - Image Service
|
|
3
|
-
*
|
|
4
|
-
* Service for image manipulation using expo-image-manipulator.
|
|
5
|
-
* Provides abstraction layer for resizing, cropping, rotating images.
|
|
6
|
-
*
|
|
7
|
-
* NOTE: File operations use @umituz/react-native-filesystem (centralized)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import * as ImageManipulator from 'expo-image-manipulator';
|
|
11
|
-
import { FileSystemService } from '@umituz/react-native-filesystem';
|
|
12
|
-
import type {
|
|
13
|
-
ImageManipulateAction,
|
|
14
|
-
ImageSaveOptions,
|
|
15
|
-
ImageManipulationResult,
|
|
16
|
-
SaveFormat,
|
|
17
|
-
} from '../../domain/entities/Image';
|
|
18
|
-
import { IMAGE_CONSTANTS, ImageUtils } from '../../domain/entities/Image';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Image manipulation service
|
|
22
|
-
*/
|
|
23
|
-
export class ImageService {
|
|
24
|
-
/**
|
|
25
|
-
* Resize image to specified dimensions
|
|
26
|
-
*/
|
|
27
|
-
static async resize(
|
|
28
|
-
uri: string,
|
|
29
|
-
width?: number,
|
|
30
|
-
height?: number,
|
|
31
|
-
options?: ImageSaveOptions
|
|
32
|
-
): Promise<ImageManipulationResult | null> {
|
|
33
|
-
try {
|
|
34
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
35
|
-
uri,
|
|
36
|
-
[{ resize: { width, height } }],
|
|
37
|
-
{
|
|
38
|
-
compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
39
|
-
format: ImageService.mapFormatToManipulator(options?.format),
|
|
40
|
-
base64: options?.base64,
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
} catch (error) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Crop image to specified area
|
|
52
|
-
*/
|
|
53
|
-
static async crop(
|
|
54
|
-
uri: string,
|
|
55
|
-
cropArea: { originX: number; originY: number; width: number; height: number },
|
|
56
|
-
options?: ImageSaveOptions
|
|
57
|
-
): Promise<ImageManipulationResult | null> {
|
|
58
|
-
try {
|
|
59
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
60
|
-
uri,
|
|
61
|
-
[{ crop: cropArea }],
|
|
62
|
-
{
|
|
63
|
-
compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
64
|
-
format: ImageService.mapFormatToManipulator(options?.format),
|
|
65
|
-
base64: options?.base64,
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return result;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Rotate image by degrees
|
|
77
|
-
*/
|
|
78
|
-
static async rotate(
|
|
79
|
-
uri: string,
|
|
80
|
-
degrees: number,
|
|
81
|
-
options?: ImageSaveOptions
|
|
82
|
-
): Promise<ImageManipulationResult | null> {
|
|
83
|
-
try {
|
|
84
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
85
|
-
uri,
|
|
86
|
-
[{ rotate: degrees }],
|
|
87
|
-
{
|
|
88
|
-
compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
89
|
-
format: ImageService.mapFormatToManipulator(options?.format),
|
|
90
|
-
base64: options?.base64,
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
return result;
|
|
95
|
-
} catch (error) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Flip image horizontally or vertically
|
|
102
|
-
*/
|
|
103
|
-
static async flip(
|
|
104
|
-
uri: string,
|
|
105
|
-
flip: { horizontal?: boolean; vertical?: boolean },
|
|
106
|
-
options?: ImageSaveOptions
|
|
107
|
-
): Promise<ImageManipulationResult | null> {
|
|
108
|
-
try {
|
|
109
|
-
const actions: ImageManipulator.Action[] = [];
|
|
110
|
-
|
|
111
|
-
if (flip.horizontal) {
|
|
112
|
-
actions.push({ flip: ImageManipulator.FlipType.Horizontal });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (flip.vertical) {
|
|
116
|
-
actions.push({ flip: ImageManipulator.FlipType.Vertical });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
120
|
-
uri,
|
|
121
|
-
actions,
|
|
122
|
-
{
|
|
123
|
-
compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
124
|
-
format: ImageService.mapFormatToManipulator(options?.format),
|
|
125
|
-
base64: options?.base64,
|
|
126
|
-
}
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
return result;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Perform multiple image manipulations
|
|
137
|
-
*/
|
|
138
|
-
static async manipulate(
|
|
139
|
-
uri: string,
|
|
140
|
-
action: ImageManipulateAction,
|
|
141
|
-
options?: ImageSaveOptions
|
|
142
|
-
): Promise<ImageManipulationResult | null> {
|
|
143
|
-
try {
|
|
144
|
-
const manipulatorActions: ImageManipulator.Action[] = [];
|
|
145
|
-
|
|
146
|
-
if (action.resize) {
|
|
147
|
-
manipulatorActions.push({ resize: action.resize });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (action.crop) {
|
|
151
|
-
manipulatorActions.push({ crop: action.crop });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (action.rotate) {
|
|
155
|
-
manipulatorActions.push({ rotate: action.rotate });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (action.flip) {
|
|
159
|
-
if (action.flip.horizontal) {
|
|
160
|
-
manipulatorActions.push({ flip: ImageManipulator.FlipType.Horizontal });
|
|
161
|
-
}
|
|
162
|
-
if (action.flip.vertical) {
|
|
163
|
-
manipulatorActions.push({ flip: ImageManipulator.FlipType.Vertical });
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
168
|
-
uri,
|
|
169
|
-
manipulatorActions,
|
|
170
|
-
{
|
|
171
|
-
compress: options?.compress || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
172
|
-
format: ImageService.mapFormatToManipulator(options?.format),
|
|
173
|
-
base64: options?.base64,
|
|
174
|
-
}
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
return result;
|
|
178
|
-
} catch (error) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Compress image to reduce file size
|
|
185
|
-
*/
|
|
186
|
-
static async compress(
|
|
187
|
-
uri: string,
|
|
188
|
-
quality: number = IMAGE_CONSTANTS.DEFAULT_QUALITY
|
|
189
|
-
): Promise<ImageManipulationResult | null> {
|
|
190
|
-
try {
|
|
191
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
192
|
-
uri,
|
|
193
|
-
[],
|
|
194
|
-
{
|
|
195
|
-
compress: quality,
|
|
196
|
-
}
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return result;
|
|
200
|
-
} catch (error) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Resize image to fit within max dimensions (maintaining aspect ratio)
|
|
207
|
-
*/
|
|
208
|
-
static async resizeToFit(
|
|
209
|
-
uri: string,
|
|
210
|
-
maxWidth: number,
|
|
211
|
-
maxHeight: number,
|
|
212
|
-
options?: ImageSaveOptions
|
|
213
|
-
): Promise<ImageManipulationResult | null> {
|
|
214
|
-
try {
|
|
215
|
-
// Use ImageUtils to calculate fitted dimensions
|
|
216
|
-
const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
|
|
217
|
-
|
|
218
|
-
return ImageService.resize(uri, dimensions.width, dimensions.height, options);
|
|
219
|
-
} catch (error) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Create thumbnail (small preview image)
|
|
226
|
-
*/
|
|
227
|
-
static async createThumbnail(
|
|
228
|
-
uri: string,
|
|
229
|
-
size: number = IMAGE_CONSTANTS.THUMBNAIL_SIZE,
|
|
230
|
-
options?: ImageSaveOptions
|
|
231
|
-
): Promise<ImageManipulationResult | null> {
|
|
232
|
-
try {
|
|
233
|
-
return ImageService.resizeToFit(uri, size, size, {
|
|
234
|
-
...options,
|
|
235
|
-
compress: options?.compress || IMAGE_CONSTANTS.COMPRESS_QUALITY.MEDIUM,
|
|
236
|
-
});
|
|
237
|
-
} catch (error) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Crop image to square (centered)
|
|
244
|
-
*/
|
|
245
|
-
static async cropToSquare(
|
|
246
|
-
uri: string,
|
|
247
|
-
width: number,
|
|
248
|
-
height: number,
|
|
249
|
-
options?: ImageSaveOptions
|
|
250
|
-
): Promise<ImageManipulationResult | null> {
|
|
251
|
-
try {
|
|
252
|
-
const cropArea = ImageUtils.getSquareCrop(width, height);
|
|
253
|
-
return ImageService.crop(uri, cropArea, options);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Convert image format
|
|
261
|
-
*/
|
|
262
|
-
static async convertFormat(
|
|
263
|
-
uri: string,
|
|
264
|
-
format: SaveFormat,
|
|
265
|
-
quality?: number
|
|
266
|
-
): Promise<ImageManipulationResult | null> {
|
|
267
|
-
try {
|
|
268
|
-
const result = await ImageManipulator.manipulateAsync(
|
|
269
|
-
uri,
|
|
270
|
-
[],
|
|
271
|
-
{
|
|
272
|
-
compress: quality || IMAGE_CONSTANTS.DEFAULT_QUALITY,
|
|
273
|
-
format: ImageService.mapFormatToManipulator(format),
|
|
274
|
-
}
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
return result;
|
|
278
|
-
} catch (error) {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Save manipulated image to device using FileSystemService
|
|
285
|
-
*/
|
|
286
|
-
static async saveImage(
|
|
287
|
-
uri: string,
|
|
288
|
-
filename?: string
|
|
289
|
-
): Promise<string | null> {
|
|
290
|
-
try {
|
|
291
|
-
const result = await FileSystemService.copyToDocuments(uri, filename);
|
|
292
|
-
return result.success ? result.uri : null;
|
|
293
|
-
} catch (error) {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Map SaveFormat to ImageManipulator.SaveFormat
|
|
300
|
-
*/
|
|
301
|
-
private static mapFormatToManipulator(
|
|
302
|
-
format?: SaveFormat
|
|
303
|
-
): ImageManipulator.SaveFormat {
|
|
304
|
-
if (!format || format === 'jpeg') {
|
|
305
|
-
return ImageManipulator.SaveFormat.JPEG;
|
|
306
|
-
}
|
|
307
|
-
if (format === 'png') {
|
|
308
|
-
return ImageManipulator.SaveFormat.PNG;
|
|
309
|
-
}
|
|
310
|
-
if (format === 'webp') {
|
|
311
|
-
return ImageManipulator.SaveFormat.WEBP;
|
|
312
|
-
}
|
|
313
|
-
return ImageManipulator.SaveFormat.JPEG;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|