Việc giải mã hình ảnh để sử dụng với canvas khá phổ biến, cho dù đó là để cho phép người dùng tuỳ chỉnh hình đại diện, cắt hình ảnh hay chỉ phóng to một bức ảnh. Vấn đề với việc giải mã hình ảnh là việc giải mã hình ảnh có thể tốn nhiều CPU và đôi khi có thể dẫn đến hiện tượng giật hoặc thao tác kiểm tra. Kể từ Chrome 50 (và trong Firefox 42 trở lên), bạn hiện có một tuỳ chọn khác: createImageBitmap()
. API này cho phép bạn giải mã hình ảnh ở chế độ nền và truy cập vào một ImageBitmap
gốc mới. Bạn có thể vẽ ImageBitmap
gốc này vào canvas theo cách tương tự như khi vẽ phần tử <img>
, một canvas khác hoặc video.
Vẽ các blob bằng createImageBitmap()
Giả sử bạn tải hình ảnh blob xuống bằng fetch()
(hoặc XHR) và muốn vẽ hình ảnh đó vào canvas. Nếu không có createImageBitmap()
, bạn sẽ phải tạo một phần tử hình ảnh và một URL Blob để chuyển hình ảnh sang định dạng mà bạn có thể sử dụng. Với ứng dụng này, bạn có thể sử dụng tính năng vẽ tranh theo cách trực tiếp:
fetch(url)
.then(response => response.blob())
.then(blob => createImageBitmap(blob))
.then(imageBitmap => ctx.drawImage(imageBitmap, 0, 0));
Phương pháp này cũng sẽ hoạt động với hình ảnh được lưu trữ dưới dạng blob trong IndexedDB, giúp blob trở thành một định dạng trung gian thuận tiện. Chrome 50 cũng hỗ trợ phương thức .toBlob()
trên các phần tử canvas. Điều này có nghĩa là bạn có thể tạo các blob từ các phần tử canvas.
Sử dụng createImageBitmap() trong worker web
Một trong những tính năng thú vị nhất của createImageBitmap()
là API này cũng hoạt động trong worker, tức là giờ đây bạn có thể giải mã hình ảnh ở bất cứ nơi nào bạn muốn. Nếu có nhiều hình ảnh cần giải mã mà bạn cho là không cần thiết, bạn sẽ gửi URL của các hình ảnh đó đến một Worker trên web. Worker này sẽ tải xuống và giải mã các hình ảnh đó khi có thời gian. Sau đó, thao tác này sẽ chuyển các hình ảnh này trở lại luồng chính để vẽ vào canvas.
Mã để thực hiện việc này có thể có dạng như sau:
// In the worker.
fetch(imageURL)
.then(response => response.blob())
.then(blob => createImageBitmap(blob))
.then(imageBitmap => {
// Transfer the imageBitmap back to main thread.
self.postMessage({ imageBitmap }, [imageBitmap]);
}, err => {
self.postMessage({ err });
});
// In the main thread.
worker.onmessage = (evt) => {
if (evt.data.err)
throw new Error(evt.data.err);
canvasContext.drawImage(evt.data.imageBitmap, 0, 0);
}
Hôm nay, nếu bạn gọi createImageBitmap()
trên luồng chính, thì đó chính là nơi quá trình giải mã sẽ diễn ra. Tuy nhiên, kế hoạch là Chrome sẽ tự động giải mã trong một luồng khác, giúp giảm tải cho luồng chính. Tuy nhiên, trong thời gian chờ đợi, bạn nên lưu ý thực hiện việc giải mã trên luồng chính, vì việc này cần nhiều công sức có thể chặn các tác vụ thiết yếu khác, như JavaScript, tính toán kiểu, bố cục, vẽ hoặc tổng hợp.
Thư viện trợ giúp
Để đơn giản hoá một chút, tôi đã tạo một thư viện trợ giúp xử lý việc giải mã trên một worker, đồng thời gửi lại hình ảnh đã giải mã về luồng chính và vẽ hình ảnh đó vào canvas. Tất nhiên, bạn nên thiết kế đảo ngược nó và áp dụng mô hình đó cho các ứng dụng của riêng mình. Lợi ích chính là quyền kiểm soát cao hơn, nhưng điều đó (như thường lệ) đi kèm với nhiều mã hơn, nhiều lượt gỡ lỗi hơn và nhiều trường hợp hiếm gặp cần được xem xét hơn so với việc sử dụng phần tử <img>
.
Nếu bạn cần có nhiều quyền kiểm soát hơn đối với việc giải mã hình ảnh, createImageBitmap()
sẽ là người bạn đồng hành mới tốt nhất của bạn. Hãy dùng thử trong Chrome 50 và cho chúng tôi biết cách triển khai của bạn!