86 votes

Html5 canvas drawImage: comment appliquer l'antialiasing

Veuillez regarder l'exemple suivant:

http://jsfiddle.net/MLGr4/47/

     var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    img=new Image();
    img.onload=function(){
        canvas.width=400;
        canvas.height=150;
        ctx.drawImage(img,0,0,img.width,img.height,0,0,400,150);
    }
    img.src="http://openwalls.com/image/1734/colored_lines_on_blue_background_1920x1200.jpg";
 

Comme vous le voyez, l'image n'est pas anti-aliasée bien qu'il soit dit que drawImage applique l'anti-aliasing automatiquement. J'ai essayé de nombreuses manières différentes mais cela ne semble pas fonctionner. Pourriez-vous s'il vous plaît me dire comment je peux obtenir une image anti-aliasée? Merci.

187voto

Ken Fyrstenberg Points 38115

Cause

Certaines images sont tout simplement très difficile de sous-échantillonnage et d' interpolation comme celui-ci avec des courbes lorsque vous voulez aller de grande taille pour une petite.

Les navigateurs semblent généralement l'utilisation de bi-linéaire (2x2 échantillonnage) interpolation avec la toile élément non bi-cubique (4x4 d'échantillonnage) pour (probablement) des raisons de performances.

Si l'étape est trop énorme, alors il n'y a simplement pas assez de pixels pour l'échantillon de ce qui se reflète dans le résultat.

À partir d'un signal/DSP point de vue on pourrait voir cela comme un filtre passe-bas de la valeur de seuil trop élevé, ce qui peut entraîner aliasing si il y a beaucoup de hautes fréquences (les détails) dans le signal.

Solution

La solution est d'utiliser l'étape-vers le bas pour obtenir un bon résultat. Étape-vers le bas signifie que vous réduire la taille des mesures pour permettre l'limité d'interpolation de gamme pour couvrir suffisamment de pixels pour l'échantillonnage.

Cela permettra à de bons résultats également avec la bi-interpolation linéaire (en fait, il se comporte un peu comme bi-cubique lors de cette opération) et la charge est minime, car il y a moins de pixels de l'échantillon dans chaque étape.

L'idéal est d'aller à la moitié de la résolution , à chaque étape, jusqu'à ce que vous définissez la taille de la cible (merci à Joe Mabel de mentionner cela!).

MODIFIÉ VIOLON

L'aide directe de la mise à l'échelle comme dans la question d'origine:

NORMAL DOWN-SCALED IMAGE

L'aide de l'étape vers le bas, comme indiqué ci-dessous:

DOWN-STEPPED IMAGE

Dans ce cas, vous aurez besoin à l'étape vers le bas, en 3 étapes:

Dans l'étape 1, nous avons réduit l'image à moitié en utilisant un écran de toile:

/// step 1 - create off-screen canvas
var oc   = document.createElement('canvas'),
    octx = oc.getContext('2d');

oc.width  = img.width  * 0.5;
oc.height = img.height * 0.5;

octx.drawImage(img, 0, 0, oc.width, oc.height);

Étape 2 réutilise le hors de l'écran en toile et dessine l'image réduite de moitié à nouveau:

/// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

Et nous attirons une fois de plus à main de toile, de nouveau réduit à la moitié , mais à la taille finale:

/// step 3
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
                  0, 0, canvas.width,   canvas.height);

Astuce:

Vous pouvez calculer le nombre total d'étapes, à l'aide de cette formule (il comprend l'étape finale pour définir la taille de la cible):

steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))

5voto

Jesús Carrera Points 980

En complément de la réponse de Ken, voici une autre solution permettant d'effectuer le sous-échantillonnage par moitiés (pour que le résultat soit satisfaisant à l'aide de l'algorithme du navigateur):

   function resize_image( src, dst, type, quality ) {
     var tmp = new Image(),
         canvas, context, cW, cH;

     type = type || 'image/jpeg';
     quality = quality || 0.92;

     cW = src.naturalWidth;
     cH = src.naturalHeight;

     tmp.src = src.src;
     tmp.onload = function() {

        canvas = document.createElement( 'canvas' );

        cW /= 2;
        cH /= 2;

        if ( cW < src.width ) cW = src.width;
        if ( cH < src.height ) cH = src.height;

        canvas.width = cW;
        canvas.height = cH;
        context = canvas.getContext( '2d' );
        context.drawImage( tmp, 0, 0, cW, cH );

        dst.src = canvas.toDataURL( type, quality );

        if ( cW <= src.width || cH <= src.height )
           return;

        tmp.src = dst.src;
     }

  }
  // The images sent as parameters can be in the DOM or be image objects
  resize_image( $( '#original' )[0], $( '#smaller' )[0] );
 

Crédits à ce post

4voto

kamil Points 11
    var getBase64Image = function(img, quality) {
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");

    //----- origin draw ---
    ctx.drawImage(img, 0, 0, img.width, img.height);

    //------ reduced draw ---
    var canvas2 = document.createElement("canvas");
    canvas2.width = img.width * quality;
    canvas2.height = img.height * quality;
    var ctx2 = canvas2.getContext("2d");
    ctx2.drawImage(canvas, 0, 0, img.width * quality, img.height * quality);

    // -- back from reduced draw ---
    ctx.drawImage(canvas2, 0, 0, img.width, img.height);

    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
    // return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}

2voto

fisch2 Points 45

J'ai créé une réutilisables Angulaire de service à la poignée de haute qualité redimensionnement des images pour tous ceux qui sont intéressés: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Le service comprend Ken le pas-sage de désagrégation de l'approche ainsi que d'une version modifiée de la lanczos convolution approche trouvé ici.

J'ai inclus à la fois des solutions parce qu'ils tous les deux ont leurs propres avantages et inconvénients. Le lanczos convolution approche est de qualité supérieure au prix d'être plus lent, tandis que le pas-sage d'en réduire approche produit raisonnablement antialiased résultats et est nettement plus rapide.

Exemple d'utilisation:

angular.module('demo').controller('ExampleCtrl', function (imageService) {
  // EXAMPLE USAGE
  // NOTE: it's bad practice to access the DOM inside a controller, 
  // but this is just to show the example usage.

  // resize by lanczos-sinc filter
  imageService.resize($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })

  // resize by stepping down image size in increments of 2x
  imageService.resizeStep($('#myimg')[0], 256, 256)
    .then(function (resizedImage) {
      // do something with resized image
    })
})

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X