Repository: xenova/transformers.js
File: src/utils/image.js (JavaScript)

Code before:

    }

    async pad([left, right, top, bottom]) {
        left = Math.max(left, 0);
        right = Math.max(right, 0);
        top = Math.max(top, 0);
        bottom = Math.max(bottom, 0);

        if (left === 0 && right === 0 && top === 0 && bottom === 0) {
            // No padding needed
            return this;
        }

        if (BROWSER_ENV) {
            // Store number of channels before padding
            const numChannels = this.channels;

            // Create canvas object for this image
            const canvas = this.toCanvas();

            const newWidth = this.width + left + right;
            const newHeight = this.height + top + bottom;

            // Create a new canvas of the desired size.
            const ctx = createCanvasFunction(newWidth, newHeight).getContext('2d');

            // Draw image to context, padding in the process
            ctx.drawImage(canvas,
                0, 0, this.width, this.height,
                left, top, newWidth, newHeight
            );

            // Create image from the padded data
            const paddedImage = new RawImage(
                ctx.getImageData(0, 0, newWidth, newHeight).data,
                newWidth, newHeight, 4);

            // Convert back so that image has the same number of channels as before
            return paddedImage.convert(numChannels);

        } else {
            const img = this.toSharp().extend({ left, right, top, bottom });
            return await loadImageFunction(img);
        }
    }

    async crop([x_min, y_min, x_max, y_max]) {
        // Ensure crop bounds are within the image
        x_min = Math.max(x_min, 0);
        y_min = Math.max(y_min, 0);

Review comment (suggestion):
The first row is effectively saying "start a selection at `0,0` and `this.width, this.height`", which gives us a rectangle.

It then draws this selection on line 2, starting at a new `x, y` of `left, top`, and the new dimensions should match what the original image looks like otherwise it gets stretched.

---

You can see why the cropping happens, as shown in my first post, by just looking at width.  
We are stretching the image to `newWidth` and the new canvas is already at a width of `newWidth` hence when we add the padding of `left` to it, we now get a new image that is larger than a canvas - `left + newWidth + right` is greater than `newWidth`!

Code after:
        right = Math.max(right, 0);
        top = Math.max(top, 0);
        bottom = Math.max(bottom, 0);

        if (left === 0 && right === 0 && top === 0 && bottom === 0) {
            // No padding needed
            return this;
        }

        if (BROWSER_ENV) {
            // Store number of channels before padding
            const numChannels = this.channels;

            // Create canvas object for this image
            const canvas = this.toCanvas();

            const newWidth = this.width + left + right;
            const newHeight = this.height + top + bottom;

            // Create a new canvas of the desired size.
            const ctx = createCanvasFunction(newWidth, newHeight).getContext('2d');

            // Draw image to context, padding in the process
            ctx.drawImage(canvas,
                0, 0, this.width, this.height,
                left, top, this.width, this.height
            );

            // Create image from the padded data
            const paddedImage = new RawImage(
                ctx.getImageData(0, 0, newWidth, newHeight).data,
                newWidth, newHeight, 4
            );

            // Convert back so that image has the same number of channels as before
            return paddedImage.convert(numChannels);

        } else {
            const img = this.toSharp().extend({ left, right, top, bottom });
            return await loadImageFunction(img);
        }
    }

    async crop([x_min, y_min, x_max, y_max]) {
        // Ensure crop bounds are within the image
        x_min = Math.max(x_min, 0);
        y_min = Math.max(y_min, 0);
        x_max = Math.min(x_max, this.width - 1);
        y_max = Math.min(y_max, this.height - 1);

        // Do nothing if the crop is the entire image