Why does my Canvas bottom become transparent after drawing Image?

I have the following function to make a “padding” for an input image:

  • Create a canvas with extra padding.

  • Fill the whole canvas with white color (it must not be transparent).

  • Draw the image at the Padding. The problem happens no matter if I specify the width or height in drawImage call or not.

const Padding = 0.2;

// ...

async #padInput(dataUrl: string): Promise<string> {
    const img = await new Promise<HTMLImageElement>((r, rej) => {
        const img = new Image();
        img.onload = () => r(img);
        img.onerror = rej;
        img.src = dataUrl;
    });

    const canvas = document.createElement("canvas");
    canvas.width = Math.ceil(img.naturalWidth * (1 + Padding * 2));
    canvas.height = Math.ceil(img.naturalHeight * (1 + Padding * 2));

    const ctx = canvas.getContext("2d")!;

    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.drawImage(
        img,
        Math.ceil(img.naturalWidth * Padding),
        Math.ceil(img.naturalHeight * Padding),
        img.naturalWidth,
        img.naturalHeight,
    );

    return canvas.toDataURL();
}

Strangely enough, the output image is like this (copied into a image editor so you can see the transparent part clearly):

enter image description here

I set some breakpoint and the canvas size is correct, after fillRect the whole canvas is correctly filled with white but right after drawImage is called, the bottom becomes transparent.

What may cause this problem?

Additional info:

  • It may be caused by data obtained from another canvas (the input dataUrl is obtained through another canvas.toDataURL() because I captured it from a video element). When debugging and replace dataUrl input with another image, I cannot reproduce the issue.

  • Adding another fillRect call to fill in the bottom works:

ctx.fillRect(
    0, canvas.height - paddingH,
    canvas.width, paddingH);

Still I don’t understand why it’s happening.