使用Go WebAssembly访问ImageData.data

时间:2019-05-03 15:08:32

标签: go webassembly

我想在Go中编写一个照片滤镜,以将其用作WebAssembly模块。

Go的类型为js.Value。我可以在其上GetSetIndexCall。但是,如何快速使用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);
}

2 个答案:

答案 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慢。我仍然不知道为什么。但是结果现在好多了。