Wyłącz interpolację podczas skalowania <canvas>

126

UWAGA : Ma to związek ze sposobem renderowania istniejących elementów kanwy po przeskalowaniu , a nie ze sposobem renderowania linii lub grafiki na powierzchni płótna . Innymi słowy, to ma wszystko wspólnego z interpolacją do skalowanych elementów i nie ma nic wspólnego z antialiasing grafiki są rysowane na płótnie. Nie obchodzi mnie, jak przeglądarka rysuje linie; Dbam o to, jak przeglądarka renderuje sam element canvas , gdy jest skalowany.


Czy istnieje właściwość kanwy lub ustawienie przeglądarki, które mogę zmienić programowo, aby wyłączyć interpolację podczas skalowania <canvas>elementów? Rozwiązanie dla różnych przeglądarek jest idealne, ale nie jest niezbędne; Moim głównym celem są przeglądarki oparte na Webkit. Wydajność jest bardzo ważna.

To pytanie jest najbardziej podobne, ale nie ilustruje wystarczająco problemu. image-rendering: -webkit-optimize-contrastBezskutecznie próbowałem .

Aplikacja będzie "retro" 8-bitową grą napisaną w HTML5 + JS, aby było jasne, czego potrzebuję.


Aby to zilustrować, oto przykład. ( wersja na żywo )

Załóżmy, że mam płótno 21x21 ...

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

... który ma css, który sprawia, że ​​element jest 5 razy większy (105x105):

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

Rysuję na płótnie prosty „X” w następujący sposób:

$('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();
});

Obraz po lewej stronie renderuje Chromium (14.0). Obraz po prawej jest tym, czego chcę (narysowany ręcznie w celach ilustracyjnych).

Chrome interpoluje skalowane elementy płótna Wersja nieinterpolowana

namuol
źródło
1
Nieco powiązane, choć nie identyczne: stackoverflow.com/questions/195262/ ...
HostileFork mówi nie ufaj SE
1
Uważam, że to pytanie odnosi się do funkcji rysowania linii, rysujących linie wygładzone. Mam na myśli sposób, w jaki elementy płótna o zmienionym rozmiarze są domyślnie renderowane z interpolacją.
namuol
Tak, przepraszam ... Najpierw pomyślałem, że pytania są takie same, a potem natychmiast zauważyłem mój błąd. : - / A co by było, gdybyś spróbował użyć filtru pikselowego jsmanipulate? joelb.me/jsmanipulate
HostileFork mówi nie ufaj SE
1
To bardziej filtr obrazu przeznaczony do zdjęć.
namuol
Tak, ale jeśli przekształci lewy obraz w wystarczająco dobrą wersję prawego obrazu, aby cię uszczęśliwić, może zapewnić rozwiązanie dla różnych przeglądarek (biorąc pod uwagę, że znalezienie flagi dla tego trybu rysowania, uniwersalnego lub innego) nie wygląda zbyt obiecująco , w WebKit). To, czy jest to opłacalny substytut, zależy od problemu, który próbujesz rozwiązać ... czy naprawdę potrzebujesz dyskretnego rysowania pikseli lub jeśli po prostu próbujesz upewnić się, że wynik jest pikselowany z określoną ziarnistością.
HostileFork mówi, że nie ufaj SE

Odpowiedzi:

126

Ostatnia aktualizacja: 2014-09-12

Czy istnieje właściwość kanwy lub ustawienie przeglądarki, które mogę zmienić programowo, aby wyłączyć interpolację podczas skalowania elementów?

Odpowiedź brzmi może kiedyś . Na razie będziesz musiał uciekać się do hacków, aby uzyskać to, czego chcesz.


image-rendering

Roboczy szkic CSS3 przedstawia nową właściwość,image-rendering która powinna zrobić to, co chcę:

Właściwość renderowania obrazu stanowi wskazówkę dla klienta użytkownika o tym, które aspekty obrazu są najważniejsze do zachowania podczas skalowania obrazu, aby pomóc agentowi użytkownika w wyborze odpowiedniego algorytmu skalowania.

Specyfikacja przedstawia trzy wartości przyjętych: auto, crisp-edges, i pixelated.

piksele:

Podczas skalowania obrazu w górę należy użyć algorytmu „najbliższego sąsiada” lub podobnego algorytmu, aby obraz wyglądał jak złożony z bardzo dużych pikseli. Skalowanie w dół działa tak samo, jak automatyczne.

Standard? Wiele przeglądarek?

Ponieważ jest to tylko robocza wersja robocza , nie ma gwarancji, że stanie się to standardem. Obsługa przeglądarek jest obecnie w najlepszym razie niestabilna.

Mozilla Developer Network ma całkiem dokładną stronę poświęconą aktualnemu stanowi techniki, którą gorąco polecam przeczytać.

Programiści Webkit początkowo zdecydowali się wstępnie zaimplementować to jako-webkit-optimize-contrast , ale Chromium / Chrome nie używają wersji Webkit, która to implementuje.

Aktualizacja: 2014-09-12

Chrome 38 obsługuje terazimage-rendering: pixelated !

Firefox ma otwarty raport o błędzie do image-rendering: pixelatedzaimplementowania, ale -moz-crisp-edgesna razie działa.

Rozwiązanie?

Jak dotąd najbardziej wieloplatformowe rozwiązanie tylko dla CSS to:

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                            */
}

Niestety, to nie zadziała jeszcze na wszystkich głównych platformach HTML5 (w szczególności Chrome).

Oczywiście można ręcznie skalować obrazy w górę za pomocą interpolacji najbliższego sąsiada na powierzchnie płótna o wysokiej rozdzielczości w javascript lub nawet obrazy wstępnie skalowane po stronie serwera, ale w moim przypadku będzie to kosztowne, więc nie jest to opłacalna opcja.

ImpactJS wykorzystuje technikę wstępnego skalowania tekstur, aby obejść to całe FUD. Deweloper firmy Impact, Dominic Szablewski, napisał na ten temat bardzo dogłębny artykuł (w swoich badaniach nawet zacytował to pytanie).

Zobacz odpowiedź Simona na rozwiązanie oparte na kanwie, które opiera się na imageSmoothingEnabledwłaściwości (niedostępne w starszych przeglądarkach, ale prostsze niż skalowanie wstępne i dość szeroko obsługiwane).

Live Demo

Jeśli chcesz przetestować właściwości CSS omówione w artykule MDN na temat canvaselementów, zrobiłem to skrzypce, które powinny wyświetlać coś takiego, rozmyte lub nie, w zależności od przeglądarki: obraz 4: 1 (64x64 do 256x256) izometrycznego telewizora w stylu pixel art

namuol
źródło
Renderowanie obrazu wydaje się działać w innym miejscu, ale przeglądarki Webkit. Naprawdę mam nadzieję, że pracownicy Chrome / Chromium / Safari dobrze czytają, co oznacza „przełącz się na interpolację EPX w sytuacjach niskiego obciążenia”. Kiedy po raz pierwszy przeczytałem zdanie, pomyślałem, że optymalizacja-kontrast pozwala na tworzenie nowych kolorów przy niskim obciążeniu, ale NIE: en.wikipedia.org/wiki/ ...
Timo Kähkönen
2
Opera 12.02 na komputery Mac i Windows obsługuje renderowanie obrazu: -o-ostre-krawędzie ( jsfiddle.net/VAXrL/21 ), więc możesz dodać to do swojej odpowiedzi.
Timo Kähkönen
Czy to ma działać, gdy zwiększam powiększenie przeglądarki? Testowałem rysowanie z FF 22, Chrome 27 i IE 10 w Windows 7, gdy powiększenie przeglądarki wynosiło 150%, a wynik jest rozmyty we wszystkich przeglądarkach. Kiedy podwajam szerokość i wysokość płótna i ustawiam z powrotem oryginalną szerokość i wysokość za pomocą CSS, rysuje ostre linie.
pablo
To dobre pytanie. Nie jestem pewien, czy istnieje specyfikacja dotycząca zachowania skalowania określonego przez użytkownika.
namuol
1
Jestem (z obecnej przyszłości!) Na Chrome 78. Widzę komunikat „renderowanie obrazu: piksele;” pracujący. Wygląda na to, że to zostało dodane w 2015 roku do Chrome 41: developers.google.com/web/updates/2015/01/pixelated
MrColes,
61

Nowa odpowiedź 31.07.2012

To jest wreszcie w specyfikacji płótna!

Specyfikacja ostatnio dodała właściwość o nazwie imageSmoothingEnabled, która jest wartością domyślną truei określa, czy obrazy narysowane na współrzędnych innych niż całkowite lub narysowane skalowane będą korzystały z płynniejszego algorytmu. Jeśli jest ustawiona na false, używany jest najbliższy sąsiad, co daje mniej gładki obraz, a zamiast tego po prostu powiększa wyglądające piksele.

Wygładzanie obrazu zostało niedawno dodane do specyfikacji kanwy i nie jest obsługiwane przez wszystkie przeglądarki, ale niektóre przeglądarki zaimplementowały wersje tej właściwości z prefiksem dostawcy. W kontekście istnieje mozImageSmoothingEnabledw Firefoksie, webkitImageSmoothingEnabledChrome i Safari, a ustawienie ich na fałsz zatrzyma antyaliasing. Niestety, w chwili pisania tego tekstu IE9 i Opera nie zaimplementowały tej właściwości, z prefiksem dostawcy lub w inny sposób.


Podgląd: JSFiddle

Wynik:

wprowadź opis obrazu tutaj

Simon Sarris
źródło
1
Niestety wygląda na to, że to też jeszcze nie działa. Może czegoś mi brakuje? Oto test: jsfiddle.net/VAXrL/187
namuol
12
aby działało, musisz skalować za pomocą API kanwy, nie możesz skalować za pomocą CSS i mieć wpływ na API kanwy! Coś takiego: jsfiddle.net/VAXrL/190
Simon Sarris
1
Ale natura tego problemu nie dotyczy tak naprawdę płótna. Chodzi o to, jak przeglądarka renderuje skalowane obrazy, w tym elementy <canvas>.
namuol
4
Jeśli skalujesz za pomocą CSS, rozwiązanie namuol będzie działać. Jeśli skalujesz obraz ręcznie na kanwie, rozwiązanie Simona działa. Fajnie, że są rozwiązania dla obu przypadków, więc dziękuję wam obojgu!
Shaun Lebron,
Dla IE 11: msImageSmoothingEnabled działa dla mnie! msdn.microsoft.com/en-us/library/dn265062(v=vs.85).aspx
steve
11

Edycja 31.07.2012 - Ta funkcja jest teraz w specyfikacji płótna! Zobacz oddzielną odpowiedź tutaj:

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

Stara odpowiedź jest poniżej dla potomnych.


W zależności od pożądanego efektu masz jedną opcję:

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/

Który to tworzy:

wprowadź opis obrazu tutaj

Prawdopodobnie nie to, czego chcesz. Ale gdybyś chciał tylko zerowego rozmycia, to byłby bilet, więc na wszelki wypadek zaoferuję go.

Trudniejszą opcją jest użycie manipulacji pikselami i samodzielne napisanie algorytmu do zadania. Każdy piksel pierwszego obrazu staje się blokiem pikseli 5x5 na nowym obrazie. Nie byłoby to trudne do zrobienia z obrazami.

Ale same płótno i CSS nie pomogą ci tutaj w skalowaniu jednego do drugiego z dokładnym efektem, którego pragniesz.

Simon Sarris
źródło
Tak, tego się bałem. Najwyraźniej image-rendering: -webkit-optimize-contrastpowinien załatwić sprawę, ale wydaje się, że nic nie robi. Mogę spojrzeć na zwijanie funkcji, aby przeskalować zawartość płótna za pomocą interpolacji najbliższego sąsiada.
namuol
Pierwotnie przyjąłem twoją odpowiedź; Cofnąłem to na razie, ponieważ wydaje się, że istnieje niejasność co do tego, czy jest to duplikat, czy nie.
namuol
Po przeprowadzeniu pewnych badań mam pełniejszą odpowiedź i chciałbym ponownie otworzyć pytanie, aby udzielić odpowiedzi.
namuol
3

W Google Chrome wzorce obrazów płótna nie są interpolowane.

Oto działający przykład zredagowany z odpowiedzi namuol http://jsfiddle.net/pGs4f/

ctx.scale(4, 4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, 64, 64);
saviski
źródło
To najbardziej atrakcyjne obejście, jakie do tej pory widziałem. Nie jestem jeszcze pewien wydajności, ale warto się temu przyjrzeć. Wypróbuję to.
namuol
używanie powtórzeń zamiast braku powtórzeń jest szybsze. Nie wiem dlaczego. Jest również dość szybki, three.js używają tego w CanvasRenderer
saviski
6
aktualizacja: nie działa już z chrome 21 (z powodu dodanej obsługi wyświetlania siatkówki?) nowa wersja jsfiddle.net/saviski/pGs4f/12 przy użyciu imageSmoothingEnabled wskazanego przez Simona
saviski
Kiedy rozdzielczość siatkówki staje się ogólna, sytuacja zmienia się ogromnie. Gdy wyświetlacz Retina w stylu iPhone4-5 o rozdzielczości 326 dpi (128 dpi) jest dostępny w monitorach 24-calowych (52 x 32 cm), rozmiar ekranu wyniesie 6656 x 4096 pikseli. Wtedy antyaliasing jest zły, a wszyscy dostawcy przeglądarek (nawet oparte na Webkit) są zmuszeni zezwolić na wyłączenie antyaliasingu. Antyaliasing jest operacją intensywnie wykorzystującą procesor, a wygładzanie obrazu 6656 x 4096 px byłoby zbyt wolne, ale na szczęście niepotrzebne przy takim wyświetlaniu.
Timo Kähkönen
1
Na marginesie, oto dobry punkt odniesienia dotyczący wzorców i obrazu: jsperf.com/drawimage-vs-canvaspattern/9
yckart
1

Obejście Saviski za wyjaśniony tutaj jest obiecujący, ponieważ działa na:

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

Ale nie działa w następujących przypadkach, ale ten sam efekt można osiągnąć za pomocą renderowania obrazu CSS:

  • Firefox 15.0.1 Mac OS X 10.6.8 (w tym działa renderowanie obrazu: -moz-ostre-krawędzie )
  • Opera 12.02 Mac OS X 10.6.8 (działa w tym renderowanie obrazu: -o-ostre-krawędzie )
  • Opera 12.02 Windows 7 (działa w tym renderowanie obrazu: -o-ostre-krawędzie )

Problematyczne są te, ponieważ ctx.XXXImageSmoothingEnabled nie działa, a renderowanie obrazu nie działa:

  • Safari 5.1.7 Mac OS X 10.6.8. (renderowanie obrazu: -webkit-optymalizacja-kontrast NIE działa)
  • Safari 5.1.7 Windows 7 (renderowanie obrazu: -webkit-optymalizacja-kontrast NIE działa)
  • IE 9 Windows 7 (-ms-tryb interpolacji: najbliższy sąsiad NIE działa)
Timo Kähkönen
źródło