diff --git a/package/package.json b/package/package.json
index 0f1e0d7..1de9bf1 100644
--- a/package/package.json
+++ b/package/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-avatar-crop",
- "version": "1.3.5",
+ "version": "2.2.0",
"description": "Crop component to crop profile images",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -22,18 +22,20 @@
"react": "*",
"react-native": "*",
"@react-native-masked-view/masked-view": "*",
- "react-native-gesture-handler": "*",
- "@react-native-community/image-editor": "https://github.com/vemarav/react-native-image-editor",
- "react-native-image-size": "*"
+ "react-native-gesture-handler": "^2.0.0",
+ "@react-native-community/image-editor": "^4.0.0",
+ "react-native-image-size": "*",
+ "react-native-reanimated": "^3.0.0"
},
"devDependencies": {
"@types/react": "^17.0.15",
"@types/react-native": "^0.64.12",
"typescript": "^4.3.5",
"@react-native-masked-view/masked-view": "*",
- "react-native-gesture-handler": "*",
- "@react-native-community/image-editor": "https://github.com/vemarav/react-native-image-editor",
- "react-native-image-size": "*"
+ "react-native-gesture-handler": "^2.0.0",
+ "@react-native-community/image-editor": "^4.0.0",
+ "react-native-image-size": "*",
+ "react-native-reanimated": "^3.0.0"
},
"jest": {
"preset": "react-native",
diff --git a/package/src/crop/index.tsx b/package/src/crop/index.tsx
index 381caf0..8932863 100644
--- a/package/src/crop/index.tsx
+++ b/package/src/crop/index.tsx
@@ -1,22 +1,23 @@
-import ImageEditor from '@react-native-community/image-editor';
-import React, {useState, useEffect} from 'react';
-import {Animated, View, Dimensions, StyleSheet} from 'react-native';
-import {State, PinchGestureHandler, PanGestureHandler, GestureEvent} from 'react-native-gesture-handler';
-import MaskedView from '@react-native-masked-view/masked-view';
-
+import ImageEditor from '@react-native-community/image-editor'
+import React, { useState, useEffect, memo, useMemo, useCallback } from 'react'
+import { View, Dimensions, StyleSheet } from 'react-native'
+import { GestureDetector, Gesture } from 'react-native-gesture-handler'
+import MaskedView from '@react-native-masked-view/masked-view'
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withTiming,
+} from 'react-native-reanimated'
import {
Size,
round,
assert,
- getValue,
getAlpha,
isInRange,
- computeScale,
computeCover,
computeContain,
translateRangeX,
computeImageSize,
- computeTranslation,
translateRangeY,
computeScaledWidth,
computeScaledHeight,
@@ -24,37 +25,44 @@ import {
computeTranslate,
computeOffset,
computeSize,
-} from '../utils';
+} from './utils'
-const {width: DEFAULT_WIDTH} = Dimensions.get('window');
-const DEFAULT_ANIM_DURATION = 180;
+const { width: DEFAULT_WIDTH } = Dimensions.get('window')
+const DEFAULT_ANIM_DURATION = 180
export type CropProps = {
- source: {uri: string};
- cropShape?: 'rect' | 'circle';
- cropArea?: Size;
- borderWidth?: number;
- backgroundColor?: string;
- opacity?: number;
- width?: number;
- height?: number;
- maxZoom?: number;
- resizeMode?: 'contain' | 'cover';
+ source: { uri: string }
+ cropShape?: 'rect' | 'circle'
+ cropArea?: Size
+ borderWidth?: number
+ backgroundColor?: string
+ borderColor?: string
+ opacity?: number
+ width?: number
+ height?: number
+ maxZoom?: number
+ resizeMode?: 'contain' | 'cover'
onCrop: (
cropCallback: (quality?: number) => Promise<{
- uri: string;
- width: number;
- height: number;
- }>,
- ) => void;
-};
-
-const Crop = (props: CropProps): JSX.Element => {
+ uri: string
+ width: number
+ height: number
+ }>
+ ) => void
+ showCornerMarkers?: boolean
+ placeholder?: React.ReactNode
+}
+
+export const Crop = memo((props: CropProps) => {
const {
source,
cropShape = 'circle',
- cropArea = {width: DEFAULT_WIDTH, height: DEFAULT_WIDTH},
+ cropArea: cropAreaProp = {
+ width: DEFAULT_WIDTH,
+ height: DEFAULT_WIDTH,
+ },
backgroundColor = '#FFFFFF',
+ borderColor,
opacity = 0.7,
width = DEFAULT_WIDTH,
height = DEFAULT_WIDTH,
@@ -62,282 +70,428 @@ const Crop = (props: CropProps): JSX.Element => {
maxZoom = 5,
resizeMode = 'contain',
onCrop,
- } = props;
-
- cropArea.width = round(cropArea.width, 2);
- cropArea.height = round(cropArea.height, 2);
-
- assert(!isInRange(opacity, 1, 0), 'opacity must be between 0 and 1');
- assert(maxZoom < 1, 'maxZoom must be greater than 1');
- assert(width < cropArea.width, 'width must be greater than crop area width');
- assert(height < cropArea.height, 'height must be greater than crop area height');
-
- let _lastScale = 1;
- let _lastTranslate = {x: 0, y: 0};
-
- const trackScale = new Animated.Value(0);
- const [scale] = useState(new Animated.Value(0));
-
- const [trackTranslationX] = useState(new Animated.Value(0));
- const [trackTranslationY] = useState(new Animated.Value(0));
-
- const [translateX] = useState(new Animated.Value(0));
- const [translateY] = useState(new Animated.Value(0));
+ showCornerMarkers = false,
+ placeholder,
+ } = props
+
+ const cropArea = useMemo(
+ () => ({
+ width: round(cropAreaProp.width, 2),
+ height: round(cropAreaProp.height, 2),
+ }),
+ [cropAreaProp.width, cropAreaProp.height]
+ )
+
+ if (opacity < 0 || opacity > 1) {
+ throw new Error('opacity must be between 0 and 1')
+ }
+
+ assert(maxZoom < 1, 'maxZoom must be equal to or greater than 1')
+ assert(width < cropArea.width, 'width must be greater than or equal to crop area width')
+ assert(
+ height < cropArea.height,
+ 'height must be greater than or equal to crop area height'
+ )
+
+ const scale = useSharedValue(1)
+ const initialScale = useSharedValue(1)
+
+ const translateX = useSharedValue(0)
+ const translateY = useSharedValue(0)
+ const initialTranslateX = useSharedValue(0)
+ const initialTranslateY = useSharedValue(0)
+
+ const imageWidth = useSharedValue(0)
+ const imageHeight = useSharedValue(0)
+ const imageRotation = useSharedValue(0)
+
+ const [minZoom, setMinZoom] = useState(1)
+ const [isLoaded, setIsLoaded] = useState(false)
+
+ const opacityStyle = {
+ opacity: isLoaded ? 1 : 0,
+ }
- const [minZoom, setMinZoom] = useState(1);
+ const init = async () => {
+ try {
+ const _imageSize = await computeImageSize(source.uri)
- const imageSize = {width: NaN, height: NaN, rotation: 0};
+ imageWidth.value = _imageSize.width
+ imageHeight.value = _imageSize.height
+ imageRotation.value = _imageSize.rotation ?? 0
- const setImageSize = ({width, height, rotation}: {width: number; height: number; rotation?: number}) => {
- imageSize.width = width;
- imageSize.height = height;
- imageSize.rotation = rotation || 0;
- };
+ const _initialScale = computeContain(_imageSize, cropArea)
- const init = async () => {
- setImageSize(await computeImageSize(source.uri));
- const _initialScale = computeContain(imageSize, cropArea);
+ setMinZoom(_initialScale)
+ scale.value = _initialScale
+ initialScale.value = _initialScale
- setMinZoom(_initialScale);
- scale.setValue(_initialScale);
+ if (resizeMode === 'cover') {
+ scale.value = computeCover(
+ _initialScale,
+ _imageSize,
+ { width, height },
+ cropArea
+ )
+ }
- if (resizeMode === 'cover') {
- scale.setValue(computeCover(getValue(scale), imageSize, {width, height}, cropArea));
+ translateX.value = 0
+ translateY.value = 0
+ setIsLoaded(true)
+ onCrop(cropImage)
+ } catch (e) {
+ console.error('Failed to load image:', e)
+ setIsLoaded(true)
}
-
- _lastScale = getValue(scale);
-
- // reset translation
- translateX.setValue(0);
- translateY.setValue(0);
- addScaleListener();
- addTranslationListeners();
- onCrop(cropImage);
- };
+ }
useEffect(() => {
- init();
-
- return () => {
- removeScaleListeners();
- removeTranslationListeners();
- };
- });
-
- // start: pinch gesture handler
-
- const onPinchGestureEvent = Animated.event([{nativeEvent: {scale: trackScale}}], {
- useNativeDriver: false,
- });
-
- const addScaleListener = () => {
- trackScale.addListener(({value}: {value: number}) => {
- // value always starts from 0
- scale.setValue(computeScale(value, _lastScale, maxZoom, minZoom));
- });
- };
+ init()
+ }, [source.uri])
+
+ const translateStyles = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { translateX: translateX.value },
+ { translateY: translateY.value },
+ ],
+ }
+ })
- const removeScaleListeners = () => {
- trackScale.removeAllListeners();
- };
+ const scaleStyle = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ }
+ })
const resetTranslate = () => {
+ 'worklet'
// after scaling if crop area has blank space then
// it will reset to fit image inside the crop area
- const scaleValue = getValue(scale);
- if (scaleValue < _lastScale) {
- const translateXValue = getValue(translateX);
- const translateYValue = getValue(translateY);
- const {max: maxTranslateX, min: minTranslateX} = translateRangeX(getValue(scale), imageSize, cropArea, minZoom);
+ const scaleValue = scale.value
+ const imageSize = {
+ width: imageWidth.value,
+ height: imageHeight.value,
+ rotation: imageRotation.value,
+ }
+ const image = imageSize
+ if (!image || isNaN(image.width)) {
+ return
+ }
+
+ if (scaleValue < initialScale.value) {
+ const translateXValue = translateX.value
+ const translateYValue = translateY.value
+ const { max: maxTranslateX, min: minTranslateX } = translateRangeX(
+ scaleValue,
+ image,
+ cropArea,
+ minZoom
+ )
if (!isInRange(translateXValue, maxTranslateX, minTranslateX)) {
- const toValue = translateXValue > 0 ? maxTranslateX : minTranslateX;
- Animated.timing(translateX, {
- toValue,
+ const toValue = translateXValue > 0 ? maxTranslateX : minTranslateX
+ translateX.value = withTiming(toValue, {
duration: DEFAULT_ANIM_DURATION,
- useNativeDriver: true,
- }).start(() => translateX.setValue(toValue));
+ })
}
- const {max: maxTranslateY, min: minTranslateY} = translateRangeY(getValue(scale), imageSize, cropArea, minZoom);
+ const { max: maxTranslateY, min: minTranslateY } = translateRangeY(
+ scaleValue,
+ image,
+ cropArea,
+ minZoom
+ )
if (!isInRange(translateYValue, maxTranslateY, minTranslateY)) {
- const toValue = translateYValue > 0 ? maxTranslateY : minTranslateY;
- Animated.timing(translateY, {
- toValue,
+ const toValue = translateYValue > 0 ? maxTranslateY : minTranslateY
+ translateY.value = withTiming(toValue, {
duration: DEFAULT_ANIM_DURATION,
- useNativeDriver: true,
- }).start(() => translateY.setValue(toValue));
+ })
}
}
- };
-
- const onPinchGestureStateChange = ({nativeEvent}: GestureEvent) => {
- if (nativeEvent.oldState === State.ACTIVE) {
- resetTranslate();
- // resetTranslate depends on _lastScale
- _lastScale = getValue(scale);
- }
- };
-
- // end: pinch gesture handler
-
- // =================================================================
-
- // start: pan gesture handler
-
- const onPanGestureEvent = Animated.event(
- [
- {
- nativeEvent: {
- translationX: trackTranslationX,
- translationY: trackTranslationY,
- },
- },
- ],
- {
- useNativeDriver: false,
- },
- );
-
- const addTranslationListeners = () => {
- trackTranslationX.addListener(({value}: {value: number}) => {
- const {max, min} = translateRangeX(getValue(scale), imageSize, cropArea, minZoom);
- const last = _lastTranslate.x;
- translateX.setValue(computeTranslation(value, last, max, min));
- });
-
- trackTranslationY.addListener(({value}: {value: number}) => {
- const {max, min} = translateRangeY(getValue(scale), imageSize, cropArea, minZoom);
- const last = _lastTranslate.y;
- translateY.setValue(computeTranslation(value, last, max, min));
- });
- };
-
- const removeTranslationListeners = () => {
- translateX.removeAllListeners();
- translateY.removeAllListeners();
- };
-
- const onPanGestureStateChange = ({nativeEvent}: GestureEvent) => {
- if (nativeEvent.oldState === State.ACTIVE) {
- _lastTranslate = {x: getValue(translateX), y: getValue(translateY)};
- }
- };
-
- // end: pan gesture handler
-
- const cropImage = async (quality: number = 1): Promise<{uri: string; height: number; width: number}> => {
- assert(!isInRange(quality, 1, 0), 'quality must be between 0 and 1');
-
- const scaleValue = getValue(scale);
- const translateXValue = getValue(translateX);
- const translateYValue = getValue(translateY);
-
- const scaledWidth = computeScaledWidth(scaleValue, imageSize, cropArea, minZoom);
- const scaledHeight = computeScaledHeight(scaleValue, imageSize, cropArea, minZoom);
- const scaledMultiplier = computeScaledMultiplier(imageSize, scaledWidth);
-
- const scaledSize = {width: scaledWidth, height: scaledHeight};
- const translate = computeTranslate(imageSize, translateXValue, translateYValue);
+ }
+
+ const panGesture = Gesture.Pan()
+ .minPointers(1)
+ .maxPointers(1)
+ .onBegin(() => {
+ initialTranslateX.value = translateX.value
+ initialTranslateY.value = translateY.value
+ })
+ .onUpdate((event) => {
+ const imageSize = {
+ width: imageWidth.value,
+ height: imageHeight.value,
+ rotation: imageRotation.value,
+ }
+ const { max: maxX, min: minX } = translateRangeX(
+ scale.value,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ const { max: maxY, min: minY } = translateRangeY(
+ scale.value,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ const newTranslateX =
+ initialTranslateX.value + event.translationX / scale.value
+ const newTranslateY =
+ initialTranslateY.value + event.translationY / scale.value
+ translateX.value = Math.min(Math.max(newTranslateX, minX), maxX)
+ translateY.value = Math.min(Math.max(newTranslateY, minY), maxY)
+ })
+
+ const pinchGesture = Gesture.Pinch()
+ .onBegin(() => {
+ initialScale.value = scale.value
+ })
+ .onChange((event) => {
+ const newScale = initialScale.value * event.scale
+ scale.value = Math.min(Math.max(newScale, minZoom), maxZoom)
+ })
+ .onEnd(() => {
+ resetTranslate()
+ })
+
+ const composedGestures = Gesture.Simultaneous(panGesture, pinchGesture)
+
+ const cropImage = useCallback(
+ async (
+ quality: number = 1
+ ): Promise<{ uri: string; height: number; width: number }> => {
+ if (quality < 0 || quality > 1) {
+ throw new Error('quality must be between 0 and 1')
+ }
- const {max: maxTranslateX} = translateRangeX(getValue(scale), imageSize, cropArea, minZoom);
- const {max: maxTranslateY} = translateRangeY(getValue(scale), imageSize, cropArea, minZoom);
+ const scaleValue = scale.value
+ const translateXValue = translateX.value
+ const translateYValue = translateY.value
- const offset = computeOffset(scaledSize, imageSize, translate, maxTranslateX, maxTranslateY, scaledMultiplier);
- const size = computeSize(cropArea, scaledMultiplier);
- const emitSize = computeSize(size, quality);
- const cropData = {offset, size, displaySize: emitSize};
+ if (!imageWidth.value || !imageHeight.value) {
+ throw new Error('Invalid image dimensions')
+ }
- try {
- const croppedImageUri = await ImageEditor.cropImage(source.uri, cropData);
- return {uri: croppedImageUri, ...emitSize};
- } catch (e) {
- console.error('Failed to crop image!');
- throw e;
- }
- };
+ const imageSize = {
+ width: imageWidth.value,
+ height: imageHeight.value,
+ rotation: imageRotation.value,
+ }
- const borderRadius = cropShape === 'circle' ? Math.max(cropArea.height, cropArea.width) : 0;
+ const scaledWidth = computeScaledWidth(
+ scaleValue,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ const scaledHeight = computeScaledHeight(
+ scaleValue,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ const scaledMultiplier = computeScaledMultiplier(imageSize, scaledWidth)
+ const scaledSize = { width: scaledWidth, height: scaledHeight }
+
+ const translate = computeTranslate(
+ imageSize,
+ translateXValue,
+ translateYValue
+ )
+
+ const { max: maxTranslateX } = translateRangeX(
+ scaleValue,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ const { max: maxTranslateY } = translateRangeY(
+ scaleValue,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+
+ const offset = computeOffset(
+ scaledSize,
+ imageSize,
+ translate,
+ maxTranslateX,
+ maxTranslateY,
+ scaledMultiplier
+ )
+ const size = computeSize(cropArea, scaledMultiplier)
+ const emitSize = computeSize(size, quality)
+ const cropData = { offset, size, displaySize: emitSize }
+
+ try {
+ const croppedImageUri = await ImageEditor.cropImage(
+ source.uri,
+ cropData
+ )
+ return {
+ uri: croppedImageUri as unknown as string,
+ ...emitSize,
+ }
+ } catch (e) {
+ console.error('Crop failed:', e)
+ throw e
+ }
+ }, [
+ imageWidth.value,
+ imageHeight.value,
+ imageRotation.value,
+ cropArea.width,
+ cropArea.height,
+ translateX.value,
+ translateY.value,
+ scale.value,
+ minZoom,
+ maxZoom,
+ ])
+
+ const borderRadius =
+ cropShape === 'circle' ? Math.max(cropArea.height, cropArea.width) : 0
return (
-
-
-
-
-
-
- }>
-
+
+
-
+
-
-
+
+ }
+ >
+
+
+ {!isLoaded && placeholder && (
+
+ {placeholder}
+
+ )}
+
+
+
-
+ ...cropArea,
+ borderWidth: borderWidth,
+ borderRadius,
+ borderColor: borderColor ?? backgroundColor,
+ }}
+ >
+ {showCornerMarkers && (
+ <>
+
+
+
+
+ >
+ )}
-
-
- );
-};
+
+
+ )
+})
-export default Crop;
+Crop.displayName = 'Crop'
const styles = StyleSheet.create({
- mask: {flex: 1},
- center: {flex: 1, justifyContent: 'center', alignItems: 'center'},
- transparentMask: {backgroundColor: '#FFFFFF'},
- overlay: {flex: 1, justifyContent: 'center', alignItems: 'center'},
- contain: {resizeMode: 'contain'},
-});
+ mask: {
+ flex: 1,
+ },
+ center: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ position: 'relative',
+ zIndex: 0,
+ },
+ transparentMask: { backgroundColor: '#FFFFFF' },
+ overlay: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+ contain: { resizeMode: 'contain' },
+ cover: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ loading: {
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ zIndex: 1,
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ cornerBase: {
+ width: 22,
+ height: 22,
+ borderColor: 'white',
+ position: 'absolute',
+ borderRadius: 2,
+ },
+ cornerTopLeft: {
+ borderTopWidth: 4,
+ borderLeftWidth: 4,
+ top: -4,
+ left: -4,
+ },
+ cornerTopRight: {
+ borderTopWidth: 4,
+ borderRightWidth: 4,
+ top: -4,
+ right: -4,
+ },
+ cornerBottomLeft: {
+ borderBottomWidth: 4,
+ borderLeftWidth: 4,
+ bottom: -4,
+ left: -4,
+ },
+ cornerBottomRight: {
+ borderBottomWidth: 4,
+ borderRightWidth: 4,
+ bottom: -4,
+ right: -4,
+ },
+})
diff --git a/package/src/utils/index.ts b/package/src/utils/index.ts
index 38078d3..2776890 100644
--- a/package/src/utils/index.ts
+++ b/package/src/utils/index.ts
@@ -1,15 +1,15 @@
-import ImageSize from 'react-native-image-size';
+import ImageSize from 'react-native-image-size'
export type Size = {
- width: number;
- height: number;
- rotation?: number;
-};
+ width: number
+ height: number
+ rotation?: number
+}
export type Range = {
- max: number;
- min: number;
-};
+ max: number
+ min: number
+}
enum Orientation {
landscape,
@@ -22,191 +22,206 @@ enum Fit {
height,
}
-export const log = (obj: any) => {
- console.info(JSON.stringify(obj, null, 2));
-};
-
export const assert = (failsTest: boolean, message: string): Error | void => {
- if (failsTest) throw new Error(message);
-};
+ if (failsTest) throw new Error(message)
+}
export const round = (num: number, precision: number) => {
try {
- return Number(num.toFixed(precision));
+ return Number(num.toFixed(precision))
} catch (e) {
- return num;
+ return num
}
-};
+}
-export const isInRange = (value: number, max: number, min: number): boolean => min <= value && value <= max;
+export const isInRange = (value: number, max: number, min: number): boolean => {
+ 'worklet'
+ return min <= value && value <= max
+}
export const getAlpha = (opacity: number): string => {
// #12345678 78 is the alpha value and the range is (0 < alpha < 100)
if (opacity === 1) {
- return '';
+ return ''
} else {
- return `${Math.ceil(opacity * 100)}`.padStart(2, '0').slice(-2);
+ return `${Math.ceil(opacity * 100)}`.padStart(2, '0').slice(-2)
}
-};
+}
export const getRatio = (imageSize: Size) => {
- const {width, height} = imageSize;
- return Math.max(width, height) / Math.min(width, height);
-};
-
-export const getValue = (animated: any): any => Number.parseFloat(JSON.stringify(animated));
+ const { width, height } = imageSize
+ return Math.max(width, height) / Math.min(width, height)
+}
export const getOrientation = (size: Size) => {
if (size.width > size.height) {
- return Orientation.landscape;
+ return Orientation.landscape
} else if (size.width < size.height) {
- return Orientation.portrait;
+ return Orientation.portrait
} else {
- return Orientation.even;
+ return Orientation.even
}
-};
+}
export const getAspectRatio = (size: Size) => {
- return size.height / size.width;
-};
+ 'worklet'
+ return size.height / size.width
+}
export const computeImageSize = async (uri: string): Promise => {
- const {width, height, rotation} = await ImageSize.getSize(uri);
+ const { width, height, rotation } = await ImageSize.getSize(uri)
if (rotation === 90 || rotation === 270) {
- return {width: height, height: width, rotation};
+ return { width: height, height: width, rotation }
} else {
- return {width, height, rotation};
+ return { width, height, rotation }
}
-};
-
-export const computeScale = (current: number, last: number, max: number, min: number) => {
- const next = current + last - 1;
- if (isInRange(next, max, min)) {
- return next;
- }
-
- if (next > max) {
- return max;
- }
-
- return min;
-};
+}
export const computeContain = (imageSize: Size, cropArea: Size) => {
- const scale = imageSize.height / cropArea.height / (imageSize.width / cropArea.width);
- return scale > 1 ? scale : 1 / scale;
-};
+ const scale =
+ imageSize.height / cropArea.height / (imageSize.width / cropArea.width)
+ return scale > 1 ? scale : 1 / scale
+}
-export const computeCover = (scale: number, imageSize: Size, size: Size, cropArea: Size) => {
- const imageOrientation = getOrientation(imageSize);
+export const computeCover = (
+ scale: number,
+ imageSize: Size,
+ size: Size,
+ cropArea: Size
+) => {
+ const imageOrientation = getOrientation(imageSize)
if (imageOrientation === Orientation.portrait) {
- return scale * (size.width / cropArea.width);
+ return scale * (size.width / cropArea.width)
} else {
- return scale * (size.height / cropArea.height);
+ return scale * (size.height / cropArea.height)
}
-};
+}
-export const translateRangeX = (scale: number, imageSize: Size, cropArea: Size, minZoom: number) => {
- const cropARatio = getAspectRatio(cropArea);
- const imageARatio = getAspectRatio(imageSize);
- const initialFit = cropARatio > imageARatio ? Fit.height : Fit.width;
+export const translateRangeX = (
+ scale: number,
+ imageSize: Size,
+ cropArea: Size,
+ minZoom: number
+) => {
+ 'worklet'
+ const cropARatio = getAspectRatio(cropArea)
+ const imageARatio = getAspectRatio(imageSize)
+ const initialFit = cropARatio > imageARatio ? Fit.height : Fit.width
if (initialFit === Fit.width) {
- const imageOutsideBoxSize = (cropArea.width * scale) / minZoom - cropArea.width;
- return {max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2};
+ const imageOutsideBoxSize =
+ (cropArea.width * scale) / minZoom - cropArea.width
+
+ return { max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2 }
} else {
- const imageOutsideBoxSize = cropArea.width * scale - cropArea.width;
- return {max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2};
+ const imageOutsideBoxSize = cropArea.width * scale - cropArea.width
+ return { max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2 }
}
-};
+}
-export const translateRangeY = (scale: number, imageSize: Size, cropArea: Size, minZoom: number) => {
- const cropARatio = getAspectRatio(cropArea);
- const imageARatio = getAspectRatio(imageSize);
- const initialFit = cropARatio < imageARatio ? Fit.width : Fit.height;
+export const translateRangeY = (
+ scale: number,
+ imageSize: Size,
+ cropArea: Size,
+ minZoom: number
+) => {
+ 'worklet'
+ const cropARatio = getAspectRatio(cropArea)
+ const imageARatio = getAspectRatio(imageSize)
+ const initialFit = cropARatio < imageARatio ? Fit.width : Fit.height
if (initialFit === Fit.height) {
- const imageOutsideBoxSize = (cropArea.height * scale) / minZoom - cropArea.height;
- return {max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2};
+ const imageOutsideBoxSize =
+ (cropArea.height * scale) / minZoom - cropArea.height
+ return { max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2 }
} else {
- const imageOutsideBoxSize = cropArea.height * scale - cropArea.height;
- return {max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2};
- }
-};
-
-export const computeTranslation = (current: number, last: number, max: number, min: number) => {
- const next = current + last;
-
- if (isInRange(next, max, min)) {
- return next;
- }
-
- if (next > max) {
- return max;
+ const imageOutsideBoxSize = cropArea.height * scale - cropArea.height
+ return { max: imageOutsideBoxSize / 2, min: -imageOutsideBoxSize / 2 }
}
+}
- return min;
-};
-
-export const computeScaledWidth = (scale: number, imageSize: Size, cropArea: Size, minZoom: number): number => {
- const {max: maxTranslateX} = translateRangeX(minZoom, imageSize, cropArea, minZoom);
- return maxTranslateX > 0 ? cropArea.width * scale : (cropArea.width * scale) / minZoom;
-};
+export const computeScaledWidth = (
+ scale: number,
+ imageSize: Size,
+ cropArea: Size,
+ minZoom: number
+): number => {
+ const { max: maxTranslateX } = translateRangeX(
+ minZoom,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ return maxTranslateX > 0
+ ? cropArea.width * scale
+ : (cropArea.width * scale) / minZoom
+}
-export const computeScaledHeight = (scale: number, imageSize: Size, cropArea: Size, minZoom: number): number => {
- const {max: maxTranslateY} = translateRangeY(minZoom, imageSize, cropArea, minZoom);
- return maxTranslateY > 0 ? cropArea.height * scale : (cropArea.height * scale) / minZoom;
-};
+export const computeScaledHeight = (
+ scale: number,
+ imageSize: Size,
+ cropArea: Size,
+ minZoom: number
+): number => {
+ const { max: maxTranslateY } = translateRangeY(
+ minZoom,
+ imageSize,
+ cropArea,
+ minZoom
+ )
+ return maxTranslateY > 0
+ ? cropArea.height * scale
+ : (cropArea.height * scale) / minZoom
+}
export const computeScaledMultiplier = (imageSize: Size, width: number) => {
- return imageSize.width / width;
-};
+ return imageSize.width / width
+}
export const computeTranslate = (imageSize: Size, x: number, y: number) => {
- if (imageSize.rotation === 90) return {x: -x, y: y};
- if (imageSize.rotation === 180) return {x: -x, y: -y};
- if (imageSize.rotation === 270) return {x: x, y: -y};
- return {x, y};
-};
+ if (imageSize.rotation === 90) return { x: -x, y: y }
+ if (imageSize.rotation === 180) return { x: -x, y: -y }
+ if (imageSize.rotation === 270) return { x: x, y: -y }
+ return { x, y }
+}
export const computeOffset = (
scaled: Size,
imageSize: Size,
- translate: {x: number; y: number},
+ translate: { x: number; y: number },
maxTranslateX: number,
maxTranslateY: number,
- multiplier: number,
-): {x: number; y: number} => {
- const initialOffsetX = scaled.width - maxTranslateX;
- const initialOffsetY = scaled.height - maxTranslateY;
- const finalOffsetX = imageSize.width - (initialOffsetX + translate.x) * multiplier;
- const finalOffsetY = imageSize.height - (initialOffsetY + translate.y) * multiplier;
- const offset = {x: round(finalOffsetX, 3), y: round(finalOffsetY, 3)};
- if (imageSize.rotation == 90 || imageSize.rotation === 270) {
- return {x: offset.y, y: offset.x};
+ multiplier: number
+): { x: number; y: number } => {
+ const initialOffsetX = scaled.width - maxTranslateX
+ const initialOffsetY = scaled.height - maxTranslateY
+ const finalOffsetX =
+ imageSize.width - (initialOffsetX + translate.x) * multiplier
+ const finalOffsetY =
+ imageSize.height - (initialOffsetY + translate.y) * multiplier
+ const offset = { x: round(finalOffsetX, 3), y: round(finalOffsetY, 3) }
+ if (imageSize.rotation === 90 || imageSize.rotation === 270) {
+ return { x: offset.y, y: offset.x }
}
- return offset;
-};
+ return offset
+}
export const computeSize = (size: Size, multiplier: number): Size => {
return {
width: round(size.width * multiplier, 3),
height: round(size.height * multiplier, 3),
- };
-};
+ }
+}
export default {
- log,
Fit,
Orientation,
assert,
- getValue,
getAlpha,
getRatio,
getOrientation,
isInRange,
- computeScale,
computeCover,
computeImageSize,
translateRangeX,
@@ -215,4 +230,4 @@ export default {
computeScaledWidth,
computeScaledHeight,
computeScaledMultiplier,
-};
+}