Skip to content

Commit

Permalink
Merge pull request #135 from callstack/feature/retyui/android-format
Browse files Browse the repository at this point in the history
feature: Add format on Android & iOS
  • Loading branch information
retyui committed Feb 11, 2024
2 parents 06cab97 + 3ed816b commit 4f7ec34
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 36 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`. <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`) |
| `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`. <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. |

```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',
};
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"
}
}
Expand All @@ -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
}
}
Expand Down
5 changes: 5 additions & 0 deletions ios/RNCImageEditor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions src/NativeRNCImageEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
}
Expand Down
11 changes: 1 addition & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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` +
Expand All @@ -23,16 +24,6 @@ const RNCImageEditor: Spec = NativeRNCImageEditor
},
});

type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];

export interface ImageCropData
extends Omit<ImageCropDataFromSpec, 'resizeMode'> {
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
Expand Down
12 changes: 1 addition & 11 deletions src/index.web.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
import type { Spec } from './NativeRNCImageEditor';

type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];

export interface ImageCropData
extends Omit<ImageCropDataFromSpec, 'resizeMode'> {
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,
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Spec } from './NativeRNCImageEditor.ts';

type ImageCropDataFromSpec = Parameters<Spec['cropImage']>[1];

export interface ImageCropData
extends Omit<ImageCropDataFromSpec, 'resizeMode' | 'format'> {
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
}

0 comments on commit 4f7ec34

Please sign in to comment.