Ursache
Einige Bilder sind nur sehr schwer herunterzusampeln und zu interpolieren, wie dieses mit Kurven, wenn Sie von einer großen zu einer kleinen Größe wechseln möchten.
Browser scheinen aus (wahrscheinlichen) Leistungsgründen in der Regel eine bi-lineare Interpolation (2x2-Abtastung) mit dem Canvas-Element anstelle einer bi-kubischen (4x4-Abtastung) zu verwenden.
Wenn der Schritt zu groß ist, gibt es einfach nicht genügend Pixel zum Abtasten, von denen sich das Ergebnis widerspiegelt.
Aus Signal- / DSP-Sicht könnte dies als zu hoher Schwellenwert eines Tiefpassfilters angesehen werden, was zu einem Aliasing führen kann, wenn das Signal viele hohe Frequenzen (Details) enthält.
Lösung
Update 2018:
Hier ist ein netter Trick, den Sie für Browser verwenden können, der die filter
Eigenschaft im 2D-Kontext unterstützt. Dadurch wird das Bild vorab verwischt, was im Wesentlichen dem Resampling entspricht, und dann verkleinert. Dies ermöglicht große Schritte, benötigt jedoch nur zwei Schritte und zwei Ziehungen.
Vorunschärfe mit Anzahl der Schritte (Originalgröße / Zielgröße / 2) als Radius (möglicherweise müssen Sie diese heuristisch basierend auf dem 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() {
const oc = document.createElement('canvas');
const octx = oc.getContext('2d');
oc.width = this.width;
oc.height = this.height;
const steps = (oc.width / canvas.width)>>1;
octx.filter = `blur(${steps}px)`;
octx.drawImage(this, 0, 0);
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>
Unterstützung für Filter ab Oktober 2018:
CanvasRenderingContext2D.filter
api.CanvasRenderingContext2D.filter
On Standard Track, Experimental
https:
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
Update 2017: In den Spezifikationen ist jetzt eine neue Eigenschaft zum Festlegen der Resampling-Qualität definiert:
context.imageSmoothingQuality = "low|medium|high"
Es wird derzeit nur in Chrome unterstützt. Die tatsächlichen Methoden, die pro Stufe verwendet werden, sind dem Anbieter überlassen, aber es ist vernünftig, Lanczos für "hoch" oder eine gleichwertige Qualität anzunehmen. Dies bedeutet, dass das Herunterschalten insgesamt übersprungen werden kann oder größere Schritte mit weniger Neuzeichnungen verwendet werden können, abhängig von der Bildgröße und
Unterstützung für imageSmoothingQuality
:
CanvasRenderingContext2D.imageSmoothingQuality
api.CanvasRenderingContext2D.imageSmoothingQuality
On Standard Track, Experimental
https:
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 dahin ..:
Ende der Übertragung
Die Lösung besteht darin, Step-Down zu verwenden, um ein korrektes Ergebnis zu erzielen. Step-down bedeutet, dass Sie die Größe schrittweise reduzieren, damit der begrenzte Interpolationsbereich genügend Pixel für die Abtastung abdeckt.
Dies ermöglicht auch bei der bi-linearen Interpolation gute Ergebnisse (sie verhält sich dabei tatsächlich ähnlich wie die bi-kubische Interpolation), und der Overhead ist minimal, da in jedem Schritt weniger Pixel abgetastet werden müssen.
Der ideale Schritt besteht darin, in jedem Schritt die Hälfte der Auflösung zu wählen, bis Sie die Zielgröße festgelegt haben (danke an Joe Mabel für die Erwähnung!).
Geänderte Geige
Verwenden der direkten Skalierung wie in der ursprünglichen Frage:
Verwenden Sie Step-Down wie unten gezeigt:
In diesem Fall müssen Sie in drei Schritten zurücktreten:
In Schritt 1 reduzieren wir das Bild auf die Hälfte, indem wir eine Leinwand außerhalb des Bildschirms verwenden:
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);
In Schritt 2 wird die Leinwand außerhalb des Bildschirms wiederverwendet und das auf die Hälfte reduzierte Bild erneut gezeichnet:
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
Und wir zeichnen noch einmal auf die Hauptleinwand, wieder auf die Hälfte reduziert , aber auf die endgültige Größe:
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
Trinkgeld:
Mit dieser Formel können Sie die Gesamtzahl der erforderlichen Schritte berechnen (sie enthält den letzten Schritt zum Festlegen der Zielgröße):
steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))