Html5 canvas drawImage: wie man Antialiasing anwendet

Lesezeit: 8 Minuten

Html5 canvas drawImage wie man Antialiasing anwendet
Dundar

Schauen Sie sich bitte folgendes Beispiel an:

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";

Wie Sie sehen, ist das Bild nicht geglättet, obwohl es heißt, dass drawImage Anti-Aliasing automatisch anwendet. Ich habe viele verschiedene Möglichkeiten ausprobiert, aber es scheint nicht zu funktionieren. Können Sie mir bitte sagen, wie ich ein geglättetes Bild erhalten kann? Danke.

Weil

Einige Bilder lassen sich nur sehr schwer heruntersampeln und interpolieren wie diese mit Kurven, wenn Sie von einer großen Größe zu einer kleinen wechseln möchten.

Browser scheinen aus (wahrscheinlichen) Leistungsgründen eher eine bilineare (2×2-Abtastung) Interpolation mit dem Canvas-Element als eine bikubische (4×4-Abtastung) zu verwenden.

Wenn der Schritt zu groß ist, gibt es einfach nicht genügend Pixel zum Abtasten, was sich im Ergebnis widerspiegelt.

Aus Signal-/DSP-Perspektive könnten Sie dies als zu hoch eingestellten Schwellenwert eines Tiefpassfilters sehen, was zu Aliasing führen kann, wenn das Signal viele hohe Frequenzen (Details) enthält.

Lösung

Aktualisierung 2018:

Hier ist ein netter Trick, den Sie für Browser verwenden können, die das unterstützen filter Eigenschaft im 2D-Kontext. Dadurch wird das Bild vorab verwischt, was im Wesentlichen einem Resampling entspricht, und dann herunterskaliert. Dies ermöglicht große Schritte, benötigt aber nur zwei Schritte und zwei Züge.

Pre-Blur mit Anzahl der Schritte (Originalgröße / Zielgröße / 2) als Radius (möglicherweise müssen Sie dies heuristisch basierend auf Browser und ungeraden/geraden Schritten anpassen – hier nur vereinfacht dargestellt):

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 = "https://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>

Unterstützung für Filter ab ogf Okt/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

Aktualisierung 2017: Es gibt jetzt eine neues Eigentum definiert in den Spezifikationen zum Einstellen der Resampling-Qualität:

context.imageSmoothingQuality = "low|medium|high"

Es wird derzeit nur in Chrome unterstützt. Die tatsächliche Methode, die pro Stufe verwendet wird, bleibt dem Anbieter überlassen, aber es ist vernünftig, Lanczos für “hoch” oder eine gleichwertige Qualität anzunehmen. Dies bedeutet, dass je nach Bildgröße und Bildgröße das Herunterstufen ganz übersprungen werden kann oder größere Schritte mit weniger Neuzeichnungen verwendet werden können

Unterstützung für 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

Browser. Bis dann..:
Ende der Übertragung

Die Lösung ist zu verwenden Absteigen um ein ordentliches Ergebnis zu bekommen. Step-down bedeutet, dass Sie die Größe schrittweise reduzieren, damit der begrenzte Interpolationsbereich genügend Pixel für das Sampling abdeckt.

Dies ermöglicht auch bei bilinearer Interpolation gute Ergebnisse (sie verhält sich dabei tatsächlich ähnlich wie bikubisch), und der Overhead ist minimal, da in jedem Schritt weniger Pixel abzutasten sind.

Der ideale Schritt ist zu gehen halbe Auflösung in jedem Schritt, bis Sie die Zielgröße festgelegt haben (danke an Joe Mabel für die Erwähnung!).

Modifizierte Geige

Verwenden der direkten Skalierung wie in der ursprünglichen Frage:

NORMAL VERKLEINERTES BILD

Verwendung von Step-Down wie unten gezeigt:

ABGESCHRITTENES BILD

In diesem Fall müssen Sie in 3 Schritten zurücktreten:

In Schritt 1 reduzieren wir das Bild auf die Hälfte, indem wir eine Off-Screen-Leinwand verwenden:

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

Schritt 2 verwendet die Leinwand außerhalb des Bildschirms erneut und zeichnet das Bild erneut auf die Hälfte reduziert:

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

Und wir ziehen noch einmal auf die Hauptleinwand, wieder reduziert zur Hälfte aber zur endgültigen größe:

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

Tipp:

Sie können die Gesamtzahl der erforderlichen Schritte mithilfe dieser Formel berechnen (sie enthält den letzten Schritt zum Festlegen der Zielgröße):

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

  • Wenn ich mit einigen sehr großen Anfangsbildern arbeite (8000 x 6000 und höher), finde ich es nützlich, Schritt 2 grundsätzlich zu wiederholen, bis ich innerhalb eines Faktors von 2 der gewünschten Größe erreiche.

    – Joe Mabel

    21. Oktober 2014 um 21:40 Uhr

  • Klappt wunderbar! Danke!

    – Vlad Zepelew

    22. Oktober 2014 um 13:34 Uhr


  • Ich bin verwirrt über den Unterschied zwischen dem 2. und 3. Schritt … kann jemand erklären?

    – Karinlynchin

    9. Februar 2016 um 21:15 Uhr

  • @Carine, es ist ein bisschen kompliziert, aber Canvas versucht, ein PNG so schnell wie möglich zu speichern. Die PNG-Datei unterstützt intern 5 verschiedene Filtertypen, die sich durch Komprimierung (gzip) verbessern können, aber um die beste Kombination zu finden, müssen alle diese Filter pro Zeile des Bildes getestet werden. Das wäre bei großen Bildern zeitaufwändig und könnte den Browser blockieren, daher verwenden die meisten Browser einfach Filter 0 und verschieben ihn in der Hoffnung, eine gewisse Komprimierung zu erzielen. Sie könnten diesen Vorgang manuell durchführen, aber es ist offensichtlich etwas mehr Arbeit. Oder führen Sie es über Service-APIs wie die von tinypng.com aus.

    Benutzer1693593

    7. April 2016 um 21:53 Uhr

  • @Kaiido es ist nicht vergessen und “Kopieren” ist sehr langsam. Wenn Sie Transparenz benötigen, ist es schneller, clearRect() zu verwenden und main oder alt zu verwenden. Leinwand als Ziel.

    Benutzer1693593

    13. September 2017 um 7:51 Uhr

Ich empfehle sehr Bild für solche Aufgaben. Seine Qualität ist dem mehrfachen Downsizing überlegen und gleichzeitig ziemlich schnell. Hier ist ein Demo.

1646179328 24 Html5 canvas drawImage wie man Antialiasing anwendet
Jesus Carrera

Als Ergänzung zu Kens Antwort hier eine weitere Lösung, um das Downsampling in zwei Hälften durchzuführen (damit das Ergebnis mit dem Algorithmus des Browsers gut aussieht):

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

1646179329 856 Html5 canvas drawImage wie man Antialiasing anwendet
kamil

    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,/, "");
}

Falls jemand anderes noch nach einer Antwort sucht, gibt es eine andere Möglichkeit, Hintergrundbilder anstelle von drawImage() zu verwenden. Auf diese Weise verlieren Sie keine Bildqualität.

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";

funktionierende Demo

1646179329 309 Html5 canvas drawImage wie man Antialiasing anwendet
Gemeinschaft

Ich habe einen wiederverwendbaren Angular-Dienst erstellt, um die Größenänderung von Bildern in hoher Qualität für alle Interessierten zu handhaben: https://gist.github.com/fisch0920/37bac5e741eaec60e983

Der Service umfasst Kens schrittweisen Herunterskalierungsansatz sowie eine modifizierte Version des Lanczos-Faltungsansatzes, der hier zu finden ist.

Ich habe beide Lösungen aufgenommen, weil sie beide ihre eigenen Vor- und Nachteile haben. Der Lanczos-Faltungsansatz hat eine höhere Qualität auf Kosten der Langsamkeit, wohingegen der schrittweise Herunterskalierungsansatz vernünftig geglättete Ergebnisse liefert und wesentlich schneller ist.

Beispielnutzung:

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
    })
})

906240cookie-checkHtml5 canvas drawImage: wie man Antialiasing anwendet

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy