web-dev-qa-db-fra.com

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é beaucoup de façons 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.

74
Dundar

Cause

Certaines images sont très difficiles à sous-échantillonner et interpolez comme celle-ci avec des courbes lorsque vous souhaitez passer d'une taille grande à une taille petite.

Les navigateurs semblent généralement utiliser une interpolation bi-linéaire (échantillonnage 2x2) avec l'élément canvas plutôt que bi-cubique (échantillonnage 4x4) pour des raisons de performances (probables).

Si le pas est trop grand, il n’ya tout simplement pas assez de pixels à échantillonner, ce qui se reflète dans le résultat.

Du point de vue du signal/DSP, cela peut être perçu comme une valeur de seuil trop élevée pour un filtre passe-bas, ce qui peut entraîner un repliement du spectre si le signal contient de nombreuses hautes fréquences (détails).

Solution

Mise à jour 2018:

Voici une astuce intéressante que vous pouvez utiliser pour les navigateurs et qui supporte la propriété filter sur le contexte 2D. Cela produit une image floue, qui est essentiellement identique à un rééchantillonnage, puis réduit. Cela permet de grandes marches, mais ne nécessite que deux marches et deux tirages.

Pré-flou en utilisant le nombre d'étapes (taille d'origine/taille de destination/2) comme rayon (vous devrez peut-être ajuster ce paramètre de manière heuristique en fonction du navigateur et des étapes impaires/paires - ici, uniquement illustré de manière simplifiée):

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

if (typeof ctx.filter === "undefined") {
 alert("Sorry, the browser doesn't support Context2D filters.")
}

const img = new Image;
img.onload = function() {

  // step 1
  const oc = document.createElement('canvas');
  const octx = oc.getContext('2d');
  oc.width = this.width;
  oc.height = this.height;

  // steo 2: pre-filter image using steps as radius
  const steps = (oc.width / canvas.width)>>1;
  octx.filter = `blur(${steps}px)`;
  octx.drawImage(this, 0, 0);

  // step 3, draw scaled
  ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);

}
img.src = "//i.stack.imgur.com/cYfuM.jpg";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<br/><p>Original was 1600x1200, reduced to 400x300 canvas</p><br/>
<canvas id="canvas" width=400 height=250></canvas>

Prise en charge du filtre en ogf oct/2018:

CanvasRenderingContext2D.filter                                                   
api.CanvasRenderingContext2D.filter                                               
On Standard Track, Experimental                                                   
https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/filter        
                                                                                  
DESKTOP >        |Chrome    |Edge      |Firefox   |IE        |Opera     |Safari   
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter !         |    52    |    ?     |    49    |    -     |    -     |    -    
                                                                                  
MOBILE >         |Chrome/A  |Edge/mob  |Firefox/A |Opera/A   |Safari/iOS|Webview/A
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter !         |    52    |    ?     |    49    |    -     |    -     |    52   
                                                                                  
! = Experimental                                                                  
                                                                                  
Data from MDN - "npm i -g mdncomp" (c) epistemex

Mise à jour 2017: Il y a maintenant un nouvelle propriété défini dans les spécifications pour définir la qualité du rééchantillonnage:

context.imageSmoothingQuality = "low|medium|high"

Il n'est actuellement pris en charge que par Chrome. Il appartient au fournisseur de décider des méthodes réellement utilisées par niveau, mais il est raisonnable de supposer que Lanczos est "élevé" ou de qualité équivalente. Cela signifie que vous pouvez ignorer complètement les étapes, ou que des étapes plus grandes peuvent être utilisées avec moins de redessin, en fonction de la taille de l'image et

Prise en charge de imageSmoothingQuality:

CanvasRenderingContext2D.imageSmoothingQuality
api.CanvasRenderingContext2D.imageSmoothingQuality
On Standard Track, Experimental
https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality

DESKTOP >              |Chrome    |Edge      |Firefox   |IE        |Opera     |Safari
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !|    54    |    ?     |    -     |    ?     |    41    |    Y

MOBILE >               |Chrome/A  |Edge/mob  |Firefox/A |Opera/A   |Safari/iOS|Webview/A
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !|    54    |    ?     |    -     |    41    |    Y     |    54

! = Experimental

Data from MDN - "npm i -g mdncomp" (c) epistemex

navigateur. Jusque là..:
Fin de la transmission

La solution consiste à utiliser étape par étape pour obtenir un résultat correct. Réduire signifie que vous réduisez la taille par étapes pour permettre à la plage d'interpolation limitée de couvrir suffisamment de pixels pour l'échantillonnage.

Cela permettra également d’obtenir de bons résultats avec l’interpolation bi-linéaire (elle se comporte en réalité assez bi-cubique) et les frais généraux sont minimes, car il y a moins de pixels à échantillonner à chaque étape.

L’idéal est d’aller à la moitié de la résolution à chaque étape jusqu’à ce que vous définissiez la taille cible (merci à Joe Mabel de l’avoir mentionné!).

Violon modifié

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

NORMAL DOWN-SCALED IMAGE

en utilisant les étapes ci-dessous:

DOWN-STEPPED IMAGE

Dans ce cas, vous devrez vous retirer en 3 étapes:

À l'étape 1, nous réduisons l'image à moitié en utilisant un canevas hors écran:

// 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);

L'étape 2 réutilise le canevas hors écran et dessine à nouveau l'image réduite de moitié:

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

Et on dessine encore une fois sur la toile principale, encore une fois réduite à 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);

Pointe:

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

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

Je recommande fortement pica pour de telles tâches. Sa qualité est supérieure à la réduction des effectifs multiple et est assez rapide dans le même temps. Voici un démo .

12
avalanche1

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

4
Jesús Carrera
    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,/, "");
}
4
kamil

J'ai créé un service réutilisable Angular permettant de redimensionner les images de haute qualité pour les personnes intéressées: https://Gist.github.com/fisch0920/37bac5e741eaec60e983)

Le service inclut l'approche de réduction progressive de Ken, ainsi qu'une version modifiée de l'approche de convolution de Lanczos trouvée ici .

J'ai inclus les deux solutions car elles ont toutes deux leur propre avantage/inconvénient. L’approche par convolution de Lanczos est de meilleure qualité au prix de ralentissement, tandis que l’approche de réduction progressive conduit à des résultats raisonnablement anti-aliasés 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
    })
})
3
fisch2

Si quelqu'un d'autre cherche toujours une réponse, il existe un autre moyen d'utiliser une image d'arrière-plan au lieu de drawImage (). Vous ne perdrez aucune qualité d'image de cette façon.

JS:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
   var url = "http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

    img=new Image();
    img.onload=function(){

        canvas.style.backgroundImage = "url(\'" + url + "\')"

    }
    img.src="http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

démo de travail