Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] [TS migration] Migrate 'cropOrRotateImage' lib to TypeScript #27419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little weird to me. Any reason not to declare the types inline?

function cropOrRotateImage(uri: string, actions: Action[], options: CropOrRotateImageOptions): Promise<FileWithUri | RNManipulator > {
  ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thiagobrez when we have platform specific files we need to follow this pattern. Thats why its like that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kubabutkiewicz Cool! 👍🏻

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

kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
const cropOrRotateImage: CropOrRotateImage = (uri, actions, options) =>
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
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 = {
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
originX: number;
originY: number;
width: number;
height: number;
};

type Action = {
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
crop?: CropOptions;
rotate?: number;
};

type FileWithUri = File & {
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
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};