我想在Go中编写一个照片滤镜,以将其用作WebAssembly模块。
Go的类型为js.Value
。我可以在其上Get
,Set
,Index
和Call
。但是,如何快速使用Go中ImageData.data
的像素阵列?使用data.Index(index).Int()
和.SetIndex(..., ...)
之类的东西非常慢。而且我没有检查,是否得到正确的结果。
第一次尝试非常慢(比JS或Rust慢大约50倍):
func Convolve(canvas js.Value, matrix []float64, factor float64) {
side := int(math.Sqrt(float64(len(matrix))))
halfSide := int(side / 2)
context := canvas.Call("getContext", "2d")
source := context.Call("getImageData", 0.0, 0.0, canvas.Get("width").Int(), canvas.Get("height").Int())
sourceData := source.Get("data")
imageWidth := source.Get("width").Int()
imageHeight := source.Get("height").Int()
output := context.Call("createImageData", imageWidth, imageHeight)
outputData := output.Get("data")
for y := 0; y < imageHeight; y++ {
for x := 0; x < imageWidth; x++ {
outputIndex := (y * imageWidth + x) * 4
r := 0.0
g := 0.0
b := 0.0
for cy := 0; cy < side; cy++ {
for cx := 0; cx < side; cx++ {
scy := y + cy - halfSide
scx := x + cx - halfSide
if scy >= 0 && scy < imageHeight && scx >= 0 && scx < imageWidth {
sourceIndex := (scy * imageWidth + scx) * 4
modify := matrix[cy * side + cx]
r += sourceData.Index(sourceIndex).Float() * modify
g += sourceData.Index(sourceIndex + 1).Float() * modify
b += sourceData.Index(sourceIndex + 2).Float() * modify
}
}
}
outputData.SetIndex(outputIndex, r * factor)
outputData.SetIndex(outputIndex + 1, g * factor)
outputData.SetIndex(outputIndex + 2, b * factor)
outputData.SetIndex(outputIndex + 3, sourceData.Index(outputIndex + 3))
}
}
context.Call("putImageData", output, 0, 0);
}
答案 0 :(得分:1)
Go 1.13(尚未推出)在syscall/js
中添加了2个函数,这些函数将允许您复制整个数组,因此您不必恢复调用Index()
和SetIndex()
到每个像素的每个分量!
您目前可以在tip
上看到它们:
https://tip.golang.org/pkg/syscall/js/#CopyBytesToGo
https://tip.golang.org/pkg/syscall/js/#CopyBytesToJS
因此,基本上,您可以做的是首先将整个图像数据复制到Go字节切片中,然后在Go中使用它(进行过滤),完成后,将更改后的切片复制回去。它仅需要2个js
系统调用。
答案 1 :(得分:0)
好的,我找到了解决方案。它可能比Rust更复杂,但对我来说有效。我正在使用内存管理Wasm模块来手动分配和释放Wasm内存。我将整个ImageData.data复制到其中,并在完成工作后将其复制回去。这样可以使整个过程更快。
const go = new window.Go();
// use the same WASM memory for all Wasm instances
const memory = new WebAssembly.Memory({initial: 1024});
Promise.all([
// The main Wasm module with my photo filter
WebAssembly.instantiateStreaming(fetch('some-go-wasm-module.wasm'), {
env: {memory},
...go.importObject
}),
// the memory library written in C provides: abort, calloc, free, malloc, memcoy, memset, sbrk
// source: https://github.com/guybedford/wasm-stdlib-hack/blob/master/dist/memory.wasm
WebAssembly.instantiateStreaming(fetch("memory.wasm"), {
env: {memory}
})
])
.then(module => {
go.run(module[0].instance);
window.wasm.memHelper = {
memory,
...module[1].instance.exports
};
});
然后我可以用它来分配可以通过Go函数访问的内存:
const context = canvas.getContext("2d");
const size = canvas.width * canvas.height * 4;
// allocate memory for the image bitmap
const ptr = window.wasm.memHelper.malloc(size);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// create a new ImageData object from this memory
const dataGo = new Uint8ClampedArray(window.wasm.memHelper.memory.buffer, ptr, size);
const imageDataGo = new ImageData(dataGo, canvas.width, canvas.height);
// copy the image from JS context to the Wasm context
imageDataGo.data.set(imageData.data);
// run my Go filter
window.wasm.go.convolve_mem(ptr, canvas.width, canvas.height);
// copy the image bitmap from Wasm context back to the canvas
context.putImageData(imageDataGo, 0, 0);
// free memory
window.wasm.memHelper.free(ptr);
过滤器本身并没有太大变化:
// somewhere in main():
// The function wich is called from JS
exports["convolve_mem"] = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ptr := uintptr(args[0].Int())
width := args[1].Int()
height := args[2].Int()
size := width * height * 4
// Create an byte array as big as possible and create a slice with the correct size. Because we can not define a array size with non-constant variable.
data := (*[1 << 30]byte)(unsafe.Pointer(ptr))[:size:size]
matrix := []float64{
0.0, 0.2, 0.0,
0.2, 0.2, 0.2,
0.0, 0.2, 0.0,
}
benchmarks.ConvolveMem(data, width, height, matrix, 1)
return nil
})
// the filter function:
func ConvolveMem(data []byte, width int, height int, matrix []float64, factor float64) {
side := int(math.Sqrt(float64(len(matrix))))
halfSide := int(side / 2)
newData := make([]byte, width*height*4)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
outputIndex := (y*width + x) * 4
r := 0.0
g := 0.0
b := 0.0
for cy := 0; cy < side; cy++ {
for cx := 0; cx < side; cx++ {
scy := y + cy - halfSide
scx := x + cx - halfSide
if scy >= 0 && scy < height && scx >= 0 && scx < width {
sourceIndex := (scy*width + scx) * 4
modify := matrix[cy*side+cx]
r += float64(data[sourceIndex]) * modify
g += float64(data[sourceIndex+1]) * modify
b += float64(data[sourceIndex+2]) * modify
}
}
}
newData[outputIndex] = byte(r * factor)
newData[outputIndex+1] = byte(g * factor)
newData[outputIndex+2] = byte(b * factor)
newData[outputIndex+3] = data[outputIndex+3]
}
}
copy(data, newData)
}
现在,整个过程比我的Rust实现要快一些。两者仍然比纯JS慢。我仍然不知道为什么。但是结果现在好多了。