@@ -29,8 +29,8 @@ function inSelection(selection: Selection, maxSelection: Selection): boolean {
29
29
return (
30
30
selection . x >= maxSelection . x &&
31
31
selection . y >= maxSelection . y &&
32
- selection . x + selection . width <= maxSelection . x + maxSelection . width &&
33
- selection . y + selection . height <= maxSelection . y + maxSelection . height
32
+ Math . ceil ( selection . x + selection . width ) <= Math . ceil ( maxSelection . x + maxSelection . width ) &&
33
+ Math . ceil ( selection . y + selection . height ) <= Math . ceil ( maxSelection . y + maxSelection . height )
34
34
) ;
35
35
}
36
36
@@ -85,7 +85,7 @@ abstract class ImageCropper {
85
85
86
86
return new Promise < File > ( ( resolve , reject ) => {
87
87
this . dialog ! . addEventListener ( "primary" , ( ) => {
88
- this . cropperSelection ! . $toCanvas ( )
88
+ void this . getCanvas ( )
89
89
. then ( ( canvas ) => {
90
90
this . resizer
91
91
. saveFile (
@@ -107,6 +107,10 @@ abstract class ImageCropper {
107
107
} ) ;
108
108
}
109
109
110
+ protected getCanvas ( ) : Promise < HTMLCanvasElement > {
111
+ return this . cropperSelection ! . $toCanvas ( ) ;
112
+ }
113
+
110
114
public async loadImage ( ) {
111
115
const { image, exif } = await this . resizer . loadFile ( this . file ) ;
112
116
this . image = image ;
@@ -138,11 +142,9 @@ abstract class ImageCropper {
138
142
this . cropperCanvas ! . style . aspectRatio = `${ this . width } /${ this . height } ` ;
139
143
140
144
if ( this . width >= this . height ) {
141
- this . cropperCanvas ! . style . width = `min(70vw, ${ this . width } px)` ;
142
- this . cropperCanvas ! . style . height = "auto" ;
145
+ this . cropperCanvas ! . style . maxHeight = "100%" ;
143
146
} else {
144
- this . cropperCanvas ! . style . height = `min(60vh, ${ this . height } px)` ;
145
- this . cropperCanvas ! . style . width = "auto" ;
147
+ this . cropperCanvas ! . style . maxWidth = "100%" ;
146
148
}
147
149
148
150
this . cropperSelection ! . aspectRatio = this . configuration . aspectRatio ;
@@ -171,12 +173,11 @@ abstract class ImageCropper {
171
173
const cropperCanvasRect = this . cropperCanvas ! . getBoundingClientRect ( ) ;
172
174
const selection = event . detail as Selection ;
173
175
174
- const cropperImageRect = this . cropperImage ! . getBoundingClientRect ( ) ;
175
176
const maxSelection : Selection = {
176
- x : Math . round ( cropperImageRect . left - cropperCanvasRect . left ) ,
177
- y : Math . round ( cropperImageRect . top - cropperCanvasRect . top ) ,
178
- width : Math . round ( cropperImageRect . width ) ,
179
- height : Math . round ( cropperImageRect . height ) ,
177
+ x : 0 ,
178
+ y : 0 ,
179
+ width : cropperCanvasRect . width ,
180
+ height : cropperCanvasRect . height ,
180
181
} ;
181
182
182
183
if ( ! inSelection ( selection , maxSelection ) ) {
@@ -247,17 +248,15 @@ class ExactImageCropper extends ImageCropper {
247
248
}
248
249
249
250
protected getCropperTemplate ( ) : string {
250
- return `<div class="cropperContainer">
251
- <cropper-canvas background>
252
- <cropper-image rotatable></cropper-image>
253
- <cropper-shade hidden></cropper-shade>
254
- <cropper-selection movable outlined keyboard>
255
- <cropper-grid role="grid" bordered covered></cropper-grid>
256
- <cropper-crosshair centered></cropper-crosshair>
257
- <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
258
- </cropper-selection>
259
- </cropper-canvas>
260
- </div>` ;
251
+ return `<cropper-canvas background>
252
+ <cropper-image rotatable></cropper-image>
253
+ <cropper-shade hidden></cropper-shade>
254
+ <cropper-selection movable outlined keyboard>
255
+ <cropper-grid role="grid" bordered covered></cropper-grid>
256
+ <cropper-crosshair centered></cropper-crosshair>
257
+ <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
258
+ </cropper-selection>
259
+ </cropper-canvas>` ;
261
260
}
262
261
263
262
protected setCropperStyle ( ) {
@@ -273,6 +272,7 @@ class ExactImageCropper extends ImageCropper {
273
272
}
274
273
275
274
class MinMaxImageCropper extends ImageCropper {
275
+ #cropperCanvasRect?: DOMRect ;
276
276
constructor ( element : WoltlabCoreFileUploadElement , file : File , configuration : CropperConfiguration ) {
277
277
super ( element , file , configuration ) ;
278
278
if ( configuration . sizes . length !== 2 ) {
@@ -292,39 +292,40 @@ class MinMaxImageCropper extends ImageCropper {
292
292
return getPhrase ( "wcf.global.button.reset" ) ;
293
293
}
294
294
295
- protected getCropperTemplate ( ) : string {
296
- return `<div class="cropperContainer">
297
- <cropper-canvas background scale-step="0.0">
298
- <cropper-image skewable scalable translatable rotatable></cropper-image>
299
- <cropper-shade hidden></cropper-shade>
300
- <cropper-handle action="scale" hidden disabled></cropper-handle>
301
- <cropper-selection movable resizable outlined>
302
- <cropper-grid role="grid" bordered covered></cropper-grid>
303
- <cropper-crosshair centered></cropper-crosshair>
304
- <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
305
- <cropper-handle action="n-resize"></cropper-handle>
306
- <cropper-handle action="e-resize"></cropper-handle>
307
- <cropper-handle action="s-resize"></cropper-handle>
308
- <cropper-handle action="w-resize"></cropper-handle>
309
- <cropper-handle action="ne-resize"></cropper-handle>
310
- <cropper-handle action="nw-resize"></cropper-handle>
311
- <cropper-handle action="se-resize"></cropper-handle>
312
- <cropper-handle action="sw-resize"></cropper-handle>
313
- </cropper-selection>
314
- </cropper-canvas>
315
- </div>` ;
316
- }
317
-
318
- protected setCropperStyle ( ) {
319
- super . setCropperStyle ( ) ;
295
+ public async loadImage ( ) : Promise < void > {
296
+ await super . loadImage ( ) ;
320
297
321
- if ( this . width >= this . height ) {
322
- this . cropperCanvas ! . style . width = `${ Math . min ( this . maxSize . width , this . width ) } px` ;
323
- } else {
324
- this . cropperCanvas ! . style . height = `${ Math . min ( this . maxSize . height , this . height ) } px` ;
298
+ if ( this . image ! . width < this . minSize . width || this . image ! . height < this . minSize . height ) {
299
+ throw new Error (
300
+ getPhrase ( "wcf.upload.error.image.tooSmall" , {
301
+ width : this . minSize . width ,
302
+ height : this . minSize . height ,
303
+ } ) ,
304
+ ) ;
325
305
}
326
306
}
327
307
308
+ protected getCropperTemplate ( ) : string {
309
+ return `<cropper-canvas background scale-step="0.0">
310
+ <cropper-image skewable scalable translatable rotatable></cropper-image>
311
+ <cropper-shade hidden></cropper-shade>
312
+ <cropper-handle action="scale" hidden disabled></cropper-handle>
313
+ <cropper-selection precise movable resizable outlined>
314
+ <cropper-grid role="grid" bordered covered></cropper-grid>
315
+ <cropper-crosshair centered></cropper-crosshair>
316
+ <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
317
+ <cropper-handle action="n-resize"></cropper-handle>
318
+ <cropper-handle action="e-resize"></cropper-handle>
319
+ <cropper-handle action="s-resize"></cropper-handle>
320
+ <cropper-handle action="w-resize"></cropper-handle>
321
+ <cropper-handle action="ne-resize"></cropper-handle>
322
+ <cropper-handle action="nw-resize"></cropper-handle>
323
+ <cropper-handle action="se-resize"></cropper-handle>
324
+ <cropper-handle action="sw-resize"></cropper-handle>
325
+ </cropper-selection>
326
+ </cropper-canvas>` ;
327
+ }
328
+
328
329
protected createCropper ( ) {
329
330
super . createCropper ( ) ;
330
331
@@ -335,31 +336,46 @@ class MinMaxImageCropper extends ImageCropper {
335
336
// Limit the selection to the min/max size
336
337
this . cropperSelection ! . addEventListener ( "change" , ( event : CustomEvent ) => {
337
338
const selection = event . detail as Selection ;
339
+ this . #cropperCanvasRect = this . cropperCanvas ! . getBoundingClientRect ( ) ;
340
+
341
+ const maxImageWidth = Math . min ( this . image ! . width , this . maxSize . width ) ;
342
+ const widthRatio = this . #cropperCanvasRect. width / maxImageWidth ;
343
+
344
+ const minWidth = this . minSize . width * widthRatio ;
345
+ const maxWidth = this . maxSize . width * widthRatio ;
346
+ const minHeight = minWidth / this . configuration . aspectRatio ;
347
+ const maxHeight = maxWidth / this . configuration . aspectRatio ;
338
348
339
349
if (
340
- selection . width < this . minSize . width ||
341
- selection . height < this . minSize . height ||
342
- selection . width > this . maxSize . width ||
343
- selection . height > this . maxSize . height
350
+ selection . width < minWidth ||
351
+ selection . height < minHeight ||
352
+ selection . width > maxWidth ||
353
+ selection . height > maxHeight
344
354
) {
345
355
event . preventDefault ( ) ;
346
356
}
347
357
} ) ;
348
358
}
349
359
360
+ protected getCanvas ( ) : Promise < HTMLCanvasElement > {
361
+ // Calculate the size of the image in relation to the window size
362
+ const maxImageWidth = Math . min ( this . image ! . width , this . maxSize . width ) ;
363
+ const widthRatio = this . #cropperCanvasRect! . width / maxImageWidth ;
364
+ const width = this . cropperSelection ! . width / widthRatio ;
365
+ const height = width / this . configuration . aspectRatio ;
366
+
367
+ return this . cropperSelection ! . $toCanvas ( {
368
+ width : Math . max ( Math . min ( Math . ceil ( width ) , this . maxSize . width ) , this . minSize . width ) ,
369
+ height : Math . max ( Math . min ( Math . ceil ( height ) , this . maxSize . height ) , this . minSize . height ) ,
370
+ } ) ;
371
+ }
372
+
350
373
protected centerSelection ( ) : void {
351
374
this . cropperImage ! . $center ( "contain" ) ;
352
375
353
376
const { width : imageWidth } = this . cropperImage ! . getBoundingClientRect ( ) ;
354
377
355
- this . cropperSelection ! . $change (
356
- 0 ,
357
- 0 ,
358
- imageWidth ,
359
- 0 ,
360
- this . configuration . aspectRatio ,
361
- true ,
362
- ) ;
378
+ this . cropperSelection ! . $change ( 0 , 0 , imageWidth , 0 , this . configuration . aspectRatio , true ) ;
363
379
this . cropperSelection ! . $center ( ) ;
364
380
this . cropperSelection ! . scrollIntoView ( { block : "center" , inline : "center" } ) ;
365
381
}
0 commit comments