diff --git a/ts/WoltLabSuite/Core/Component/Image/Cropper.ts b/ts/WoltLabSuite/Core/Component/Image/Cropper.ts index 9f3a22de7ae..9d9b942ce3e 100644 --- a/ts/WoltLabSuite/Core/Component/Image/Cropper.ts +++ b/ts/WoltLabSuite/Core/Component/Image/Cropper.ts @@ -28,10 +28,10 @@ export interface CropperConfiguration { function inSelection(selection: Selection, maxSelection: Selection): boolean { return ( - Math.ceil(selection.x) >= maxSelection.x && - Math.ceil(selection.y) >= maxSelection.y && - Math.ceil(selection.x + selection.width) <= Math.ceil(maxSelection.x + maxSelection.width) && - Math.ceil(selection.y + selection.height) <= Math.ceil(maxSelection.y + maxSelection.height) + Math.round(selection.x) >= maxSelection.x && + Math.round(selection.y) >= maxSelection.y && + Math.round(selection.x + selection.width) <= Math.round(maxSelection.x + maxSelection.width) && + Math.round(selection.y + selection.height) <= Math.round(maxSelection.y + maxSelection.height) ); } @@ -355,18 +355,22 @@ class MinMaxImageCropper extends ImageCropper { this.#cropperCanvasRect = this.cropperCanvas!.getBoundingClientRect(); const maxImageWidth = Math.min(this.image!.width, this.maxSize.width); - const widthRatio = this.#cropperCanvasRect.width / maxImageWidth; + const maxImageHeight = Math.min(this.image!.height, this.maxSize.height); + const selectionRatio = Math.min( + this.#cropperCanvasRect.width / maxImageWidth, + this.#cropperCanvasRect.height / maxImageHeight, + ); - const minWidth = this.minSize.width * widthRatio; - const maxWidth = this.maxSize.width * widthRatio; + const minWidth = this.minSize.width * selectionRatio; + const maxWidth = this.maxSize.width * selectionRatio; const minHeight = minWidth / this.configuration.aspectRatio; const maxHeight = maxWidth / this.configuration.aspectRatio; if ( - selection.width < minWidth || - selection.height < minHeight || - selection.width > maxWidth || - selection.height > maxHeight + Math.round(selection.width) < minWidth || + Math.round(selection.height) < minHeight || + Math.round(selection.width) > maxWidth || + Math.round(selection.height) > maxHeight ) { event.preventDefault(); } @@ -387,20 +391,33 @@ class MinMaxImageCropper extends ImageCropper { } protected centerSelection(): void { - // Reset to get the maximum available height + // Reset to get the maximum available height and width this.cropperCanvas!.style.height = ""; + this.cropperCanvas!.style.width = ""; + + const dimension = DomUtil.innerDimensions(this.cropperCanvas!.parentElement!); + const ratio = Math.min(dimension.width / this.image!.width, dimension.height / this.image!.height); - const dimensions = DomUtil.outerDimensions(this.cropperCanvas!.parentElement!); - this.cropperCanvas!.style.height = `${dimensions.height}px`; + this.cropperCanvas!.style.height = `${this.image!.height * ratio}px`; + this.cropperCanvas!.style.width = `${this.image!.width * ratio}px`; this.cropperImage!.$center("contain"); this.#cropperCanvasRect = this.cropperImage!.getBoundingClientRect(); - if (this.configuration.aspectRatio >= 1.0) { - this.cropperSelection!.$change(0, 0, this.#cropperCanvasRect.width, 0, this.configuration.aspectRatio, true); - } else { - this.cropperSelection!.$change(0, 0, 0, this.#cropperCanvasRect.height, this.configuration.aspectRatio, true); - } + const selectionRatio = Math.min( + this.#cropperCanvasRect.width / this.maxSize.width, + this.#cropperCanvasRect.height / this.maxSize.height, + ); + + this.cropperSelection!.$change( + 0, + 0, + this.maxSize.width * selectionRatio, + this.maxSize.height * selectionRatio, + this.configuration.aspectRatio, + true, + ); + this.cropperSelection!.$center(); this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" }); } diff --git a/ts/WoltLabSuite/Core/Dom/Util.ts b/ts/WoltLabSuite/Core/Dom/Util.ts index a3469471005..66c3f53837e 100644 --- a/ts/WoltLabSuite/Core/Dom/Util.ts +++ b/ts/WoltLabSuite/Core/Dom/Util.ts @@ -96,6 +96,42 @@ const DomUtil = { return id; }, + /** + * Returns the inner height of an element including paddings. + */ + innerHeight(element: HTMLElement, styles?: CSSStyleDeclaration): number { + styles = styles || window.getComputedStyle(element); + + let height = element.clientHeight; + height += ~~styles.paddingTop + ~~styles.paddingBottom; + + return height; + }, + + /** + * Returns the inner width of an element including paddings. + */ + innerWidth(element: HTMLElement, styles?: CSSStyleDeclaration): number { + styles = styles || window.getComputedStyle(element); + + let width = element.clientWidth; + width += ~~styles.paddingLeft + ~~styles.paddingRight; + + return width; + }, + + /** + * Returns the inner dimensions of an element including paddings. + */ + innerDimensions(element: HTMLElement): Dimensions { + const styles = window.getComputedStyle(element); + + return { + height: DomUtil.innerHeight(element, styles), + width: DomUtil.innerWidth(element, styles), + }; + }, + /** * Returns the outer height of an element including margins. */ diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js index 7631ca30838..60bd94b1b28 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js @@ -15,10 +15,10 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL exifreader_1 = tslib_1.__importDefault(exifreader_1); Util_1 = tslib_1.__importDefault(Util_1); function inSelection(selection, maxSelection) { - return (Math.ceil(selection.x) >= maxSelection.x && - Math.ceil(selection.y) >= maxSelection.y && - Math.ceil(selection.x + selection.width) <= Math.ceil(maxSelection.x + maxSelection.width) && - Math.ceil(selection.y + selection.height) <= Math.ceil(maxSelection.y + maxSelection.height)); + return (Math.round(selection.x) >= maxSelection.x && + Math.round(selection.y) >= maxSelection.y && + Math.round(selection.x + selection.width) <= Math.round(maxSelection.x + maxSelection.width) && + Math.round(selection.y + selection.height) <= Math.round(maxSelection.y + maxSelection.height)); } class ImageCropper { configuration; @@ -267,15 +267,16 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL const selection = event.detail; this.#cropperCanvasRect = this.cropperCanvas.getBoundingClientRect(); const maxImageWidth = Math.min(this.image.width, this.maxSize.width); - const widthRatio = this.#cropperCanvasRect.width / maxImageWidth; - const minWidth = this.minSize.width * widthRatio; - const maxWidth = this.maxSize.width * widthRatio; + const maxImageHeight = Math.min(this.image.height, this.maxSize.height); + const selectionRatio = Math.min(this.#cropperCanvasRect.width / maxImageWidth, this.#cropperCanvasRect.height / maxImageHeight); + const minWidth = this.minSize.width * selectionRatio; + const maxWidth = this.maxSize.width * selectionRatio; const minHeight = minWidth / this.configuration.aspectRatio; const maxHeight = maxWidth / this.configuration.aspectRatio; - if (selection.width < minWidth || - selection.height < minHeight || - selection.width > maxWidth || - selection.height > maxHeight) { + if (Math.round(selection.width) < minWidth || + Math.round(selection.height) < minHeight || + Math.round(selection.width) > maxWidth || + Math.round(selection.height) > maxHeight) { event.preventDefault(); } }); @@ -292,18 +293,17 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL }); } centerSelection() { - // Reset to get the maximum available height + // Reset to get the maximum available height and width this.cropperCanvas.style.height = ""; - const dimensions = Util_1.default.outerDimensions(this.cropperCanvas.parentElement); - this.cropperCanvas.style.height = `${dimensions.height}px`; + this.cropperCanvas.style.width = ""; + const dimension = Util_1.default.innerDimensions(this.cropperCanvas.parentElement); + const ratio = Math.min(dimension.width / this.image.width, dimension.height / this.image.height); + this.cropperCanvas.style.height = `${this.image.height * ratio}px`; + this.cropperCanvas.style.width = `${this.image.width * ratio}px`; this.cropperImage.$center("contain"); this.#cropperCanvasRect = this.cropperImage.getBoundingClientRect(); - if (this.configuration.aspectRatio >= 1.0) { - this.cropperSelection.$change(0, 0, this.#cropperCanvasRect.width, 0, this.configuration.aspectRatio, true); - } - else { - this.cropperSelection.$change(0, 0, 0, this.#cropperCanvasRect.height, this.configuration.aspectRatio, true); - } + const selectionRatio = Math.min(this.#cropperCanvasRect.width / this.maxSize.width, this.#cropperCanvasRect.height / this.maxSize.height); + this.cropperSelection.$change(0, 0, this.maxSize.width * selectionRatio, this.maxSize.height * selectionRatio, this.configuration.aspectRatio, true); this.cropperSelection.$center(); this.cropperSelection.scrollIntoView({ block: "center", inline: "center" }); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js index c5a472f2b54..f981c4f49a8 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js @@ -81,6 +81,34 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo } return id; }, + /** + * Returns the inner height of an element including paddings. + */ + innerHeight(element, styles) { + styles = styles || window.getComputedStyle(element); + let height = element.clientHeight; + height += ~~styles.paddingTop + ~~styles.paddingBottom; + return height; + }, + /** + * Returns the inner width of an element including paddings. + */ + innerWidth(element, styles) { + styles = styles || window.getComputedStyle(element); + let width = element.clientWidth; + width += ~~styles.paddingLeft + ~~styles.paddingRight; + return width; + }, + /** + * Returns the inner dimensions of an element including paddings. + */ + innerDimensions(element) { + const styles = window.getComputedStyle(element); + return { + height: DomUtil.innerHeight(element, styles), + width: DomUtil.innerWidth(element, styles), + }; + }, /** * Returns the outer height of an element including margins. */ diff --git a/wcfsetup/install/files/style/ui/dialog.scss b/wcfsetup/install/files/style/ui/dialog.scss index c573f0ab761..cca59d6c5bb 100644 --- a/wcfsetup/install/files/style/ui/dialog.scss +++ b/wcfsetup/install/files/style/ui/dialog.scss @@ -469,6 +469,9 @@ html[data-color-scheme="dark"] .dialog::backdrop { .dialog cropper-canvas { margin-left: auto; margin-right: auto; + /* overwrites the default values of `min-height: 100px` and `min-width: 200px` */ + min-height: 1px; + min-width: 1px; } /* If the height of the image is many times greater than the width, a white area would be displayed at the bottom and/or top. */