javascripthtml5-canvasoffscreen-canvas

Which Algorithm Does Chrome/Firefox Use in context.drawImage()?


The specs say:

When scaling up, if the imageSmoothingEnabled attribute is set to false, then the image must be rendered using nearest-neighbor interpolation.

So when imageSmoothingEnabled is false and the process is scaling up, it uses nearest-neighbor which is what I want. But I also want to use nearest-neighbor for scaling down (and if it's considered another thing, when the scaling is same), basically all the time.

The specs also say these:

When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt to apply a smoothing algorithm to the image data when it is scaled. User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute to guide the choice of filtering algorithm when the imageSmoothingEnabled attribute is set to true. Otherwise, the image must be rendered using nearest-neighbor interpolation.

This specification does not define the precise algorithm to use when scaling an image down, or when scaling an image up when the imageSmoothingEnabled attribute is set to true.

As far as I understand when imageSmoothingEnabled is false and if it's not scaling up, it is not guaranteed to use nearest-neighbor.

I don't know any way to test it properly and definitively. Chrome (Chromium) and Firefox are open source. I checked their repos but couldn't find anything useful. All I could find was test code.

Is there a way to be sure that the drawImage uses nearest-neighbor? If so, how?

I don't think so but if it matters I use 9 parameter version and do all scaling up/down/same from time to time. Also it's a browser extension project. So no frameworks and OffscreenCanvas.

I've seen this question but it's pretty old and the answer didn't satisfy me. An example code to run or a link to source code is preferred.

Thanks in advance.

I tried searching the repo's but couldn't find the related functions. I couldn't think any other way.


Solution

  • MDN says;

    For imageSmoothingQuality property to have an effect, imageSmoothingEnabled must be true.

    So setting imageSmoothingEnabled false and imageSmoothingQuality "low" wouldn't matter. Also it wouldn't be feasible for you because Firefox doesn't support imageSmoothingQuality.

    There's a test in here. The image is still alive at the moment. With the help of MS Paint, I changed it a bit and got a mess. The code doesn't explain itself because it's a mess. I'm not gonna spend more time to make it better. I'm just gonna try to explain it.

    In the left canvas top left one is the original one. it's a 64x64 pixel image. The star is 60x60. The stroke/edge is 1 pixel. The other ones are its upscaled versions with 9 parameter drawImage with imageSmoothingEnabled set to false.

    In the right one the big one is the original one. It's 576x576 pixel image (64 * 9). The star is 574x574. The stroke/edge is 1 pixel. The other ones are its downscaled versions with 9 parameter drawImage with imageSmoothingEnabled set to false and true. True ones are on the left. It's not symmetrical you should be able to figure that out from the size difference.

    My Chrome doesn't render it the way I expected. You can save the images with right click & save image and see it in MS Paint. Firefox has the same rendering behavior. Somehow when I do right click & save image, nothing happens. I opened it in a new tab and saved it.

    In the end, I believe the messy code answers some questions.

    <canvas id="canvas1" width="320" height="730"></canvas>
    <canvas id="canvas2" width="730" height="730"></canvas>
    <script>
        const canvas1 = document.getElementById('canvas1');
        const ctx1 = canvas1.getContext('2d');
        ctx1.font = '16px sans-serif';
        ctx1.textAlign = 'center';
        const img1 = new Image();
        img1.src = 'https://i.imgur.com/PqxiAWS.png';
        img1.onload = function () {
            let heightMultiplier = 0.85;
            let vOffset = 0;
            const w = img1.width;
            const h = img1.height;
            let dw1 = w;
            let dh1 = h;
            ctx1.drawImage(img1, 0, 0, w, h, 0, vOffset, dw1, dh1);
            let dw2 = w * 3.7;
            let dh2 = h * 3.7;
            ctx1.imageSmoothingEnabled = false;
            ctx1.drawImage(img1, 0, 0, w, h, 0, vOffset, dw2, dh2);
            let dw3 = w * 4;
            let dh3 = h * 4;
            ctx1.imageSmoothingEnabled = false;
            ctx1.drawImage(img1, 0, 0, w, h, 0, vOffset += dh2 * heightMultiplier, dw3, dh3);
            let dw4 = w * 4.9;
            let dh4 = h * 4.9;
            ctx1.imageSmoothingEnabled = false;
            ctx1.drawImage(img1, 0, 0, w, h, 0, vOffset += dh3 * heightMultiplier, dw4, dh4);
        };
        const canvas2 = document.getElementById('canvas2');
        const ctx2 = canvas2.getContext('2d');
        ctx2.font = '16px sans-serif';
        ctx2.textAlign = 'center';
        const img2 = new Image();
        img2.src = 'https://i.imgur.com/RKwtgC5.png';
        img2.onload = function () {
            const w = img2.width;
            const h = img2.height;
            const offset = 64;
            let dw1 = w;
            let dh1 = h;
            ctx2.drawImage(img2, 0, 0, w, h, offset, 0, dw1, dh1);
            let dw2 = w * 0.2;
            let dh2 = h * 0.2;
            ctx2.imageSmoothingEnabled = true;
            ctx2.drawImage(img2, 0, 0, w, h, 0 + offset, 0, dw2, dh2);
            ctx2.imageSmoothingEnabled = false;
            ctx2.drawImage(img2, 0, 0, w, h, dw2 + offset, 0, dw2, dh2);
            let dw3 = w * 0.25;
            let dh3 = h * 0.25;
            ctx2.imageSmoothingEnabled = true;
            ctx2.drawImage(img2, 0, 0, w, h, w - dw3 * 2 + 2 * offset, 0, dw3, dh3);
            ctx2.imageSmoothingEnabled = false;
            ctx2.drawImage(img2, 0, 0, w, h, w - dw3 + 2 * offset, 0, dw3, dh3);
            let dw4 = w * 0.4;
            let dh4 = h * 0.4;
            ctx2.imageSmoothingEnabled = true;
            ctx2.drawImage(img2, 0, 0, w, h, w / 2 - dw4 + offset, dh3, dw4, dh4);
            ctx2.imageSmoothingEnabled = false;
            ctx2.drawImage(img2, 0, 0, w, h, w / 2 + offset, dh3, dw4, dh4);
            let dw5 = w * 0.53;
            let dh5 = h * 0.53;
            ctx2.imageSmoothingEnabled = true;
            ctx2.drawImage(img2, 0, 0, w, h, 0, dh5, dw5, dh5);
            ctx2.imageSmoothingEnabled = false;
            ctx2.drawImage(img2, 0, 0, w, h, w + 2 * offset - dw5, dh5, dw5, dh5);
        };
    </script>

    As the code says, Firefox doesn't use nearest-neighbor when its downscaling. But Chrome does. We've seen it. And the code is in here. It's technically imageSmoothingQuality function, when you change imageSmoothingEnabled it calls it.