Deaktivieren Sie die Interpolation beim Skalieren eines <Canvas>

125

HINWEIS : Dies hängt damit zusammen, wie vorhandene Canvas-Elemente beim Skalieren gerendert werden , nicht damit, wie Linien oder Grafiken auf einer Canvas-Oberfläche gerendert werden . Mit anderen Worten, hat dies alles zu tun mit Interpolation von skalierten Elementen , und nichts mitzu tun , den Anti - Aliasing von Grafiken auf einer Leinwand gezogen. Es geht mir nicht darum, wie der Browser Linien zeichnet. Mir ist wichtig, wie der Browser das Canvas-Element selbst rendert,wenn es vergrößert wird.


Gibt es eine Canvas-Eigenschaft oder eine Browsereinstellung, die ich programmgesteuert ändern kann, um die Interpolation beim Skalieren von <canvas>Elementen zu deaktivieren ? Eine browserübergreifende Lösung ist ideal, aber nicht unbedingt erforderlich. Webkit-basierte Browser sind mein Hauptziel. Leistung ist sehr wichtig.

Diese Frage ist am ähnlichsten, veranschaulicht das Problem jedoch nicht ausreichend. Für das, was es wert ist, habe ich vergeblich versucht image-rendering: -webkit-optimize-contrast.

Die Anwendung wird ein "Retro" 8-Bit-Spiel sein, das in HTML5 + JS geschrieben wurde, um klar zu machen, was ich brauche.


Zur Veranschaulichung hier ein Beispiel. ( Live-Version )

Angenommen, ich habe eine 21x21 Leinwand ...

<canvas id='b' width='21' height='21'></canvas>

... mit CSS, das das Element 5-mal größer macht (105x105):

canvas { border: 5px solid #ddd; }
canvas#b { width: 105px; height: 105px; } /* 5 * 21 = 105 */

Ich zeichne ein einfaches 'X' wie folgt auf die Leinwand:

$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

Das Bild links zeigt Chrom (14.0). Das Bild rechts ist das, was ich möchte (zur Veranschaulichung handgezeichnet).

Chrome interpoliert skalierte Canvas-Elemente Eine nicht interpolierte Version

namuol
quelle
1
Etwas verwandt, aber nicht identisch: stackoverflow.com/questions/195262/…
HostileFork sagt, vertraue SE
1
Ich glaube, diese Frage bezieht sich auf die Strichzeichnungsfunktionen, die Anti-Alias-Linien zeichnen. Ich beziehe mich darauf, wie die Größe von Canvas-Elementen standardmäßig mit Interpolation gerendert wird.
Namuol
Ja, sorry ... Ich dachte zuerst die gleichen Fragen und bemerkte dann sofort meinen Fehler. : - / Was wäre, wenn Sie versuchen würden, den Pixelierungsfilter von jsmanipulate zu verwenden? joelb.me/jsmanipulate
HostileFork sagt, vertraue SE
1
Das ist eher ein Bildfilter für Fotos.
Namuol
Ja, aber wenn es Ihr linkes Bild in eine ausreichend gute Version Ihres rechten Bildes verwandelt, um Sie glücklich zu machen, könnte es eine browserübergreifende Lösung bieten (wenn man bedenkt, dass es nicht zu vielversprechend ist, eine Flagge für diesen Zeichenmodus zu finden, universell oder auf andere Weise , in WebKit). Ob es sich um einen brauchbaren Ersatz handelt, hängt von dem Problem ab, das Sie lösen möchten ... ob Sie wirklich eine diskrete Pixelzeichnung benötigen oder nur versuchen, sicherzustellen, dass das Ergebnis mit einer bestimmten Granularität pixelig ist.
HostileFork sagt, vertraue SE

Antworten:

125

Letzte Aktualisierung: 12.09.2014

Gibt es eine Canvas-Eigenschaft oder eine Browsereinstellung, die ich programmgesteuert ändern kann, um die Interpolation beim Skalieren von Elementen zu deaktivieren?

Die Antwort ist vielleicht eines Tages . Im Moment müssen Sie auf Hack-arounds zurückgreifen, um das zu bekommen, was Sie wollen.


image-rendering

Der Arbeitsentwurf von CSS3 beschreibt eine neue Eigenschaft,image-rendering die tun soll, was ich will:

Die Eigenschaft zum Rendern von Bildern gibt dem Benutzeragenten einen Hinweis darauf, welche Aspekte eines Bildes bei der Skalierung des Bildes am wichtigsten sind, um den Benutzeragenten bei der Auswahl eines geeigneten Skalierungsalgorithmus zu unterstützen.

Die Spezifikation beschreibt drei akzeptierte Werte: auto, crisp-edges, und pixelated.

pixelig:

Beim Skalieren des Bildes muss der "nächste Nachbar" oder ein ähnlicher Algorithmus verwendet werden, damit das Bild einfach aus sehr großen Pixeln besteht. Beim Verkleinern entspricht dies Auto.

Standard? Browserübergreifend?

Da dies lediglich ein Arbeitsentwurf ist , gibt es keine Garantie dafür, dass dies zum Standard wird. Die Browserunterstützung ist derzeit bestenfalls unvollständig.

Das Mozilla Developer Network hat eine ziemlich gründliche Seite, die dem aktuellen Stand der Technik gewidmet ist und die ich sehr empfehlen kann.

Die Webkit-Entwickler haben dies zunächst vorläufig als implementiert-webkit-optimize-contrast , aber Chromium / Chrome scheint keine Version von Webkit zu verwenden, die dies implementiert.

Update: 2014-09-12

Chrome 38 unterstützt jetztimage-rendering: pixelated !

Firefox hat einen Fehlerbericht geöffnet, der image-rendering: pixelatedimplementiert werden kann, -moz-crisp-edgesfunktioniert aber vorerst.

Lösung?

Die bisher plattformübergreifendste CSS-Lösung ist daher:

canvas {
  image-rendering: optimizeSpeed;             /* Older versions of FF          */
  image-rendering: -moz-crisp-edges;          /* FF 6.0+                       */
  image-rendering: -webkit-optimize-contrast; /* Safari                        */
  image-rendering: -o-crisp-edges;            /* OS X & Windows Opera (12.02+) */
  image-rendering: pixelated;                 /* Awesome future-browsers       */
  -ms-interpolation-mode: nearest-neighbor;   /* IE                            */
}

Leider funktioniert dies noch nicht auf allen wichtigen HTML5-Plattformen (insbesondere Chrome).

Natürlich könnte man Bilder manuell mithilfe der Interpolation des nächsten Nachbarn auf hochauflösende Leinwandoberflächen in Javascript skalieren oder sogar Bilder serverseitig vorskalieren, aber in meinem Fall ist dies unerschwinglich teuer, so dass dies keine praktikable Option ist.

ImpactJS verwendet eine Textur- Vorskalierungstechnik , um all diese FUD zu umgehen . Der Entwickler von Impact, Dominic Szablewski, schrieb einen sehr ausführlichen Artikel darüber (er zitierte diese Frage sogar in seiner Forschung).

Siehe Simons Antwort für eine Canvas-basierte Lösung, die auf der imageSmoothingEnabledEigenschaft basiert (nicht in älteren Browsern verfügbar, aber einfacher als die Vorskalierung und ziemlich weit verbreitet).

Live-Demo

Wenn Sie die im MDN-Artikel beschriebenen CSS-Eigenschaften für canvasElemente testen möchten , habe ich diese Geige erstellt, die je nach Browser verschwommen oder nicht angezeigt werden soll: Ein 4: 1-Bild (64 x 64 bis 256 x 256) eines isometrischen Fernsehgeräts im Pixel-Art-Stil

namuol
quelle
Das Rendern von Bildern scheint anderswo zu funktionieren, aber in Webkit-Browsern. Ich hoffe wirklich, dass die Mitarbeiter von Chrome / Chromium / Safari gut lesen, was "Umschalten auf EPX-Interpolation in Situationen mit geringer Last" bedeutet. Als ich den Satz zum ersten Mal las, dachte ich, dass der Optimierungskontrast neue Farben ermöglicht, die bei geringer Last erstellt werden, aber NEIN: en.wikipedia.org/wiki/…
Timo Kähkönen
2
Opera 12.02 unter Mac und Windows unterstützt das Rendern von Bildern : -o-scharfe Kanten ( jsfiddle.net/VAXrL/21 ), sodass Sie es Ihrer Antwort hinzufügen können.
Timo Kähkönen
Soll es funktionieren, wenn ich den Browser-Zoom vergrößere? Ich habe das Zeichnen mit FF 22, Chrome 27 und IE 10 unter Windows 7 getestet, wenn der Browser-Zoom 150% beträgt und das Ergebnis in allen Browsern verschwommen ist. Wenn ich die Breite und Höhe der Leinwand verdopple und sie mit CSS auf die ursprüngliche Breite und Höhe zurücksetze, werden scharfe Linien gezeichnet.
Pablo
Das ist eine gute Frage. Ich bin nicht sicher, ob es eine Spezifikation für das benutzerdefinierte Skalierungsverhalten gibt.
Namuol
1
Ich bin (aus der gegenwärtigen Zukunft!) Auf Chrome 78. Ich sehe "Bildwiedergabe: pixelig;" Arbeiten. Es sieht so aus, als ob dies 2015 zu Chrome 41 hinzugefügt wurde: developer.google.com/web/updates/2015/01/pixelated
MrColes
61

Neue Antwort 31.07.2012

Dies ist endlich in der Canvas-Spezifikation!

Die Spezifikation hat kürzlich eine Eigenschaft namens aufgerufen imageSmoothingEnabled, die standardmäßig verwendet trueund bestimmt, ob Bilder, die auf nicht ganzzahligen Koordinaten gezeichnet oder skaliert gezeichnet wurden, einen weicheren Algorithmus verwenden. Wenn es auf " falseNächster Nachbar" eingestellt ist, wird ein weniger glattes Bild erzeugt und stattdessen nur größer aussehende Pixel erzeugt.

Die Bildglättung wurde erst kürzlich zur Canvas-Spezifikation hinzugefügt und wird nicht von allen Browsern unterstützt. Einige Browser haben jedoch Versionen dieser Eigenschaft mit Herstellerpräfix implementiert. In dem mozImageSmoothingEnabledin Firefox sowie webkitImageSmoothingEnabledin Chrome und Safari vorhandenen Kontext wird das Auftreten von Anti-Aliasing verhindert, wenn diese auf false gesetzt werden. Leider haben IE9 und Opera diese Eigenschaft zum Zeitpunkt des Schreibens nicht implementiert, weder vorangestellt noch auf andere Weise.


Vorschau: JSFiddle

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Simon Sarris
quelle
1
Leider sieht es so aus, als ob dies auch noch nicht funktioniert. Vielleicht fehlt mir etwas? Hier ist ein Test: jsfiddle.net/VAXrL/187
namuol
12
Sie müssen mit der Canvas-API skalieren, damit es funktioniert. Sie können nie mit CSS skalieren und die Canvas-API hat Auswirkungen darauf! Also so etwas wie das: jsfiddle.net/VAXrL/190
Simon Sarris
1
Aber die Natur dieses Problems ist nicht wirklich Leinwand. Es geht darum, wie der Browser skalierte Bilder einschließlich <canvas> -Elemente rendert.
Namuol
4
Wenn Sie mit CSS skalieren, funktioniert die Lösung von namuol. Wenn Sie das Bild manuell auf die Leinwand skalieren, funktioniert Simons Lösung. Es ist schön, dass es für beide Fälle Lösungen gibt, also danke euch beiden!
Shaun Lebron
Für IE 11: msImageSmoothingEnabled funktioniert bei mir! msdn.microsoft.com/en-us/library/dn265062(v=vs.85).aspx
steve
11

Bearbeiten 31.07.2012 - Diese Funktionalität ist jetzt in der Canvas-Spezifikation! Siehe separate Antwort hier:

https://stackoverflow.com/a/11751817/154112

Alte Antwort ist unten für die Nachwelt.


Abhängig von Ihrem gewünschten Effekt haben Sie folgende Option:

var can = document.getElementById('b');
var ctx = can.getContext('2d');
ctx.scale(5,5);
$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

http://jsfiddle.net/wa95p/

Was das schafft:

Geben Sie hier die Bildbeschreibung ein

Wahrscheinlich nicht was du willst. Aber wenn Sie nur nach Unschärfe suchen, dann ist das das Ticket, also biete ich es für alle Fälle an.

Eine schwierigere Option besteht darin, die Pixelmanipulation zu verwenden und selbst einen Algorithmus für den Job zu schreiben. Jedes Pixel des ersten Bildes wird zu einem 5x5-Pixelblock auf dem neuen Bild. Mit Bilddaten wäre es nicht allzu schwer.

Aber Canvas und CSS allein helfen Ihnen hier nicht dabei, genau den gewünschten Effekt zu erzielen.

Simon Sarris
quelle
Ja, davor hatte ich Angst. Anscheinend image-rendering: -webkit-optimize-contrastsollte der Trick tun, scheint aber nichts zu tun. Ich könnte versuchen, eine Funktion zu rollen, um den Inhalt einer Leinwand mithilfe der Interpolation des nächsten Nachbarn zu skalieren.
Namuol
Ich habe Ihre Antwort ursprünglich akzeptiert. Ich habe es vorerst widerrufen, weil es Verwirrung darüber zu geben scheint, ob dies ein Duplikat ist oder nicht.
Namuol
Ich habe nach einigen Recherchen eine vollständigere Antwort und möchte die Frage erneut öffnen, um meine Antwort zu geben.
Namuol
3

In Google Chrome werden Leinwandbildmuster nicht interpoliert.

Hier ist ein Arbeitsbeispiel, das aus der namuol-Antwort http://jsfiddle.net/pGs4f/ bearbeitet wurde.

ctx.scale(4, 4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, 64, 64);
Saviski
quelle
Dies ist die attraktivste Problemumgehung, die ich bisher gesehen habe. Ich bin mir noch nicht sicher über die Leistung, aber es lohnt sich, einen Blick darauf zu werfen. Ich werde es ausprobieren.
Namuol
Die Verwendung von Wiederholung anstelle von Nicht-Wiederholung ist schneller. Ich weiß nicht warum. Es ist auch ziemlich schnell, three.js verwenden dies in CanvasRenderer
saviski
6
Update: funktioniert nicht mehr mit Chrome 21 (aufgrund der Unterstützung für Retina-Displays hinzugefügt?) Neue Version jsfiddle.net/saviski/pGs4f/12 mit imageSmoothingEnabled, gezeigt von Simon
saviski
Wenn die Auflösung der Netzhaut allgemein ist, ändern sich die Dinge enorm. Wenn das Retina-Display mit 326 dpi (128 dpcm) im iPhone4-5-Stil auf 52 x 32 cm (24 Zoll) Monitore kommt, beträgt die Bildschirmgröße 6656 x 4096 px. Dann ist Antialiasing schlecht und alle Browser-Anbieter (auch Webkit-basierte) müssen das Deaktivieren von Antialiasing zulassen. Antialiasing ist ein CPU-intensiver Betrieb, und Antialiasing-Bilder mit 6656 x 4096 Pixel sind zu langsam, aber in einer solchen Anzeige glücklicherweise nicht erforderlich.
Timo Kähkönen
1
Nur als Randnotiz, hier ist ein guter Benchmark über Muster gegen Bild: jsperf.com/drawimage-vs-canvaspattern/9
yckart
1

Saviskis hier erläuterte Problemumgehung ist vielversprechend, da sie funktioniert auf:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chromium 18.0.1025.168 (Developer Build 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

Funktioniert aber nicht im Folgenden, aber der gleiche Effekt kann mit CSS-Bildwiedergabe erzielt werden:

  • Firefox 15.0.1 Mac OS X 10.6.8 (Bildwiedergabe: -moz-crisp-edge funktioniert hier )
  • Opera 12.02 Mac OS X 10.6.8 (Bildwiedergabe: -o-scharfe Kanten funktionieren hier )
  • Opera 12.02 Windows 7 (Bildwiedergabe: -o-scharfe Kanten funktionieren hier )

Das Problem sind diese, weil ctx.XXXImageSmoothingEnabled nicht funktioniert und das Rendern von Bildern nicht funktioniert:

  • Safari 5.1.7 Mac OS X 10.6.8. (Bildwiedergabe: -webkit-Optimize-Contrast funktioniert NICHT)
  • Safari 5.1.7 Windows 7 (Bildwiedergabe: -webkit-Optimize-Contrast funktioniert NICHT)
  • IE 9 Windows 7 (-ms-Interpolationsmodus: Nächster Nachbar funktioniert NICHT)
Timo Kähkönen
quelle