Skip to content

Commit 4f7ec34

Browse files
authored
Merge pull request #135 from callstack/feature/retyui/android-format
feature: Add format on Android & iOS
2 parents 06cab97 + 3ed816b commit 4f7ec34

File tree

7 files changed

+55
-36
lines changed

7 files changed

+55
-36
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ ImageEditor.cropImage(uri, cropData).then((url) => {
5454
| `displaySize` | No | Size to which you want to scale the cropped image |
5555
| `resizeMode` | No | Resizing mode to use when scaling the image (iOS only, Android resize mode is always 'cover', Web - no support) **Default value**: 'contain' |
5656
| `quality` | No | The quality of the resulting image, expressed as a value from `0.0` to `1.0`. <br/>The value `0.0` represents the maximum compression (or lowest quality) while the value `1.0` represents the least compression (or best quality).<br/>iOS supports only `JPEG` format, while Android/Web supports both `JPEG`, `WEBP` and `PNG` formats.<br/>**Default value**: (iOS: `1`), (Android: `0.9`) |
57-
| `format` | No | **(WEB ONLY)** The format of the resulting image, possible values are `jpeg`, `png`, `webp`, **Default value**: `jpeg` |
57+
| `format` | No | The format of the resulting image, possible values are `jpeg`, `png`, `webp`. <br/> **Default value**: based on the provided image; if value determination is not possible, `jpeg` will be used as a fallback. <br/> `webp` isn't supported by iOS. |
5858

5959
```ts
6060
cropData: ImageCropData = {
61-
offset: {x: number, y: number},
62-
size: {width: number, height: number},
63-
displaySize: {width: number, height: number},
61+
offset: { x: number, y: number },
62+
size: { width: number, height: number },
63+
displaySize: { width: number, height: number },
6464
resizeMode: 'contain' | 'cover' | 'stretch',
6565
quality: number, // 0...1
66-
format: 'jpeg' | 'png' | 'webp' // web only
66+
format: 'jpeg' | 'png' | 'webp',
6767
};
6868
```
6969

android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ import kotlinx.coroutines.cancel
4242
import kotlinx.coroutines.isActive
4343
import kotlinx.coroutines.launch
4444

45+
object MimeType {
46+
const val JPEG = "image/jpeg"
47+
const val PNG = "image/png"
48+
const val WEBP = "image/webp"
49+
}
50+
4551
class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
4652
private val moduleCoroutineScope = CoroutineScope(Dispatchers.Default)
4753

@@ -91,6 +97,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
9197
* is passed to this is the file:// URI of the new image
9298
*/
9399
fun cropImage(uri: String?, options: ReadableMap, promise: Promise) {
100+
val format = if (options.hasKey("format")) options.getString("format") else null
94101
val offset = if (options.hasKey("offset")) options.getMap("offset") else null
95102
val size = if (options.hasKey("size")) options.getMap("size") else null
96103
val quality =
@@ -149,14 +156,10 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
149156
if (cropped == null) {
150157
throw IOException("Cannot decode bitmap: $uri")
151158
}
152-
val mimeType = outOptions.outMimeType
153-
if (mimeType.isNullOrEmpty()) {
154-
throw IOException("Could not determine MIME type")
155-
}
156-
159+
val mimeType = getMimeType(outOptions, format)
157160
val tempFile = createTempFile(reactContext, mimeType)
158161
writeCompressedBitmapToFile(cropped, mimeType, tempFile, quality)
159-
if (mimeType == "image/jpeg") {
162+
if (mimeType == MimeType.JPEG) {
160163
copyExif(reactContext, Uri.parse(uri), tempFile)
161164
}
162165
promise.resolve(Uri.fromFile(tempFile).toString())
@@ -434,6 +437,20 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
434437
)
435438

436439
// Utils
440+
private fun getMimeType(outOptions: BitmapFactory.Options, format: String?): String {
441+
val mimeType =
442+
when (format) {
443+
"webp" -> MimeType.WEBP
444+
"png" -> MimeType.PNG
445+
"jpeg" -> MimeType.JPEG
446+
else -> outOptions.outMimeType
447+
}
448+
if (mimeType.isNullOrEmpty()) {
449+
return MimeType.JPEG
450+
}
451+
return mimeType
452+
}
453+
437454
private fun getOrientation(context: Context, uri: Uri): Int {
438455
val file = getFileFromUri(context, uri)
439456
if (file == null) {
@@ -501,8 +518,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
501518

502519
private fun getFileExtensionForType(mimeType: String?): String {
503520
return when (mimeType) {
504-
"image/png" -> ".png"
505-
"image/webp" -> ".webp"
521+
MimeType.PNG -> ".png"
522+
MimeType.WEBP -> ".webp"
506523
else -> ".jpg"
507524
}
508525
}
@@ -515,8 +532,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
515532
@Suppress("DEPRECATION") CompressFormat.WEBP
516533
}
517534
return when (mimeType) {
518-
"image/png" -> CompressFormat.PNG
519-
"image/webp" -> webpCompressFormat
535+
MimeType.PNG -> CompressFormat.PNG
536+
MimeType.WEBP -> webpCompressFormat
520537
else -> CompressFormat.JPEG
521538
}
522539
}

ios/RNCImageEditor.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ - (void) cropImage:(NSString *)uri
4646
resolve:(RCTPromiseResolveBlock)resolve
4747
reject:(RCTPromiseRejectBlock)reject
4848
{
49+
NSString *format = data.format();
4950
CGSize size = [RCTConvert CGSize:@{ @"width": @(data.size().width()), @"height": @(data.size().height()) }];
5051
CGPoint offset = [RCTConvert CGPoint:@{ @"x": @(data.offset().x()), @"y": @(data.offset().y()) }];
5152
CGSize targetSize = size;
@@ -66,6 +67,7 @@ - (void) cropImage:(NSString *)uri
6667
resolve:(RCTPromiseResolveBlock)resolve
6768
reject:(RCTPromiseRejectBlock)reject)
6869
{
70+
NSString *format = cropData[@"format"];
6971
CGSize size = [RCTConvert CGSize:cropData[@"size"]];
7072
CGPoint offset = [RCTConvert CGPoint:cropData[@"offset"]];
7173
CGSize targetSize = size;
@@ -82,6 +84,9 @@ - (void) cropImage:(NSString *)uri
8284
NSURL *url = [imageRequest URL];
8385
NSString *urlPath = [url path];
8486
NSString *extension = [urlPath pathExtension];
87+
if([format isEqualToString:@"png"] || [format isEqualToString:@"jpeg"]){
88+
extension = format;
89+
}
8590

8691
[[_bridge moduleForName:@"ImageLoader" lazilyLoadIfNecessary:YES] loadImageWithURLRequest:imageRequest callback:^(NSError *error, UIImage *image) {
8792
if (error) {

src/NativeRNCImageEditor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export interface Spec extends TurboModule {
4040
* (Optional) Compression quality jpg images (number from 0 to 1).
4141
*/
4242
quality?: Float;
43+
44+
/**
45+
* (Optional) The format of the resulting image. Default auto-detection based on given image
46+
*/
47+
format?: string;
4348
}
4449
): Promise<string>;
4550
}

src/index.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { Platform } from 'react-native';
99
import NativeRNCImageEditor from './NativeRNCImageEditor';
1010
import type { Spec } from './NativeRNCImageEditor';
11+
import type { ImageCropData } from './types.ts';
1112

1213
const LINKING_ERROR =
1314
`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
2324
},
2425
});
2526

26-
type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];
27-
28-
export interface ImageCropData
29-
extends Omit<ImageCropDataFromSpec, 'resizeMode'> {
30-
resizeMode?: 'contain' | 'cover' | 'stretch';
31-
// ^^^ codegen doesn't support union types yet
32-
// so to provide more type safety we override the type here
33-
format?: 'png' | 'jpeg' | 'webp'; // web only
34-
}
35-
3627
class ImageEditor {
3728
/**
3829
* Crop the image specified by the URI param. If URI points to a remote

src/index.web.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
1-
import type { Spec } from './NativeRNCImageEditor';
2-
3-
type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];
4-
5-
export interface ImageCropData
6-
extends Omit<ImageCropDataFromSpec, 'resizeMode'> {
7-
resizeMode?: 'contain' | 'cover' | 'stretch';
8-
// ^^^ codegen doesn't support union types yet
9-
// so to provide more type safety we override the type here
10-
format?: 'png' | 'jpeg' | 'webp'; // web only
11-
}
1+
import type { ImageCropData } from './types.ts';
122

133
function drawImage(
144
img: HTMLImageElement,

src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Spec } from './NativeRNCImageEditor.ts';
2+
3+
type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];
4+
5+
export interface ImageCropData
6+
extends Omit<ImageCropDataFromSpec, 'resizeMode' | 'format'> {
7+
format?: 'png' | 'jpeg' | 'webp';
8+
resizeMode?: 'contain' | 'cover' | 'stretch';
9+
// ^^^ codegen doesn't support union types yet
10+
// so to provide more type safety we override the type here
11+
}

0 commit comments

Comments
 (0)