@dynlabs/react-native-image-to-webp 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 (38) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +247 -0
  3. package/ReactNativeImageToWebp.podspec +35 -0
  4. package/android/build.gradle +85 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/cpp/CMakeLists.txt +67 -0
  7. package/android/src/main/cpp/ImageToWebPJNI.cpp +73 -0
  8. package/android/src/main/java/com/dynlabs/reactnativeimagetowebp/ReactNativeImageToWebpModule.kt +258 -0
  9. package/android/src/main/java/com/dynlabs/reactnativeimagetowebp/ReactNativeImageToWebpPackage.kt +33 -0
  10. package/cpp/ImageToWebP.cpp +132 -0
  11. package/cpp/ImageToWebP.h +41 -0
  12. package/cpp/README.md +21 -0
  13. package/cpp/SETUP.md +71 -0
  14. package/ios/ReactNativeImageToWebp.h +5 -0
  15. package/ios/ReactNativeImageToWebp.mm +342 -0
  16. package/lib/module/NativeReactNativeImageToWebp.js +5 -0
  17. package/lib/module/NativeReactNativeImageToWebp.js.map +1 -0
  18. package/lib/module/index.js +78 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/package.json +1 -0
  21. package/lib/module/presets.js +64 -0
  22. package/lib/module/presets.js.map +1 -0
  23. package/lib/module/validation.js +36 -0
  24. package/lib/module/validation.js.map +1 -0
  25. package/lib/typescript/package.json +1 -0
  26. package/lib/typescript/src/NativeReactNativeImageToWebp.d.ts +24 -0
  27. package/lib/typescript/src/NativeReactNativeImageToWebp.d.ts.map +1 -0
  28. package/lib/typescript/src/index.d.ts +35 -0
  29. package/lib/typescript/src/index.d.ts.map +1 -0
  30. package/lib/typescript/src/presets.d.ts +3 -0
  31. package/lib/typescript/src/presets.d.ts.map +1 -0
  32. package/lib/typescript/src/validation.d.ts +9 -0
  33. package/lib/typescript/src/validation.d.ts.map +1 -0
  34. package/package.json +139 -0
  35. package/src/NativeReactNativeImageToWebp.ts +32 -0
  36. package/src/index.tsx +109 -0
  37. package/src/presets.ts +80 -0
  38. package/src/validation.ts +55 -0
@@ -0,0 +1,342 @@
1
+ #import "ReactNativeImageToWebp.h"
2
+ #import <React/RCTUtils.h>
3
+ #import <ImageIO/ImageIO.h>
4
+ #import <CoreGraphics/CoreGraphics.h>
5
+ #import <Accelerate/Accelerate.h>
6
+ #import <Foundation/Foundation.h>
7
+ #import "ImageToWebP.h"
8
+
9
+ // Error domain
10
+ static NSString *const kErrorDomain = @"ReactNativeImageToWebp";
11
+
12
+ // Error codes matching JS API
13
+ static NSString *const kErrorCodeInvalidInput = @"INVALID_INPUT";
14
+ static NSString *const kErrorCodeFileNotFound = @"FILE_NOT_FOUND";
15
+ static NSString *const kErrorCodeDecodeFailed = @"DECODE_FAILED";
16
+ static NSString *const kErrorCodeEncodeFailed = @"ENCODE_FAILED";
17
+ static NSString *const kErrorCodeIOError = @"IO_ERROR";
18
+ static NSString *const kErrorCodeUnsupportedFormat = @"UNSUPPORTED_FORMAT";
19
+
20
+ @interface ReactNativeImageToWebp ()
21
+ @end
22
+
23
+ @implementation ReactNativeImageToWebp
24
+
25
+ + (NSString *)moduleName {
26
+ return @"ReactNativeImageToWebp";
27
+ }
28
+
29
+ // Helper to get CGImageSource from file path
30
+ static CGImageSourceRef createImageSource(NSString *path, NSError **error) {
31
+ NSURL *url = [NSURL fileURLWithPath:path];
32
+ if (!url) {
33
+ if (error) {
34
+ *error = [NSError errorWithDomain:kErrorDomain
35
+ code:1
36
+ userInfo:@{NSLocalizedDescriptionKey: @"Invalid file path",
37
+ @"code": kErrorCodeInvalidInput}];
38
+ }
39
+ return NULL;
40
+ }
41
+
42
+ CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
43
+ if (!source) {
44
+ if (error) {
45
+ *error = [NSError errorWithDomain:kErrorDomain
46
+ code:1
47
+ userInfo:@{NSLocalizedDescriptionKey: @"File not found or cannot be read",
48
+ @"code": kErrorCodeFileNotFound}];
49
+ }
50
+ return NULL;
51
+ }
52
+
53
+ return source;
54
+ }
55
+
56
+ // Get image properties including orientation
57
+ static NSDictionary *getImageProperties(CGImageSourceRef source) {
58
+ return (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
59
+ }
60
+
61
+ // Apply EXIF orientation to get correctly oriented image
62
+ static CGImageRef createOrientedImage(CGImageRef image, NSDictionary *properties) {
63
+ NSNumber *orientationValue = properties[(__bridge NSString *)kCGImagePropertyOrientation];
64
+ if (!orientationValue) {
65
+ return CGImageRetain(image);
66
+ }
67
+
68
+ int orientation = [orientationValue intValue];
69
+ if (orientation == 1) {
70
+ return CGImageRetain(image); // No rotation needed
71
+ }
72
+
73
+ // Calculate transform based on orientation
74
+ CGAffineTransform transform = CGAffineTransformIdentity;
75
+ CGFloat width = CGImageGetWidth(image);
76
+ CGFloat height = CGImageGetHeight(image);
77
+
78
+ switch (orientation) {
79
+ case 2: // Flip horizontal
80
+ transform = CGAffineTransformMakeScale(-1, 1);
81
+ transform = CGAffineTransformTranslate(transform, -width, 0);
82
+ break;
83
+ case 3: // Rotate 180
84
+ transform = CGAffineTransformMakeTranslation(width, height);
85
+ transform = CGAffineTransformRotate(transform, M_PI);
86
+ break;
87
+ case 4: // Flip vertical
88
+ transform = CGAffineTransformMakeScale(1, -1);
89
+ transform = CGAffineTransformTranslate(transform, 0, -height);
90
+ break;
91
+ case 5: // Rotate 90 CCW and flip
92
+ transform = CGAffineTransformMakeTranslation(height, 0);
93
+ transform = CGAffineTransformRotate(transform, M_PI_2);
94
+ transform = CGAffineTransformScale(transform, -1, 1);
95
+ break;
96
+ case 6: // Rotate 90 CW
97
+ transform = CGAffineTransformMakeTranslation(height, 0);
98
+ transform = CGAffineTransformRotate(transform, M_PI_2);
99
+ break;
100
+ case 7: // Rotate 90 CW and flip
101
+ transform = CGAffineTransformMakeTranslation(0, width);
102
+ transform = CGAffineTransformRotate(transform, -M_PI_2);
103
+ transform = CGAffineTransformScale(transform, -1, 1);
104
+ break;
105
+ case 8: // Rotate 90 CCW
106
+ transform = CGAffineTransformMakeTranslation(0, width);
107
+ transform = CGAffineTransformRotate(transform, -M_PI_2);
108
+ break;
109
+ default:
110
+ return CGImageRetain(image);
111
+ }
112
+
113
+ // Create bitmap context and draw transformed image
114
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
115
+ CGContextRef context = CGBitmapContextCreate(NULL,
116
+ (orientation >= 5 && orientation <= 8) ? height : width,
117
+ (orientation >= 5 && orientation <= 8) ? width : height,
118
+ 8, 0, colorSpace,
119
+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
120
+ CGColorSpaceRelease(colorSpace);
121
+
122
+ if (!context) {
123
+ return CGImageRetain(image);
124
+ }
125
+
126
+ CGContextConcatCTM(context, transform);
127
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
128
+ CGImageRef orientedImage = CGBitmapContextCreateImage(context);
129
+ CGContextRelease(context);
130
+
131
+ return orientedImage ?: CGImageRetain(image);
132
+ }
133
+
134
+ // Resize image if maxLongEdge is specified
135
+ static CGImageRef resizeImageIfNeeded(CGImageRef image, NSNumber *maxLongEdge) {
136
+ if (!maxLongEdge || [maxLongEdge doubleValue] <= 0) {
137
+ return CGImageRetain(image);
138
+ }
139
+
140
+ CGFloat width = CGImageGetWidth(image);
141
+ CGFloat height = CGImageGetHeight(image);
142
+ CGFloat maxEdge = [maxLongEdge doubleValue];
143
+ CGFloat currentMax = MAX(width, height);
144
+
145
+ if (currentMax <= maxEdge) {
146
+ return CGImageRetain(image);
147
+ }
148
+
149
+ CGFloat scale = maxEdge / currentMax;
150
+ CGFloat newWidth = width * scale;
151
+ CGFloat newHeight = height * scale;
152
+
153
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
154
+ CGContextRef context = CGBitmapContextCreate(NULL,
155
+ (size_t)newWidth,
156
+ (size_t)newHeight,
157
+ 8, 0, colorSpace,
158
+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
159
+ CGColorSpaceRelease(colorSpace);
160
+
161
+ if (!context) {
162
+ return CGImageRetain(image);
163
+ }
164
+
165
+ CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
166
+ CGContextDrawImage(context, CGRectMake(0, 0, newWidth, newHeight), image);
167
+ CGImageRef resizedImage = CGBitmapContextCreateImage(context);
168
+ CGContextRelease(context);
169
+
170
+ return resizedImage ?: CGImageRetain(image);
171
+ }
172
+
173
+ // Convert CGImage to RGBA buffer
174
+ static uint8_t *createRGBABuffer(CGImageRef image, uint32_t *outWidth, uint32_t *outHeight) {
175
+ size_t width = CGImageGetWidth(image);
176
+ size_t height = CGImageGetHeight(image);
177
+ size_t bytesPerPixel = 4;
178
+ size_t bytesPerRow = width * bytesPerPixel;
179
+ size_t bufferSize = bytesPerRow * height;
180
+
181
+ uint8_t *buffer = (uint8_t *)malloc(bufferSize);
182
+ if (!buffer) {
183
+ return NULL;
184
+ }
185
+
186
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
187
+ CGContextRef context = CGBitmapContextCreate(buffer,
188
+ width,
189
+ height,
190
+ 8,
191
+ bytesPerRow,
192
+ colorSpace,
193
+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
194
+ CGColorSpaceRelease(colorSpace);
195
+
196
+ if (!context) {
197
+ free(buffer);
198
+ return NULL;
199
+ }
200
+
201
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
202
+ CGContextRelease(context);
203
+
204
+ *outWidth = (uint32_t)width;
205
+ *outHeight = (uint32_t)height;
206
+ return buffer;
207
+ }
208
+
209
+ // Derive output path from input path if not provided
210
+ static NSString *deriveOutputPath(NSString *inputPath, NSString *outputPath) {
211
+ if (outputPath && outputPath.length > 0) {
212
+ return outputPath;
213
+ }
214
+
215
+ NSString *directory = [inputPath stringByDeletingLastPathComponent];
216
+ NSString *filename = [[inputPath lastPathComponent] stringByDeletingPathExtension];
217
+ return [directory stringByAppendingPathComponent:[filename stringByAppendingPathExtension:@"webp"]];
218
+ }
219
+
220
+ - (void)convertImageToWebP:(NSDictionary *)options
221
+ resolver:(RCTPromiseResolveBlock)resolve
222
+ rejecter:(RCTPromiseRejectBlock)reject {
223
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
224
+ @autoreleasepool {
225
+ NSError *error = nil;
226
+
227
+ // Parse options
228
+ NSString *inputPath = options[@"inputPath"];
229
+ if (!inputPath || ![inputPath isKindOfClass:[NSString class]]) {
230
+ reject(kErrorCodeInvalidInput, @"inputPath is required", nil);
231
+ return;
232
+ }
233
+
234
+ NSString *outputPath = deriveOutputPath(inputPath, options[@"outputPath"]);
235
+ NSNumber *maxLongEdge = options[@"maxLongEdge"];
236
+ NSNumber *quality = options[@"quality"] ?: @80;
237
+ NSNumber *method = options[@"method"] ?: @3;
238
+ NSNumber *lossless = options[@"lossless"] ?: @NO;
239
+ NSNumber *stripMetadata = options[@"stripMetadata"];
240
+ if (!stripMetadata) {
241
+ stripMetadata = @YES;
242
+ }
243
+
244
+ // Check if input file exists
245
+ if (![[NSFileManager defaultManager] fileExistsAtPath:inputPath]) {
246
+ reject(kErrorCodeFileNotFound, [NSString stringWithFormat:@"File not found: %@", inputPath], nil);
247
+ return;
248
+ }
249
+
250
+ // Create image source
251
+ CGImageSourceRef source = createImageSource(inputPath, &error);
252
+ if (!source) {
253
+ reject(error.userInfo[@"code"] ?: kErrorCodeDecodeFailed,
254
+ error.localizedDescription ?: @"Failed to create image source",
255
+ error);
256
+ return;
257
+ }
258
+
259
+ // Get image properties
260
+ NSDictionary *properties = getImageProperties(source);
261
+ if (!properties) {
262
+ CGImageSourceRelease(source);
263
+ reject(kErrorCodeDecodeFailed, @"Failed to read image properties", nil);
264
+ return;
265
+ }
266
+
267
+ // Create CGImage
268
+ CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
269
+ CGImageSourceRelease(source);
270
+ if (!image) {
271
+ reject(kErrorCodeDecodeFailed, @"Failed to decode image", nil);
272
+ return;
273
+ }
274
+
275
+ // Apply orientation
276
+ CGImageRef orientedImage = createOrientedImage(image, properties);
277
+ CGImageRelease(image);
278
+ if (!orientedImage) {
279
+ reject(kErrorCodeDecodeFailed, @"Failed to apply orientation", nil);
280
+ return;
281
+ }
282
+
283
+ // Resize if needed
284
+ CGImageRef finalImage = resizeImageIfNeeded(orientedImage, maxLongEdge);
285
+ CGImageRelease(orientedImage);
286
+ if (!finalImage) {
287
+ reject(kErrorCodeDecodeFailed, @"Failed to resize image", nil);
288
+ return;
289
+ }
290
+
291
+ // Convert to RGBA buffer
292
+ uint32_t width, height;
293
+ uint8_t *rgbaData = createRGBABuffer(finalImage, &width, &height);
294
+ CGImageRelease(finalImage);
295
+ if (!rgbaData) {
296
+ reject(kErrorCodeDecodeFailed, @"Failed to create RGBA buffer", nil);
297
+ return;
298
+ }
299
+
300
+ // Prepare WebP encoding options
301
+ WebPEncodeOptions encodeOptions;
302
+ encodeOptions.quality = [quality intValue];
303
+ encodeOptions.method = [method intValue];
304
+ encodeOptions.lossless = [lossless boolValue];
305
+ encodeOptions.stripMetadata = [stripMetadata boolValue];
306
+ encodeOptions.threadLevel = 1;
307
+
308
+ // Encode to WebP
309
+ std::string outputPathStr = [outputPath UTF8String];
310
+ WebPEncodeResult result = encodeWebP(rgbaData, width, height, encodeOptions, outputPathStr);
311
+ free(rgbaData);
312
+
313
+ if (!result.success) {
314
+ NSString *errorMsg = [NSString stringWithUTF8String:result.errorMessage.c_str()];
315
+ reject(kErrorCodeEncodeFailed, errorMsg, nil);
316
+ return;
317
+ }
318
+
319
+ // Get file size
320
+ NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:outputPath error:&error];
321
+ NSNumber *fileSize = fileAttributes[NSFileSize];
322
+ if (!fileSize) {
323
+ fileSize = @0;
324
+ }
325
+
326
+ // Return result
327
+ resolve(@{
328
+ @"outputPath": outputPath,
329
+ @"width": @(result.width ?: width),
330
+ @"height": @(result.height ?: height),
331
+ @"sizeBytes": fileSize,
332
+ });
333
+ }
334
+ });
335
+ }
336
+
337
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
338
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
339
+ return std::make_shared<facebook::react::NativeReactNativeImageToWebpSpecJSI>(params);
340
+ }
341
+
342
+ @end
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('ReactNativeImageToWebp');
5
+ //# sourceMappingURL=NativeReactNativeImageToWebp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeReactNativeImageToWebp.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AA+BpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,wBAAwB,CAAC","ignoreList":[]}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ import NativeReactNativeImageToWebp from "./NativeReactNativeImageToWebp.js";
4
+ import { validateOptions } from "./validation.js";
5
+ import { applyPreset } from "./presets.js";
6
+ const ERROR_CODES = {
7
+ INVALID_INPUT: 'INVALID_INPUT',
8
+ FILE_NOT_FOUND: 'FILE_NOT_FOUND',
9
+ DECODE_FAILED: 'DECODE_FAILED',
10
+ ENCODE_FAILED: 'ENCODE_FAILED',
11
+ IO_ERROR: 'IO_ERROR',
12
+ UNSUPPORTED_FORMAT: 'UNSUPPORTED_FORMAT'
13
+ };
14
+ export class ImageToWebPError extends Error {
15
+ constructor(code, message) {
16
+ super(message);
17
+ this.name = 'ImageToWebPError';
18
+ this.code = code;
19
+ Object.setPrototypeOf(this, ImageToWebPError.prototype);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Convert an image file to WebP format.
25
+ *
26
+ * @param options - Conversion options
27
+ * @returns Promise resolving to conversion result with output path and metadata
28
+ * @throws {ImageToWebPError} If conversion fails
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const result = await convertImageToWebP({
33
+ * inputPath: '/path/to/image.jpg',
34
+ * preset: 'balanced',
35
+ * maxLongEdge: 2048,
36
+ * });
37
+ * console.log(`Output: ${result.outputPath}, Size: ${result.sizeBytes} bytes`);
38
+ * ```
39
+ */
40
+ export async function convertImageToWebP(options) {
41
+ // Validate input
42
+ const validationError = validateOptions(options);
43
+ if (validationError) {
44
+ throw new ImageToWebPError(validationError.code, validationError.message);
45
+ }
46
+
47
+ // Apply preset defaults
48
+ const finalOptions = applyPreset(options);
49
+ try {
50
+ return await NativeReactNativeImageToWebp.convertImageToWebP(finalOptions);
51
+ } catch (error) {
52
+ // Map native errors to our error types
53
+ if (error instanceof Error) {
54
+ const message = error.message;
55
+ if (message.includes('FILE_NOT_FOUND')) {
56
+ throw new ImageToWebPError(ERROR_CODES.FILE_NOT_FOUND, `File not found: ${options.inputPath}`);
57
+ }
58
+ if (message.includes('DECODE_FAILED')) {
59
+ throw new ImageToWebPError(ERROR_CODES.DECODE_FAILED, `Failed to decode image: ${message}`);
60
+ }
61
+ if (message.includes('ENCODE_FAILED')) {
62
+ throw new ImageToWebPError(ERROR_CODES.ENCODE_FAILED, `Failed to encode WebP: ${message}`);
63
+ }
64
+ if (message.includes('IO_ERROR')) {
65
+ throw new ImageToWebPError(ERROR_CODES.IO_ERROR, `I/O error: ${message}`);
66
+ }
67
+ if (message.includes('UNSUPPORTED_FORMAT')) {
68
+ throw new ImageToWebPError(ERROR_CODES.UNSUPPORTED_FORMAT, `Unsupported image format: ${message}`);
69
+ }
70
+ if (message.includes('INVALID_INPUT')) {
71
+ throw new ImageToWebPError(ERROR_CODES.INVALID_INPUT, `Invalid input: ${message}`);
72
+ }
73
+ }
74
+ throw error;
75
+ }
76
+ }
77
+ export { ERROR_CODES };
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeReactNativeImageToWebp","validateOptions","applyPreset","ERROR_CODES","INVALID_INPUT","FILE_NOT_FOUND","DECODE_FAILED","ENCODE_FAILED","IO_ERROR","UNSUPPORTED_FORMAT","ImageToWebPError","Error","constructor","code","message","name","Object","setPrototypeOf","prototype","convertImageToWebP","options","validationError","finalOptions","error","includes","inputPath"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,4BAA4B,MAI5B,mCAAgC;AACvC,SAASC,eAAe,QAAQ,iBAAc;AAC9C,SAASC,WAAW,QAAQ,cAAW;AAIvC,MAAMC,WAAW,GAAG;EAClBC,aAAa,EAAE,eAAe;EAC9BC,cAAc,EAAE,gBAAgB;EAChCC,aAAa,EAAE,eAAe;EAC9BC,aAAa,EAAE,eAAe;EAC9BC,QAAQ,EAAE,UAAU;EACpBC,kBAAkB,EAAE;AACtB,CAAU;AAIV,OAAO,MAAMC,gBAAgB,SAASC,KAAK,CAAC;EAG1CC,WAAWA,CAACC,IAAe,EAAEC,OAAe,EAAE;IAC5C,KAAK,CAACA,OAAO,CAAC;IACd,IAAI,CAACC,IAAI,GAAG,kBAAkB;IAC9B,IAAI,CAACF,IAAI,GAAGA,IAAI;IAChBG,MAAM,CAACC,cAAc,CAAC,IAAI,EAAEP,gBAAgB,CAACQ,SAAS,CAAC;EACzD;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,kBAAkBA,CACtCC,OAAuB,EACC;EACxB;EACA,MAAMC,eAAe,GAAGpB,eAAe,CAACmB,OAAO,CAAC;EAChD,IAAIC,eAAe,EAAE;IACnB,MAAM,IAAIX,gBAAgB,CAACW,eAAe,CAACR,IAAI,EAAEQ,eAAe,CAACP,OAAO,CAAC;EAC3E;;EAEA;EACA,MAAMQ,YAAY,GAAGpB,WAAW,CAACkB,OAAO,CAAC;EAEzC,IAAI;IACF,OAAO,MAAMpB,4BAA4B,CAACmB,kBAAkB,CAACG,YAAY,CAAC;EAC5E,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd;IACA,IAAIA,KAAK,YAAYZ,KAAK,EAAE;MAC1B,MAAMG,OAAO,GAAGS,KAAK,CAACT,OAAO;MAC7B,IAAIA,OAAO,CAACU,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QACtC,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACE,cAAc,EAC1B,mBAAmBe,OAAO,CAACK,SAAS,EACtC,CAAC;MACH;MACA,IAAIX,OAAO,CAACU,QAAQ,CAAC,eAAe,CAAC,EAAE;QACrC,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACG,aAAa,EACzB,2BAA2BQ,OAAO,EACpC,CAAC;MACH;MACA,IAAIA,OAAO,CAACU,QAAQ,CAAC,eAAe,CAAC,EAAE;QACrC,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACI,aAAa,EACzB,0BAA0BO,OAAO,EACnC,CAAC;MACH;MACA,IAAIA,OAAO,CAACU,QAAQ,CAAC,UAAU,CAAC,EAAE;QAChC,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACK,QAAQ,EACpB,cAAcM,OAAO,EACvB,CAAC;MACH;MACA,IAAIA,OAAO,CAACU,QAAQ,CAAC,oBAAoB,CAAC,EAAE;QAC1C,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACM,kBAAkB,EAC9B,6BAA6BK,OAAO,EACtC,CAAC;MACH;MACA,IAAIA,OAAO,CAACU,QAAQ,CAAC,eAAe,CAAC,EAAE;QACrC,MAAM,IAAId,gBAAgB,CACxBP,WAAW,CAACC,aAAa,EACzB,kBAAkBU,OAAO,EAC3B,CAAC;MACH;IACF;IACA,MAAMS,KAAK;EACb;AACF;AAEA,SAASpB,WAAW","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ const PRESETS = {
4
+ balanced: {
5
+ quality: 80,
6
+ method: 3,
7
+ lossless: false,
8
+ stripMetadata: true,
9
+ threadLevel: 1
10
+ },
11
+ small: {
12
+ quality: 74,
13
+ method: 5,
14
+ lossless: false,
15
+ stripMetadata: true,
16
+ threadLevel: 1
17
+ },
18
+ fast: {
19
+ quality: 78,
20
+ method: 1,
21
+ lossless: false,
22
+ stripMetadata: true,
23
+ threadLevel: 1
24
+ },
25
+ lossless: {
26
+ lossless: true,
27
+ method: 4,
28
+ stripMetadata: true
29
+ },
30
+ document: {
31
+ quality: 82,
32
+ method: 4,
33
+ lossless: false,
34
+ stripMetadata: true,
35
+ exact: true // Will be set conditionally if alpha present
36
+ }
37
+ };
38
+ export function applyPreset(options) {
39
+ const preset = options.preset || 'balanced';
40
+ const presetConfig = PRESETS[preset];
41
+ const result = {
42
+ ...options
43
+ };
44
+
45
+ // Apply preset values only if not explicitly overridden
46
+ if (result.quality === undefined && presetConfig.quality !== undefined) {
47
+ result.quality = presetConfig.quality;
48
+ }
49
+ if (result.method === undefined && presetConfig.method !== undefined) {
50
+ result.method = presetConfig.method;
51
+ }
52
+ if (result.lossless === undefined && presetConfig.lossless !== undefined) {
53
+ result.lossless = presetConfig.lossless;
54
+ }
55
+ if (result.stripMetadata === undefined && presetConfig.stripMetadata !== undefined) {
56
+ result.stripMetadata = presetConfig.stripMetadata;
57
+ }
58
+
59
+ // Note: threadLevel and exact are handled natively, not passed through JS API
60
+ // They are documented here for reference but applied in native code
61
+
62
+ return result;
63
+ }
64
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["PRESETS","balanced","quality","method","lossless","stripMetadata","threadLevel","small","fast","document","exact","applyPreset","options","preset","presetConfig","result","undefined"],"sourceRoot":"..\\..\\src","sources":["presets.ts"],"mappings":";;AAcA,MAAMA,OAA4C,GAAG;EACnDC,QAAQ,EAAE;IACRC,OAAO,EAAE,EAAE;IACXC,MAAM,EAAE,CAAC;IACTC,QAAQ,EAAE,KAAK;IACfC,aAAa,EAAE,IAAI;IACnBC,WAAW,EAAE;EACf,CAAC;EACDC,KAAK,EAAE;IACLL,OAAO,EAAE,EAAE;IACXC,MAAM,EAAE,CAAC;IACTC,QAAQ,EAAE,KAAK;IACfC,aAAa,EAAE,IAAI;IACnBC,WAAW,EAAE;EACf,CAAC;EACDE,IAAI,EAAE;IACJN,OAAO,EAAE,EAAE;IACXC,MAAM,EAAE,CAAC;IACTC,QAAQ,EAAE,KAAK;IACfC,aAAa,EAAE,IAAI;IACnBC,WAAW,EAAE;EACf,CAAC;EACDF,QAAQ,EAAE;IACRA,QAAQ,EAAE,IAAI;IACdD,MAAM,EAAE,CAAC;IACTE,aAAa,EAAE;EACjB,CAAC;EACDI,QAAQ,EAAE;IACRP,OAAO,EAAE,EAAE;IACXC,MAAM,EAAE,CAAC;IACTC,QAAQ,EAAE,KAAK;IACfC,aAAa,EAAE,IAAI;IACnBK,KAAK,EAAE,IAAI,CAAE;EACf;AACF,CAAC;AAED,OAAO,SAASC,WAAWA,CAACC,OAAuB,EAAkB;EACnE,MAAMC,MAAqB,GAAGD,OAAO,CAACC,MAAM,IAAI,UAAU;EAC1D,MAAMC,YAAY,GAAGd,OAAO,CAACa,MAAM,CAAC;EAEpC,MAAME,MAAsB,GAAG;IAC7B,GAAGH;EACL,CAAC;;EAED;EACA,IAAIG,MAAM,CAACb,OAAO,KAAKc,SAAS,IAAIF,YAAY,CAACZ,OAAO,KAAKc,SAAS,EAAE;IACtED,MAAM,CAACb,OAAO,GAAGY,YAAY,CAACZ,OAAO;EACvC;EACA,IAAIa,MAAM,CAACZ,MAAM,KAAKa,SAAS,IAAIF,YAAY,CAACX,MAAM,KAAKa,SAAS,EAAE;IACpED,MAAM,CAACZ,MAAM,GAAGW,YAAY,CAACX,MAAM;EACrC;EACA,IAAIY,MAAM,CAACX,QAAQ,KAAKY,SAAS,IAAIF,YAAY,CAACV,QAAQ,KAAKY,SAAS,EAAE;IACxED,MAAM,CAACX,QAAQ,GAAGU,YAAY,CAACV,QAAQ;EACzC;EACA,IACEW,MAAM,CAACV,aAAa,KAAKW,SAAS,IAClCF,YAAY,CAACT,aAAa,KAAKW,SAAS,EACxC;IACAD,MAAM,CAACV,aAAa,GAAGS,YAAY,CAACT,aAAa;EACnD;;EAEA;EACA;;EAEA,OAAOU,MAAM;AACf","ignoreList":[]}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ export function validateOptions(options) {
4
+ if (!options.inputPath || typeof options.inputPath !== 'string') {
5
+ return {
6
+ code: 'INVALID_INPUT',
7
+ message: 'inputPath is required and must be a string'
8
+ };
9
+ }
10
+ if (options.maxLongEdge !== undefined) {
11
+ if (typeof options.maxLongEdge !== 'number' || options.maxLongEdge <= 0) {
12
+ return {
13
+ code: 'INVALID_INPUT',
14
+ message: 'maxLongEdge must be a positive number'
15
+ };
16
+ }
17
+ }
18
+ if (options.quality !== undefined) {
19
+ if (typeof options.quality !== 'number' || options.quality < 0 || options.quality > 100) {
20
+ return {
21
+ code: 'INVALID_INPUT',
22
+ message: 'quality must be a number between 0 and 100'
23
+ };
24
+ }
25
+ }
26
+ if (options.method !== undefined) {
27
+ if (typeof options.method !== 'number' || options.method < 0 || options.method > 6) {
28
+ return {
29
+ code: 'INVALID_INPUT',
30
+ message: 'method must be a number between 0 and 6'
31
+ };
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["validateOptions","options","inputPath","code","message","maxLongEdge","undefined","quality","method"],"sourceRoot":"..\\..\\src","sources":["validation.ts"],"mappings":";;AAQA,OAAO,SAASA,eAAeA,CAC7BC,OAAuB,EACC;EACxB,IAAI,CAACA,OAAO,CAACC,SAAS,IAAI,OAAOD,OAAO,CAACC,SAAS,KAAK,QAAQ,EAAE;IAC/D,OAAO;MACLC,IAAI,EAAE,eAAe;MACrBC,OAAO,EAAE;IACX,CAAC;EACH;EAEA,IAAIH,OAAO,CAACI,WAAW,KAAKC,SAAS,EAAE;IACrC,IAAI,OAAOL,OAAO,CAACI,WAAW,KAAK,QAAQ,IAAIJ,OAAO,CAACI,WAAW,IAAI,CAAC,EAAE;MACvE,OAAO;QACLF,IAAI,EAAE,eAAe;QACrBC,OAAO,EAAE;MACX,CAAC;IACH;EACF;EAEA,IAAIH,OAAO,CAACM,OAAO,KAAKD,SAAS,EAAE;IACjC,IACE,OAAOL,OAAO,CAACM,OAAO,KAAK,QAAQ,IACnCN,OAAO,CAACM,OAAO,GAAG,CAAC,IACnBN,OAAO,CAACM,OAAO,GAAG,GAAG,EACrB;MACA,OAAO;QACLJ,IAAI,EAAE,eAAe;QACrBC,OAAO,EAAE;MACX,CAAC;IACH;EACF;EAEA,IAAIH,OAAO,CAACO,MAAM,KAAKF,SAAS,EAAE;IAChC,IACE,OAAOL,OAAO,CAACO,MAAM,KAAK,QAAQ,IAClCP,OAAO,CAACO,MAAM,GAAG,CAAC,IAClBP,OAAO,CAACO,MAAM,GAAG,CAAC,EAClB;MACA,OAAO;QACLL,IAAI,EAAE,eAAe;QACrBC,OAAO,EAAE;MACX,CAAC;IACH;EACF;EAEA,OAAO,IAAI;AACb","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,24 @@
1
+ import { type TurboModule } from 'react-native';
2
+ export type ConvertPreset = 'balanced' | 'small' | 'fast' | 'lossless' | 'document';
3
+ export interface ConvertOptions {
4
+ inputPath: string;
5
+ outputPath?: string;
6
+ preset?: ConvertPreset;
7
+ maxLongEdge?: number;
8
+ quality?: number;
9
+ method?: number;
10
+ lossless?: boolean;
11
+ stripMetadata?: boolean;
12
+ }
13
+ export interface ConvertResult {
14
+ outputPath: string;
15
+ width: number;
16
+ height: number;
17
+ sizeBytes: number;
18
+ }
19
+ export interface Spec extends TurboModule {
20
+ convertImageToWebP(options: ConvertOptions): Promise<ConvertResult>;
21
+ }
22
+ declare const _default: Spec;
23
+ export default _default;
24
+ //# sourceMappingURL=NativeReactNativeImageToWebp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeReactNativeImageToWebp.d.ts","sourceRoot":"","sources":["../../../src/NativeReactNativeImageToWebp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,OAAO,GACP,MAAM,GACN,UAAU,GACV,UAAU,CAAC;AAEf,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACrE;;AAED,wBAAgF"}
@@ -0,0 +1,35 @@
1
+ import { type ConvertOptions, type ConvertResult, type ConvertPreset } from './NativeReactNativeImageToWebp';
2
+ export type { ConvertOptions, ConvertResult, ConvertPreset };
3
+ declare const ERROR_CODES: {
4
+ readonly INVALID_INPUT: "INVALID_INPUT";
5
+ readonly FILE_NOT_FOUND: "FILE_NOT_FOUND";
6
+ readonly DECODE_FAILED: "DECODE_FAILED";
7
+ readonly ENCODE_FAILED: "ENCODE_FAILED";
8
+ readonly IO_ERROR: "IO_ERROR";
9
+ readonly UNSUPPORTED_FORMAT: "UNSUPPORTED_FORMAT";
10
+ };
11
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
12
+ export declare class ImageToWebPError extends Error {
13
+ code: ErrorCode;
14
+ constructor(code: ErrorCode, message: string);
15
+ }
16
+ /**
17
+ * Convert an image file to WebP format.
18
+ *
19
+ * @param options - Conversion options
20
+ * @returns Promise resolving to conversion result with output path and metadata
21
+ * @throws {ImageToWebPError} If conversion fails
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const result = await convertImageToWebP({
26
+ * inputPath: '/path/to/image.jpg',
27
+ * preset: 'balanced',
28
+ * maxLongEdge: 2048,
29
+ * });
30
+ * console.log(`Output: ${result.outputPath}, Size: ${result.sizeBytes} bytes`);
31
+ * ```
32
+ */
33
+ export declare function convertImageToWebP(options: ConvertOptions): Promise<ConvertResult>;
34
+ export { ERROR_CODES };
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAqC,EACnC,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AAIxC,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AAE7D,QAAA,MAAM,WAAW;;;;;;;CAOP,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEvE,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,IAAI,EAAE,SAAS,CAAC;gBAEJ,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;CAM7C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAuDxB;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ConvertOptions } from './NativeReactNativeImageToWebp';
2
+ export declare function applyPreset(options: ConvertOptions): ConvertOptions;
3
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,gCAAgC,CAAC;AA+CxC,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CA6BnE"}
@@ -0,0 +1,9 @@
1
+ import type { ConvertOptions } from './NativeReactNativeImageToWebp';
2
+ import type { ErrorCode } from './index';
3
+ interface ValidationError {
4
+ code: ErrorCode;
5
+ message: string;
6
+ }
7
+ export declare function validateOptions(options: ConvertOptions): ValidationError | null;
8
+ export {};
9
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,GACtB,eAAe,GAAG,IAAI,CA4CxB"}