HTML5 toile drawImage: comment appliquer antialiasing

, jetez un oeil à 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 s'applique anti-aliasing automatiquement. J'ai essayé de nombreuses façons, mais ça n'a pas l'air de marcher. Pouvez-vous me dire comment je peux avoir une image anti-aliasée? Grâce.

71
demandé sur epistemex 2013-07-25 18:51:00

6 réponses

Cause

certaines images sont juste très difficile à descendre-échantillon et interpolate comme celui-ci avec des courbes quand vous voulez passer d'une grande taille à une petite.

Les navigateurs

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

si le pas est trop grand alors il n'y a tout simplement pas assez de pixels pour échantillonner à partir desquels se reflète le résultat.

du point de vue signal/DSP, vous pourriez voir cela comme une valeur de seuil du filtre passe-bas trop élevée, ce qui peut avoir pour résultat d'aliasing s'il y a beaucoup de hautes fréquences (détails) dans le signal.

Solution

mise à jour 2018:

Voici une astuce soignée que vous pouvez utiliser pour les navigateurs qui prend en charge le filter propriété sur le contexte 2D. Cela pré-efface l'image qui est en essence la même qu'un rééchantillonnage, puis se dégrade. Cela permet de grandes étapes, mais ne nécessite que deux étapes et deux tirages.

pré-flou en utilisant le nombre de pas (taille d'Origine / Taille de destination / 2) comme Rayon (vous pouvez avoir besoin d'ajuster cette heuristique basée sur le navigateur et les pas impairs/pairs-ici seulement montré simplifié):

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 comme 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 de 2017: Il y a maintenant un nouvelle propriété défini dans les spécifications pour le réglage de rééchantillonnage de la qualité:

context.imageSmoothingQuality = "low|medium|high"

il n'est actuellement pris en charge que dans Chrome. Les méthodes utilisées par niveau sont laissées au vendeur pour décider, mais il est raisonnable de supposer Lanczos pour "haut" ou quelque chose équivalent en qualité. Cela signifie que le retrait peut être complètement sauté, ou des pas plus grands peuvent être utilisés avec moins de redessins, selon la taille de l'image et

Soutien pour 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

du navigateur. Jusqu'à ce moment..:

fin de la transmission

la solution est d'utiliser step-down pour obtenir un résultat correct. La réduction progressive 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 de bons résultats aussi avec une interpolation bi-linéaire (il se comporte en fait un peu comme bi-cubic en faisant cela) et le plafond est minimal car il y a moins de pixels à échantillonner à chaque étape.

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

violon modifié

utilisant l'échelle directe comme dans la question originale:

NORMAL DOWN-SCALED IMAGE

en descendant comme indiqué ci-dessous:

DOWN-STEPPED IMAGE

dans ce cas, vous aurez besoin de descendre en 3 étapes:

à l'étape 1, nous réduisons l'image à la moitié en utilisant une toile 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);

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

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

et nous dessinons une fois de plus à la toile principale, encore une fois 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 nécessaires, en utilisant cette formule (elle inclut l'étape finale pour fixer la taille cible):

steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))
158
répondu epistemex 2018-10-06 06:23:56

je recommande fortement pica pour de telles tâches. Sa qualité est supérieure à de multiples réduction de taille et est très rapide en même temps. Voici une Démo .

12
répondu avalanche1 2015-06-30 19:25:13
    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
répondu kamil 2014-04-23 14:41:33

en plus de la réponse de Ken, voici une autre solution pour effectuer le downsampling en moitiés (de sorte que le résultat semble bon en utilisant 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
répondu Jesús Carrera 2014-08-27 14:12:29

j'ai créé un service angulaire réutilisable pour gérer le redimensionnement de haute qualité des images pour quiconque est intéressé: https://gist.github.com/fisch0920/37bac5e741eaec60e983

le service inclut L'approche de réduction d'échelle 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 parce qu'elles ont toutes deux leurs avantages et inconvénients. Le l'approche par convolution de lanczos est de meilleure qualité au prix d'un ralentissement, tandis que l'approche par étapes de réduction d'échelle produit des résultats raisonnablement antialiasés et est significativement plus rapide.

exemple d'usage:

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
répondu fisch2 2017-05-23 11:47:08

dans le cas où quelqu'un d'autre cherche réponse encore, il y a une autre façon dont vous pouvez utiliser l'image de fond au lieu de drawImage(). Vous ne perdrez pas de 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";

"151960920 de travail" démo

0
répondu Munkhjargal Narmandakh 2018-05-24 20:59:41