Skip to content

Commit

Permalink
Merge pull request #140 from callstack/feature/retyui/change-return-type
Browse files Browse the repository at this point in the history
feature: [web/android/ios] Upon success, return more image information
  • Loading branch information
retyui authored Feb 22, 2024
2 parents 5e91a92 + 53f1763 commit 8ee9eb2
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 22 deletions.
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,21 @@ Crop the image specified by the URI param. If URI points to a remote image, it w
If the cropping process is successful, the resultant cropped image will be stored in the cache path, and the URI returned in the promise will point to the image in the cache path. Remember to delete the cropped image from the cache path when you are done with it.

```ts
ImageEditor.cropImage(uri, cropData).then((url) => {
console.log('Cropped image uri', url);
// In case of Web, the `url` is the base64 string
});
ImageEditor.cropImage(uri, cropData).then(
({
uri, // the path to the image file (example: 'file:///data/user/0/.../image.jpg')
path, // the URI of the image (example: '/data/user/0/.../image.jpg')
name, // the name of the image file. (example: 'image.jpg')
width, // the width of the image in pixels
height, // height of the image in pixels
size, // the size of the image in bytes
}) => {
console.log('Cropped image uri:', uri);
// WEB has different response:
// - `uri` is the base64 string (example `data:image/jpeg;base64,/4AAQ...AQABAA`)
// - `path` is the blob URL (example `blob:https://example.com/43ff7a16...e46b1`)
}
);
```

### `cropData: ImageCropData`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import android.util.Base64 as AndroidUtilBase64
import androidx.exifinterface.media.ExifInterface
import com.facebook.common.logging.FLog
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.common.ReactConstants
import java.io.ByteArrayInputStream
import java.io.File
Expand Down Expand Up @@ -162,7 +164,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
if (mimeType == MimeType.JPEG) {
copyExif(reactContext, Uri.parse(uri), tempFile)
}
promise.resolve(Uri.fromFile(tempFile).toString())
promise.resolve(getResultMap(tempFile, cropped))
} catch (e: Exception) {
promise.reject(e)
}
Expand Down Expand Up @@ -437,6 +439,17 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
)

// Utils
private fun getResultMap(resizedImage: File, image: Bitmap): WritableMap {
val response = Arguments.createMap()
response.putString("path", resizedImage.absolutePath)
response.putString("uri", Uri.fromFile(resizedImage).toString())
response.putString("name", resizedImage.name)
response.putInt("size", resizedImage.length().toInt())
response.putInt("width", image.width)
response.putInt("height", image.height)
return response
}

private fun getMimeType(outOptions: BitmapFactory.Options, format: String?): String {
val mimeType =
when (format) {
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ PODS:
- React-jsinspector (0.72.6)
- React-logger (0.72.6):
- glog
- react-native-image-editor (3.1.0):
- react-native-image-editor (3.2.0):
- RCT-Folly (= 2021.07.22.00)
- RCTRequired
- RCTTypeSafety
Expand Down Expand Up @@ -1129,7 +1129,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: faca9c368233f59ed24601aca0185870466a96e9
React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072
React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289
react-native-image-editor: a58ef0223f36bd9b8aa5c2b9d45926ce51946e50
react-native-image-editor: ed27495b9a98482d6f4642c42b165dfe23523b8d
React-NativeModulesApple: 63505fb94b71e2469cab35bdaf36cca813cb5bfd
React-perflogger: e3596db7e753f51766bceadc061936ef1472edc3
React-RCTActionSheet: 17ab132c748b4471012abbcdcf5befe860660485
Expand Down
6 changes: 3 additions & 3 deletions example/src/SquareImageCropper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ export class SquareImageCropper extends Component<Props, State> {
if (!this._transformData) {
return;
}
const croppedImageURI = await ImageEditor.cropImage(
const { uri } = await ImageEditor.cropImage(
this.state.photo.uri,
this._transformData
);
if (croppedImageURI) {
this.setState({ croppedImageURI });
if (uri) {
this.setState({ croppedImageURI: uri });
}
} catch (cropError) {
if (cropError instanceof Error) {
Expand Down
17 changes: 15 additions & 2 deletions ios/RNCImageEditor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ - (void) cropImage:(NSString *)uri
path = [RNCFileSystem generatePathInDirectory:[[RNCFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"ReactNative_cropped_image_"] withExtension:@".png"];
}
else{

imageData = UIImageJPEGRepresentation(croppedImage, compressionQuality);
path = [RNCFileSystem generatePathInDirectory:[[RNCFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"ReactNative_cropped_image_"] withExtension:@".jpg"];
}
Expand All @@ -135,7 +134,21 @@ - (void) cropImage:(NSString *)uri
return;
}

resolve(uri);
NSURL *fileurl = [[NSURL alloc] initFileURLWithPath:path];
NSString *filename = fileurl.lastPathComponent;
NSError *attributesError = nil;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&attributesError];
NSNumber *fileSize = fileAttributes == nil ? 0 : [fileAttributes objectForKey:NSFileSize];
NSDictionary *response = @{
@"path": path,
@"uri": uri,
@"name": filename,
@"size": fileSize ?: @(0),
@"width": @(croppedImage.size.width),
@"height": @(croppedImage.size.height),
};

resolve(response);
}];
}

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

export interface Spec extends TurboModule {
Expand Down Expand Up @@ -46,7 +50,32 @@ export interface Spec extends TurboModule {
*/
format?: string;
}
): Promise<string>;
): Promise<{
/**
* The path to the image file (example: '/data/user/0/.../image.jpg')
*/
path: string;
/**
* The URI of the image (example: 'file:///data/user/0/.../image.jpg')
*/
uri: string;
/**
* The name of the image file. (example: 'image.jpg')
*/
name: string;
/**
* The width of the image in pixels
*/
width: Int32;
/**
* The height of the image in pixels
*/
height: Int32;
/**
* The size of the image in bytes
*/
size: Int32;
}>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('RNCImageEditor');
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { Platform } from 'react-native';
import NativeRNCImageEditor from './NativeRNCImageEditor';
import type { Spec } from './NativeRNCImageEditor';
import type { ImageCropData } from './types.ts';
import type { ImageCropData, CropResult } from './types.ts';

const LINKING_ERROR =
`The package '@react-native-community/image-editor' doesn't seem to be linked. Make sure: \n\n` +
Expand Down Expand Up @@ -37,7 +37,7 @@ class ImageEditor {
* will point to the image in the cache path. Remember to delete the
* cropped image from the cache path when you are done with it.
*/
static cropImage(uri: string, cropData: ImageCropData): Promise<string> {
static cropImage(uri: string, cropData: ImageCropData): CropResult {
return RNCImageEditor.cropImage(uri, cropData);
}
}
Expand Down
43 changes: 37 additions & 6 deletions src/index.web.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ImageCropData } from './types.ts';
import type { ImageCropData, CropResult } from './types.ts';

function drawImage(
img: HTMLImageElement,
Expand Down Expand Up @@ -51,16 +51,47 @@ function fetchImage(imgSrc: string): Promise<HTMLImageElement> {
const DEFAULT_COMPRESSION_QUALITY = 0.9;

class ImageEditor {
static cropImage(imgSrc: string, cropData: ImageCropData): Promise<string> {
static cropImage(imgSrc: string, cropData: ImageCropData): CropResult {
/**
* Returns a promise that resolves with the base64 encoded string of the cropped image
*/
return fetchImage(imgSrc).then(function onfulfilledImgToCanvas(image) {

Check warning on line 58 in src/index.web.ts

View workflow job for this annotation

GitHub Actions / Code Quality

Prefer await to then()/catch()/finally()
const ext = cropData.format ?? 'jpeg';
const type = `image/${ext}`;
const quality = cropData.quality ?? DEFAULT_COMPRESSION_QUALITY;
const canvas = drawImage(image, cropData);
return canvas.toDataURL(
`image/${cropData.format ?? 'jpeg'}`,
cropData.quality ?? DEFAULT_COMPRESSION_QUALITY
);

return new Promise<Blob | null>(function onfulfilledCanvasToBlob(
resolve
) {
canvas.toBlob(resolve, type, quality);
}).then((blob) => {

Check warning on line 68 in src/index.web.ts

View workflow job for this annotation

GitHub Actions / Code Quality

Prefer await to then()/catch()/finally()
if (!blob) {
throw new Error('Image cannot be created from canvas');
}

let _path: string, _uri: string;

return {
width: canvas.width,
height: canvas.height,
name: 'ReactNative_cropped_image.' + ext,
size: blob.size,
// Lazy getters to avoid unnecessary memory usage
get path() {
if (!_path) {
_path = URL.createObjectURL(blob);
}
return _path;
},
get uri() {
if (!_uri) {
_uri = canvas.toDataURL(type, quality);
}
return _uri;
},
};
});
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export interface ImageCropData
// ^^^ codegen doesn't support union types yet
// so to provide more type safety we override the type here
}

export type CropResult = ReturnType<Spec['cropImage']>;

0 comments on commit 8ee9eb2

Please sign in to comment.