Skip to content

Commit

Permalink
Merge pull request #36019 from software-mansion-labs/add-expo-image-m…
Browse files Browse the repository at this point in the history
…anipulator

[New architecture] Replace `@oguzhnatly/react-native-image-manipulator` with `expo-image-manipulator`
  • Loading branch information
roryabraham authored Mar 5, 2024
2 parents e3b10f7 + a070e12 commit f4f5de1
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 170 deletions.
22 changes: 15 additions & 7 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ PODS:
- EXAV (13.10.4):
- ExpoModulesCore
- ReactCommon/turbomodule/core
- EXImageLoader (4.6.0):
- ExpoModulesCore
- React-Core
- Expo (50.0.4):
- ExpoModulesCore
- ExpoImage (1.10.1):
Expand All @@ -43,6 +46,9 @@ PODS:
- SDWebImageAVIFCoder (~> 0.10.1)
- SDWebImageSVGCoder (~> 1.7.0)
- SDWebImageWebPCoder (~> 0.13.0)
- ExpoImageManipulator (11.8.0):
- EXImageLoader
- ExpoModulesCore
- ExpoModulesCore (1.11.8):
- glog
- RCT-Folly (= 2022.05.16.00)
Expand Down Expand Up @@ -1173,8 +1179,6 @@ PODS:
- React-Core
- react-native-geolocation (3.0.6):
- React-Core
- react-native-image-manipulator (1.0.5):
- React
- react-native-image-picker (7.0.3):
- React-Core
- react-native-key-command (1.0.6):
Expand Down Expand Up @@ -1473,8 +1477,10 @@ DEPENDENCIES:
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXAV (from `../node_modules/expo-av/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- Expo (from `../node_modules/expo`)
- ExpoImage (from `../node_modules/expo-image/ios`)
- ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
Expand Down Expand Up @@ -1534,7 +1540,6 @@ DEPENDENCIES:
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- "react-native-image-manipulator (from `../node_modules/@oguzhnatly/react-native-image-manipulator`)"
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-key-command (from `../node_modules/react-native-key-command`)
- react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`)
Expand Down Expand Up @@ -1656,10 +1661,14 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EXAV:
:path: "../node_modules/expo-av/ios"
EXImageLoader:
:path: "../node_modules/expo-image-loader/ios"
Expo:
:path: "../node_modules/expo"
ExpoImage:
:path: "../node_modules/expo-image/ios"
ExpoImageManipulator:
:path: "../node_modules/expo-image-manipulator/ios"
ExpoModulesCore:
:path: "../node_modules/expo-modules-core"
FBLazyVector:
Expand Down Expand Up @@ -1729,8 +1738,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-geolocation:
:path: "../node_modules/@react-native-community/geolocation"
react-native-image-manipulator:
:path: "../node_modules/@oguzhnatly/react-native-image-manipulator"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-key-command:
Expand Down Expand Up @@ -1858,8 +1865,10 @@ SPEC CHECKSUMS:
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
EXAV: 09a4d87fa6b113fbb0ada3aade6799f78271cb44
EXImageLoader: 55080616b2fe9da19ef8c7f706afd9814e279b6b
Expo: 1e3bcf9dd99de57a636127057f6b488f0609681a
ExpoImage: 1cdaa65a6a70bb01067e21ad1347ff2d973885f5
ExpoImageManipulator: c1d7cb865eacd620a35659f3da34c70531f10b59
ExpoModulesCore: 96d1751929ad10622773bb729ab28a8423f0dd0c
FBLazyVector: fbc4957d9aa695250b55d879c1d86f79d7e69ab4
FBReactNativeSpec: 86de768f89901ef6ed3207cd686362189d64ac88
Expand Down Expand Up @@ -1933,7 +1942,6 @@ SPEC CHECKSUMS:
react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e
react-native-document-picker: 3599b238843369026201d2ef466df53f77ae0452
react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903
react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56
react-native-image-picker: 2381c008bbb09e72395a2d043c147b11bd1523d9
react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5
react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d
Expand Down Expand Up @@ -1997,7 +2005,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2
VisionCamera: 0a6794d1974aed5d653d0d0cb900493e2583e35a
Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
Yoga: 13c8ef87792450193e117976337b8527b49e8c03

PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2

Expand Down
27 changes: 20 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
"@invertase/react-native-apple-authentication": "^2.2.2",
"@kie/act-js": "^2.6.0",
"@kie/mock-github": "^1.0.0",
"@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52",
"@onfido/react-native-sdk": "10.6.0",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-native-camera-roll/camera-roll": "5.4.0",
Expand Down Expand Up @@ -105,6 +104,7 @@
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.10.1",
"expo-image-manipulator": "11.8.0",
"fbjs": "^3.0.2",
"htmlparser2": "^7.2.0",
"idb-keyval": "^6.2.1",
Expand Down
19 changes: 0 additions & 19 deletions patches/@oguzhnatly+react-native-image-manipulator+1.0.5.patch

This file was deleted.

6 changes: 6 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,12 @@ const CONST = {
VIDEO: 'video',
},

IMAGE_FILE_FORMAT: {
PNG: 'image/png',
WEBP: 'image/webp',
JPEG: 'image/jpeg',
},

FILE_TYPE_REGEX: {
// Image MimeTypes allowed by iOS photos app.
IMAGE: /\.(jpg|jpeg|png|webp|gif|tiff|bmp|heic|heif)$/,
Expand Down
17 changes: 17 additions & 0 deletions src/libs/cropOrRotateImage/getSaveFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {SaveFormat} from 'expo-image-manipulator';
import CONST from '@src/CONST';

function getSaveFormat(type: string) {
switch (type) {
case CONST.IMAGE_FILE_FORMAT.PNG:
return SaveFormat.PNG;
case CONST.IMAGE_FILE_FORMAT.WEBP:
return SaveFormat.WEBP;
case CONST.IMAGE_FILE_FORMAT.JPEG:
return SaveFormat.JPEG;
default:
return SaveFormat.JPEG;
}
}

export default getSaveFormat;
6 changes: 4 additions & 2 deletions src/libs/cropOrRotateImage/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator';
import {manipulateAsync} from 'expo-image-manipulator';
import RNFetchBlob from 'react-native-blob-util';
import getSaveFormat from './getSaveFormat';
import type {CropOrRotateImage} from './types';

/**
* Crops and rotates the image on ios/android
*/
const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) =>
new Promise((resolve) => {
RNImageManipulator.manipulate(uri, actions, options).then((result) => {
const format = getSaveFormat(options.type);
manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => {
RNFetchBlob.fs.stat(result.uri.replace('file://', '')).then(({size}) => {
resolve({
...result,
Expand Down
136 changes: 14 additions & 122 deletions src/libs/cropOrRotateImage/index.ts
Original file line number Diff line number Diff line change
@@ -1,127 +1,19 @@
import type {CropOptions, CropOrRotateImage, CropOrRotateImageOptions} from './types';

type SizeFromAngle = {
width: number;
height: number;
};

/**
* Calculates a size of canvas after rotation
*/
function sizeFromAngle(width: number, height: number, angle: number): SizeFromAngle {
const radians = (angle * Math.PI) / 180;
let sine = Math.cos(radians);
let cosine = Math.sin(radians);
if (cosine < 0) {
cosine = -cosine;
}
if (sine < 0) {
sine = -sine;
}
return {width: height * cosine + width * sine, height: height * sine + width * cosine};
}

/**
* Creates a new rotated canvas
*/
function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasElement {
const {width, height} = sizeFromAngle(canvas.width, canvas.height, degrees);

// We have to create a new canvas because it is not possible to change already drawn
// elements. Transformations such as rotation have to be applied before drawing
const result = document.createElement('canvas');
result.width = width;
result.height = height;

const context = result.getContext('2d');
if (context) {
// In order to rotate image along its center we have to apply next transformation
context.translate(result.width / 2, result.height / 2);

const radians = (degrees * Math.PI) / 180;
context.rotate(radians);

context.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
}
return result;
}

/**
* Creates new cropped canvas and returns it
*/
function cropCanvas(canvas: HTMLCanvasElement, options: CropOptions) {
let {originX = 0, originY = 0, width = 0, height = 0} = options;
const clamp = (value: number, max: number) => Math.max(0, Math.min(max, value));

width = clamp(width, canvas.width);
height = clamp(height, canvas.height);
originX = clamp(originX, canvas.width);
originY = clamp(originY, canvas.height);

width = Math.min(originX + width, canvas.width) - originX;
height = Math.min(originY + height, canvas.height) - originY;

const result = document.createElement('canvas');
result.width = width;
result.height = height;

const context = result.getContext('2d');
context?.drawImage(canvas, originX, originY, width, height, 0, 0, width, height);

return result;
}

function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateImageOptions): Promise<File> {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
if (!blob) {
return;
}
const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'});
file.uri = URL.createObjectURL(file);
resolve(file);
});
});
}

/**
* Loads image from specified url
*/
function loadImageAsync(uri: string): Promise<HTMLCanvasElement> {
return new Promise((resolve, reject) => {
const imageSource = new Image();
imageSource.crossOrigin = 'anonymous';
const canvas = document.createElement('canvas');
imageSource.onload = () => {
canvas.width = imageSource.naturalWidth;
canvas.height = imageSource.naturalHeight;

const context = canvas.getContext('2d');
context?.drawImage(imageSource, 0, 0, imageSource.naturalWidth, imageSource.naturalHeight);

resolve(canvas);
};
imageSource.onerror = () => reject(canvas);
imageSource.src = uri;
});
}

/**
* Crops and rotates the image on web
*/
import {manipulateAsync} from 'expo-image-manipulator';
import getSaveFormat from './getSaveFormat';
import type {CropOrRotateImage} from './types';

const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) =>
loadImageAsync(uri).then((originalCanvas) => {
const resultCanvas = actions.reduce((canvas, action) => {
if (action.crop) {
return cropCanvas(canvas, action.crop);
}
if (action.rotate) {
return rotateCanvas(canvas, action.rotate);
}
return canvas;
}, originalCanvas);
return convertCanvasToFile(resultCanvas, options);
new Promise((resolve) => {
const format = getSaveFormat(options.type);
manipulateAsync(uri, actions, {compress: options.compress, format}).then((result) => {
fetch(result.uri)
.then((res) => res.blob())
.then((blob) => {
const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'});
file.uri = URL.createObjectURL(file);
resolve(file);
});
});
});

export default cropOrRotateImage;
Loading

0 comments on commit f4f5de1

Please sign in to comment.