Доступ к ImageData.data с помощью Go WebAssembly - PullRequest
1 голос
/ 03 мая 2019

Я хочу написать фотофильтр в Go, чтобы использовать его в качестве модуля WebAssembly.

Go имеет тип js.Value.Я могу Get, Set, Index и Call на нем.Но как быстро использовать массив пикселей из ImageData.data в Go?Использовать что-то вроде data.Index(index).Int() и .SetIndex(..., ...) крайне медленно.И я не проверял, если это дает правильный результат.

Первая попытка очень медленная (примерно в 50 раз медленнее, чем JS или Rust):

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 ]

1 голос
/ 03 июня 2019

Go 1.13 (еще не вышло) добавляет 2 функции к 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 системных вызовов.

1 голос
/ 23 мая 2019

ОК, я нашел решение.Это может быть сложнее, чем в 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.Я до сих пор не знаю почему.Но результат теперь намного лучше.

...