隨著網頁應用程式日益複雜,前端效能最佳化變得至關重要。WebAssembly 提供了在瀏覽器中執行高效能程式碼的途徑。本文以圖片處理為例,展示如何結合 Rust 和 WebAssembly 開發更流暢的網頁體驗。首先,使用 Rust 的 image crate 進行圖片操作,並透過 wasm-pack 將程式碼編譯成 Wasm 模組。接著,前端 JavaScript 程式碼從 Canvas 元素取得圖片資料,並呼叫 Wasm 模組中的函式進行圖片縮放。最後,將處理後的圖片資料重新繪製到 Canvas 上,展現 WebAssembly 帶來的效能提升。這個方法有效解決了 JavaScript 在處理大量圖片資料時的效能瓶頸,提供更快速、更流暢的使用者經驗。
使用 Rust 與 WebAssembly 進行圖片處理
本章節將介紹如何使用 Rust 與 WebAssembly 進行圖片處理。我們將使用 image crate 來處理圖片,並將 Rust 編譯為 WebAssembly,以便在網頁中使用。
建立 WebAssembly 專案
首先,我們需要建立一個新的 WebAssembly 專案。使用以下命令建立一個新的專案:
wasm-pack new wasm-image-processing
然後,在 Cargo.toml 檔案中新增 image crate:
[dependencies]
wasm-bindgen = "0.2.63"
image = "0.24.5"
確保 edition 設定為 "2021"。
定義 JavaScript API
在開始撰寫 Rust 程式碼之前,我們需要思考一下要如何設計 JavaScript API。我們希望能夠調整圖片大小,因此需要定義一個函式來實作此功能。為了簡化範例,我們將圖片縮小一半。
瞭解 image crate 的 resize 函式
image crate 中的 resize 函式定義如下:
pub fn resize<I: GenericImageView>(
image: &I,
nwidth: u32,
nheight: u32,
filter: FilterType
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
where
I::Pixel: 'static,
<I::Pixel as Pixel>::Subpixel: 'static,
該函式接受一個實作 GenericImageView trait 的圖片物件、新的寬度、新的高度和一個 FilterType 列舉值。我們需要將圖片資料轉換為實作 GenericImageView trait 的型別。
建立前端專案
在 wasm-image-processing 資料夾中建立一個新的前端專案:
npm init wasm-app client
在 client/index.html 檔案中新增以下 HTML 程式碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cat image processor</title>
</head>
<body>
<noscript>
This page contains webassembly and javascript content,
please enable javascript in your browser.
</noscript>
<input type="file" name="image-upload" id="image-upload" value="">
<br>
<button id="shrink">Shrink</button>
<br>
<canvas id="preview"></canvas>
<script src="./bootstrap.js"></script>
</body>
</html>
該頁面包含一個檔案選擇器、一個按鈕和一個 <canvas> 元素,用於顯示圖片。
載入圖片檔案到 <canvas>
在 index.js 檔案中新增以下程式碼:
function setup(event) {
const fileInput = document.getElementById('image-upload')
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0]
const imageUrl = window.URL.createObjectURL(file)
const image = new Image()
image.src = imageUrl
image.addEventListener('load', (loadEvent) => {
const canvas = document.getElementById('preview')
canvas.width = image.naturalWidth
canvas.height = image.naturalHeight
canvas.getContext('2d').drawImage(
image,
0,
0,
canvas.width,
canvas.height
)
})
})
}
內容解密:
此段程式碼用於載入圖片檔案到 <canvas> 元素中。首先,我們取得檔案選擇器的元素,並為其新增一個事件監聽器。當檔案選擇器中的檔案發生變化時,我們取得所選檔案的 URL,並建立一個新的 Image 物件。然後,我們將圖片的 src 屬性設定為所選檔案的 URL,並為其新增一個 load 事件監聽器。當圖片載入完成後,我們取得 <canvas> 元素,並將其寬度和高度設定為圖片的自然寬度和高度。最後,我們使用 drawImage 方法將圖片繪製到 <canvas> 上。
此段程式碼分為以下幾個步驟:
- 取得檔案選擇器的元素,並為其新增事件監聽器。
- 當檔案選擇器中的檔案發生變化時,取得所選檔案的 URL。
- 建立一個新的
Image物件,並將其src屬性設定為所選檔案的 URL。 - 當圖片載入完成後,取得
<canvas>元素,並設定其寬度和高度。 - 使用
drawImage方法將圖片繪製到<canvas>上。
此段程式碼的目的是將使用者所選的圖片檔案載入到 <canvas> 元素中,以便後續進行圖片處理。
將圖片傳遞給 WebAssembly
現在已經有了一個基本的 JavaScript 應用程式,可以將圖片載入到 <canvas> 中,但如何將這些資料傳遞給 Wasm 程式碼?如前所述,圖片可以表示為畫素的集合,每個畫素的顏色可以用整數表示。JavaScript 和 Rust 都非常擅長處理整數陣列,因此將使用這種格式在 JavaScript 和 Rust 程式碼之間傳遞資料。
圖片資料的表示方法
在這裡,畫素將被表示為四個顏色資料:紅色、綠色、藍色和 Alpha 通道,分別代表畫素的紅色強度、綠色強度、藍色強度和透明度。Alpha 為 0% 表示完全透明,Alpha 為 100% 表示完全不透明。每個值都由一個 u8 表示,因此範圍在 0 到 255 之間。在 Rust 端,這可以由 Vec<u8> 表示;在 JavaScript 端,可以由 Uint8ClampedArray 表示。
在 Rust 中定義 shrink_by_half 函式
在 Rust 端,可以完成函式定義,更新 lib.rs 檔案如下:
use image::{RgbaImage};
use image::imageops;
use wasm_bindgen::prelude::*;
// 當啟用 'wee_alloc' 功能時,使用 'wee_alloc' 作為全域分配器。
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub fn shrink_by_half(
original_image: Vec<u8>,
width: u32,
height: u32
) -> Vec<u8> {
let image: RgbaImage = image::ImageBuffer::from_vec(width, height, original_image).unwrap();
let output_image = imageops::resize(&image, width / 2, height / 2, imageops::FilterType::Nearest);
output_image.into_vec()
}
內容解密:
original_image引數:代表原始圖片資料的Vec<u8>,包含每個畫素的 RGBA 資訊。width和height引數:代表原始圖片的寬度和高度,用於從一維陣列重建二維圖片。image::ImageBuffer::from_vec:將Vec<u8>轉換為RgbaImage,以便進行圖片處理。imageops::resize:使用指定的縮放演算法(此處為FilterType::Nearest)將圖片縮放至指定大小。output_image.into_vec():將處理後的圖片轉換回Vec<u8>,以便傳回給 JavaScript。
在前端呼叫 shrink_by_half 函式
在前端頁面中,可以為「Shrink」按鈕新增事件監聽器,以觸發對 shrink_by_half() Wasm 函式的呼叫。修改 index.js 檔案如下:
import * as wasm from '../pkg/wasm_image_processing';
// 在 setup 函式中新增事件監聽器
document.getElementById('shrink').addEventListener('click', () => {
const canvas = document.getElementById('preview');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const originalImage = imageData.data;
const width = canvas.width;
const height = canvas.height;
const shrunkImage = wasm.shrink_by_half(originalImage, width, height);
// 將 shrunkImage 資料繪製到 canvas 上
const shrunkCanvas = document.getElementById('shrunk-preview');
shrunkCanvas.width = width / 2;
shrunkCanvas.height = height / 2;
const shrunkCtx = shrunkCanvas.getContext('2d');
const shrunkImageData = shrunkCtx.createImageData(width / 2, height / 2);
shrunkImageData.data.set(shrunkImage);
shrunkCtx.putImageData(shrunkImageData, 0, 0);
});
內容解密:
- 取得 Canvas 資料:從
<canvas>中取得圖片資料,包括寬度、高度和畫素資料。 - 呼叫
shrink_by_half:將原始圖片資料、寬度和高度傳遞給 Wasm 中的shrink_by_half函式,獲得縮放後的圖片資料。 - 繪製縮放後的圖片:將縮放後的圖片資料繪製到另一個
<canvas>上,以顯示處理結果。
使用WebAssembly提升網頁前端效能:以圖片處理為例
隨著網頁應用程式變得越來越複雜,提升前端效能成為了一個重要的課題。WebAssembly(Wasm)是一種二進位制指令格式,可以在現代網頁瀏覽器中執行,提供了一種提升網頁效能的方法。本文將介紹如何使用Rust和WebAssembly來建立一個高效能的圖片處理網頁應用程式。
建立Rust與WebAssembly的開發環境
首先,我們需要建立一個Rust專案,並使用wasm-pack工具將Rust程式碼編譯成WebAssembly。wasm-pack是一個用於建立和封裝WebAssembly模組的工具,可以簡化Rust與JavaScript之間的互動。
步驟1:建立Rust專案並新增wasm-image-processing crate
cargo new wasm-image-processing --lib
在Cargo.toml中新增以下設定:
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.63"
步驟2:實作圖片縮小功能
在src/lib.rs中實作一個簡單的圖片縮小功能:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn shrink_by_half(input: &[u8], width: u32, height: u32) -> Vec<u8> {
// 省略實作細節...
let mut output = Vec::new();
// 將輸入圖片縮小一半並存入output
output
}
步驟3:使用wasm-pack編譯Rust程式碼成WebAssembly
wasm-pack build
在JavaScript中使用WebAssembly模組
接下來,我們需要在JavaScript中使用編譯好的WebAssembly模組。首先,建立一個新的JavaScript專案,並安裝必要的依賴項。
步驟1:安裝依賴項
{
"dependencies": {
"wasm-image-processing": "file:../pkg"
},
"devDependencies": {
"webpack": "^4.29.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5",
"copy-webpack-plugin": "^5.0.0"
}
}
步驟2:在JavaScript中匯入和使用WebAssembly模組
import * as wasmImage from "wasm-image-processing";
function setup(event) {
// ...
const shrinkButton = document.getElementById('shrink');
shrinkButton.addEventListener('click', function(event) {
const canvas = document.getElementById('preview');
const canvasContext = canvas.getContext('2d');
const imageBuffer = canvasContext.getImageData(0, 0, canvas.width, canvas.height).data;
const outputBuffer = wasmImage.shrink_by_half(imageBuffer, canvas.width, canvas.height);
const u8OutputBuffer = new ImageData(new Uint8ClampedArray(outputBuffer), canvas.width / 2);
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = canvas.width / 2;
canvas.height = canvas.height / 2;
canvasContext.putImageData(u8OutputBuffer, 0, 0);
});
}
程式碼解密:
- 首先,我們從canvas元素中取得2D繪圖上下文。
- 使用
getImageData方法取得canvas中的圖片資料。 - 將圖片資料傳遞給
wasmImage.shrink_by_half函式,該函式是由Rust編譯成WebAssembly的模組提供的。 - 將縮小後的圖片資料轉換成
ImageData物件,並繪製到canvas上。
執行與測試
- 在
wasm-image-processing目錄下執行wasm-pack build。 - 切換到
client目錄,執行npm install && npm run start。 - 開啟瀏覽器,存取
http://localhost:8080。