-
-
Notifications
You must be signed in to change notification settings - Fork 167
/
helpers.js
163 lines (149 loc) · 5.81 KB
/
helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* Compute the dimension of the crop area based on image size and aspect ratio
* @param {number} imgWidth width of the src image in pixels
* @param {number} imgHeight height of the src image in pixels
* @param {number} aspect aspect ratio of the crop
*/
export function getCropSize(imgWidth, imgHeight, aspect) {
if (imgWidth >= imgHeight * aspect) {
return {
width: imgHeight * aspect,
height: imgHeight,
}
}
return {
width: imgWidth,
height: imgWidth / aspect,
}
}
/**
* Ensure a new image position stays in the crop area.
* @param {{x: number, y number}} position new x/y position requested for the image
* @param {{width: number, height: number}} imageSize width/height of the src image
* @param {{width: number, height: number}} cropSize width/height of the crop area
* @param {number} zoom zoom value
* @returns {{x: number, y number}}
*/
export function restrictPosition(position, imageSize, cropSize, zoom) {
return {
x: restrictPositionCoord(position.x, imageSize.width, cropSize.width, zoom),
y: restrictPositionCoord(position.y, imageSize.height, cropSize.height, zoom),
}
}
function restrictPositionCoord(position, imageSize, cropSize, zoom) {
const maxPosition = (imageSize * zoom) / 2 - cropSize / 2
return Math.min(maxPosition, Math.max(position, -maxPosition))
}
export function getDistanceBetweenPoints(pointA, pointB) {
return Math.sqrt(Math.pow(pointA.y - pointB.y, 2) + Math.pow(pointA.x - pointB.x, 2))
}
export function getRotationBetweenPoints(pointA, pointB) {
return (Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180) / Math.PI
}
/**
* Compute the output cropped area of the image in percentages and pixels.
* x/y are the top-left coordinates on the src image
* @param {{x: number, y number}} crop x/y position of the current center of the image
* @param {{width: number, height: number, naturalWidth: number, naturelHeight: number}} imageSize width/height of the src image (default is size on the screen, natural is the original size)
* @param {{width: number, height: number}} cropSize width/height of the crop area
* @param {number} aspect aspect value
* @param {number} zoom zoom value
* @param {boolean} restrictPosition whether we should limit or not the cropped area
*/
export function computeCroppedArea(crop, imgSize, cropSize, aspect, zoom, restrictPosition = true) {
const limitAreaFn = restrictPosition ? limitArea : noOp
const croppedAreaPercentages = {
x: limitAreaFn(
100,
(((imgSize.width - cropSize.width / zoom) / 2 - crop.x / zoom) / imgSize.width) * 100
),
y: limitAreaFn(
100,
(((imgSize.height - cropSize.height / zoom) / 2 - crop.y / zoom) / imgSize.height) * 100
),
width: limitAreaFn(100, ((cropSize.width / imgSize.width) * 100) / zoom),
height: limitAreaFn(100, ((cropSize.height / imgSize.height) * 100) / zoom),
}
// we compute the pixels size naively
const widthInPixels = limitAreaFn(
imgSize.naturalWidth,
(croppedAreaPercentages.width * imgSize.naturalWidth) / 100,
true
)
const heightInPixels = limitAreaFn(
imgSize.naturalHeight,
(croppedAreaPercentages.height * imgSize.naturalHeight) / 100,
true
)
const isImgWiderThanHigh = imgSize.naturalWidth >= imgSize.naturalHeight * aspect
// then we ensure the width and height exactly match the aspect (to avoid rounding approximations)
// if the image is wider than high, when zoom is 0, the crop height will be equals to iamge height
// thus we want to compute the width from the height and aspect for accuracy.
// Otherwise, we compute the height from width and aspect.
const sizePixels = isImgWiderThanHigh
? {
width: Math.round(heightInPixels * aspect),
height: heightInPixels,
}
: {
width: widthInPixels,
height: Math.round(widthInPixels / aspect),
}
const croppedAreaPixels = {
...sizePixels,
x: limitAreaFn(
imgSize.naturalWidth - sizePixels.width,
(croppedAreaPercentages.x * imgSize.naturalWidth) / 100,
true
),
y: limitAreaFn(
imgSize.naturalHeight - sizePixels.height,
(croppedAreaPercentages.y * imgSize.naturalHeight) / 100,
true
),
}
return { croppedAreaPercentages, croppedAreaPixels }
}
/**
* Ensure the returned value is between 0 and max
* @param {number} max
* @param {number} value
* @param {boolean} shouldRound
*/
function limitArea(max, value, shouldRound = false) {
const v = shouldRound ? Math.round(value) : value
return Math.min(max, Math.max(0, v))
}
function noOp(max, value) {
return value
}
/**
* Compute the crop and zoom from the croppedAreaPixels
* @param {{x: number, y: number, width: number, height: number}} croppedAreaPixels
* @param {{width: number, height: number, naturalWidth: number, naturelHeight: number}} imageSize width/height of the src image (default is size on the screen, natural is the original size)
*/
export function getInitialCropFromCroppedAreaPixels(croppedAreaPixels, imageSize) {
const aspect = croppedAreaPixels.width / croppedAreaPixels.height
const imageZoom = imageSize.width / imageSize.naturalWidth
const isHeightMaxSize = imageSize.naturalWidth >= imageSize.naturalHeight * aspect
const zoom = isHeightMaxSize
? imageSize.naturalHeight / croppedAreaPixels.height
: imageSize.naturalWidth / croppedAreaPixels.width
const cropZoom = imageZoom * zoom
const crop = {
x: ((imageSize.naturalWidth - croppedAreaPixels.width) / 2 - croppedAreaPixels.x) * cropZoom,
y: ((imageSize.naturalHeight - croppedAreaPixels.height) / 2 - croppedAreaPixels.y) * cropZoom,
}
return { crop, zoom }
}
/**
* Return the point that is the center of point a and b
* @param {{x: number, y: number}} a
* @param {{x: number, y: number}} b
*/
export function getCenter(a, b) {
return {
x: (b.x + a.x) / 2,
y: (b.y + a.y) / 2,
}
}