Skip to content

feature: Add compression quality prop #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ ImageEditor.cropImage(uri, cropData).then(url => {
| `size` | Yes | Size (dimensions) of the cropped image |
| `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') **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 supports both `JPEG`, `WEBP` and `PNG` formats.<br/>**Default value**: (iOS: `1`), (Android: `0.9`) |

```ts
cropData: ImageCropData = {
offset: {x: number, y: number},
size: {width: number, height: number},
displaySize: {width: number, height: number},
resizeMode: 'contain' | 'cover' | 'stretch',
quality: number // 0...1
};
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
fun cropImage(uri: String?, options: ReadableMap, promise: Promise) {
val offset = if (options.hasKey("offset")) options.getMap("offset") else null
val size = if (options.hasKey("size")) options.getMap("size") else null
val quality =
if (options.hasKey("quality")) (options.getDouble("quality") * 100).toInt() else 90
if (
offset == null ||
size == null ||
Expand All @@ -103,6 +105,12 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
if (uri.isNullOrEmpty()) {
throw JSApplicationIllegalArgumentException("Please specify a URI")
}
if (quality > 100 || quality < 0) {
promise.reject(
JSApplicationIllegalArgumentException("quality must be a number between 0 and 1")
)
return
}
val x = offset.getDouble("x").toInt()
val y = offset.getDouble("y").toInt()
val width = size.getDouble("width").toInt()
Expand Down Expand Up @@ -144,7 +152,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
}

val tempFile = createTempFile(reactContext, mimeType)
writeCompressedBitmapToFile(cropped, mimeType, tempFile)
writeCompressedBitmapToFile(cropped, mimeType, tempFile, quality)
if (mimeType == "image/jpeg") {
copyExif(reactContext, Uri.parse(uri), tempFile)
}
Expand Down Expand Up @@ -275,9 +283,6 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
)
private const val TEMP_FILE_PREFIX = "ReactNative_cropped_image_"

/** Compress quality of the output file. */
private const val COMPRESS_QUALITY = 90

@SuppressLint("InlinedApi")
private val EXIF_ATTRIBUTES =
arrayOf(
Expand Down Expand Up @@ -374,9 +379,14 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
}

@Throws(IOException::class)
private fun writeCompressedBitmapToFile(cropped: Bitmap, mimeType: String, tempFile: File) {
private fun writeCompressedBitmapToFile(
cropped: Bitmap,
mimeType: String,
tempFile: File,
compressQuality: Int
) {
FileOutputStream(tempFile).use {
cropped.compress(getCompressFormatForType(mimeType), COMPRESS_QUALITY, it)
cropped.compress(getCompressFormatForType(mimeType), compressQuality, it)
}
}

Expand Down
14 changes: 13 additions & 1 deletion ios/RNCImageEditor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ - (void) cropImage:(NSString *)uri
}
NSString *displaySize = data.resizeMode();
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: uri]];
CGFloat compressionQuality = 1;
if (data.quality().has_value()) {
compressionQuality = *data.quality();
}
#else
RCT_EXPORT_METHOD(cropImage:(NSURLRequest *)imageRequest
cropData:(NSDictionary *)cropData
Expand All @@ -69,6 +73,10 @@ - (void) cropImage:(NSString *)uri
if(displaySize){
targetSize = [RCTConvert CGSize:cropData[@"displaySize"]];
}
CGFloat compressionQuality = 1;
if(cropData[@"quality"]){
compressionQuality = [RCTConvert CGFloat:cropData[@"quality"]];
}
#endif
CGRect rect = {offset,size};
NSURL *url = [imageRequest URL];
Expand All @@ -80,6 +88,10 @@ - (void) cropImage:(NSString *)uri
reject(@(error.code).stringValue, error.description, error);
return;
}
if (compressionQuality > 1 || compressionQuality < 0) {
reject(RCTErrorUnspecified, @("quality must be a number between 0 and 1"), nil);
return;
}

// Crop image
CGRect targetRect = {{-rect.origin.x, -rect.origin.y}, image.size};
Expand All @@ -104,7 +116,7 @@ - (void) cropImage:(NSString *)uri
}
else{

imageData = UIImageJPEGRepresentation(croppedImage, 1);
imageData = UIImageJPEGRepresentation(croppedImage, compressionQuality);
path = [RNCFileSystem generatePathInDirectory:[[RNCFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"ReactNative_cropped_image_"] withExtension:@".jpg"];
}

Expand Down
7 changes: 6 additions & 1 deletion src/NativeRNCImageEditor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { TurboModule } from 'react-native';
import type { Double } from 'react-native/Libraries/Types/CodegenTypes';
import type { Double, Float } from 'react-native/Libraries/Types/CodegenTypes';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
Expand Down Expand Up @@ -35,6 +35,11 @@ export interface Spec extends TurboModule {
* `displaySize` param is not specified, this has no effect.
*/
resizeMode?: string;

/**
* (Optional) Compression quality jpg images (number from 0 to 1).
*/
quality?: Float;
}
): Promise<string>;
}
Expand Down