Skip to content

Commit

Permalink
Merge pull request #27419 from kubabutkiewicz/ts-migration/cropOrRota…
Browse files Browse the repository at this point in the history
…teImage-lib

[No QA] [TS migration] Migrate 'cropOrRotateImage' lib to TypeScript
  • Loading branch information
srikarparsi authored Sep 19, 2023
2 parents f700942 + fd3e146 commit 209fc85
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import RNImageManipulator from '@oguzhnatly/react-native-image-manipulator';
import RNFetchBlob from 'react-native-blob-util';
import {CropOrRotateImage} from './types';

/**
* Crops and rotates the image on ios/android
*
* @param {String} uri
* @param {Array<Object>} actions
* @param {Object} options
* @returns {Promise<Object>} Returns cropped and rotated image
*/
function cropOrRotateImage(uri, actions, options = {}) {
return new Promise((resolve) => {
const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) =>
new Promise((resolve) => {
RNImageManipulator.manipulate(uri, actions, options).then((result) => {
RNFetchBlob.fs.stat(result.uri.replace('file://', '')).then(({size}) => {
resolve({
Expand All @@ -22,6 +18,5 @@ function cropOrRotateImage(uri, actions, options = {}) {
});
});
});
}

export default cropOrRotateImage;
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import _ from 'underscore';
import {CropOptions, CropOrRotateImage, CropOrRotateImageOptions, FileWithUri} from './types';

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

/**
* Calculates a size of canvas after rotation
*
* @param {Number} width
* @param {Number} height
* @param {Number} angle
* @returns {Object} Returns width and height of new canvas
*/
function sizeFromAngle(width, height, angle) {
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);
Expand All @@ -23,12 +23,8 @@ function sizeFromAngle(width, height, angle) {

/**
* Creates a new rotated canvas
*
* @param {Object} canvas
* @param {Number} degrees
* @returns {Object}
*/
function rotateCanvas(canvas, degrees) {
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
Expand All @@ -38,28 +34,24 @@ function rotateCanvas(canvas, degrees) {
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);

// 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);
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
*
* @param {Object} canvas
* @param {Object} options
* @returns {Object}
*/
function cropCanvas(canvas, options) {
function cropCanvas(canvas: HTMLCanvasElement, options: CropOptions) {
let {originX = 0, originY = 0, width = 0, height = 0} = options;
const clamp = (value, max) => Math.max(0, Math.min(max, value));
const clamp = (value: number, max: number) => Math.max(0, Math.min(max, value));

width = clamp(width, canvas.width);
height = clamp(height, canvas.height);
Expand All @@ -74,20 +66,18 @@ function cropCanvas(canvas, options) {
result.height = height;

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

return result;
}

/**
* @param {Object} canvas
* @param {Object} options
* @returns {Promise<File>}
*/
function convertCanvasToFile(canvas, options = {}) {
function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateImageOptions): Promise<FileWithUri> {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'});
if (!blob) {
return;
}
const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}) as FileWithUri;
file.uri = URL.createObjectURL(file);
resolve(file);
});
Expand All @@ -96,11 +86,8 @@ function convertCanvasToFile(canvas, options = {}) {

/**
* Loads image from specified url
*
* @param {String} uri
* @returns {Promise<Object>}
*/
function loadImageAsync(uri) {
function loadImageAsync(uri: string): Promise<HTMLCanvasElement> {
return new Promise((resolve, reject) => {
const imageSource = new Image();
imageSource.crossOrigin = 'anonymous';
Expand All @@ -110,7 +97,7 @@ function loadImageAsync(uri) {
canvas.height = imageSource.naturalHeight;

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

resolve(canvas);
};
Expand All @@ -121,30 +108,20 @@ function loadImageAsync(uri) {

/**
* Crops and rotates the image on web
*
* @param {String} uri
* @param {Object} actions
* @param {Object} options
* @returns {Promise<Object>} Returns cropped and rotated image
*/
function cropOrRotateImage(uri, actions, options) {
return loadImageAsync(uri).then((originalCanvas) => {
const resultCanvas = _.reduce(
actions,
(canvas, action) => {
if ('crop' in action) {
return cropCanvas(canvas, action.crop);
}
if ('rotate' in action) {
return rotateCanvas(canvas, action.rotate);
}
return canvas;
},
originalCanvas,
);

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);
});
}

export default cropOrRotateImage;
29 changes: 29 additions & 0 deletions src/libs/cropOrRotateImage/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {RNImageManipulatorResult} from '@oguzhnatly/react-native-image-manipulator';

type CropOrRotateImageOptions = {
type: string;
name: string;
compress: number;
};

type CropOptions = {
originX: number;
originY: number;
width: number;
height: number;
};

type Action = {
crop?: CropOptions;
rotate?: number;
};

type FileWithUri = File & {
uri: string;
};

type CustomRNImageManipulatorResult = RNImageManipulatorResult & {size: number; type: string; name: string};

type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise<FileWithUri | CustomRNImageManipulatorResult>;

export type {CropOrRotateImage, CropOptions, Action, FileWithUri, CropOrRotateImageOptions};

0 comments on commit 209fc85

Please sign in to comment.