diff --git a/README.md b/README.md index 4a02c39..67a79fa 100755 --- a/README.md +++ b/README.md @@ -54,16 +54,16 @@ ImageEditor.cropImage(uri, cropData).then((url) => { | `displaySize` | No | Size to which you want to scale the cropped image | | `resizeMode` | No | Resizing mode to use when scaling the image (iOS only, Android resize mode is always 'cover', Web - no support) **Default value**: 'contain' | | `quality` | No | The quality of the resulting image, expressed as a value from `0.0` to `1.0`.
The value `0.0` represents the maximum compression (or lowest quality) while the value `1.0` represents the least compression (or best quality).
iOS supports only `JPEG` format, while Android/Web supports both `JPEG`, `WEBP` and `PNG` formats.
**Default value**: (iOS: `1`), (Android: `0.9`) | -| `format` | No | **(WEB ONLY)** The format of the resulting image, possible values are `jpeg`, `png`, `webp`, **Default value**: `jpeg` | +| `format` | No | The format of the resulting image, possible values are `jpeg`, `png`, `webp`.
**Default value**: based on the provided image; if value determination is not possible, `jpeg` will be used as a fallback.
`webp` isn't supported by iOS. | ```ts cropData: ImageCropData = { - offset: {x: number, y: number}, - size: {width: number, height: number}, - displaySize: {width: number, height: number}, + offset: { x: number, y: number }, + size: { width: number, height: number }, + displaySize: { width: number, height: number }, resizeMode: 'contain' | 'cover' | 'stretch', quality: number, // 0...1 - format: 'jpeg' | 'png' | 'webp' // web only + format: 'jpeg' | 'png' | 'webp', }; ``` diff --git a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt index f8d9b92..453c2d9 100644 --- a/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt +++ b/android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt @@ -42,6 +42,12 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +object MimeType { + const val JPEG = "image/jpeg" + const val PNG = "image/png" + const val WEBP = "image/webp" +} + class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { private val moduleCoroutineScope = CoroutineScope(Dispatchers.Default) @@ -91,6 +97,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { * is passed to this is the file:// URI of the new image */ fun cropImage(uri: String?, options: ReadableMap, promise: Promise) { + val format = if (options.hasKey("format")) options.getString("format") else null val offset = if (options.hasKey("offset")) options.getMap("offset") else null val size = if (options.hasKey("size")) options.getMap("size") else null val quality = @@ -149,14 +156,10 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { if (cropped == null) { throw IOException("Cannot decode bitmap: $uri") } - val mimeType = outOptions.outMimeType - if (mimeType.isNullOrEmpty()) { - throw IOException("Could not determine MIME type") - } - + val mimeType = getMimeType(outOptions, format) val tempFile = createTempFile(reactContext, mimeType) writeCompressedBitmapToFile(cropped, mimeType, tempFile, quality) - if (mimeType == "image/jpeg") { + if (mimeType == MimeType.JPEG) { copyExif(reactContext, Uri.parse(uri), tempFile) } promise.resolve(Uri.fromFile(tempFile).toString()) @@ -434,6 +437,20 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { ) // Utils + private fun getMimeType(outOptions: BitmapFactory.Options, format: String?): String { + val mimeType = + when (format) { + "webp" -> MimeType.WEBP + "png" -> MimeType.PNG + "jpeg" -> MimeType.JPEG + else -> outOptions.outMimeType + } + if (mimeType.isNullOrEmpty()) { + return MimeType.JPEG + } + return mimeType + } + private fun getOrientation(context: Context, uri: Uri): Int { val file = getFileFromUri(context, uri) if (file == null) { @@ -501,8 +518,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { private fun getFileExtensionForType(mimeType: String?): String { return when (mimeType) { - "image/png" -> ".png" - "image/webp" -> ".webp" + MimeType.PNG -> ".png" + MimeType.WEBP -> ".webp" else -> ".jpg" } } @@ -515,8 +532,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) { @Suppress("DEPRECATION") CompressFormat.WEBP } return when (mimeType) { - "image/png" -> CompressFormat.PNG - "image/webp" -> webpCompressFormat + MimeType.PNG -> CompressFormat.PNG + MimeType.WEBP -> webpCompressFormat else -> CompressFormat.JPEG } } diff --git a/ios/RNCImageEditor.mm b/ios/RNCImageEditor.mm index bc66552..148c147 100644 --- a/ios/RNCImageEditor.mm +++ b/ios/RNCImageEditor.mm @@ -46,6 +46,7 @@ - (void) cropImage:(NSString *)uri resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + NSString *format = data.format(); CGSize size = [RCTConvert CGSize:@{ @"width": @(data.size().width()), @"height": @(data.size().height()) }]; CGPoint offset = [RCTConvert CGPoint:@{ @"x": @(data.offset().x()), @"y": @(data.offset().y()) }]; CGSize targetSize = size; @@ -66,6 +67,7 @@ - (void) cropImage:(NSString *)uri resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + NSString *format = cropData[@"format"]; CGSize size = [RCTConvert CGSize:cropData[@"size"]]; CGPoint offset = [RCTConvert CGPoint:cropData[@"offset"]]; CGSize targetSize = size; @@ -82,6 +84,9 @@ - (void) cropImage:(NSString *)uri NSURL *url = [imageRequest URL]; NSString *urlPath = [url path]; NSString *extension = [urlPath pathExtension]; + if([format isEqualToString:@"png"] || [format isEqualToString:@"jpeg"]){ + extension = format; + } [[_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES] loadImageWithURLRequest:imageRequest callback:^(NSError *error, UIImage *image) { if (error) { diff --git a/src/NativeRNCImageEditor.ts b/src/NativeRNCImageEditor.ts index 96db64c..896063a 100644 --- a/src/NativeRNCImageEditor.ts +++ b/src/NativeRNCImageEditor.ts @@ -40,6 +40,11 @@ export interface Spec extends TurboModule { * (Optional) Compression quality jpg images (number from 0 to 1). */ quality?: Float; + + /** + * (Optional) The format of the resulting image. Default auto-detection based on given image + */ + format?: string; } ): Promise; } diff --git a/src/index.ts b/src/index.ts index 0a6e807..88ba526 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { Platform } from 'react-native'; import NativeRNCImageEditor from './NativeRNCImageEditor'; import type { Spec } from './NativeRNCImageEditor'; +import type { ImageCropData } from './types.ts'; const LINKING_ERROR = `The package '@react-native-community/image-editor' doesn't seem to be linked. Make sure: \n\n` + @@ -23,16 +24,6 @@ const RNCImageEditor: Spec = NativeRNCImageEditor }, }); -type ImageCropDataFromSpec = Parameters[1]; - -export interface ImageCropData - extends Omit { - resizeMode?: 'contain' | 'cover' | 'stretch'; - // ^^^ codegen doesn't support union types yet - // so to provide more type safety we override the type here - format?: 'png' | 'jpeg' | 'webp'; // web only -} - class ImageEditor { /** * Crop the image specified by the URI param. If URI points to a remote diff --git a/src/index.web.ts b/src/index.web.ts index 0aefae7..761a642 100644 --- a/src/index.web.ts +++ b/src/index.web.ts @@ -1,14 +1,4 @@ -import type { Spec } from './NativeRNCImageEditor'; - -type ImageCropDataFromSpec = Parameters[1]; - -export interface ImageCropData - extends Omit { - resizeMode?: 'contain' | 'cover' | 'stretch'; - // ^^^ codegen doesn't support union types yet - // so to provide more type safety we override the type here - format?: 'png' | 'jpeg' | 'webp'; // web only -} +import type { ImageCropData } from './types.ts'; function drawImage( img: HTMLImageElement, diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..8647b38 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +import type { Spec } from './NativeRNCImageEditor.ts'; + +type ImageCropDataFromSpec = Parameters[1]; + +export interface ImageCropData + extends Omit { + format?: 'png' | 'jpeg' | 'webp'; + resizeMode?: 'contain' | 'cover' | 'stretch'; + // ^^^ codegen doesn't support union types yet + // so to provide more type safety we override the type here +}