wasm? WebAssembly?
最近我开始问自己:“ Wasm值得关注吗?”
让我们找出答案。很少有语言可以直接编译为WASM。无论如何,让我们尝试。
我们将制作一个简单的Web应用程序,将您的网络摄像头转换为ASCII ART。
目的是尽可能多地编写代码。
让我们走!
# go mod init asciifyme
就是这样!
只是开玩笑。这不是那么简单。
我们将需要以下部分:
-
webcam
-将从Web Cam初始化和获取图像 -
canvas
-我们需要它来从图像获取像素数据 -
asciifyier
-将图像数据转换为字符串
网络摄像头:
这个模块将:
- 用
document.createElement
创建一个video
元素4 - 用koude初始化网络摄像头5
我们需要创建一个文件webcam/webcam.go
第一部分看起来像这样:
package webcam
import (
"fmt"
"syscall/js"
)
var (
navigator js.Value
video js.Value
)
func init() {
navigator = js.Global().Get("navigator")
video = js.Global().Get("document").Call("createElement", "video")
}
使用此代码,我们正在创建video
元素,并获取navigator
以备将来使用。
是时候设置网络摄像头:
func Setup() js.Value {
user_media_params := map[string]interface{}{
"video": true,
}
navigator.Call("getUserMedia", user_media_params, js.FuncOf(stream), js.FuncOf(err))
return video
}
我们将从主调用此功能,它将设置网络摄像头并返回video
对象以获取数据。
可是等等!我们需要实现两个回调stream
和err
:
func err(this js.Value, args []js.Value) interface{} {
fmt.Println("err")
return nil
}
func stream(this js.Value, args []js.Value) interface{} {
video.Set("srcObject", args[0])
video.Call("addEventListener", "canplaythrough", js.FuncOf(canPlay))
return nil
}
目前,我们将忽略错误并在控制台上写入错误。
stream
功能在视频元素中添加了一个流并收听canplaythrough
事件。
另一个回调?是的! video
将在有足够的数据时致电canPlay
回调。
func canPlay(this js.Value, args []js.Value) interface{} {
video.Call("play")
return nil
}
当我们有足够的数据播放时!
我们有一个video
,现在我们需要一个像素数据。让我们在canvas/canvas.go
中创建canvas
package canvas
import (
"syscall/js"
)
const (
CanvasWidth = 80
CanvasHeight = 40
)
var (
ctx js.Value
)
func init() {
ctx = js.Global().Get("document").Call("createElement", "canvas").Call("getContext", "2d")
}
我们正在创建canvas
元素并获取context
。将使用它绘制和获取像素数据。
func DrawImage(video js.Value) {
ctx.Call("drawImage", video, 0, 0, CanvasWidth, CanvasHeight)
}
我们可以通过将其传递到drawImage
函数中从video
绘制帧。
func GetImageData() []uint8 {
data := ctx.Call("getImageData", 0, 0, CanvasWidth, CanvasHeight).Get("data")
lenght := data.Get("length").Int()
goData := make([]uint8, lenght)
js.CopyBytesToGo(goData, data)
return goData
}
提取像素数据更为复杂。我们必须将uint8
的JS阵列获取到GO。
此功能从canvas
中获取数据长度,创建GO数组,然后将整个数据复制到Go数组中。
瞧!我们有一个像素数据。
剩下什么?将其转换为asciiart。
asciifyier/asciifyier.go
是我们需要的!
package asciifyier
import (
"asciifyme/canvas"
)
const (
Chars = " .,:;i1tfLCG08@"
CharsLength = 16
)
我们在这里不需要任何JS的东西。但是需要导入我们的canvas
以获取其大小。
func Asciify(data []uint8) string {
output := ""
for y := 0; y < canvas.CanvasHeight; y++ {
for x := 0; x < canvas.CanvasWidth; x++ {
offset := (y*canvas.CanvasWidth + x) * 4
red := data[offset]
green := data[offset+1]
blue := data[offset+2]
//alpha := data[offset+3]
brightness := (0.3*float64(red) + 0.59*float64(green) + 0.11*float64(blue)) / 255.0
char_index := CharsLength - int(brightness*CharsLength)
output += string(Chars[char_index])
}
output += "\n"
}
return output
}
我们在这里做什么?我们正在从uint8
数组中获取每个像素数据并创建一个字符串。我们的asciiart。
是时候到了main.go
...
package main
import (
"asciifyme/asciifyier"
"asciifyme/canvas"
"asciifyme/webcam"
"syscall/js"
)
var (
camera js.Value
window js.Value
pre js.Value
)
func init() {
camera = webcam.Setup()
window = js.Global().Get("window")
pre = js.Global().Get("document").Call("getElementById", "pre")
}
将所有碎片放在一起。我们将需要一个camera
,window.requestAnimationFrame
和pre
元素来显示我们的asciiart。
func loop(this js.Value, args []js.Value) interface{} {
window.Call("requestAnimationFrame", js.FuncOf(loop))
canvas.DrawImage(camera)
imageData := canvas.GetImageData()
output := asciifyier.Asciify(imageData)
pre.Set("innerHTML", output)
return nil
}
func main() {
loop(js.ValueOf(nil), make([]js.Value, 0))
select {}
}
在主循环中我们:
- 从
video
获取数据3 - 在
canvas
上绘制它 - 从
canvas
获取像素数据 - 使用
asciifyier
创建Asciiart2 - 将assiify绘制为
pre
另外一件事! select {}
使WASM程序不要退出!
就是这样。编译时间!
要在我们需要的浏览器中运行此操作:
- index.html
- wasm_exec.js
- 编译的应用程序
简单的index.html
文件
<html>
<head>
<title>asscify-me</title>
<style>
body{background-color:#000}pre{text-align:center}header{color:#daa520;font-size:18px;font-weight:700;text-shadow:0 0 3px gold}section{margin-top:30px;color:#32cd32;text-shadow:0 0 15px #0f0;font-size:14px}footer,footer a{margin-top:30px;color:red;text-shadow:0 0 15px tomato;font-size:14px}
</style>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("asciifyme.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body>
<pre id="pre"></pre>
</body>
</html>
和构建脚本:
#/bin/bash
export GOOS=js
export GOARCH=wasm
mkdir -p build
cp index.html build/
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" build/
go build -o build/asciifyme.wasm
现在我们需要运行:
# ./build.sh
从build
文件夹中使用文件,并使用浏览器。
请注意,当您使用https://
或localhost
P.S
但是等等!尺寸! 〜2MB是很多!是!
尝试thinygo
编译器,〜200KB好多了!
# tinygo build -o build/asciifyme.wasm -target wasm