diff --git a/src/libs/cropOrRotateImage/index.native.js b/src/libs/cropOrRotateImage/index.native.ts similarity index 71% rename from src/libs/cropOrRotateImage/index.native.js rename to src/libs/cropOrRotateImage/index.native.ts index c1e21879f2be..08a5ecb04f6b 100644 --- a/src/libs/cropOrRotateImage/index.native.js +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -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} actions - * @param {Object} options - * @returns {Promise} 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({ @@ -22,6 +18,5 @@ function cropOrRotateImage(uri, actions, options = {}) { }); }); }); -} export default cropOrRotateImage; diff --git a/src/libs/cropOrRotateImage/index.js b/src/libs/cropOrRotateImage/index.ts similarity index 54% rename from src/libs/cropOrRotateImage/index.js rename to src/libs/cropOrRotateImage/index.ts index ec19aff7c7e3..6b222c9759b5 100644 --- a/src/libs/cropOrRotateImage/index.js +++ b/src/libs/cropOrRotateImage/index.ts @@ -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); @@ -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 @@ -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); @@ -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} - */ -function convertCanvasToFile(canvas, options = {}) { +function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateImageOptions): Promise { 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); }); @@ -96,11 +86,8 @@ function convertCanvasToFile(canvas, options = {}) { /** * Loads image from specified url - * - * @param {String} uri - * @returns {Promise} */ -function loadImageAsync(uri) { +function loadImageAsync(uri: string): Promise { return new Promise((resolve, reject) => { const imageSource = new Image(); imageSource.crossOrigin = 'anonymous'; @@ -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); }; @@ -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} 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; diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts new file mode 100644 index 000000000000..6abbdab49ea5 --- /dev/null +++ b/src/libs/cropOrRotateImage/types.ts @@ -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; + +export type {CropOrRotateImage, CropOptions, Action, FileWithUri, CropOrRotateImageOptions};