2012-04-08 10 views
13

Tôi hiện đang làm việc trên một sửa chữa heatmap.js và tôi đã tự hỏi liệu có ai biết nếu nó có thể đạt được hiệu ứng sau đây với bối cảnh rendering 2d của <canvas>.HTML5 Canvas globalCompositeOperation cho lớp phủ gradient không thêm lên đến cường độ cao hơn?

  • Tôi có một độ dốc xuyên tâm từ bán kính 40px đến đen trong suốt. tâm của gradient xuyên tâm là x = 50, y = 50
  • Tôi có một gradient xuyên tâm khác từ bán kính đen (alpha 0.5) đến trong suốt, 40 pixel. tâm của gradient hướng tâm là x = 80, y = 50

Hai gradient được chồng lên nhau. Vấn đề của tôi bây giờ là: vùng chồng chéo được cộng lại với nhau tạo ra một giá trị alpha cao hơn so với các tâm gradient hướng tâm và do đó hiển thị dữ liệu sai (ví dụ như các vùng nóng hơn trong bản đồ nhiệt). tại gist sau đây, bằng cách thực hiện nó trong bảng điều khiển của bạn, bạn có thể thấy vấn đề.

Hành vi mong muốn sẽ là: Khu vực tối nhất là các trung tâm gradient, vùng chồng chéo của hai gradient được hợp nhất nhưng không thêm.

Sau khi thấy rằng không có GlobalCompositeOperations nào dẫn đến hành vi mong đợi, tôi đã thử kết hợp các hoạt động đó. Một cách tôi nghĩ rằng nó có lẽ sẽ có thể là như sau:

  • vẽ đầu tiên dốc
  • sử dụng compositeOperation 'nơi-out'
  • vẽ thứ hai dốc -> substracts khu vực chồng chéo từ gradient đầu tiên
  • sử dụng compositeOperation 'nguồn-over'
  • vẽ thứ hai dốc một lần nữa

Nhưng unfo Rtunately tôi không tìm thấy một sự kết hợp mà làm việc. Tôi rất muốn nghe phản hồi của bạn, cảm ơn trước!

PS: Tôi biết điều này có thể được thực hiện bằng cách thao tác các pixel theo cách thủ công, nhưng tôi đã tự hỏi liệu có giải pháp dễ dàng hơn, thanh lịch và nhanh hơn cho điều đó hay không.

+2

Khu vực chồng chéo của hai gradient được hợp nhất nhưng không cộng lại. - Bạn có thể mô tả sự hợp nhất trên cơ sở từng pixel. –

Trả lời

20

Ohhhhhh. Tôi đã có một cái gì đó cho bạn ở đây. Nó thực sự lập dị nhưng nó làm những gì bạn muốn mà không cần phải liên quan đến imageData.

Điều quan trọng nhất là bạn muốn có chính xác chức năng mà bản thân đường dẫn có trên canvas khi bạn vuốt chúng. Để trích dẫn thông số:

Do thuật toán để theo dõi đường dẫn được xác định, các phần chồng chéo của đường dẫn trong một thao tác đột quỵ được coi như là công đoàn của chúng được vẽ.

Bạn có thể đọc thêm về điều đó here.

Dù sao, những gì bạn muốn, về cơ bản, là một con đường mờ của không có gì nhưng vòng cung mà bạn có thể bị đột quỵ một lần và bạn muốn một cách hoàn hảo có được hiệu quả bạn đã tìm kiếm.

Vấn đề duy nhất là không có cách nào tạo ra đường dẫn mờ trong canvas. Hoặc hầu như không có cách nào.

Thay vì sử dụng chính đường dẫn, chúng tôi có thể sử dụng bóng của đường dẫn để mô phỏng các vòng tròn mờ tuân theo cùng các quy tắc mà đường dẫn thực hiện.

Vấn đề duy nhất ở đó, sau đó, là bạn không muốn nhìn thấy đường dẫn thực tế, bạn chỉ muốn nhìn thấy bóng của nó. Làm cho stroke trong suốt sẽ không hoạt động: Một bóng sẽ chỉ vẽ sẽ không vẽ ở độ đục cao hơn so với thứ mà nó đang đổ bóng.

Nhưng bóng có các thuộc tính shadowOffsetXshadowOffsetY, thường được sử dụng để dịch chuyển bóng bằng một hoặc hai pixel để tạo ảo giác về nguồn sáng.

Nhưng điều gì sẽ xảy ra nếu chúng ta vẽ bóng tối đến mức bạn không thể nhìn thấy chúng? Hay đúng hơn, điều gì sẽ xảy ra nếu chúng ta vẽ các đường dẫn cho đến nay mà bạn không thể nhìn thấy chúng, tất cả những gì bạn có thể thấy là các bóng tối?

Điều đó xảy ra để thực hiện thủ thuật. Đây là một kết quả nhanh chóng, mã ban đầu của bạn là trên đầu trang và bóng tối là vải thứ hai:

enter image description here

Đó không phải là chính xác những gì bạn có trước khi về gradient và kích thước nhưng nó rất gần gũi và tôi m chắc chắn rằng bằng cách không quan trọng với các giá trị bạn có thể làm cho nó thậm chí còn gần hơn. Một vài console.log xác nhận rằng điều chúng tôi muốn, một alpha không vượt quá 124 (trong số 255) là chính xác xảy ra ở những nơi mà nó được sử dụng để được 143 và 134 làm theo cách cũ.

Các fiddle để xem mã trong hành động: http://jsfiddle.net/g54Mz/

Vì vậy, có bạn có nó. Bắt được hiệu ứng của sự kết hợp của hai gradient xuyên tâm là có thể mà không cần imageData nếu bạn sử dụng bóng và bù đắp các đường dẫn thực tế quá nhiều đến mức chúng đang tắt màn hình.

+3

Cảm ơn bạn đã trả lời câu hỏi này, đây là một kỹ thuật thực sự gọn gàng. Tôi nhanh chóng tấn công một triển khai với nhau và có vẻ như nó hoạt động tốt. :) –

0

composite modes giống như bạn tìm thấy chúng. Bạn không thể kết hợp bất kỳ điều gì tốt hơn điều này theo như tôi biết, mà không cần theo cách thủ công setting pixels. Nếu bạn cập nhật câu hỏi của mình với mô tả rõ ràng về cách bạn muốn trộn các pixel, tôi sẽ cập nhật câu trả lời của mình cho phù hợp.

Vì vậy, giải pháp cho mỗi pixel là gì?

Có 2 phương pháp chính pixel dựa tôi có thể thấy rằng sẽ bắt đầu để giải quyết vấn đề này

  1. Vẽ lên một bối cảnh ẩn và pha trộn với một chức năng của nhãn hiệu

  2. Viết một hàm để tính toán gradient theo cách thủ công và áp dụng chức năng trộn tùy chỉnh.

Tùy chọn đầu tiên của bạn linh hoạt hơn thứ hai theo ý bạn có thể vẽ bất kỳ thứ gì bạn thích bằng cách sử dụng các phương pháp bình thường. Xem dự án @Phrogz context-blender để có ý tưởng tốt về cách pha trộn một ngữ cảnh với một hình ảnh khác

Tùy chọn thứ hai là cần thiết khi bạn cần vẽ theo cách mà canvas không tạo điều kiện theo mặc định.

Khó khăn lớn nhất để tranh luận là độ trong suốt alpha do thực tế bạn có thể có nội dung đằng sau gradient xuyên tâm. Một khi bạn đã vẽ trên đầu trang của một hình ảnh nền, nó gần như không thể nhìn thấy những gì đã được trước khi bạn đã vẽ trên đầu trang của nó, trừ khi bạn giữ một bản sao của nền đó. Ngay cả trên cơ sở mỗi pixel, bạn gặp khó khăn: kết hợp hình ảnh trên đầu một hình ảnh khác sẽ không hoạt động. Về cơ bản, điều đó có nghĩa là kết hợp hình ảnh của nhiều gradient mờ trên đầu khung hình hiển thị của bạn, bất kể lựa chọn chức năng tổng hợp chung của bạn, sẽ không hoạt động trừ khi canvas đó có nền trong suốt để bắt đầu.

Theo tinh thần của tùy chọn 1, bạn có thể tạo một ngữ cảnh trống và hiển thị nhiều gradient vào nó. Sau đó, làm điều này trên đầu trang.

Và theo tinh thần của tùy chọn 2, nếu bạn có thể xác định tất cả của các điểm trước khi hiển thị, bạn có thể tính toán hình ảnh và pha trộn từ các điểm đó trong một lần truyền.

Kết hợp kỹ thuật kết xuất một lần với ngữ cảnh nền sẽ cho phép bạn vẽ đường viền ở trên cùng của khung nhìn có thể nhìn thấy mà không cần đọc một pixel bằng tay, chỉ ghi pixel.

Không nơi nào gần như tao nhã tôi biết, nhưng nó có thể là cách duy nhất thực sự để đạt được những gì tôi sẽ gọi là hiệu ứng đường viền alpha pha trộn trên canvas 2D.


Tôi nghĩ rằng chức năng hòa trộn trên mỗi pixel bạn cần sẽ chọn pixel từ nguồn và đích có giá trị alpha lớn nhất.

if (src.a <= dst.a) { 
    result = dst; 
} else { 
    result = src; 
} 
1

Fiddle http://jsfiddle.net/2qQLz/ là một nỗ lực để cung cấp giải pháp. Nếu nó gần với những gì bạn cần nó có thể được phát triển hơn nữa. Nó giới hạn tô màu gradient vào một hình chữ nhật bao quanh một bên trong đó là đường giao nhau của 'vòng tròn'. Đối với hai 'vòng tròn' của cùng bán kính nằm dọc theo một đường ngang, đủ dễ dàng để tìm giá trị x của các điểm giao nhau của 'vòng tròn' và vẽ hình chữ nhật giới hạn cho tô màu gradient cho mỗi 'vòng tròn'.

Sẽ khó khăn hơn cho hai vòng tròn tùy ý nhưng vẫn có thể tìm thấy đường giao nhau và hình chữ nhật có thể được vẽ cho mỗi vòng tròn. Nó sẽ trở nên ngày càng phức tạp hơn khi có thêm 'vòng tròn' được thêm vào.

2

Tôi đang làm việc trên trò chơi dựa trên HTML5, trong đó tôi muốn pha trộn các khu vực bán nguyệt có màu sắc khác nhau được vẽ trên hàng trăm ô vuông trong lưới. Hiệu ứng này giống như bản đồ nhiệt. Sau một số nghiên cứu, tôi phát hiện ra kỹ thuật "bóng tối" được Simon Sarris ghi lại ở trên.

Triển khai kỹ thuật này cung cấp giao diện tôi muốn. Và tôi thích điều đó thật dễ hiểu. Tuy nhiên, trong thực tế, tôi thấy rằng việc dựng hình ngay cả một vài (~ 150) bóng tối chậm hơn nhiều so với kỹ thuật trước đây của tôi (tuy nhiên không hấp dẫn) trong việc vẽ hàng ngàn rặng đầy.

Vì vậy, tôi quyết định thực hiện một số phân tích.Tôi đã viết một số JavaScript cơ bản (phiên bản sửa đổi có thể xem tại https://jsfiddle.net/Flatfingers/4vd22rgg/) để vẽ 2000 bản sao của năm loại hình dạng khác nhau lên các phần không chồng chéo của canvas 1250x600, ghi lại thời gian đã trôi qua cho từng hoạt động trong số năm hoạt động này các phiên bản của năm trình duyệt máy tính để bàn lớn cùng với Safari di động. (Xin lỗi, máy tính để bàn Safari. Tôi cũng không có một Android tiện dụng để kiểm tra.) Sau đó, tôi đã thử kết hợp khác nhau của các hiệu ứng và ghi lại thời gian trôi qua.

Đây là một ví dụ đơn giản về cách tôi vẽ hai gradient với sự xuất hiện tương tự như shadowed cung điền:

var gradient1 = context.createRadialGradient(75,100,2,75,100,80); 
gradient1.addColorStop(0,"yellow"); 
gradient1.addColorStop(1,"black"); 

var gradient2 = context.createRadialGradient(125,100,2,125,100,80); 
gradient2.addColorStop(0,"blue"); 
gradient2.addColorStop(1,"black"); 

context.beginPath(); 
context.globalCompositeOperation = "lighter"; 
context.globalAlpha = 0.5; 
context.fillStyle = gradient1; 
context.fillRect(0,0,200,200); 
context.fillStyle = gradient2; 
context.fillRect(0,0,200,200); 
context.globalAlpha = 1.0; 
context.closePath(); 

timings

(2000 hình dạng không chồng chéo, bộ globalAlpha, drawImage() được sử dụng cho gradient nhưng không được sử dụng cho bóng đổ)

IE 11 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 35 ms 
Gradients = 57 ms 
Images = 8 ms 
Shadows = 160 ms 

Edge (64-bit Windows 10) 
Rects  = 3 ms 
Arcs  = 47 ms 
Gradients = 52 ms 
Images = 7 ms 
Shadows = 171 ms 

Chrome 48 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 10 ms 
Gradients = 8 ms 
Images = 8 ms 
Shadows = 203 ms 

Firefox 44 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 21 ms 
Gradients = 7 ms 
Images = 8 ms 
Shadows = 468 ms 

Opera 34 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 9 ms 
Gradients = 8 ms 
Images = 8 ms 
Shadows = 202 ms 

Mobile Safari (iPhone5, iOS 9) 
Rects  = 12 ms 
Arcs  = 31 ms 
Gradients = 67 ms 
Images = 82 ms 
Shadows = 32 ms 

QUAN SÁT

  1. Trong số các hình đầy, các thao tác điền luôn là hoạt động nhanh nhất trong tất cả các trình duyệt và môi trường được kiểm tra.
  2. Đầy vòng cung (vòng tròn) có tốc độ chậm hơn khoảng 10 lần so với IE 11 và Edge so với các rãnh đầy, so với khoảng 3,5x chậm hơn trong các trình duyệt chính khác.
  3. Độ dốc khoảng 3 lần chậm hơn so với các đường viền trong IE 11, Chrome 48 và Opera 34, nhưng chậm hơn 100 lần đáng kể trong Firefox 44 (xem Bugzilla report 728453).
  4. Hình ảnh qua drawImage() nhanh hơn khoảng 1,5 lần so với các đường viền đầy trong tất cả các trình duyệt trên máy tính để bàn.
  5. Vòng cung có bóng tối chậm nhất, dao động từ khoảng 50x chậm hơn so với các rãnh đầy trong IE, Edge, Chrome và Opera chậm hơn 100 lần trong Firefox.
  6. Chrome 48 và Opera 34 đều có tốc độ đáng kể trong mọi danh mục hình dạng ngoại trừ hình cung có bóng, nhưng chúng không tệ hơn các trình duyệt khác ở đó.
  7. Sự cố Chrome và Opera khi drawImage() thu hút 1000 hình dạng trong đó shadowOffsetX hoặc shadowOffsetY được cung cấp một giá trị ngoài độ phân giải màn hình vật lý.
  8. IE 11 và Edge chậm hơn để vẽ các cung và độ dốc so với các trình duyệt máy tính để bàn khác.
  9. drawImage() chậm trên Safari trên thiết bị di động. Nó thực sự nhanh hơn để vẽ nhiều gradient và bóng tối hơn là vẽ một bản sao nhiều lần với drawImage().
  10. Bản vẽ trong Firefox rất nhạy cảm với các thao tác trước: vẽ bóng và tô màu làm cho bản vẽ vòng cung chậm hơn. Thời gian nhanh nhất được hiển thị.
  11. Vẽ trong Mobile Safari rất nhạy cảm với các hoạt động trước: bóng làm cho độ dốc chậm hơn; gradient và vòng cung tạo drawImage() thậm chí còn chậm hơn bình thường. Thời gian nhanh nhất được hiển thị.

PHÂN TÍCH

Trong khi tính năng shadowOffset là một cách đơn giản và hiệu quả trực quan để kết hợp hình dạng, nó là chậm hơn so với tất cả các kỹ thuật khác đáng kể. Điều này giới hạn tính hữu dụng của nó đối với các ứng dụng chỉ cần vẽ một vài bóng, và không cần phải vẽ nhiều bóng một cách nhanh chóng và liên tục.Hơn nữa, khi tăng tốc sử dụng drawImage(), cho shadowOffsetX hoặc shadowOffsetY một giá trị lớn hơn khoảng 3000 khiến Chrome 48 và Opera 34 bị treo trong gần một phút, tiêu thụ chu kỳ CPU và sau đó làm hỏng trình điều khiển hiển thị nVidia của tôi, ngay cả sau khi cập nhật nó lên phiên bản mới nhất. (Google Tìm kiếm không tìm thấy báo cáo lỗi nào cho Chromium mô tả lỗi này khi một shadowOffset lớn và drawImage() được sử dụng cùng nhau.)

Đối với các ứng dụng cần pha trộn các hình dạng không rõ ràng, cách tiếp cận tương tự nhất với bóng tối là đặt globalCompositeOperation thành "nhẹ hơn" và sử dụng drawImage() với giá trị globalAlpha để vẽ liên tục độ dốc xuyên tâm, hoặc vẽ các gradient riêng lẻ nếu chúng cần màu khác nhau. Đây không phải là một kết hợp hoàn hảo cho các bóng chồng chéo, nhưng nó gần và tránh thực hiện các phép tính trên mỗi pixel. (Tuy nhiên, lưu ý rằng trong Safari di động, trực tiếp vẽ các hình cung đầy bóng thực sự nhanh hơn gradient và drawImage().) Trong khi thiết lập globalCompositeOperation thành "lighter" khiến IE 11 và Edge giảm khoảng 10 lần trong bản vẽ, sử dụng gradient xuyên tâm vẫn còn nhanh hơn việc sử dụng các cung tròn được tô bóng trong tất cả các trình duyệt dành cho máy tính để bàn chính và chỉ chậm hơn hai lần so với các cung tròn được tô bóng trong Safari trên thiết bị di động.

KẾT LUẬN

Nếu nền tảng mục tiêu duy nhất của bạn là iPad/iPhone, phương pháp nhanh nhất cho hình dạng pha trộn đẹp hình dáng như shadowed cung điền. Nếu không, phương pháp nhanh nhất với diện mạo tương tự mà tôi đã tìm thấy cho đến nay hoạt động trong tất cả các trình duyệt máy tính để bàn chính là vẽ gradient xuyên tâm với globalCompositeOperation được đặt thành "lighter" và kiểm soát độ mờ với globalAlpha.

Lưu ý: Có một số cách rõ ràng rằng hiệu suất có thể được cải thiện trong các bài kiểm tra bản vẽ mà tôi đã thực hiện. Đặc biệt, vẽ từng thể hiện của từng hình dạng vào một bộ đệm ngoài màn hình và sau đó vẽ toàn bộ bộ đệm một lần vào khung nhìn thấy được sẽ mang lại một cải thiện hiệu suất đáng kể. Nhưng điều đó sẽ phủ nhận mục tiêu của thử nghiệm này, đó là so sánh tốc độ tương đối của việc vẽ các loại hình dạng khác nhau trên khung nhìn thấy được.