@octanejs/image-crop 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +33 -0
- package/UPSTREAM.md +45 -0
- package/package.json +54 -0
- package/src/ReactCrop.css +1 -0
- package/src/ReactCrop.tsrx +918 -0
- package/src/addons/browserCrop.ts +84 -0
- package/src/index.ts +7 -0
- package/src/types.ts +20 -0
- package/src/utils.ts +347 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PixelCrop } from '../types'
|
|
2
|
+
|
|
3
|
+
const TO_RADIANS = Math.PI / 180
|
|
4
|
+
|
|
5
|
+
export async function cropToCanvas(
|
|
6
|
+
image: HTMLImageElement,
|
|
7
|
+
canvas: HTMLCanvasElement,
|
|
8
|
+
crop: PixelCrop,
|
|
9
|
+
scale = 1,
|
|
10
|
+
rotate = 0
|
|
11
|
+
) {
|
|
12
|
+
const ctx = canvas.getContext('2d')
|
|
13
|
+
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error('No 2d context')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const scaleX = image.naturalWidth / image.width
|
|
19
|
+
const scaleY = image.naturalHeight / image.height
|
|
20
|
+
// devicePixelRatio slightly increases sharpness on retina devices
|
|
21
|
+
// at the expense of slightly slower render times and needing to
|
|
22
|
+
// size the image back down if you want to download/upload and be
|
|
23
|
+
// true to the images natural size.
|
|
24
|
+
const pixelRatio = window.devicePixelRatio
|
|
25
|
+
// const pixelRatio = 1
|
|
26
|
+
|
|
27
|
+
canvas.width = Math.floor(crop.width * scaleX * pixelRatio)
|
|
28
|
+
canvas.height = Math.floor(crop.height * scaleY * pixelRatio)
|
|
29
|
+
|
|
30
|
+
ctx.scale(pixelRatio, pixelRatio)
|
|
31
|
+
ctx.imageSmoothingQuality = 'high'
|
|
32
|
+
|
|
33
|
+
const cropX = crop.x * scaleX
|
|
34
|
+
const cropY = crop.y * scaleY
|
|
35
|
+
|
|
36
|
+
const rotateRads = rotate * TO_RADIANS
|
|
37
|
+
const centerX = image.naturalWidth / 2
|
|
38
|
+
const centerY = image.naturalHeight / 2
|
|
39
|
+
|
|
40
|
+
ctx.save()
|
|
41
|
+
|
|
42
|
+
// 5) Move the crop origin to the canvas origin (0,0)
|
|
43
|
+
ctx.translate(-cropX, -cropY)
|
|
44
|
+
// 4) Move the origin to the center of the original position
|
|
45
|
+
ctx.translate(centerX, centerY)
|
|
46
|
+
// 3) Rotate around the origin
|
|
47
|
+
ctx.rotate(rotateRads)
|
|
48
|
+
// 2) Scale the image
|
|
49
|
+
ctx.scale(scale, scale)
|
|
50
|
+
// 1) Move the center of the image to the origin (0,0)
|
|
51
|
+
ctx.translate(-centerX, -centerY)
|
|
52
|
+
ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, 0, image.naturalWidth, image.naturalHeight)
|
|
53
|
+
|
|
54
|
+
ctx.restore()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toBlob(canvas: HTMLCanvasElement): Promise<Blob | null> {
|
|
58
|
+
return new Promise(resolve => {
|
|
59
|
+
canvas.toBlob(resolve)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let previewUrl = ''
|
|
64
|
+
|
|
65
|
+
// Returns an image source you should set to state and pass
|
|
66
|
+
// `{previewSrc && <img alt="Crop preview" src={previewSrc} />}`
|
|
67
|
+
export async function cropToImg(image: HTMLImageElement, crop: PixelCrop, scale = 1, rotate = 0) {
|
|
68
|
+
const canvas = document.createElement('canvas')
|
|
69
|
+
cropToCanvas(image, canvas, crop, scale, rotate)
|
|
70
|
+
|
|
71
|
+
const blob = await toBlob(canvas)
|
|
72
|
+
|
|
73
|
+
if (!blob) {
|
|
74
|
+
console.error('Failed to create blob')
|
|
75
|
+
return ''
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (previewUrl) {
|
|
79
|
+
URL.revokeObjectURL(previewUrl)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
previewUrl = URL.createObjectURL(blob)
|
|
83
|
+
return previewUrl
|
|
84
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type XOrds = 'e' | 'w'
|
|
2
|
+
export type YOrds = 'n' | 's'
|
|
3
|
+
export type XYOrds = 'nw' | 'ne' | 'se' | 'sw'
|
|
4
|
+
export type Ords = XOrds | YOrds | XYOrds
|
|
5
|
+
|
|
6
|
+
export interface Crop {
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
unit: 'px' | '%'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PixelCrop extends Crop {
|
|
15
|
+
unit: 'px'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PercentCrop extends Crop {
|
|
19
|
+
unit: '%'
|
|
20
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { PixelCrop, PercentCrop, Crop, Ords } from './types'
|
|
2
|
+
|
|
3
|
+
export const defaultCrop: PixelCrop = {
|
|
4
|
+
x: 0,
|
|
5
|
+
y: 0,
|
|
6
|
+
width: 0,
|
|
7
|
+
height: 0,
|
|
8
|
+
unit: 'px',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max)
|
|
12
|
+
|
|
13
|
+
export const cls = (...args: unknown[]) => args.filter(v => v && typeof v === 'string').join(' ')
|
|
14
|
+
|
|
15
|
+
export const areCropsEqual = (cropA: Partial<Crop>, cropB: Partial<Crop>) =>
|
|
16
|
+
cropA === cropB ||
|
|
17
|
+
(cropA.width === cropB.width &&
|
|
18
|
+
cropA.height === cropB.height &&
|
|
19
|
+
cropA.x === cropB.x &&
|
|
20
|
+
cropA.y === cropB.y &&
|
|
21
|
+
cropA.unit === cropB.unit)
|
|
22
|
+
|
|
23
|
+
export function makeAspectCrop(
|
|
24
|
+
crop: Pick<PercentCrop, 'unit'> & Partial<Omit<PercentCrop, 'unit'>>,
|
|
25
|
+
aspect: number,
|
|
26
|
+
containerWidth: number,
|
|
27
|
+
containerHeight: number
|
|
28
|
+
): PercentCrop
|
|
29
|
+
export function makeAspectCrop(
|
|
30
|
+
crop: Pick<PixelCrop, 'unit'> & Partial<Omit<PixelCrop, 'unit'>>,
|
|
31
|
+
aspect: number,
|
|
32
|
+
containerWidth: number,
|
|
33
|
+
containerHeight: number
|
|
34
|
+
): PixelCrop
|
|
35
|
+
export function makeAspectCrop(crop: Partial<Crop>, aspect: number, containerWidth: number, containerHeight: number) {
|
|
36
|
+
const pixelCrop = convertToPixelCrop(crop, containerWidth, containerHeight)
|
|
37
|
+
|
|
38
|
+
if (crop.width) {
|
|
39
|
+
pixelCrop.height = pixelCrop.width / aspect
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (crop.height) {
|
|
43
|
+
pixelCrop.width = pixelCrop.height * aspect
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (pixelCrop.y + pixelCrop.height > containerHeight) {
|
|
47
|
+
pixelCrop.height = containerHeight - pixelCrop.y
|
|
48
|
+
pixelCrop.width = pixelCrop.height * aspect
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (pixelCrop.x + pixelCrop.width > containerWidth) {
|
|
52
|
+
pixelCrop.width = containerWidth - pixelCrop.x
|
|
53
|
+
pixelCrop.height = pixelCrop.width / aspect
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (crop.unit === '%') {
|
|
57
|
+
return convertToPercentCrop(pixelCrop, containerWidth, containerHeight)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return pixelCrop
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function centerCrop(
|
|
64
|
+
crop: Pick<PercentCrop, 'unit'> & Partial<Omit<PercentCrop, 'unit'>>,
|
|
65
|
+
containerWidth: number,
|
|
66
|
+
containerHeight: number
|
|
67
|
+
): PercentCrop
|
|
68
|
+
export function centerCrop(
|
|
69
|
+
crop: Pick<PixelCrop, 'unit'> & Partial<Omit<PixelCrop, 'unit'>>,
|
|
70
|
+
containerWidth: number,
|
|
71
|
+
containerHeight: number
|
|
72
|
+
): PixelCrop
|
|
73
|
+
export function centerCrop(crop: Partial<Crop>, containerWidth: number, containerHeight: number) {
|
|
74
|
+
const pixelCrop = convertToPixelCrop(crop, containerWidth, containerHeight)
|
|
75
|
+
|
|
76
|
+
pixelCrop.x = (containerWidth - pixelCrop.width) / 2
|
|
77
|
+
pixelCrop.y = (containerHeight - pixelCrop.height) / 2
|
|
78
|
+
|
|
79
|
+
if (crop.unit === '%') {
|
|
80
|
+
return convertToPercentCrop(pixelCrop, containerWidth, containerHeight)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return pixelCrop
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function convertToPercentCrop(
|
|
87
|
+
crop: Partial<Crop>,
|
|
88
|
+
containerWidth: number,
|
|
89
|
+
containerHeight: number
|
|
90
|
+
): PercentCrop {
|
|
91
|
+
if (crop.unit === '%') {
|
|
92
|
+
return { ...defaultCrop, ...crop, unit: '%' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
unit: '%',
|
|
97
|
+
x: crop.x ? (crop.x / containerWidth) * 100 : 0,
|
|
98
|
+
y: crop.y ? (crop.y / containerHeight) * 100 : 0,
|
|
99
|
+
width: crop.width ? (crop.width / containerWidth) * 100 : 0,
|
|
100
|
+
height: crop.height ? (crop.height / containerHeight) * 100 : 0,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function convertToPixelCrop(crop: Partial<Crop>, containerWidth: number, containerHeight: number): PixelCrop {
|
|
105
|
+
if (!crop.unit) {
|
|
106
|
+
return { ...defaultCrop, ...crop, unit: 'px' }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (crop.unit === 'px') {
|
|
110
|
+
return { ...defaultCrop, ...crop, unit: 'px' }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
unit: 'px',
|
|
115
|
+
x: crop.x ? (crop.x * containerWidth) / 100 : 0,
|
|
116
|
+
y: crop.y ? (crop.y * containerHeight) / 100 : 0,
|
|
117
|
+
width: crop.width ? (crop.width * containerWidth) / 100 : 0,
|
|
118
|
+
height: crop.height ? (crop.height * containerHeight) / 100 : 0,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Sorry.
|
|
123
|
+
export function containCrop(
|
|
124
|
+
pixelCrop: PixelCrop,
|
|
125
|
+
aspect: number,
|
|
126
|
+
ord: Ords,
|
|
127
|
+
containerWidth: number,
|
|
128
|
+
containerHeight: number,
|
|
129
|
+
minWidth = 0,
|
|
130
|
+
minHeight = 0,
|
|
131
|
+
maxWidth = containerWidth,
|
|
132
|
+
maxHeight = containerHeight
|
|
133
|
+
) {
|
|
134
|
+
const containedCrop = { ...pixelCrop }
|
|
135
|
+
let _minWidth = Math.min(minWidth, containerWidth)
|
|
136
|
+
let _minHeight = Math.min(minHeight, containerHeight)
|
|
137
|
+
let _maxWidth = Math.min(maxWidth, containerWidth)
|
|
138
|
+
let _maxHeight = Math.min(maxHeight, containerHeight)
|
|
139
|
+
|
|
140
|
+
if (aspect) {
|
|
141
|
+
if (aspect > 1) {
|
|
142
|
+
// Landscape - increase width min + max.
|
|
143
|
+
_minWidth = minHeight ? minHeight * aspect : _minWidth
|
|
144
|
+
_minHeight = _minWidth / aspect
|
|
145
|
+
_maxWidth = maxWidth * aspect
|
|
146
|
+
} else {
|
|
147
|
+
// Portrait - increase height min + max.
|
|
148
|
+
_minHeight = minWidth ? minWidth / aspect : _minHeight
|
|
149
|
+
_minWidth = _minHeight * aspect
|
|
150
|
+
_maxHeight = maxHeight / aspect
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Stop underflow on top.
|
|
155
|
+
if (containedCrop.y < 0) {
|
|
156
|
+
containedCrop.height = Math.max(containedCrop.height + containedCrop.y, _minHeight)
|
|
157
|
+
containedCrop.y = 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Stop underflow on left.
|
|
161
|
+
if (containedCrop.x < 0) {
|
|
162
|
+
containedCrop.width = Math.max(containedCrop.width + containedCrop.x, _minWidth)
|
|
163
|
+
containedCrop.x = 0
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Stop overflow on right.
|
|
167
|
+
const xOverflow = containerWidth - (containedCrop.x + containedCrop.width)
|
|
168
|
+
if (xOverflow < 0) {
|
|
169
|
+
containedCrop.x = Math.min(containedCrop.x, containerWidth - _minWidth)
|
|
170
|
+
containedCrop.width += xOverflow
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Stop overflow on bottom.
|
|
174
|
+
const yOverflow = containerHeight - (containedCrop.y + containedCrop.height)
|
|
175
|
+
if (yOverflow < 0) {
|
|
176
|
+
containedCrop.y = Math.min(containedCrop.y, containerHeight - _minHeight)
|
|
177
|
+
containedCrop.height += yOverflow
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Make crop respect min width generally.
|
|
181
|
+
if (containedCrop.width < _minWidth) {
|
|
182
|
+
if (ord === 'sw' || ord == 'nw') {
|
|
183
|
+
// Stops box moving when min is hit.
|
|
184
|
+
containedCrop.x -= _minWidth - containedCrop.width
|
|
185
|
+
}
|
|
186
|
+
containedCrop.width = _minWidth
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Make crop respect min height generally.
|
|
190
|
+
if (containedCrop.height < _minHeight) {
|
|
191
|
+
if (ord === 'nw' || ord == 'ne') {
|
|
192
|
+
// Stops box moving when min is hit.
|
|
193
|
+
containedCrop.y -= _minHeight - containedCrop.height
|
|
194
|
+
}
|
|
195
|
+
containedCrop.height = _minHeight
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Make crop respect max width generally.
|
|
199
|
+
if (containedCrop.width > _maxWidth) {
|
|
200
|
+
if (ord === 'sw' || ord == 'nw') {
|
|
201
|
+
// Stops box moving when max is hit.
|
|
202
|
+
containedCrop.x -= _maxWidth - containedCrop.width
|
|
203
|
+
}
|
|
204
|
+
containedCrop.width = _maxWidth
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Make crop respect max height generally.
|
|
208
|
+
if (containedCrop.height > _maxHeight) {
|
|
209
|
+
if (ord === 'nw' || ord == 'ne') {
|
|
210
|
+
// Stops box moving when min is hit.
|
|
211
|
+
containedCrop.y -= _maxHeight - containedCrop.height
|
|
212
|
+
}
|
|
213
|
+
containedCrop.height = _maxHeight
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Maintain aspect after size fixing.
|
|
217
|
+
if (aspect) {
|
|
218
|
+
const currAspect = containedCrop.width / containedCrop.height
|
|
219
|
+
if (currAspect < aspect) {
|
|
220
|
+
// Crop is shrunk on the width so adjust the height.
|
|
221
|
+
const newHeight = Math.max(containedCrop.width / aspect, _minHeight)
|
|
222
|
+
|
|
223
|
+
if (ord === 'nw' || ord == 'ne') {
|
|
224
|
+
// Stops box moving when min is hit.
|
|
225
|
+
containedCrop.y -= newHeight - containedCrop.height
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
containedCrop.height = newHeight
|
|
229
|
+
} else if (currAspect > aspect) {
|
|
230
|
+
// Crop is shrunk on the height so adjust the width.
|
|
231
|
+
const newWidth = Math.max(containedCrop.height * aspect, _minWidth)
|
|
232
|
+
|
|
233
|
+
if (ord === 'sw' || ord == 'nw') {
|
|
234
|
+
// Stops box moving when max is hit.
|
|
235
|
+
containedCrop.x -= newWidth - containedCrop.width
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
containedCrop.width = newWidth
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return containedCrop
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function nudgeCrop(pixelCrop: PixelCrop, key: string, offset: number, ord: Ords) {
|
|
246
|
+
const nextCrop = { ...pixelCrop }
|
|
247
|
+
|
|
248
|
+
if (key === 'ArrowLeft') {
|
|
249
|
+
if (ord === 'nw') {
|
|
250
|
+
nextCrop.x -= offset
|
|
251
|
+
nextCrop.y -= offset
|
|
252
|
+
nextCrop.width += offset
|
|
253
|
+
nextCrop.height += offset
|
|
254
|
+
} else if (ord === 'w') {
|
|
255
|
+
nextCrop.x -= offset
|
|
256
|
+
nextCrop.width += offset
|
|
257
|
+
} else if (ord === 'sw') {
|
|
258
|
+
nextCrop.x -= offset
|
|
259
|
+
nextCrop.width += offset
|
|
260
|
+
nextCrop.height += offset
|
|
261
|
+
} else if (ord === 'ne') {
|
|
262
|
+
nextCrop.y += offset
|
|
263
|
+
nextCrop.width -= offset
|
|
264
|
+
nextCrop.height -= offset
|
|
265
|
+
} else if (ord === 'e') {
|
|
266
|
+
nextCrop.width -= offset
|
|
267
|
+
} else if (ord === 'se') {
|
|
268
|
+
nextCrop.width -= offset
|
|
269
|
+
nextCrop.height -= offset
|
|
270
|
+
}
|
|
271
|
+
} else if (key === 'ArrowRight') {
|
|
272
|
+
if (ord === 'nw') {
|
|
273
|
+
nextCrop.x += offset
|
|
274
|
+
nextCrop.y += offset
|
|
275
|
+
nextCrop.width -= offset
|
|
276
|
+
nextCrop.height -= offset
|
|
277
|
+
} else if (ord === 'w') {
|
|
278
|
+
// Niche: Will move right if minWidth hit.
|
|
279
|
+
nextCrop.x += offset
|
|
280
|
+
nextCrop.width -= offset
|
|
281
|
+
} else if (ord === 'sw') {
|
|
282
|
+
nextCrop.x += offset
|
|
283
|
+
nextCrop.width -= offset
|
|
284
|
+
nextCrop.height -= offset
|
|
285
|
+
} else if (ord === 'ne') {
|
|
286
|
+
nextCrop.y -= offset
|
|
287
|
+
nextCrop.width += offset
|
|
288
|
+
nextCrop.height += offset
|
|
289
|
+
} else if (ord === 'e') {
|
|
290
|
+
nextCrop.width += offset
|
|
291
|
+
} else if (ord === 'se') {
|
|
292
|
+
nextCrop.width += offset
|
|
293
|
+
nextCrop.height += offset
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (key === 'ArrowUp') {
|
|
298
|
+
if (ord === 'nw') {
|
|
299
|
+
nextCrop.x -= offset
|
|
300
|
+
nextCrop.y -= offset
|
|
301
|
+
nextCrop.width += offset
|
|
302
|
+
nextCrop.height += offset
|
|
303
|
+
} else if (ord === 'n') {
|
|
304
|
+
nextCrop.y -= offset
|
|
305
|
+
nextCrop.height += offset
|
|
306
|
+
} else if (ord === 'ne') {
|
|
307
|
+
nextCrop.y -= offset
|
|
308
|
+
nextCrop.width += offset
|
|
309
|
+
nextCrop.height += offset
|
|
310
|
+
} else if (ord === 'sw') {
|
|
311
|
+
nextCrop.x += offset
|
|
312
|
+
nextCrop.width -= offset
|
|
313
|
+
nextCrop.height -= offset
|
|
314
|
+
} else if (ord === 's') {
|
|
315
|
+
nextCrop.height -= offset
|
|
316
|
+
} else if (ord === 'se') {
|
|
317
|
+
nextCrop.width -= offset
|
|
318
|
+
nextCrop.height -= offset
|
|
319
|
+
}
|
|
320
|
+
} else if (key === 'ArrowDown') {
|
|
321
|
+
if (ord === 'nw') {
|
|
322
|
+
nextCrop.x += offset
|
|
323
|
+
nextCrop.y += offset
|
|
324
|
+
nextCrop.width -= offset
|
|
325
|
+
nextCrop.height -= offset
|
|
326
|
+
} else if (ord === 'n') {
|
|
327
|
+
// Niche: Will move down if minHeight hit.
|
|
328
|
+
nextCrop.y += offset
|
|
329
|
+
nextCrop.height -= offset
|
|
330
|
+
} else if (ord === 'ne') {
|
|
331
|
+
nextCrop.y += offset
|
|
332
|
+
nextCrop.width -= offset
|
|
333
|
+
nextCrop.height -= offset
|
|
334
|
+
} else if (ord === 'sw') {
|
|
335
|
+
nextCrop.x -= offset
|
|
336
|
+
nextCrop.width += offset
|
|
337
|
+
nextCrop.height += offset
|
|
338
|
+
} else if (ord === 's') {
|
|
339
|
+
nextCrop.height += offset
|
|
340
|
+
} else if (ord === 'se') {
|
|
341
|
+
nextCrop.width += offset
|
|
342
|
+
nextCrop.height += offset
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return nextCrop
|
|
347
|
+
}
|