Clamp the values when the selection violates the boundaries
authorAlexander Ebert <ebert@woltlab.com>
Thu, 19 Dec 2024 18:04:52 +0000 (19:04 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 19 Dec 2024 18:04:52 +0000 (19:04 +0100)
ts/WoltLabSuite/Core/Component/Image/Cropper.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js

index 8212edbefeb3d0a1385899c05f4464a9927a76ef..85d8319da6c9faeeddf3324e9005cffa923fa2f0 100644 (file)
@@ -35,6 +35,18 @@ function inSelection(selection: Selection, maxSelection: Selection): boolean {
   );
 }
 
+function clampValue(position: number, length: number, availableLength: number): number {
+  if (position < 0) {
+    return 0;
+  }
+
+  if (position + length > availableLength) {
+    return Math.floor(availableLength - length);
+  }
+
+  return Math.floor(position);
+}
+
 abstract class ImageCropper {
   readonly configuration: CropperConfiguration;
   readonly file: File;
@@ -203,6 +215,12 @@ abstract class ImageCropper {
 
       if (!inSelection(selection, maxSelection)) {
         event.preventDefault();
+
+        // Try to clamp the position to the boundaries.
+        this.cropperSelection!.$moveTo(
+          clampValue(selection.x, selection.width, maxSelection.width),
+          clampValue(selection.y, selection.height, maxSelection.height),
+        );
       }
     });
 
@@ -216,17 +234,17 @@ abstract class ImageCropper {
         this.cropperCanvasRect.height / this.height,
       );
 
-      const minWidth = this.minSize.width * selectionRatio;
-      const maxWidth = this.cropperCanvasRect.width;
-      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
-      ) {
+      // Round all values to integers to avoid dealing with the wonderful world
+      // of IEEE 754 numbers.
+      const minWidth = Math.round(this.minSize.width * selectionRatio);
+      const maxWidth = Math.round(this.cropperCanvasRect.width);
+      const minHeight = Math.round(minWidth / this.configuration.aspectRatio);
+      const maxHeight = Math.round(maxWidth / this.configuration.aspectRatio);
+
+      const width = Math.round(selection.width);
+      const height = Math.round(selection.height);
+
+      if (width < minWidth || height < minHeight || width > maxWidth || height > maxHeight) {
         event.preventDefault();
       }
     });
index 06c2cf3796899abde816d68a2f929196731a98da..6f209bc62d8e19629821c3230aff652f7d79b85a 100644 (file)
@@ -20,6 +20,15 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
             Math.round(selection.x + selection.width) <= Math.round(maxSelection.x + maxSelection.width) &&
             Math.round(selection.y + selection.height) <= Math.round(maxSelection.y + maxSelection.height));
     }
+    function clampValue(position, length, availableLength) {
+        if (position < 0) {
+            return 0;
+        }
+        if (position + length > availableLength) {
+            return Math.floor(availableLength - length);
+        }
+        return Math.floor(position);
+    }
     class ImageCropper {
         configuration;
         file;
@@ -154,6 +163,8 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                 };
                 if (!inSelection(selection, maxSelection)) {
                     event.preventDefault();
+                    // Try to clamp the position to the boundaries.
+                    this.cropperSelection.$moveTo(clampValue(selection.x, selection.width, maxSelection.width), clampValue(selection.y, selection.height, maxSelection.height));
                 }
             });
             // Limit the selection to the min/max size
@@ -161,14 +172,15 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                 const selection = event.detail;
                 this.cropperCanvasRect = this.cropperCanvas.getBoundingClientRect();
                 const selectionRatio = Math.min(this.cropperCanvasRect.width / this.width, this.cropperCanvasRect.height / this.height);
-                const minWidth = this.minSize.width * selectionRatio;
-                const maxWidth = this.cropperCanvasRect.width;
-                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) {
+                // Round all values to integers to avoid dealing with the wonderful world
+                // of IEEE 754 numbers.
+                const minWidth = Math.round(this.minSize.width * selectionRatio);
+                const maxWidth = Math.round(this.cropperCanvasRect.width);
+                const minHeight = Math.round(minWidth / this.configuration.aspectRatio);
+                const maxHeight = Math.round(maxWidth / this.configuration.aspectRatio);
+                const width = Math.round(selection.width);
+                const height = Math.round(selection.height);
+                if (width < minWidth || height < minHeight || width > maxWidth || height > maxHeight) {
                     event.preventDefault();
                 }
             });