Czy mogę wyłączyć antyaliasing w elemencie HTML <canvas>?

86

Bawię się <canvas>elementem, rysuję linie i tym podobne.

Zauważyłem, że moje ukośne linie są wygładzone. Wolałbym postrzępiony wygląd tego, co robię - czy jest jakiś sposób na wyłączenie tej funkcji?

Blorgbeard wyszedł
źródło
Myślę, że jest to raczej związane z przeglądarką. Może pomocne byłyby dodatkowe informacje na temat używanego oprogramowania.
Tomalak
Wolałbym metodę między przeglądarkami, ale metoda, która działa w dowolnej przeglądarce, nadal byłaby dla mnie interesująca
Blorgbeard jest dostępny
Chciałem tylko sprawdzić, czy zaszły już jakieś zmiany w tym temacie?
vternal3
czy jest jakaś aktualizacja?
Roland,

Odpowiedzi:

61

W przypadku obrazów jest teraz .context.imageSmoothingEnabled= false

Jednak nie ma nic, co bezpośrednio kontroluje rysowanie linii. Być może będziesz musiał narysować własne linie ( trudna droga ) za pomocą getImageDatai putImageData.

Kornel
źródło
1
Zastanawiam się nad wydajnością algorytmu liniowego javascript ... Może w którymś momencie może dać Bresenham szansę.
Blorgbeard wychodzi
Dostawcy przeglądarek ostatnio reklamują nowe superszybkie silniki JS, więc w końcu przydałoby się to.
Kornel
1
Czy to naprawdę działa? Rysuję linię za pomocą, putImageData ale nadal działa aliasing pobliskich pikseli.
Pacerier
Jeśli narysuję na mniejszym płótnie (pamięci podręcznej), a następnie narysuję obraz na innym płótnie z tym ustawieniem, czy zadziała zgodnie z oczekiwaniami?
SparK
69

Narysuj swoje 1-pixellinie na współrzędnych takich jak ctx.lineTo(10.5, 10.5). Narysowanie jednopikselowej linii nad punktem (10, 10)oznacza, że ​​ten 1piksel w tym miejscu sięga od 9.5do, 10.5co powoduje powstanie dwóch linii, które zostaną narysowane na płótnie.

Fajną sztuczką, która nie zawsze musi być dodawana 0.5do rzeczywistej współrzędnej, którą chcesz narysować, jeśli masz dużo jednopikselowych linii, jest ctx.translate(0.5, 0.5)na początku całe płótno.

allan
źródło
hmm, mam problem z pozbyciem się antyaliasingu przy użyciu tej techniki. Może brakuje mi zrozumienia czegoś? Czy mógłbyś zamieścić przykład gdzieś?
Xavi
7
Nie eliminuje to antyaliasingu, ale sprawia, że ​​wygładzane linie wyglądają dużo lepiej - na przykład pozbycie się tych kłopotliwych linii poziomych lub pionowych, które mają grubość dwóch pikseli, podczas gdy w rzeczywistości chciałeś mieć jeden piksel.
David Given
1
@porneL: Nie, linie są rysowane między rogami pikseli. Gdy linia ma 1 piksel szerokości, to rozszerza się o pół piksela w dowolnym kierunku
Eric,
Dodanie +0,5 działa dla mnie, ale ctx.translate(0.5,0.5)nie. na FF39.0
Paulo Bueno
Dziękuję Ci bardzo! Nie mogę uwierzyć, że dla odmiany mam linie 1px!
Chunky Chunk
24

Można to zrobić w przeglądarce Mozilla Firefox. Dodaj to do swojego kodu:

contextXYZ.mozImageSmoothingEnabled = false;

W Operze jest to obecnie propozycja funkcji, ale miejmy nadzieję, że zostanie wkrótce dodana.

francholi
źródło
fajne. +1 za Twój wkład. Zastanawiam się, czy wyłączenie AA przyspieszy rysowanie w linii
Marcusklaas,
7
OP chce anulować wygładzanie linii, ale działa to tylko na obrazach. Zgodnie ze specyfikacją określa"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

Musi antialias grafiki wektorowej

Antyaliasing jest wymagany do prawidłowego kreślenia grafiki wektorowej z wykorzystaniem współrzędnych innych niż całkowite (0,4, 0,4), co robi wszyscy oprócz bardzo niewielu klientów.

Gdy podane współrzędne nie są liczbami całkowitymi, płótno ma dwie opcje:

  • Antialias - pomaluj piksele wokół współrzędnej w oparciu o to, jak daleko między współrzędną całkowitą różni się od niecałkowitej (tj. Błąd zaokrąglenia).
  • Round - zastosuj funkcję zaokrąglania do niecałkowitej współrzędnej (na przykład 1.4 stanie się 1).

Późniejsza strategia będzie działać w przypadku grafik statycznych, chociaż w przypadku małych grafik (okrąg o promieniu 2) krzywe będą zawierały wyraźne kroki, a nie gładką krzywą.

Prawdziwym problemem jest to, gdy grafika jest tłumaczona (przenoszona) - skoki między jednym pikselem a drugim (1,6 => 2, 1,4 => 1), oznaczają, że pochodzenie kształtu może przeskakiwać w stosunku do kontenera nadrzędnego (ciągle przesuwa się 1 piksel w górę / w dół i w lewo / w prawo).

Kilka porad

Wskazówka 1 : Możesz zmiękczyć (lub utwardzić) antyaliasing, skalując płótno (powiedzmy przez x), a następnie zastosuj skalę odwrotności (1 / x) do geometrii samodzielnie (nie używając płótna).

Porównaj (bez skalowania):

Kilka prostokątów

z (skala płótna: 0,75; skala ręczna: 1,33):

Te same prostokąty z bardziej miękkimi krawędziami

oraz (skala płótna: 1,33; skala ręczna: 0,75):

Te same prostokąty z ciemniejszymi krawędziami

Wskazówka 2 : Jeśli naprawdę szukasz postrzępionego wyglądu, spróbuj narysować każdy kształt kilka razy (bez wymazywania). Z każdym rysowaniem piksele antyaliasingu stają się ciemniejsze.

Porównać. Po jednorazowym narysowaniu:

Kilka ścieżek

Po trzykrotnym losowaniu:

Te same ścieżki, ale ciemniejsze i bez widocznego wygładzania krawędzi.

Izhaki
źródło
@vanowm możesz klonować i bawić się z: github.com/Izhaki/gefri . Wszystkie obrazy to zrzuty ekranu z folderu / demo (z nieznacznie zmodyfikowanym kodem dla wskazówki nr 2). Jestem pewien, że łatwo będzie wprowadzić zaokrąglanie liczb całkowitych do narysowanych liczb (zajęło mi to 4 minuty), a następnie po prostu przeciągnij, aby zobaczyć efekt.
Izhaki
9

Narysowałbym wszystko za pomocą niestandardowego algorytmu liniowego, takiego jak algorytm liniowy Bresenham. Sprawdź tę implementację javascript: http://members.chello.at/easyfilter/canvas.html

Myślę, że to z pewnością rozwiąże Twoje problemy.

Jón Trausti Arason
źródło
2
Dokładnie to, czego potrzebowałem, jedyne, co chciałbym dodać, to to, że musisz zaimplementować setPixel(x, y); Użyłem zaakceptowanej odpowiedzi tutaj: stackoverflow.com/questions/4899799/…
Tina Vall
8

Chcę dodać, że miałem problem podczas zmniejszania rozmiaru obrazu i rysowania na płótnie, nadal korzystałem z wygładzania, mimo że nie było używane podczas skalowania.

Rozwiązałem za pomocą tego:

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

Możesz użyć tej funkcji w następujący sposób:

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

Może to się komuś przyda.

eri0o
źródło
dlaczego nie context.imageSmoothingEnabled = false?
Martijn Scheffer
To nie zadziałało w momencie, gdy pisałem odpowiedź. Czy to teraz działa?
eri0o
1
tak się stało, to DOKŁADNIE to samo, w javascript zapisywanie obj ['name'] lub obj.name zawsze było i zawsze będzie takie samo, obiekt jest zbiorem nazwanych wartości (krotek), używając czegoś, co przypomina tablica hash, obie notacje będą traktowane tak samo, nie ma żadnego powodu, aby Twój kod wcześniej nie działał, w najgorszym przypadku przypisuje wartość, która nie ma żadnego efektu (ponieważ jest przeznaczona dla innej przeglądarki. prosty przykład: napisz obj = {a: 123}; console.log (obj ['a'] === obj.a? "tak, to prawda": "nie, to nie jest")
Martijn Scheffer
Myślałem, że miałeś na myśli, dlaczego masz wszystkie inne rzeczy, co miałem na myśli w moim komentarzu, że w tamtym czasie przeglądarki wymagały różnych właściwości.
eri0o
ok tak oczywiście :) mówiłem o składni, a nie o poprawności samego kodu (to działa)
Martijn Scheffer
6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

Dzięki tej kombinacji mogę narysować ładne cienkie linie 1px.

retepaskab
źródło
6
Nie musisz ustawiać lineWidth na .5 ... to spowoduje (lub powinno) tylko półprzezroczystość.
aaaidan
4

Zwróć uwagę na bardzo ograniczoną sztuczkę. Jeśli chcesz stworzyć obraz w dwóch kolorach, możesz narysować dowolny kształt kolorem # 010101 na tle w kolorze # 000000. Gdy to zrobisz, możesz przetestować każdy piksel w imageData.data [] i ustawić na 0xFF dowolną wartość inną niż 0x00:

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);

Rezultatem będzie czarno-biały obraz bez antyaliasingu. Nie będzie to idealne, ponieważ będzie miał miejsce jakiś antyaliasing, ale ten antyaliasing będzie bardzo ograniczony, a kolor kształtu będzie bardzo podobny do koloru tła.

StashOfCode
źródło
1

Dla tych, którzy wciąż szukają odpowiedzi. oto moje rozwiązanie.

Zakładając, że obraz jest 1-kanałowy szary. Właśnie przekroczyłem próg po ctx.stroke ().

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;
    }
  }
}

jeśli twój kanał obrazu to 3 lub 4. musisz zmodyfikować indeks tablicy, np

x*image.height*number_channel + y*number_channel + channel
Jaewon.AC
źródło
0

Tylko dwie uwagi na temat odpowiedzi StashOfCode:

  1. Działa tylko na nieprzezroczystym płótnie w skali szarości (wypełnijRect białym, a następnie narysuj czarnym lub odwrotnie)
  2. Może się nie powieść, gdy linie są cienkie (szerokość linii ~ 1 piksel)

Zamiast tego lepiej to zrobić:

Obrysuj i wypełnij #FFFFFF, a następnie zrób to:

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

To rozwiązuje problem dla linii o szerokości 1 piksela.

Poza tym rozwiązanie StashOfCode jest idealne, ponieważ nie wymaga pisania własnych funkcji rasteryzacji (pomyśl nie tylko o liniach, ale także bezierach, łukach kołowych, wypełnionych wielokątach z dziurami itp.)

Matías Moreno
źródło
0

Oto podstawowa implementacja algorytmu Bresenham w JavaScript. Opiera się na wersji integer-arytmetycznej opisanej w tym artykule na Wikipedii: 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
źródło