Kann ich Antialiasing für ein HTML-Element <canvas> deaktivieren?

84

Ich spiele mit dem <canvas>Element herum , zeichne Linien und so.

Ich habe festgestellt, dass meine diagonalen Linien antialiasiert sind. Ich würde den zackigen Look für das, was ich mache, vorziehen - gibt es eine Möglichkeit, diese Funktion auszuschalten?

Blorgbeard ist raus
quelle
Ich denke, das hängt eher mit dem Browser zusammen. Vielleicht wären einige zusätzliche Informationen darüber, welche Software Sie verwenden, hilfreich.
Tomalak
Ich würde eine browserübergreifende Methode bevorzugen, aber eine Methode, die mit jedem einzelnen Browser funktioniert, wäre für mich immer noch interessant
Blorgbeard erscheint
Ich wollte nur sehen, ob sich an diesem Thema noch etwas geändert hat.
vternal3
Gibt es ein Update dazu?
Roland

Antworten:

60

Für Bilder gibt es jetzt .context.imageSmoothingEnabled= false

Es gibt jedoch nichts, was das Zeichnen von Linien explizit steuert. Möglicherweise müssen Sie Ihre eigenen Linien (auf die harte Tour ) mit getImageDataund zeichnen putImageData.

Kornel
quelle
1
Ich frage mich über die Leistung eines Javascript-Zeilenalgorithmus. Könnte Bresenham irgendwann einmal ausprobieren.
Blorgbeard ist
Browser-Anbieter werben in letzter Zeit für neue superschnelle JS-Engines, sodass es endlich eine gute Verwendung dafür geben würde.
Kornel
1
Funktioniert das wirklich? Ich zeichne eine Linie mit, putImageData aber es macht immer noch Aliasing von nahegelegenen Pixeln, verdammt.
Pacerier
Wenn ich auf eine kleinere Leinwand (Cache) zeichne und dann mit dieser Einstellung auf eine andere Leinwand zeichne, funktioniert dies wie beabsichtigt?
SparK
69

Zeichnen Sie Ihre 1-pixelLinien auf Koordinaten wie ctx.lineTo(10.5, 10.5). Das Zeichnen einer Ein-Pixel-Linie über den Punkt (10, 10)bedeutet, dass dieses 1Pixel an dieser Position bis 9.5zu 10.5zwei Linien reicht, die auf der Leinwand gezeichnet werden.

Ein netter Trick, den Sie nicht immer 0.5zu der tatsächlichen Koordinate hinzufügen müssen, über die Sie zeichnen möchten, wenn Sie viele Ein-Pixel-Linien haben, ist zu ctx.translate(0.5, 0.5)Beginn Ihre gesamte Leinwand.

Allan
quelle
hmm, ich habe Probleme, Anti-Aliasing mit dieser Technik loszuwerden. Vielleicht vermisse ich etwas zu verstehen? Würde es Ihnen etwas ausmachen, ein Beispiel zu veröffentlichen, wo?
Xavi
7
Dadurch wird Antialiasing nicht beseitigt, aber Antialiasing-Linien sehen viel besser aus - beispielsweise die peinlichen horizontalen oder vertikalen Linien, die zwei Pixel dick sind, wenn Sie tatsächlich ein Pixel wollten.
David gegeben
1
@porneL: Nein, Linien werden zwischen Ecken von Pixeln gezogen. Wenn Ihre Linie 1 Pixel breit ist, erstreckt sich das ein halbes Pixel in beide Richtungen
Eric
Das Hinzufügen von +0,5 funktioniert bei mir, hat es ctx.translate(0.5,0.5)aber nicht getan. am FF39.0
Paulo Bueno
Vielen Dank! Ich kann nicht glauben, dass ich zur Abwechslung tatsächlich 1px-Zeilen habe!
Chunky Chunk
24

Dies kann in Mozilla Firefox erfolgen. Fügen Sie dies Ihrem Code hinzu:

contextXYZ.mozImageSmoothingEnabled = false;

In Opera handelt es sich derzeit um eine Funktionsanforderung, die jedoch hoffentlich bald hinzugefügt wird.

Francholi
quelle
cool. +1 für Ihren Beitrag. Ich frage mich, ob die Deaktivierung von AA das Zeichnen von
Linien
7
Das OP möchte Anti-Alias-Zeilen entfernen, dies funktioniert jedoch nur bei Bildern. Per der Spezifikation bestimmt sie"whether pattern fills and the drawImage() method will attempt to smooth images if their pixels don't line up exactly with the display, when scaling images up"
rvighne
14

Es muss Antialias Vektorgrafiken

Antialiasing ist erforderlich, um Vektorgrafiken mit nicht ganzzahligen Koordinaten (0,4, 0,4) korrekt zu zeichnen, was alle bis auf sehr wenige Clients tun.

Wenn nicht ganzzahlige Koordinaten angegeben werden, hat die Zeichenfläche zwei Optionen:

  • Antialias - Malen Sie die Pixel um die Koordinate basierend darauf, wie weit die Ganzzahlkoordinate von der Nicht-Ganzzahlkoordinate entfernt ist (dh der Rundungsfehler).
  • Runden - Wenden Sie eine Rundungsfunktion auf die nicht ganzzahlige Koordinate an (so wird beispielsweise 1,4 zu 1).

Die spätere Strategie funktioniert für statische Grafiken, obwohl Kurven für kleine Grafiken (ein Kreis mit einem Radius von 2) eher klare Schritte als eine glatte Kurve zeigen.

Das eigentliche Problem ist, wenn die Grafiken übersetzt (verschoben) werden - die Sprünge zwischen einem Pixel und einem anderen (1,6 => 2, 1,4 => 1) bedeuten, dass der Ursprung der Form in Bezug auf den übergeordneten Container springen kann (sich ständig verschieben) 1 Pixel hoch / runter und links / rechts).

Einige Hinweise

Tipp 1 : Sie können Antialiasing mildern (oder härten), indem Sie die Leinwand skalieren (z. B. mit x) und dann die reziproke Skala (1 / x) selbst auf die Geometrien anwenden (ohne die Leinwand zu verwenden).

Vergleichen (keine Skalierung):

Ein paar Rechtecke

mit (Leinwandmaßstab: 0,75; manueller Maßstab: 1,33):

Gleiche Rechtecke mit weicheren Kanten

und (Leinwandmaßstab: 1,33; manueller Maßstab: 0,75):

Gleiche Rechtecke mit dunkleren Kanten

Tipp 2 : Wenn Sie wirklich nach einem zackigen Look suchen, versuchen Sie, jede Form einige Male zu zeichnen (ohne sie zu löschen). Mit jeder Zeichnung werden die Antialiasing-Pixel dunkler.

Vergleichen Sie. Nach einmaligem Zeichnen:

Ein paar Wege

Nach dreimaligem Zeichnen:

Gleiche Pfade, aber dunkler und kein sichtbares Antialiasing.

Izhaki
quelle
@vanowm Fühlen Sie sich frei zu klonen und zu spielen mit: github.com/Izhaki/gefri . Alle Bilder sind Screenshots aus dem Ordner / demo (mit leicht geändertem Code für Tipp 2). Ich bin sicher, Sie werden es leicht finden, den gezeichneten Figuren eine ganzzahlige Rundung hinzuzufügen (ich habe 4 Minuten gebraucht) und dann einfach zu ziehen, um den Effekt zu sehen.
Izhaki
9

Ich würde alles mit einem benutzerdefinierten Linienalgorithmus wie Bresenhams Linienalgorithmus zeichnen. Schauen Sie sich diese Javascript-Implementierung an: http://members.chello.at/easyfilter/canvas.html

Ich denke, das wird definitiv Ihre Probleme lösen.

Jón Trausti Arason
quelle
2
Genau das, was ich brauchte, würde ich nur hinzufügen, dass Sie implementieren müssen setPixel(x, y); Ich habe die akzeptierte Antwort hier verwendet: stackoverflow.com/questions/4899799/…
Tina Vall
8

Ich möchte hinzufügen, dass ich Probleme hatte, ein Bild zu verkleinern und auf Leinwand zu zeichnen. Es wurde immer noch geglättet, obwohl es beim Hochskalieren nicht verwendet wurde.

Ich habe damit gelöst:

function setpixelated(context){
    context['imageSmoothingEnabled'] = false;       /* standard */
    context['mozImageSmoothingEnabled'] = false;    /* Firefox */
    context['oImageSmoothingEnabled'] = false;      /* Opera */
    context['webkitImageSmoothingEnabled'] = false; /* Safari */
    context['msImageSmoothingEnabled'] = false;     /* IE */
}

Sie können diese Funktion folgendermaßen verwenden:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))

Vielleicht ist das für jemanden nützlich.

eri0o
quelle
warum nicht context.imageSmoothingEnabled = false?
Martijn Scheffer
Dies funktionierte nicht, als ich meine Antwort schrieb. Funktioniert es jetzt?
eri0o
1
Es war genau das Gleiche. Beim Schreiben von Javascript war und ist obj ['name'] oder obj.name immer das gleiche. Ein Objekt ist eine Sammlung benannter Werte (Tupel), die etwas verwenden, das a ähnelt Hash-Tabelle, beide Notationen werden gleich behandelt, es gibt überhaupt keinen Grund, warum Ihr Code vorher nicht funktioniert hätte, im schlimmsten Fall weist er einen Wert zu, der keine Auswirkung hat (weil er für einen anderen Browser bestimmt ist. Ein einfaches Beispiel: Schreiben obj = {a: 123}; console.log (obj ['a'] === obj.a? "Ja, es ist wahr": "Nein, ist es nicht")
Martijn Scheffer
Ich dachte du meinst warum all die anderen Dinge haben, was ich mit meinem Kommentar meinte ist, dass zu der Zeit Browser unterschiedliche Eigenschaften benötigten.
eri0o
ok ja natürlich :) ich sprach über die Syntax, nicht die Gültigkeit des Codes selbst (es funktioniert)
Martijn Scheffer
6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

Mit dieser Kombination kann ich schöne 1px dünne Linien zeichnen.

Retepaskab
quelle
6
Sie müssen die Linienbreite nicht auf 0,5 setzen ... das macht (oder sollte) nur die halbe Deckkraft.
aaaidan
4

Beachten Sie einen sehr begrenzten Trick. Wenn Sie ein 2-Farben-Bild erstellen möchten, können Sie eine beliebige Form mit der Farbe # 010101 auf einem Hintergrund mit der Farbe # 000000 zeichnen. Sobald dies erledigt ist, können Sie jedes Pixel in der imageData.data [] testen und auf 0xFF setzen, unabhängig davon, welcher Wert nicht 0x00 ist:

imageData = context2d.getImageData (0, 0, g.width, g.height);
for (i = 0; i != imageData.data.length; i ++) {
    if (imageData.data[i] != 0x00)
        imageData.data[i] = 0xFF;
}
context2d.putImageData (imageData, 0, 0);

Das Ergebnis ist ein nicht antialiasiertes Schwarzweißbild. Dies wird nicht perfekt sein, da ein gewisses Antialiasing stattfinden wird, aber dieses Antialiasing wird sehr begrenzt sein, da die Farbe der Form der Farbe des Hintergrunds sehr ähnlich ist.

StashOfCode
quelle
1

Für diejenigen, die noch nach Antworten suchen. Hier ist meine Lösung.

Angenommen, das Bild ist 1 Kanal grau. Ich habe gerade nach ctx.stroke () einen Schwellenwert festgelegt.

ctx.beginPath();
ctx.moveTo(some_x, some_y);
ctx.lineTo(some_x, some_y);
...
ctx.closePath();
ctx.fill();
ctx.stroke();

let image = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
for(let x=0; x < ctx.canvas.width; x++) {
  for(let y=0; y < ctx.canvas.height; y++) {
    if(image.data[x*image.height + y] < 128) {
      image.data[x*image.height + y] = 0;
    } else {
      image.data[x*image.height + y] = 255;
    }
  }
}

Wenn Ihr Bildkanal 3 oder 4 ist, müssen Sie den Array-Index wie folgt ändern

x*image.height*number_channel + y*number_channel + channel
Jaewon.AC
quelle
0

Nur zwei Anmerkungen zur Antwort von StashOfCode:

  1. Es funktioniert nur für eine graustufige, undurchsichtige Leinwand (fillRect mit Weiß, dann Zeichnen mit Schwarz oder umgekehrt)
  2. Es kann fehlschlagen, wenn die Linien dünn sind (~ 1px Linienbreite).

Es ist besser, dies stattdessen zu tun:

Strich und fülle mit #FFFFFF, dann mache das:

imageData.data[i] = (imageData.data[i] >> 7) * 0xFF

Das löst es für Linien mit 1px Breite.

Abgesehen davon ist die Lösung von StashOfCode perfekt, da Sie keine eigenen Rasterfunktionen schreiben müssen (denken Sie nicht nur an Linien, sondern auch an Bezier, Kreisbögen, gefüllte Polygone mit Löchern usw.).

Matías Moreno
quelle
0

Hier ist eine grundlegende Implementierung des Bresenham-Algorithmus in JavaScript. Es basiert auf der in diesem Wikipedia-Artikel beschriebenen ganzzahlig-arithmetischen Version: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

    function range(f=0, l) {
        var list = [];
        const lower = Math.min(f, l);
        const higher = Math.max(f, l);
        for (var i = lower; i <= higher; i++) {
            list.push(i);
        }
        return list;
    }

    //Don't ask me.
    //https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    function bresenhamLinePoints(start, end) {

        let points = [];

        if(start.x === end.x) {
            return range(f=start.y, l=end.y)
                        .map(yIdx => {
                            return {x: start.x, y: yIdx};
                        });
        } else if (start.y === end.y) {
            return range(f=start.x, l=end.x)
                        .map(xIdx => {
                            return {x: xIdx, y: start.y};
                        });
        }

        let dx = Math.abs(end.x - start.x);
        let sx = start.x < end.x ? 1 : -1;
        let dy = -1*Math.abs(end.y - start.y);
        let sy = start.y < end.y ? 1 : - 1;
        let err = dx + dy;

        let currX = start.x;
        let currY = start.y;

        while(true) {
            points.push({x: currX, y: currY});
            if(currX === end.x && currY === end.y) break;
            let e2 = 2*err;
            if (e2 >= dy) {
                err += dy;
                currX += sx;
            }
            if(e2 <= dx) {
                err += dx;
                currY += sy;
            }
        }

        return points;

    }
elliottdehn
quelle