Przyczyna
Niektóre obrazy są po prostu bardzo trudne do próbkowania i interpolacji, na przykład ten z krzywymi, gdy chcesz przejść od dużego do małego.
Wygląda na to, że przeglądarki zazwyczaj używają interpolacji bi-liniowej (próbkowanie 2x2) z elementem canvas, a nie bi-sześciennej (próbkowanie 4x4) z (prawdopodobnych) powodów wydajnościowych.
Jeśli krok jest zbyt duży, po prostu nie ma wystarczającej liczby pikseli do próbkowania, co znajduje odzwierciedlenie w wyniku.
Z perspektywy sygnału / procesora DSP można to postrzegać jako zbyt wysoką wartość progową filtru dolnoprzepustowego, co może skutkować aliasowaniem, jeśli w sygnale występuje wiele wysokich częstotliwości (szczegółów).
Rozwiązanie
Aktualizacja 2018:
Oto fajna sztuczka, której możesz użyć w przeglądarkach, które obsługują filter
właściwość w kontekście 2D. To wstępnie rozmywa obraz, co w istocie jest tym samym, co resampling, a następnie skaluje się w dół. Pozwala to na duże kroki, ale wymaga tylko dwóch kroków i dwóch losowań.
Rozmycie wstępne, używając liczby kroków (rozmiar oryginalny / rozmiar docelowy / 2) jako promienia (może być konieczne dostosowanie tego heurystycznego w oparciu o przeglądarkę i kroki nieparzyste / parzyste - tutaj pokazano tylko uproszczone):
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>
Obsługa filtra jako ogf Oct / 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
Aktualizacja 2017: W specyfikacji została zdefiniowana nowa właściwość do ustawiania jakości ponownego próbkowania:
context.imageSmoothingQuality = "low|medium|high"
Obecnie jest obsługiwany tylko w Chrome. Decyzję o metodach stosowanych na poziomie pozostawia się sprzedawcy, ale rozsądnie jest założyć, że Lanczos ma „wysoką” lub równoważną jakość. Oznacza to, że obniżanie może zostać całkowicie pominięte lub można zastosować większe kroki z mniejszą liczbą przerysowań, w zależności od rozmiaru obrazu i
Wsparcie dla 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
przeglądarka. Do tego czasu ...:
Koniec transmisji
Rozwiązaniem jest użycie obniżenia, aby uzyskać właściwy wynik. Zmniejszenie oznacza stopniowe zmniejszanie rozmiaru, aby umożliwić ograniczonemu zakresowi interpolacji pokrycie wystarczającej liczby pikseli do próbkowania.
Pozwoli to na dobre wyniki również w przypadku interpolacji bi-liniowej (w rzeczywistości zachowuje się ona podobnie jak bi-sześcienna), a narzut jest minimalny, ponieważ w każdym kroku jest mniej pikseli do próbkowania.
Idealnym krokiem jest przejście do połowy rozdzielczości w każdym kroku, aż do ustawienia docelowego rozmiaru (dzięki Joe Mabel za wspomnienie o tym!).
Zmodyfikowane skrzypce
Używanie bezpośredniego skalowania jak w pierwotnym pytaniu:
Korzystanie z obniżania, jak pokazano poniżej:
W takim przypadku musisz zejść w 3 krokach:
W kroku 1 zmniejszamy obraz do połowy, używając płótna pozaekranowego:
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);
Krok 2 ponownie wykorzystuje obszar roboczy poza ekranem i ponownie rysuje obraz zmniejszony do połowy:
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
I jeszcze raz rysujemy na głównym płótnie, ponownie zmniejszonym do połowy, ale do ostatecznego rozmiaru:
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
Wskazówka:
Możesz obliczyć całkowitą liczbę potrzebnych kroków, korzystając z tego wzoru (zawiera ostatni krok ustawiania rozmiaru docelowego):
steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))
Gorąco polecam pica do takich zadań. Jego jakość przewyższa wielokrotne zmniejszanie rozmiaru i jest jednocześnie dość szybka. Oto demo .
źródło
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,/, ""); }
źródło
Jako dodatek do odpowiedzi Kena, oto inne rozwiązanie, aby wykonać downsampling w połowie (więc wynik wygląda dobrze przy użyciu algorytmu przeglądarki):
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] );
źródło
W przypadku, gdy ktoś jeszcze szuka odpowiedzi, istnieje inny sposób, w jaki możesz użyć obrazka tła zamiast drawImage (). W ten sposób nie stracisz żadnej jakości obrazu.
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";
działające demo
źródło
Stworzyłem usługę Angular wielokrotnego użytku do obsługi wysokiej jakości zmiany rozmiaru obrazów dla każdego, kto jest zainteresowany: https://gist.github.com/fisch0920/37bac5e741eaec60e983
Usługa obejmuje stopniowe zmniejszanie skali Kena, a także zmodyfikowaną wersję podejścia splotu lanczosa, które można znaleźć tutaj .
Uwzględniłem oba rozwiązania, ponieważ oba mają swoje wady / zalety. Podejście splotu Lanczosa zapewnia wyższą jakość kosztem wolniejszego, podczas gdy podejście stopniowego zmniejszania skali daje rozsądnie antyaliasowane wyniki i jest znacznie szybsze.
Przykładowe użycie:
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 }) })
źródło