web-dev-qa-db-fra.com

Comment mettre à l'échelle des images sur un canevas html5 avec une meilleure interpolation?

Tout d'abord: qu'est-ce que j'essaie de faire?

J'ai une application pour visualiser les images. Il utilise l'élément canvas pour rendre l'image. Vous pouvez effectuer un zoom avant, un zoom arrière et le déplacer. Cette partie fonctionne parfaitement en ce moment.

Mais disons que j'ai une image avec beaucoup de texte. Il a une résolution de 1200x1700 et ma toile a 1200x900. Initialement, lors d'un zoom arrière, cela conduit à une résolution de ~ 560x800.

Mon dessin réel ressemble à ceci:

drawImage(src, srcOffsetX, srcOffsetY, sourceViewWidth, sourceViewHeight,
destOffsetX, destOffsetY, destWidth, destHeight);

Le petit texte sur cette image semble vraiment, vraiment mauvais, surtout par rapport à d'autres visualiseurs d'images (par exemple IrfanView), ou même à l'élément html <img>.

J'ai compris que l'algorithme d'interpolation des navigateurs est la cause de ce problème. La comparaison de différents navigateurs a montré que Chrome rend les images à l'échelle les meilleures, mais toujours pas assez bonnes.

Eh bien, j'ai cherché dans tous les coins de l'Interwebs pendant 4 à 5 heures d'affilée et je n'ai pas trouvé ce dont j'avais besoin. J'ai trouvé l'option "imageSmoothingEnabled", les styles CSS "rendu d'image" que vous ne pouvez pas utiliser sur le canevas, le rendu à des positions flottantes et de nombreuses implémentations JavaScript d'algorithmes d'interpolation (ce sont loin pour ralentir à mon propos) ).

Vous vous demandez peut-être pourquoi je vous dis tout cela: pour vous faire gagner du temps et me donner des réponses que je connais déjà

Donc: y a-t-il tout moyen bon et rapide d'avoir une meilleure interpolation? Mon idée actuelle est de créer un objet image, de le redimensionner (car img a une bonne interpolation lorsqu'il est mis à l'échelle!) Et de le rendre ensuite. Malheureusement, appliquer img.width ne semble affecter que la largeur affichée ...

Mise à jour: Grâce à Simon, j'ai pu résoudre mon problème. Voici l'algorithme de mise à l'échelle dynamique que j'ai utilisé. Notez qu'il garde le rapport d'aspect, le paramètre de hauteur sert uniquement à éviter plus de calcul flottant. Il ne diminue que pour le moment.

scale(destWidth, destHeight){
        var start = new Date().getTime();
        var scalingSteps = 0;
        var ctx = this._sourceImageCanvasContext;
        var curWidth = this._sourceImageWidth;
        var curHeight = this._sourceImageHeight;

        var lastWidth = this._sourceImageWidth;
        var lastHeight = this._sourceImageHeight;

        var end = false;
        var scale=0.75;
        while(end==false){
            scalingSteps +=1;
            curWidth *= scale;
            curHeight *= scale;
            if(curWidth < destWidth){
                curWidth = destWidth;
                curHeight = destHeight;
                end=true;
            }
            ctx.drawImage(this._sourceImageCanvas, 0, 0, Math.round(lastWidth), Math.round(lastHeight), 0, 0, Math.round(curWidth), Math.round(curHeight));
            lastWidth = curWidth;
            lastHeight = curHeight;
        }
        var endTime =new Date().getTime();
        console.log("execution time: "+ ( endTime - start) + "ms. scale per frame: "+scale+ " scaling step count: "+scalingSteps);
    }
37
Kumpu

Vous devez "démissionner" plusieurs fois. Au lieu de passer d'une très grande image à une très petite, vous devez la redimensionner à des tailles intermédiaires.

Considérez une image que vous souhaitez dessiner à l'échelle 1/6. Vous pouvez faire ceci:

var w = 1280;
var h = 853;

ctx.drawImage(img, 0, 0, w/6, h/6);   

Ou vous pouvez le dessiner sur un canevas en mémoire à l'échelle 1/2, puis à nouveau à l'échelle 1/2, puis à nouveau à l'échelle 1/2. Le résultat est une image à l'échelle 1/6, mais nous utilisons trois étapes:

var can2 = document.createElement('canvas');
can2.width = w/2;
can2.height = w/2;
var ctx2 = can2.getContext('2d');

ctx2.drawImage(img, 0, 0, w/2, h/2);
ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);

Ensuite, vous pouvez ramener cela à votre contexte d'origine:

ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);

Vous pouvez voir la différence en direct, ici:

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');

var img = new Image();
var w = 1280;
var h = 853;
img.onload = function() {
    // step it down only once to 1/6 size:
    ctx.drawImage(img, 0, 0, w/6, h/6);   
    
    // Step it down several times
    var can2 = document.createElement('canvas');
    can2.width = w/2;
    can2.height = w/2;
    var ctx2 = can2.getContext('2d');
    
    // Draw it at 1/2 size 3 times (step down three times)
    
    ctx2.drawImage(img, 0, 0, w/2, h/2);
    ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
    ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);
    ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);
}



img.src = 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Equus_quagga_%28Namutoni%2C_2012%29.jpg/1280px-Equus_quagga_%28Namutoni%2C_2012%29.jpg'
canvas {
    border: 1px solid gray;
}
<canvas id="canvas1" width="400" height="400"></canvas>

Voir le même extrait sur jsfiddle.

49
Simon Sarris