iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0

昨天學習到使用 OffscreenCanvasWeb worker 中操作圖像,避免主線程處理 canvas 的耗時操作導致畫面卡頓,今天打算繼續優化前兩天寫的程式,改用 OffscreenCanvas 實作來看是否能提升效能

範例 Demo

目的

  1. 接續 第 15 天的範例,但圖像操作的部分,改成在 worker 中使用 OffscreenCanvas,希望藉此來進一步增進效能

說明

  1. 原先第 15 天的範例中,將 操作圖片像素(ImageData) 的運算改用 worker 處理已經不會讓主線程的動畫卡頓,但當圖片畫質過高,取得圖片像素(getImageData) 所耗費的時間有可能達到 100~200ms,此時還是一樣會造成動畫卡頓
  2. 今天打算使用 OffscreenCanvas,將 取得圖片像素(getImageData) 的邏輯也移到 worker 中處理。希望最後驗證不論圖片的畫質多高,只要將全部操作圖像的邏輯移到 worker 中執行,都不會阻塞主線程
  3. 今天的範例新增了 使用 worker (OffscreenCanvas) 按鈕,按下後操作圖像的邏輯都會使用 OffscreenCanvasworker 線程中處理

https://ithelp.ithome.com.tw/upload/images/20231001/20162687lT25IRrL9i.png

首先新增了一個 使用 worker (OffscreenCanvas) 按鈕,按下去後會產生 OffscreenCanvas ,並將 OffscreenCanvas 傳遞到 worker

// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");

const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();

// 將 OffscreenCanvas 傳遞到 worker
offscreenCanvasWorker.postMessage(
  { canvas: offscreen, imageUrl },
  [offscreen]
);

從網路 fetch Image 圖片

由於 worker 中無法控制 DOM 元素,因此原本使用的 new Image() 加載圖片的方式變得不可行,所以改用 fetch 的方式將圖片從網路拿回來,並轉成之後需要用到的 blob 格式

// worker 線程
const fetchImageBlob = async (imageUrl) => {
  const response = await fetch(imageUrl);
  const imageBlob = await response.blob();
  return imageBlob;
};

將圖片從 blob 轉為 ImageBitmap

接著將圖片從 blob 轉為 ImageBitmap 型別,以供下一步的 drawImage 使用

// worker 線程
const bitmap = await createImageBitmap(imageBlob);

將 ImageBitmap 繪製到 canvas

將以上準備好的 ImageBitmap 繪製到主線程傳來的 canvas

// worker 線程

// canvas 為從主線程傳來的 OffscreenCanvas
const ctx = canvas.getContext("2d"); 

const width = bitmap.width;
const height = bitmap.height;
canvas.width = width;
canvas.height = height;

ctx.drawImage(bitmap, 0, 0, width, height);

利用 OffscreenCanvas 操作圖像

接下來的步驟就跟原先的一樣,先取得 ImageData 後再執行圖像外框透明化的像素操作,最後利用 putImageDataImageData 更新回 OffscreenCanvas

// worker 線程
const imageData = ctx.getImageData(0, 0, width, height);

const newImageData = makeImageDataTransparent(imageData, option);
ctx.putImageData(newImageData, 0, 0);

主線程中將 canvas 渲染到畫面上

到目前為止,worker 線程中的 canvas 都處理完操作圖像的邏輯了,由於 worker 中的 canvas 是從主線程傳來的,所以最後只需要將主線程一開始傳入的 canvas 渲染在畫面上

// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");

const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();

offscreenCanvasWorker.postMessage(
  { canvas: offscreen, imageUrl },
  [offscreen]
);
offscreenCanvasWorker.onmessage = () => {
  // 將 worker 處理過的 canvas 渲染在畫面上
  renderCanvas(canvas);
};

結果

https://ithelp.ithome.com.tw/upload/images/20231001/20162687Y5szALCVTE.png

  1. 原本預期使用 OffscreenCanvas 來改善效能,但似乎沒有想像中的順利,可以發現按下按鈕後,紅色方塊動畫一樣有卡頓的現象,看來瓶頸一樣是當執行 取得圖片像素(getImageData) 時會佔用到主線程的資源,推測原因可能是 canvas 一開始就是由主線程傳到 worker 線程,而在 worker 線程呼叫 getImageData 時,主線程一樣需要資源把任何操作圖像的邏輯回寫到主線程的 canvas
  2. 但無論如何將 操作像素(ImageData) 使圖片外圍透明化 的邏輯移到 worker 中處理,還是能夠增進效能,避免主線程的動畫卡頓

額外補充

今天的範例中用到了 createImageBitmap,將 blob 轉換為 ImageBitmap 型態的資料,但 Safari 似乎還不支援以 blob 參數傳入的方式,我實測在 Safari 中執行圖片會跑不出來,但也沒有錯誤訊息出現

// Safari 似乎無法正常處理傳入 blob 格式的資料
const bitmap = await createImageBitmap(imageBlob);

Reference

Loading Images with Web Workers


上一篇
Transferable objects - OffscreenCanvas
下一篇
共享的 Web worker - Shared worker
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言