2010-10-17 25 views
33

Pracuję ze stosunkowo dużym płótnem, na którym rysuje się różne (złożone) rzeczy. Następnie chcę zapisać stan Canvas, więc mogę szybko zresetować go do stanu, w którym znajduje się teraz. Używam do tego getImageData i przechowuję dane w zmiennej. Następnie narysuję trochę więcej rzeczy na płótnie, a potem zmienię płótno na miejsce, w którym zapisałem jego stan, używając putImageData.Dlaczego plik putImageData jest tak wolny?

Jednak okazuje się, że putImageData działa bardzo wolno. W rzeczywistości, jest wolniejszy niż zwykłe przerysowywanie całego płótna od zera, co wiąże się z kilkoma rysunkami, które obejmują większość powierzchni, oraz ponad 40 000 operacji lineTo, po których następuje uderzenie i wypełnienie.

Ponowne rysowanie płótna o wymiarach 2000 x 5000 pikseli od zera trwa ~ 170 ms, a użycie metody putImageData zajmuje nawet 240 ms. Dlaczego putImageData jest tak powolny w porównaniu do przerysowywania płótna, chociaż ponowne rysowanie płótna wymaga wypełnienia niemal całego płótna drawImage, a następnie ponownego wypełnienia około 50% płótna wielobokami za pomocą lineTo, stroke i fill. Zasadniczo każdy piksel jest dotykany co najmniej raz podczas przerysowywania.

Ponieważ drawImage wydaje się być znacznie szybszy niż putImageData (w końcu część drawImage przerysowywania płótna zajmuje mniej niż 30 ms). Postanowiłem spróbować zapisać stan płótna bez użycia getImageData, ale zamiast tego użyć canvas.toDataURL, a następnie utworzyć obraz z adresu URL danych, który wstawiłbym do drawImage, aby narysować go na płótnie. Okazuje się, że ta cała procedura jest znacznie szybsza i zajmuje tylko około 35 ms.

Dlaczego więc putImageData jest o wiele wolniejszy niż alternatywy (używając getDataURL lub po prostu przerysowywania)? Jak mogę przyspieszyć dalszy rozwój? Czy istnieje i, jeśli w ogóle, jaki jest najlepszy sposób przechowywania stanu płótna?

(wszystkie liczby są mierzone za pomocą Firebug z poziomu Firefoksa)

+1

Byłoby interesujące, gdyby można opublikować demonstrację swojego problemu w Internecie gdzieś. W noVNC (http://github.com/kanaka/noVNC) używam putImageData dla wielu małych i średnich macierzy danych obrazu i nie widzę problemu z wydajnością z putImageData. Być może masz do czynienia z konkretnym przypadkiem wydajności pesymu, który powinien być bug'd. – kanaka

+0

Możesz zajrzeć tutaj http://www.danielbaulig.de/A3O/ To nie zadziała w 100%, jeśli konsola firebug zostanie przełączona, więc upewnij się, że ją włączysz. Wersja wyewidencjonowana to ta, która używa putImageData. Możesz go uruchomić, klikając dowolny "kafelek". Odświeży to płótno buforowe za pomocą putImageData, a następnie "podświetli" wybrany kafelek. W a3o_oo.js zostało skomentowanych kilka linii, które mogą być użyte do przełączania pomiędzy putImageData (current), za pomocą getDataURL (dwa wiersze wspominające this.boardBuffer) i zwykłego przerysowywania (linia drawBoard) obszaru roboczego bufora. –

+0

Świetne pytanie i świetne rozwiązania. Ale czy kiedykolwiek odkryłeś prawdziwy powód, dlaczego putImageData jest tak powolny w porównaniu do drawImage? – cherouvim

Odpowiedz

71

tylko mały update na co najlepsze sposób to zrobić. Tak naprawdę napisałem pracę licencjacką na temat High Performance ECMAScript and HTML5 Canvas (pdf, german), więc do tej pory zebrałem trochę wiedzy na ten temat. Najlepszym rozwiązaniem jest użycie wielu elementów canvas. Rysowanie z jednego płótna na innym płótnie jest równie szybkie, jak rysowanie arbitralnego obrazu na płótnie.W ten sposób "przechowywanie" stanu płótna jest równie szybkie, jak przywrócenie go później, gdy użyjemy dwóch elementów canvas.

This jsPerf testcase bardzo wyraźnie pokazuje różne podejścia i ich zalety oraz wady.

Tylko dla kompletności, tutaj jak naprawdę powinien zrobić to:

// setup 
var buffer = document.createElement('canvas'); 
buffer.width = canvas.width; 
buffer.height = canvas.height; 


// save 
buffer.getContext('2d').drawImage(canvas, 0, 0); 

// restore 
canvas.getContext('2d').drawImage(buffer, 0, 0); 

Rozwiązanie to, w zależności od przeglądarki, do 5000X szybciej niż jednego uzyskiwanie przez upvotes.

+5

+1 Dla świetnych informacji i fantastycznych przypadków testowych –

+0

Co zrobić, jeśli chcesz przechowywać wiele stanów w tablicy? Czy należy stworzyć tablicę mnóstwa płócien? na przykład 'var numBuffers = 20; var tmpCan = document.createElement ("canvas"); bufory var = [tmpCan]; dla (var i = 1, len = numBuffers, i dylnmc

2

Najpierw mówisz, że pomiar z Firebug. Właściwie to stwierdziłem, że Firebug znacznie spowalnia działanie JS, więc możesz nie uzyskać dobrych wyników.

Jeśli chodzi o putImageData, podejrzewam, że to dlatego, że funkcje pobierają dużą tablicę JS zawierającą wiele obiektów Number, z których wszystkie muszą być sprawdzone pod względem zakresu (0..255) i skopiowane do rodzimego bufora obszaru roboczego.

Być może po udostępnieniu typów WebGL ByteArray tego typu rzeczy można uczynić szybszymi.

Wydaje się dziwne, że dekodowanie i dekompresja danych base64 (z adresem URL danych PNG) jest szybsza, ale wywołuje tylko jedną funkcję JS z jednym ciągiem JS, dlatego używa głównie kodu natywnego i typów.

+0

ponieważ moje liczby są w większości wykonane z natywnego kodu, wątpię, że Firebug będzie miał na nie znaczący wpływ. Niemniej jednak nie mówimy o ułamkach milisekund, ale w rzeczywistości, ale w rzeczywistości o ćwierć sekundy dla podstawowego pojedynczego wywołania funkcji (putImageData). Zła wydajność może wynikać z tablicy JS. Sprawdzę to, sprawdzając, jak szybko JS może obsłużyć (skopiować, manipulować itp.) Taką tablicę poza putImageData. –

+0

Kontynuacja: Deocding, uncompressing, etc nie dzieje się w punkcie, w którym stan canvas jest przywracany, ale gdy jest zapisany. Tak więc zdarza się to tylko raz, a ja właściwie nie mierzyłem tego, ponieważ czas potrzebny na uratowanie stanu nie budzi wielkiego zaniepokojenia. Najważniejszą częścią jest przywrócenie stanu płótna. W tym momencie mam obiekt Image długo stworzony. Jeśli więc obiekt Image zawiera dane w macierzystym buforze, może to być przyczyną problemu (lub lepiej jego braku w przypadku podejścia drawImage). –

+0

Wiem, że wydajność 'putImageData' może być w porządku (80-100 fps przy buforze 480x320) - ale masz do czynienia z bardzo dużymi obrazami! – andrewmu

11

W Firefoksie 3.6.8 Udało mi się obejść powolność putImageData za pomocą metody toDataUrl/drawImage. Dla mnie to działa na tyle szybko, że mogę nazwać ciągu dotknięciem mouseMove wydarzenie:

Aby zapisać:

savedImage = new Image() 
savedImage.src = canvas.toDataURL("image/png") 

przywrócić:

ctx = canvas.getContext('2d') 
ctx.drawImage(savedImage,0,0) 
+1

Rozpoznano teraz Twoją odpowiedź. Rzeczywiście robię to w ten sam sposób :) Dodatkowo eksperymentuję z wykorzystaniem dodatkowego, ukrytego płótna jako bufora. Powinno to zwiększyć wydajność tworzenia bufora, który jest raczej powolny przy użyciu parametru doDataURL, a prędkość rysowania powinna pozostać w przybliżeniu taka sama (ponieważ drawImage może również przyjmować element canvas jako obraz). –

+3

Po prostu uświadomiłem sobie, że ta odpowiedź ciągle wygrywa. Doceniam tę odpowiedź, ale wyjaśnione rozwiązanie jest w rzeczywistości fatalne z punktu widzenia wydajności. Zamiast tego proszę odnieść się do zaakceptowanej odpowiedzi sam. –