2010-10-17 30 views
33

Tôi đang làm việc với một Canvas tương đối lớn có nhiều thứ phức tạp khác nhau được vẽ. Sau đó tôi muốn lưu trạng thái Canvas, vì vậy tôi có thể nhanh chóng đặt lại trạng thái của nó ở trạng thái hiện tại. Tôi sử dụng getImageData cho điều này và lưu trữ dữ liệu trong một biến. Sau đó, tôi vẽ thêm một số nội dung vào canvas và sau đó sẽ đặt lại Canvas thành vị trí khi tôi lưu trạng thái của nó, sử dụng putImageData.Tại sao putImageData lại quá chậm?

Tuy nhiên, hóa ra là putImageData rất chậm. Infact, nó là chậm hơn so với chỉ vẽ lại toàn bộ Canvas từ đầu, trong đó bao gồm một số drawImage bao gồm hầu hết các bề mặt, và hơn 40.000 lineTo hoạt động theo sau bởi đột quỵ và điền vào.

Vẽ lại canvas khoảng 2000 x 5000 pixel từ đầu mất ~ 170ms, sử dụng putImageData mặc dù mất 240ms khổng lồ. Tại sao putImageData quá chậm so với vẽ lại canvas, mặc dù vẽ lại canvas bao gồm việc lấp đầy toàn bộ canvas bằng drawImage và sau đó điền lại khoảng 50% canvas với đa giác bằng lineTo, stroke và fill. Vì vậy, cơ bản mỗi ist điểm ảnh duy nhất chạm ít nhất một lần khi vẽ lại.

Bởi vì drawImage có vẻ nhanh hơn rất nhiều sau đó putImageData (sau khi tất cả, phần drawImage của vẽ lại canvas mất ít hơn 30 ms). Tôi quyết định cố gắng lưu trạng thái của canvas không sử dụng getImageData, nhưng thay vì sử dụng canvas.toDataURL và sau đó tạo một Image từ URL dữ liệu mà tôi sẽ dính vào drawImage để vẽ nó vào khung vẽ. Hóa ra toàn bộ quy trình này nhanh hơn nhiều và chỉ mất khoảng 35ms để hoàn thành.

Vậy tại sao putImageData chậm hơn rất nhiều thì các lựa chọn thay thế (sử dụng getDataURL hoặc vẽ lại đơn giản)? Làm thế nào tôi có thể tăng tốc độ hơn nữa? Có và nếu, nói chung cách tốt nhất để lưu trữ trạng thái của canvas là gì?

(Tất cả các số được đo bằng Firebug từ trong Firefox)

+1

Sẽ rất thú vị nếu bạn có thể đăng tải bản trình bày sự cố trực tuyến ở đâu đó. Trong noVNC (http://github.com/kanaka/noVNC) Tôi sử dụng putImageData cho rất nhiều mảng dữ liệu hình ảnh kích thước vừa và nhỏ và tôi không thấy vấn đề về hiệu suất với putImageData. Có lẽ bạn đang chạy vào một trường hợp hiệu suất cụ thể pessimal mà nên được bug'd. – kanaka

+0

Bạn có thể xem tại đây http://www.danielbaulig.de/A3O/ Nó sẽ không hoạt động 100% nếu bảng điều khiển firebug được bật, vì vậy hãy đảm bảo bật nó lên. Phiên bản đã kiểm tra là phiên bản sử dụng putImageData. Bạn có thể kích hoạt nó bằng cách nhấp vào bất kỳ "gạch" nào. Nó sẽ làm mới khung đệm bằng cách sử dụng putImageData và sau đó "tô sáng" ô được chọn. Trong a3o_oo.js có một số dòng nhận xét, có thể được sử dụng để chuyển đổi giữa việc sử dụng putImageData (hiện tại), sử dụng getDataURL (hai dòng đề cập đến this.boardBuffer) và vẽ lại đồng bằng (dòng drawBoard) của canvas đệm. –

+0

Câu hỏi hay và giải pháp tuyệt vời. Nhưng bạn đã bao giờ tìm ra lý do thực sự tại sao putImageData lại quá chậm so với drawImage? – cherouvim

Trả lời

71

Chỉ cần một cập nhật nhỏ về những gì là tốt nhất cách là để làm điều này. Tôi thực sự đã viết Luận văn Cử nhân của tôi trên High Performance ECMAScript and HTML5 Canvas (pdf, tiếng Đức), vì vậy tôi đã tập hợp một số chuyên môn về chủ đề này ngay bây giờ. Giải pháp rõ ràng nhất là sử dụng nhiều phần tử canvas. Vẽ từ một khung hình lên một khung hình khác cũng nhanh như vẽ một hình ảnh đơn phương vào khung vẽ.Do đó "lưu trữ" trạng thái của canvas chỉ nhanh như khôi phục lại sau này khi sử dụng hai phần tử canvas.

This jsPerf testcase cho thấy các cách tiếp cận khác nhau cũng như lợi ích và nhược điểm của chúng rất rõ ràng.

Chỉ cần cho đầy đủ, ở đây như thế nào bạn thực sự nên làm điều đó:

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

Giải pháp này là, tùy thuộc vào trình duyệt, lên đến 5000x nhanh hơn so với một nhận upvotes.

+5

+1 Để biết thông tin tuyệt vời và các trường hợp thử nghiệm tuyệt vời –

+0

Nếu bạn cần lưu trữ nhiều trạng thái trong một mảng thì sao? Nên tạo ra một mảng của một loạt các bức tranh? ví dụ. 'var numBuffers = 20; var tmpCan = document.createElement ('canvas'); var buffer = [tmpCan]; cho (var i = 1, len = numBuffers, i dylnmc

2

Trước hết bạn nói bạn đang đo bằng Firebug. Tôi thực sự thấy rằng Firebug làm chậm JS thực hiện đáng kể, vì vậy bạn có thể không nhận được số lượng tốt cho hiệu suất.

Đối với putImageData, tôi nghi ngờ đó là vì các hàm lấy một mảng JS lớn chứa nhiều đối tượng Number, tất cả đều phải được kiểm tra phạm vi (0..255) và được sao chép vào bộ đệm gốc.

Có thể một khi các loại WebGL ByteArray có sẵn, loại điều này có thể được thực hiện nhanh hơn. Có vẻ kỳ quặc khi giải mã base64 và giải nén dữ liệu (với URL dữ liệu PNG) nhanh hơn, nhưng chỉ gọi một hàm JS với một chuỗi JS, vì vậy nó đang sử dụng hầu hết các mã và kiểu gốc.

+0

vì số của tôi là phần lớn từ việc thực thi mã gốc, tôi nghi ngờ rằng Firebug sẽ có ảnh hưởng đáng kể đến chúng. Tuy nhiên, chúng ta không nói về các phân số của mili giây, nhưng trên thực tế là một phần tư giây cho một cuộc gọi hàm cơ bản (putImageData). Hiệu suất kém do mảng JS có thể là. Tôi sẽ kiểm tra xem bằng cách kiểm tra xem JS có thể xử lý nhanh như thế nào (sao chép, thao tác, vv) như một mảng bên ngoài putImageData. –

+0

Tiếp tục: Deocding, uncompressing, vv không xảy ra tại thời điểm trạng thái canvas được khôi phục, nhưng khi nó được lưu. Vì vậy, điều này xảy ra chỉ một lần và tôi thực sự đã không đo lường nó, bởi vì phải mất bao lâu để tiết kiệm nhà nước không phải là mối quan tâm nhiều. Phần quan trọng là khôi phục trạng thái canvas. Vào thời điểm đó, tôi đã tạo đối tượng Hình ảnh dài. Vì vậy, nếu đối tượng Image chứa dữ liệu của nó trong một bộ đệm gốc, điều này thực sự có thể là nguyên nhân của vấn đề (hoặc tốt hơn là thiếu nó cho phương thức drawImage). –

+0

Tôi biết rằng hiệu suất 'putImageData' có thể được chấp nhận (80-100fps với bộ đệm 480x320) - nhưng bạn đang xử lý các hình ảnh rất lớn! – andrewmu

11

Trong Firefox 3.6.8, tôi đã có thể giải quyết sự chậm trễ của putImageData bằng cách sử dụng toDataUrl/drawImage thay thế. Đối với tôi nó làm việc đủ nhanh mà tôi có thể gọi nó là trong xử lý một sự kiện MouseMove:

Để tiết kiệm:

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

Các khôi phục:

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

Đã nhận ra câu trả lời của bạn ngay bây giờ. Infact Tôi hiện đang làm nó theo cùng một cách chính xác :) Ngoài ra tôi hiện đang thử nghiệm với việc sử dụng một, ẩn vải ẩn như một bộ đệm. Điều này sẽ làm tăng hiệu suất tạo bộ đệm, mà là khá chậm bằng cách sử dụng toDataURL và tốc độ vẽ nên vẫn giữ nguyên (vì drawImage cũng có thể lấy một phần tử canvas làm hình ảnh). –

+3

Chỉ nhận ra rằng câu trả lời này tiếp tục nhận được upvotes. Tôi đánh giá cao câu trả lời này, nhưng giải pháp được giải thích là thực sự khủng khiếp hiệu suất-khôn ngoan. Thay vào đó, hãy tự mình tham khảo câu trả lời được chấp nhận. –

Các vấn đề liên quan