内容简介:上一篇(环境搭建,简单接入):这一次,我们尝试使用WebAssembly来做简单的图片处理。我们选取一种最基本的图像处理——高斯模糊来尝试实现。原理可参考
上一篇(环境搭建,简单接入): C++编写WebAssembly初探
这一次,我们尝试使用WebAssembly来做简单的图片处理。
我们选取一种最基本的图像处理——高斯模糊来尝试实现。原理可参考 高斯模糊和卷积滤波简介
js向wasm传递数组
与传递number不同,传递数组时,需要js将数组拷贝到 wasm内存 中,并通过传递指针(数据在内存中的位置),让wasm通过访问内存的具体位置,来获取或修改数组。
另外,不同于js,wasm的内存管理由开发者进行控制,我们需要手动分配和释放内存。
这里的过程是,首先我们获得表示图片像素的数组,将这个数组复制到wasm内存,再调用wasm模块处理这些像素数据,处理完后js重新读取这块内存,并将处理过的图片画到canvas上。
// 被处理的图片 const srcImg = document.getElementById('srcImg'); srcImg.onload = () => { // onload时将图片画到canvas上,以获得像素数据 const { clientWidth = 0, clientHeight = 0 } = srcImg; var canvas = document.getElementById("drawerCanvas"); canvas.width = clientWidth; canvas.height = clientHeight; var ctx = canvas.getContext("2d"); ctx.drawImage(srcImg, 0, 0, clientWidth, clientHeight); // 获得像素数据 const imageData = ctx.getImageData(0, 0, clientWidth, clientHeight); // 处理数据 const resImageData = wasmProcess(imageData, clientWidth, clientHeight); // 将处理后的图片数据画到canvas上 ctx.putImageData(resImageData, 0, 0); } // 将js的typedarray复制到wasm的堆内存 function copyToHeap(typedArray) { const numBytes = typedArray.byteLength; const ptr = Module._malloc(numBytes); const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes); heapBytes.set(new Uint8Array(typedArray.buffer)); return heapBytes; } // 释放一块wasm内存 function freeHeap(heapBytes) { Module._free(heapBytes.byteOffset); } // 图片处理的函数 function wasmProcess(imgData, width, height) { const heapBytes = copyToHeap(imgData.data); // 调用c++暴露的方法。其中heapBytes.byteoffset传递的是wasm内存中数组的指针 Module.ccall( 'easyBlur', 'number', ['number', 'number', 'number', 'number', 'number'], [heapBytes.byteOffset, width, height, 3, 3] ); // 从wasm内存读取出处理后的数据 const newData = new Uint8ClampedArray(heapBytes); // 释放wasm内存 freeHeap(heapBytes); const newImageData = new ImageData(newData, width, height); return newImageData; }
简单的高斯模糊算法实现
这里取最简单的滤波器,即矩阵所有项都相等的滤波器。要使得滤波器的各项和为1,则每一项的值为1 / (cw*ch).
如一个3*3的滤波器为 [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11].我们可以简单地通过改变cw和ch来调整模糊的强度,cw和ch越大,扩散程度越大,则模糊强度也越大。
另外我们需要观察ctx.getImageData()得到的数组格式:获得的data是一个一维数组,按照从从左到右,从上到下的顺序记录了图片每个像素的值。其中每4个值为一组,分别代表同一个像素的r, g, b, a四个通道的数值。我们模糊时对每个通道进行单独处理。
我的代码:
#include <cstdint> #include <cmath> // 卷积操作,传入imageData像素数组的指针,imageData宽高,滤波器及滤波器宽高。 void conv(uint8_t *ptr, int width, int height, float* filter, int cw, int ch) { for (int i = ch / 2; i < height - ceil((float)ch / 2) + 1; i++) { for (int j = cw / 2; j < width - ceil((float)cw / 2) + 1; j++) { // rgba取前3个通道进行处理 for (int k = 0; k < 3; k++) { float sum = 0; int count = 0; for (int x = -ch / 2; x < ceil((float)ch / 2); x++) { for (int y = -cw / 2; y < ceil((float)cw / 2); y++) { sum += filter[count] * (float)ptr[((i+x)*width+(y+j))*4+k]; count++; } } ptr[(i*width+j)*4+k] = (uint8_t)sum; } } } } #ifdef __cplusplus extern "C" { #endif // 供js调用的函数,传入像素数组的指针,宽高,以及滤波器的宽高 // 这里为了简单,默认滤波器矩阵每一项的值相同,即1/(cw*ch)。 void easyBlur(uint8_t *ptr, int width, int height, int cw = 3, int ch = 3) { float* filter = new float[cw * ch]; float value = 1 / (float)(cw * ch); for (int i = 0; i < cw * ch; i++) { filter[i] = value; } conv(ptr, width, height, filter, cw, ch); delete [] filter; } #ifdef __cplusplus } #endif
效果预览
对于宽度200px左右的图片,使用长宽为5的滤波器效果如下:
瓶颈
使用js以相同的方法重新实现了一次,发现在图片较小时js处理的耗时更短,而图片较大时wasm虽然速度快于js,但处理的时间也非常长,是不能忍受的。
问题的原因很可能是将数据在js内存和wasm内存之间复制消耗大量的时间,影响性能。所以这种数据量非常大的场景下,wasm虽然优化了计算的时间,但因为数据传递的时间大大增加,反而成为了性能的瓶颈。
另外,对于前端来说,自己实现相关的处理算法,性能远不如线上一些库优化得好。
这里有更多前端可用的 图片处理库 。
Ref
以上所述就是小编给大家介绍的《C++ 编写 WebAssembly初探(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。