From 060876a144c10bdf31d4bad9dfd628a614bfe352 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 13 Sep 2023 15:27:35 +0200 Subject: [PATCH 1/4] ref: move cropOrRotateImage index to TS --- .../{index.native.js => index.native.ts} | 0 .../cropOrRotateImage/{index.js => index.ts} | 103 +++++++++--------- src/libs/cropOrRotateImage/types.ts | 0 3 files changed, 49 insertions(+), 54 deletions(-) rename src/libs/cropOrRotateImage/{index.native.js => index.native.ts} (100%) rename src/libs/cropOrRotateImage/{index.js => index.ts} (57%) create mode 100644 src/libs/cropOrRotateImage/types.ts diff --git a/src/libs/cropOrRotateImage/index.native.js b/src/libs/cropOrRotateImage/index.native.ts similarity index 100% rename from src/libs/cropOrRotateImage/index.native.js rename to src/libs/cropOrRotateImage/index.native.ts diff --git a/src/libs/cropOrRotateImage/index.js b/src/libs/cropOrRotateImage/index.ts similarity index 57% rename from src/libs/cropOrRotateImage/index.js rename to src/libs/cropOrRotateImage/index.ts index ec19aff7c7e3..617e13b4a130 100644 --- a/src/libs/cropOrRotateImage/index.js +++ b/src/libs/cropOrRotateImage/index.ts @@ -1,14 +1,30 @@ -import _ from 'underscore'; +type SizeFromAngle = { + width: number; + height: number; +}; +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; +}; /** * 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 +39,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 @@ -40,26 +52,22 @@ function rotateCanvas(canvas, degrees) { const context = result.getContext('2d'); // In order to rotate image along its center we have to apply next transformation - context.translate(result.width / 2, result.height / 2); + context?.translate(result.width / 2, result.height / 2); const radians = (degrees * Math.PI) / 180; - context.rotate(radians); + context?.rotate(radians); - context.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); + 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,33 +82,28 @@ 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) => { + if (!blob) { + return; + } const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); - file.uri = URL.createObjectURL(file); - resolve(file); + const fileWithUri = Object.assign(file, {uri: URL.createObjectURL(file)}); + resolve(fileWithUri); }); }); } /** * 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 +113,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,27 +124,19 @@ 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) { + +function cropOrRotateImage(uri: string, actions: Action[], options: CropOrRotateImageOptions): Promise { 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 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); }); diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts new file mode 100644 index 000000000000..e69de29bb2d1 From 5c15ebbeaf8ae22af1de4a91bd52a1093aec8596 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Sep 2023 10:46:34 +0200 Subject: [PATCH 2/4] ref: move native file to ts --- src/libs/cropOrRotateImage/index.native.ts | 19 +++++----------- src/libs/cropOrRotateImage/index.ts | 26 +++++----------------- src/libs/cropOrRotateImage/types.ts | 26 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/libs/cropOrRotateImage/index.native.ts b/src/libs/cropOrRotateImage/index.native.ts index c1e21879f2be..d4dd6b698eea 100644 --- a/src/libs/cropOrRotateImage/index.native.ts +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -1,27 +1,18 @@ 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({ - ...result, - size, - type: options.type || 'image/jpeg', - name: options.name || 'fileName.jpg', - }); + const file = Object.assign(result, {size, type: options.type || 'image/jpeg', name: options.name || 'fileName.jpg'}); + resolve(file); }); }); }); -} export default cropOrRotateImage; diff --git a/src/libs/cropOrRotateImage/index.ts b/src/libs/cropOrRotateImage/index.ts index 617e13b4a130..4d327e95b707 100644 --- a/src/libs/cropOrRotateImage/index.ts +++ b/src/libs/cropOrRotateImage/index.ts @@ -1,25 +1,9 @@ +import {CropOptions, CropOrRotateImage, CropOrRotateImageOptions, FileWithUri} from './types'; + type SizeFromAngle = { width: number; height: number; }; -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; -}; /** * Calculates a size of canvas after rotation @@ -95,6 +79,7 @@ function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateIma } const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); const fileWithUri = Object.assign(file, {uri: URL.createObjectURL(file)}); + resolve(fileWithUri); }); }); @@ -126,8 +111,8 @@ function loadImageAsync(uri: string): Promise { * Crops and rotates the image on web */ -function cropOrRotateImage(uri: string, actions: Action[], options: CropOrRotateImageOptions): Promise { - return loadImageAsync(uri).then((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); @@ -140,6 +125,5 @@ function cropOrRotateImage(uri: string, actions: Action[], options: CropOrRotate return convertCanvasToFile(resultCanvas, options); }); -} export default cropOrRotateImage; diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts index e69de29bb2d1..259cc98a5655 100644 --- a/src/libs/cropOrRotateImage/types.ts +++ b/src/libs/cropOrRotateImage/types.ts @@ -0,0 +1,26 @@ +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 RNManipulator = RNImageManipulatorResult & {size: number; type: string; name: string}; + +type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise; + +export type {CropOrRotateImage, CropOptions, Action, FileWithUri, CropOrRotateImageOptions}; From 0cc9181097757bd75504bb2d1f0095a3ebf8e134 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Sep 2023 13:04:56 +0200 Subject: [PATCH 3/4] fix: wrap context with if statment instead using optional chaining --- src/libs/cropOrRotateImage/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/cropOrRotateImage/index.ts b/src/libs/cropOrRotateImage/index.ts index 4d327e95b707..306e836888dc 100644 --- a/src/libs/cropOrRotateImage/index.ts +++ b/src/libs/cropOrRotateImage/index.ts @@ -34,15 +34,15 @@ function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasEle 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; } From fd3e14607cfec08ca1023c2568657b3c0fbb5bc7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 15 Sep 2023 09:49:30 +0200 Subject: [PATCH 4/4] fix: resolve comments --- src/libs/cropOrRotateImage/index.native.ts | 8 ++++++-- src/libs/cropOrRotateImage/index.ts | 8 +++----- src/libs/cropOrRotateImage/types.ts | 7 +++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/libs/cropOrRotateImage/index.native.ts b/src/libs/cropOrRotateImage/index.native.ts index d4dd6b698eea..08a5ecb04f6b 100644 --- a/src/libs/cropOrRotateImage/index.native.ts +++ b/src/libs/cropOrRotateImage/index.native.ts @@ -9,8 +9,12 @@ 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}) => { - const file = Object.assign(result, {size, type: options.type || 'image/jpeg', name: options.name || 'fileName.jpg'}); - resolve(file); + resolve({ + ...result, + size, + type: options.type || 'image/jpeg', + name: options.name || 'fileName.jpg', + }); }); }); }); diff --git a/src/libs/cropOrRotateImage/index.ts b/src/libs/cropOrRotateImage/index.ts index 306e836888dc..6b222c9759b5 100644 --- a/src/libs/cropOrRotateImage/index.ts +++ b/src/libs/cropOrRotateImage/index.ts @@ -77,10 +77,9 @@ function convertCanvasToFile(canvas: HTMLCanvasElement, options: CropOrRotateIma if (!blob) { return; } - const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}); - const fileWithUri = Object.assign(file, {uri: URL.createObjectURL(file)}); - - resolve(fileWithUri); + const file = new File([blob], options.name || 'fileName.jpeg', {type: options.type || 'image/jpeg'}) as FileWithUri; + file.uri = URL.createObjectURL(file); + resolve(file); }); }); } @@ -122,7 +121,6 @@ const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) => } return canvas; }, originalCanvas); - return convertCanvasToFile(resultCanvas, options); }); diff --git a/src/libs/cropOrRotateImage/types.ts b/src/libs/cropOrRotateImage/types.ts index 259cc98a5655..6abbdab49ea5 100644 --- a/src/libs/cropOrRotateImage/types.ts +++ b/src/libs/cropOrRotateImage/types.ts @@ -5,22 +5,25 @@ type CropOrRotateImageOptions = { 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 RNManipulator = RNImageManipulatorResult & {size: number; type: string; name: string}; +type CustomRNImageManipulatorResult = RNImageManipulatorResult & {size: number; type: string; name: string}; -type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise; +type CropOrRotateImage = (uri: string, actions: Action[], options: CropOrRotateImageOptions) => Promise; export type {CropOrRotateImage, CropOptions, Action, FileWithUri, CropOrRotateImageOptions};