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

Code before:

        // Create canvas object for the cloned image
        const clonedCanvas = createCanvasFunction(cloned.width, cloned.height);

        // Draw image to context
        const data = new ImageDataClass(cloned.data, cloned.width, cloned.height);
        clonedCanvas.getContext('2d').putImageData(data, 0, 0);

        return clonedCanvas;
    }

    /**
     * Splits the image data into separate channels.
     * The number of elements in the array corresponds to the number of channels in the image.
     * @returns {Array<Uint8ClampedArray|Uint8Array>} An array containing separate channel data.
     */
    toChannels() {
        // Split each channel into a separate entry in a `channels` array.
        const channels = [];
        for (let c = 0; c < this.channels; c++) {
            channels.push(
                this.data.filter((_, i) => i % this.channels === c)
            );
        }
        return channels;
    }

    /**
     * Helper method to update the image data.
     * @param {Uint8ClampedArray} data The new image data.
     * @param {number} width The new width of the image.
     * @param {number} height The new height of the image.
     * @param {1|2|3|4|null} [channels] The new number of channels of the image.
     * @private
     */
    _update(data, width, height, channels = null) {
        this.data = data;
        this.width = width;
        this.height = height;
        if (channels !== null) {
            this.channels = channels;
        }
        return this;
    }

    /**
     * Clone the image
     * @returns {RawImage} The cloned image
     */
    clone() {
        return new RawImage(this.data.slice(), this.width, this.height, this.channels);

Review comment (suggestion):
I did a bit of [benchmarking](https://jsbenchmark.com/#eyJjYXNlcyI6W3siaWQiOiJiSU40RnVXN3RTVE5Rblp2NDg4N1MiLCJjb2RlIjoiY29uc3QgW2RhdGEsIG51bV9jaGFubmVsc10gPSBEQVRBO1xuXG5jb25zdCBjaGFubmVscyA9IFtdO1xuZm9yIChsZXQgYyA9IDA7IGMgPCBudW1fY2hhbm5lbHM7ICsrYykge1xuICAgIGNoYW5uZWxzLnB1c2goXG4gICAgICAgIGRhdGEuZmlsdGVyKChfLCBpKSA9PiBpICUgbnVtX2NoYW5uZWxzID09PSBjKVxuICAgICk7XG59XG5cbnJldHVybiBjaGFubmVsczsiLCJuYW1lIjoiVXNlIGZpbHRlciIsImRlcGVuZGVuY2llcyI6W119LHsiaWQiOiJ2T1AzTE94NFlIZ1hTY1RwUFlrT3kiLCJjb2RlIjoiY29uc3QgW2RhdGEsIG51bV9jaGFubmVsc10gPSBEQVRBO1xuXG4vLyBQcmUtYWxsb2NhdGUgcmVzdWx0c1xuY29uc3QgZGF0YV90eXBlID0gZGF0YS5jb25zdHJ1Y3RvcjtcbmNvbnN0IHBlcl9jaGFubmVsX2xlbmd0aCA9IGRhdGEubGVuZ3RoIC8gbnVtX2NoYW5uZWxzO1xuY29uc3QgY2hhbm5lbHMgPSBBcnJheS5mcm9tKHsgbGVuZ3RoOiBudW1fY2hhbm5lbHMgfSwgKCkgPT4gbmV3IGRhdGFfdHlwZShwZXJfY2hhbm5lbF9sZW5ndGgpKTtcblxuLy8gV3JpdGUgY2hhbm5lbCBkYXRhXG5mb3IgKGxldCBpID0gMDsgaSA8IGRhdGEubGVuZ3RoOyArK2kpIHtcbiAgICBjb25zdCBjaGFubmVsSW5kZXggPSBpICUgbnVtX2NoYW5uZWxzO1xuICAgIGNvbnN0IHBpeGVsSW5kZXggPSBNYXRoLmZsb29yKGkgLyBudW1fY2hhbm5lbHMpO1xuICAgIGNoYW5uZWxzW2NoYW5uZWxJbmRleF1bcGl4ZWxJbmRleF0gPSBkYXRhW2ldO1xufVxuXG5yZXR1cm4gY2hhbm5lbHM7XG4iLCJuYW1lIjoiVXNlIGRpdm1vZCIsImRlcGVuZGVuY2llcyI6W119LHsiaWQiOiJfa1hSZkNkTkswY1JZeUpWR0hLT0UiLCJjb2RlIjoiY29uc3QgW2RhdGEsIG51bV9jaGFubmVsc10gPSBEQVRBO1xuXG4vLyBQcmUtYWxsb2NhdGUgcmVzdWx0c1xuY29uc3QgZGF0YV90eXBlID0gZGF0YS5jb25zdHJ1Y3RvcjtcbmNvbnN0IHBlcl9jaGFubmVsX2xlbmd0aCA9IGRhdGEubGVuZ3RoIC8gbnVtX2NoYW5uZWxzO1xuY29uc3QgY2hhbm5lbHMgPSBBcnJheS5mcm9tKHsgbGVuZ3RoOiBudW1fY2hhbm5lbHMgfSwgKCkgPT4gbmV3IGRhdGFfdHlwZShwZXJfY2hhbm5lbF9sZW5ndGgpKTtcblxuLy8gV3JpdGUgY2hhbm5lbCBkYXRhXG5mb3IgKGxldCBpID0gMDsgaSA8IHBlcl9jaGFubmVsX2xlbmd0aDsgKytpKSB7XG4gIGNvbnN0IGRhdGFfb2Zmc2V0ID0gbnVtX2NoYW5uZWxzICogaTtcbiAgZm9yKGxldCBqID0gMDsgaiA8IG51bV9jaGFubmVsczsgKytqKSB7XG4gICAgY2hhbm5lbHNbal1baV0gPSBkYXRhW2RhdGFfb2Zmc2V0ICsgal07XG4gIH1cbn1cblxucmV0dXJuIGNoYW5uZWxzO1xuIiwiZGVwZW5kZW5jaWVzIjpbXSwibmFtZSI6Im5lc3RlZCBmb3IgbG9vcCJ9XSwiY29uZmlnIjp7Im5hbWUiOiJCYXNpYyBleGFtcGxlIiwicGFyYWxsZWwiOnRydWUsImdsb2JhbFRlc3RDb25maWciOnsiZGVwZW5kZW5jaWVzIjpbXX0sImRhdGFDb2RlIjoiY29uc3Qgd2lkdGggPSAyMjQ7XG5jb25zdCBoZWlnaHQgPSAyMjQ7XG5jb25zdCBudW1fY2hhbm5lbHMgPSA0O1xuY29uc3QgZGF0YSA9IFVpbnQ4QXJyYXkuZnJvbSh7bGVuZ3RoOiB3aWR0aCAqIGhlaWdodCAqIG51bV9jaGFubmVsc30sIChfLCBpKSA9PiBpKTtcblxucmV0dXJuIFtkYXRhLCBudW1fY2hhbm5lbHNdOyJ9fQ) and found it's significantly better to do it this way:

```suggestion
    /**
     * Split the image data into individual bands. This method returns an array of individual image bands from an image.
     * For example, splitting an "RGB" image creates three new images each containing a copy of one of the original bands (red, green, blue).
     * 
     * Inspired by PIL's `Image.split()` [function](https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.split).
     * @returns {(Uint8Array|Uint8ClampedArray)[]} An array containing bands.
     */
    split() {
        const { data, channels } = this;

        /** @type {typeof Uint8Array | typeof Uint8ClampedArray} */
        const data_type = /** @type {any} */(data.constructor);
        const per_channel_length = data.length / channels;

        // Pre-allocate buffers for each channel
        const split_data = Array.from(
            { length: channels },
            () => new data_type(per_channel_length),
        );

        // Write pixel data
        for (let i = 0; i < per_channel_length; ++i) {
            const data_offset = channels * i;
            for (let j = 0; j < channels; ++j) {
                split_data[j][i] = data[data_offset + j];
            }
        }
        return split_data;
    }
```

![image](https://github.com/user-attachments/assets/8cdba941-9775-45a4-b111-8bfb4443f589)


Code after:
            [this.height, this.width, this.channels]
        );

        if (channel_format === 'HWC') {
            // Do nothing
        } else if (channel_format === 'CHW') { // hwc -> chw
            tensor = tensor.permute(2, 0, 1);
        } else {
            throw new Error(`Unsupported channel format: ${channel_format}`);
        }
        return tensor;
    }

    toCanvas() {
        if (!BROWSER_ENV) {
            throw new Error('toCanvas() is only supported in browser environments.')
        }

        // Clone, and convert data to RGBA before drawing to canvas.
        // This is because the canvas API only supports RGBA
        const cloned = this.clone().rgba();

        // Create canvas object for the cloned image
        const clonedCanvas = createCanvasFunction(cloned.width, cloned.height);

        // Draw image to context
        const data = new ImageDataClass(cloned.data, cloned.width, cloned.height);
        clonedCanvas.getContext('2d').putImageData(data, 0, 0);

        return clonedCanvas;
    }

    /**
     * Split this image into individual bands. This method returns an array of individual image bands from an image.
     * For example, splitting an "RGB" image creates three new images each containing a copy of one of the original bands (red, green, blue).
     * 
     * Inspired by PIL's `Image.split()` [function](https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.split).
     * @returns {RawImage[]} An array containing bands.
     */
    split() {
        const { data, width, height, channels } = this;

        /** @type {typeof Uint8Array | typeof Uint8ClampedArray} */
        const data_type = /** @type {any} */(data.constructor);
        const per_channel_length = data.length / channels;

        // Pre-allocate buffers for each channel
        const split_data = Array.from(
            { length: channels },
            () => new data_type(per_channel_length),
        );