【问题标题】:Access ImageData.data using Go WebAssembly使用 Go WebAssembly 访问 ImageData.data
【发布时间】:2019-05-03 15:08:32
【问题描述】:

我想在 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);
}

【问题讨论】:

  • 我不确定 webassembly 能否在如此简单的场景中为您提供加速。在 JS 和 WS 之间切换的开销可能大于收益。
  • 您认为照片滤镜是一个简单的场景吗?如果我用 9x9 矩阵和全高清图片在 Javascript 中运行它,处理起来需要很长时间。我想说这并不比 3D 计算复杂。

标签: image go slice webassembly


【解决方案1】:

Go 1.13(尚未发布)向syscall/js 添加了两个函数,允许您复制整个数组,因此您不必再调用Index()SetIndex() 来调用每个像素的每个组件!

您目前可以在tip 上看到它们:

https://tip.golang.org/pkg/syscall/js/#CopyBytesToGo

https://tip.golang.org/pkg/syscall/js/#CopyBytesToJS

所以基本上你可以做的就是首先将整个图像数据复制到一个 Go 字节切片中,然后在 Go 中使用它(进行过滤),一旦你完成,复制回改变的切片。它只需要 2 个js 系统调用。

【讨论】:

    【解决方案2】:

    好的,我找到了解决方案。它可能比 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 慢。我仍然不知道为什么。但结果现在好多了。

    【讨论】:

    • 能否分享一下,Go 中如何访问缓冲区?
    • 哦,对不起,答案不正确。几周后,我发现这不起作用。没有时间写一个新的,但简而言之: - 将字节数组传递给 WASM 函数。在 Go js.TypedArrayOf(bytearray)。在 Go >=1.13 中使用 icza 的答案,使用 CopyBytesToGo
    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 2018-08-16
    • 2020-07-01
    • 2019-09-16
    • 1970-01-01
    相关资源
    最近更新 更多