wasm实例开发005-图像高斯模糊

本文我们将介绍如何通过wasm来实现图像的高斯模糊效果,在开始之前我对高斯模糊做一些简单的了解。

一、什么是高斯模糊

高斯模糊(Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像杂讯以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。

高斯模糊运用了高斯的正态分布的密度函数,计算图像中每个像素的变换。

二、C代码编写

#include <stdio.h>
#include <math.h>
#include <stdint.h>
#include <malloc.h>

#define PI 3.141592653589793

uint8_t *gaussBlur(uint8_t *, uint8_t *, int, int, int);
int handleEdge(int, int, int);
double *makeGaussMatrix(int);

int main() {
    return 0;
}

uint8_t *gaussBlur(uint8_t *imageData, uint8_t *outImageData, int width, int height, int radius) {
    int gaussEdge = radius * 2 + 1;
    double *gaussMatrix = makeGaussMatrix(radius);
    for (int x=1; x<width; x++) {
        for (int y=0; y<height; y++) {
            int r = 0, g = 0, b = 0;
            for (int i=-radius; i<=radius; i++) {
                int m = handleEdge(i, x, width);
                for (int j=-radius; j<=radius; j++) {
                    int n = handleEdge(j, y, height);
                    int index = (n * width + m) * 4;
                    int ii = i + radius;
                    int jj = j + radius;
                    double gauss = gaussMatrix[jj * gaussEdge + ii];
                    r += (int)(imageData[index] * gauss);
                    g += (int)(imageData[index+1] * gauss);
                    b += (int)(imageData[index+2] * gauss);
                }
            }
            int index = (y * width + x) * 4;
            outImageData[index] = r;
            outImageData[index + 1] = g;
            outImageData[index + 2] = b;
            outImageData[index + 3] = 255;
        }
    }
    return outImageData;
}

int handleEdge(int i, int k, int length) {
    int m = k + i;
    if (m < 0) {
        m = -m;
    } else if (m >= length) {
        m = length + i - k;
    }
    return m;
}

double *makeGaussMatrix(int radius) {
    double sigma = (double)(radius / 3);
    double a = 1 / (2 * PI * pow(sigma, 2));
    double b = -1 / (2 * pow(sigma, 2));

    double gaussSum = 0;
    double length = pow(2*radius, 2);
    double *gaussMatrix = (double *)malloc(length);
    int index = 0;

    // 生成高斯矩阵
    for (int i=-radius; i<=radius; i++) {
        for (int j=-radius; j<=radius; j++) {
            double gxy = a * exp((pow(i, 2) + pow(j, 2)) * b);
            gaussMatrix[index++] = gxy;
            gaussSum += gxy;
        }
    }

    // 归一化,保证高斯矩阵的值在[0,1]之间
    for (int i=0; i<length; i++) {
        gaussMatrix[i] /= gaussSum;
    }

    return gaussMatrix;
}

gaussBlur是最终暴露给js调用的方法,该方法接受5个参数:

①、imageData 原图的像素数据

②、outImageData 基于imageData生成的用于存储结果的像素数据,可以在C程序里面定义,但是一定不能仅用imageDatalengthmalloc申请数组内存,后面存储数据的时候可能会出现内存越界的情况。

③、width 图片的宽

④、height 图片的高

⑤、radius 模糊半径 不要小于3

在处理像素的过程中,需要注意边界的处理。

三、将C程序编译成wasm应用

我们将上面的C代码保存为guass.c,注意头文件的引用,然后在.c文件相同目录下新建build.sh文件,输入以下内容并保存:

#! /usr/bin/env bash

emcc -o gauss.js ./gauss.c \
     -Oz \
     -s ALLOW_MEMORY_GROWTH=1 \
     -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
     -s EXPORTED_FUNCTIONS='["_gaussBlur", "_malloc", "_free"]' \
     -s LINKABLE=1

注意-s LINKABLE=1这个选项一定要加,如果不加我们没办法通过Module.asm.memory这个key拿到内存引用,每一次编译对应内存引用的key可能会变。

执行source 你的emsdk目录/emsdk_env.sh命令链接emcc命令,并在当前目录通过chmod +x ./build.sh命令赋予打包脚本执行权限,最后通过./build.sh命令完成C程序到wasm应用的编译。

四、使用js调用wasm应用

这里直接使用ccallcwrap会报错,不知道为啥,所以我们直接自己操作内存,将数据传递给C程序:

const inputImageData = new Uint8Array(imageData.data);
const inputBuf = _malloc(inputImageData.byteLength);
Module.HEAPU8.set(inputImageData, inputBuf);
const outImageData = new Uint8Array(imageData.data);
const outBuf = _malloc(outImageData.byteLength);
Module.HEAPU8.set(outImageData, outBuf);
const pointer = _gaussBlur(inputBuf, outBuf, imageData.width, imageData.height, radius.value);
const data = new Uint8Array(Module.asm.memory.buffer, pointer, imageData.data.length);
const newImageData = new ImageData(new Uint8ClampedArray(data), canvas.width, canvas.height);

其中inputImageData是输入的图片像素数据,outImageData是基于像素数据的一个副本,用来记录结果数据,newImageData是C程序处理完后返回的结果数据。

五、结语

本文我们实现了一个简单的用于处理图片的wasm应用,本例的难点在于如何将图片数据传递给C程序,并从C程序获得处理好的图片数据,这里我们放弃了使用ccall/cwrap的方式,使用原生的方式完成了数据的传递和接受,过程中遇到了各类内存越界的问题,大家在编写此类应用的时候一定要注意js传递的数据类型要和C程序接受的数据类型是对应的。

在线demo:https://demo.deanhan.cn/gauss/

如需下载本文涉及的源码及素材包,请在关注本站公众号后发送:gauss

系列文章导航:

第1节: Wasm实例开发001-斐波那契数列

第2节: Wasm实例开发002-最长回文字符串

第3节: wasm实例开发003-简单数据的加密解密

第4节: wasm实例开发004-矩阵乘法

第6节: wasm实例开发006-证件照换底色

第7节: wasm实例开发007-图片菱形渐变

  • 支付宝二维码 支付宝
  • 微信二维码 微信

本文地址: /wasm-c-gauss.html

版权声明: 本文为原创文章,版权归 逐梦个人博客 所有,欢迎分享本文,转载请保留出处!

相关文章