WebAssembly
为前端开发带来了新的可能性,一些原本由 C/C++ 开发的库经过很简单的改造,就可以编译到 WebAssembly
,在 Node.js、浏览器端使用。
对于 Node.js,我们之前已经有了 node-ffi
等方式来调用 C++ 库,但是 node-ffi
并不能用在浏览器里,WebAssembly
使在浏览器环境使用 C/C++ 库成为可能。
WebAssembly
一样受浏览器沙箱限制,并没有比普通 js 更多的能力,但是在计算密集型任务中拥有比普通 js 更好的性能表现,否则移植 C/C++ 库也没有意义。
ImageMagick 是著名的 C/C++ 图形工具库,有命令行上的 PhotoShop
之称,支持包括 psd,ai 等超过 200 种格式图像的各种处理,本次我们把 ImageMagick
移植到前端,用它来在浏览器中完成各种图像处理操作。
移植主要使用基于 LLVM
的 Emscripten
工具链。
LLVM
是一个开源编译器平台,以 LLVM Intermediate Representation (LLVM IR)
作为中间代码,实现了多种语言编译到多种目标平台。
如图所示:
LLVM示意图
一种编程语言只要实现 LLVM
前端,就可以支持x86、arm等目标平台。
一个新的目标平台只要实现 LLVM
后端,C/C++、haskell 等语言就可以编译到此平台。
WebAssembly
就是一个新的目标平台。而 Emscripten
则是一个 LLVM
后端,能够把 LLVM IR
编译到 WebAssembly
。
工作流程
Emscripten
工具链包括 emcc
,emconfigure
等工具,借助这些工具,可以把 C/C++ 库编译、构建成 WebAssembly
文件。
配合其他 LLVM
相关工具,可以把更多语言编译到 WebAssembly
,例如 AssemblyScript,它可以把 TypeScript
编译到 WebAssembly
。
官网 (https://emscripten.org/docs/getting_started/downloads.html) 提供的环境搭建方式:
# Get the emsdk repo git clone https://github.com/emscripten-core/emsdk.git # Enter that directory cd emsdk # Fetch the latest version of the emsdk (not needed the first time you clone) git pull # Download and install the latest SDK tools. ./emsdk install latest # Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file) ./emsdk activate latest # Activate PATH and other environment variables in the current terminal # On Windows, run emsdk instead of ./emsdk, and emsdk_env.bat instead of source ./emsdk_env.sh. source ./emsdk_env.sh
除此之外,还需要安装 CMake
、 autoconf
、 libtool
、 pkg-config
工具。
常规的环境搭建方式,可能会遇到下载慢、下载失败的问题,甚至导致环境有部分组件缺失。因此强烈建议使用 docker
,从 Docker Hub
下载已经搭建好的环境。
本文使用 docker
搭建环境。
Docker Hub
上使用最多的 emscripten
镜像是 trzeci/emscripten,除了 emsdk
外,还安装了 CMake
、 make
等构建工具。
但是对于我们想构建 ImageMagick
,这些工具还不够,因此我以 trzeci/emscripten 为基础镜像,构建了新的镜像 mk33mk33/wasm-base,在 trzeci/emscripten 的基础上,安装了 autoconf
、 libtool
、 pkg-config
三个构建工具。
安装命令如下(没有 docker
的同学请先安装 docker
):
docker pull mk33mk33/wasm-base
对 docker 构建过程有兴趣的同学可以查看以上两个镜像的 Dockfile
了解镜像构建细节。
trzeci/emscripten
:https://github.com/trzecieu/emscripten-docker/blob/master/docker/trzeci/emscripten/Dockerfile
mk33mk33/wasm-base
:https://github.com/mk33mk333/wasm-im/blob/master/docker/wasm-base/Dockerfile
ImageMagick
功能强大,依赖库也众多,但是大部分都是可选的,我们可以根据我们需要的功能选择使用哪些依赖。
本次我们选择最常用的 jpg、png、webp 支持。
支持jpg格式需要 libjpeg
库,支持 png 格式需要 libpng
库,另外 libpng
需要依赖 zlib
,支持 webp 需要 libwebp
库,libwebp
依赖前面的所有库。
因此我们需要先把 libjpeg
、 libpng
、 zlib
、 libwebp
用 emsdk
编译成目标平台为 WebAssembly
的静态库。
然后用 ImageMagick
和以上静态库,一起编译成最终的 wasm 文件。
C项目一般使用 make
工具链进行构建,主要是根据当前环境,对源码进行编译、链接,生成动态库、静态库和二进制应用程序。
项目庞大时会使用 autotool
、 CMake
等工具辅助生成 Makefile
,Makefile
就是 make
工具执行构建使用的脚本。
如此构建的 C 库我们安装时,一般流程就是:
./configure # 检查系统环境,判断当前环境是否满足编译条件 make # 执行编译 make install # 将二进制应用程序安装的指定位置
详细的 CMake
、 autotool
使用可以写一本书,本次我们主要关注生成静态库、查找依赖、查找头文件的配置。
环境:win10 下的 virtualbox6.0.4 内的 Ubuntu 18.10
本文源码链接:https://github.com/mk33mk333/wasm-im
项目结构:
# tree -L 2 . ├── docker # 存放docker文件 │ ├── emscripten │ └── wasm-base ├── external │ ├── build-item.sh # 构建脚本 │ ├── build.sh # 构建脚本入口 │ ├── dist # 生成的js和wasm │ ├── ImageMagick # 依赖库 │ ├── libjpeg # 依赖库 │ ├── libpng # 依赖库 │ ├── libwebp # 依赖库 │ ├── README.md │ └── zlib # 依赖库 ├── README.md ├── todo.md └── web # 验证页面 ├── img ├── index.html ├── README.md ├── wasm # 存放wasm模块和胶水js ├── webp-wasm.html #webp 测试 └── worker.js
首先进入存放外部依赖和编译脚本的目录 external
。
因为编译都要在docker内进行,因此先建立一个编译脚本入口文件 build.sh
。
具体的编译流程写在 build-item.sh
里。
# build.sh docker run --rm --workdir /wasm -v $(pwd):/wasm mk33mk33/wasm-base bash ./build-item.sh
将当前目录映射到docker中的 /wasm 目录下,执行 build-item.sh
。
首先在 build-item.sh
中加入 wasm 用的编译参数。
export CFLAGS="-O3 -s BINARYEN_TRAP_MODE=clamp -s ALLOW_MEMORY_GROWTH=1 -s USE_PTHREADS=0" export CXXFLAGS="-O3 -s BINARYEN_TRAP_MODE=clamp -s ALLOW_MEMORY_GROWTH=1 -s USE_PTHREADS=0"
CFLAGS
为编译 c 语言文件的编译参数,CXXFLAGS
为编译 C++ 文件的编译参数。
-O3 为生产环境的优化级别。
ALLOW_MEMORY_GROWTH=1 允许 wasm 使用的堆动态增加,如果现有的大小不足,可以重新改变堆的大小,以满足程序运行过程中不断扩充的内存使用。
BINARYEN_TRAP_MODE=clamp 可以避免一些数字导致的错误。
USE_PTHREADS=0 暂不使用多线程。
make 工具会直接使用上述环境变量。
git clone
源码 (https://github.com/madler/zlib) 到 external
目录下,进入 zlib
源码目录,可以看到有 CMakeLists.txt
,因此我们可以使用 CMake
工具来构建 zlib
。
查看 CMakeLists.txt
,并无复杂配置,也没有外部依赖,并且已经做了生成静态库的配置。
# zlib/CMakeLists.txt ... add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) ...
因此直接在 build-item.sh
中加入
cd /wasm/zlib # 在docker环境下进入映射的源码目录 emconfigure cmake . # 使用 emconfigure 调用 cmake 生成 makefile emmake make # 使用 emmake 调用 make 生成 libz.a
执行 sh build.sh
,编译成功后,我们可以在 zlib
目录下看到 libz.a 文件。
注意:cmake 执行后会生成缓存文件,包括 CMakeCache.txt、CMakeFiles 目录等,修改配置后需要删掉缓存文件再执行构建。
git clone
源码 (https://github.com/glennrp/libpng) 并进入源码目录,有 CMakeLists.txt
,也可以用 CMake
来构建。
查看 CMakeLists.txt
,发现里面有生成动态库的选项 PNG_SHARED
和测试的选项 PNG_TESTS
,都可以不用。另外有两个外部依赖 zlib
和 m
,m
在 ubuntu 环境下不能自动被搜索到,因此需要自己配置。
# libpng/CMakeLists.txt ... option(PNG_BUILD_ZLIB "Custom zlib Location, else find_package is used" OFF) if(NOT PNG_BUILD_ZLIB) find_package(ZLIB REQUIRED) include_directories(${ZLIB_INCLUDE_DIR}) endif() if(UNIX AND NOT APPLE AND NOT BEOS AND NOT HAIKU) find_library(M_LIBRARY m) else() # libm is not needed and/or not available set(M_LIBRARY "") endif() # COMMAND LINE OPTIONS option(PNG_SHARED "Build shared lib" ON) option(PNG_STATIC "Build static lib" ON) option(PNG_TESTS "Build libpng tests" ON) ...
在 build-item.sh
中加入
cd /wasm/libpng emconfigure cmake . -DPNG_SHARED=OFF -DPNG_TESTS=OFF -DZLIB_INCLUDE_DIR=/wasm/zlib -DZLIB_LIBRARY=/wasm/zlib -DM_LIBRARY=/usr/lib/x86_64-linux-gnu # -D<key>=<value>是在命令行上给cmake添加变量的格式 emmake make
git clone
源码 (https://github.com/LuaDist/libjpeg) 并进入源码目录,查看 CMakeLists.txt
,发现编译静态库选项默认关闭,需要手动开启。
# libjpeg/CMakeLists.txt ... OPTION(BUILD_STATIC OFF) OPTION(BUILD_EXECUTABLES ON) OPTION(BUILD_TESTS ON) ...
在 build-item.sh
中加入
cd /wasm/libjpeg emconfigure cmake . -DBUILD_STATIC=ON emmake make
git clone
源码 (https://github.com/webmproject/libwebp) 并进入源码目录,查看 CMakeLists.txt
,libwebp
为编译到 WebAssembly
增加了专门的编译选项 WEBP_BUILD_WEBP_JS
,需要我们手动开启。另外需要提供 zlib
,libpng
,libjpeg
的库路径和头文件路径。
但是之后从 ImageMagick
的编译检查项中发现,ImageMagick
不但需要 libwebp
还需要 libwebpmux
,目前的 CMakeLists.txt
在开启 WEBP_BUILD_WEBP_JS
时不会编译出 libwebpmux
,因此我们对 CMakeLists.txt
做如下修改:
# libwebp/CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(WebP C) # Options for coder / decoder executables. option(WEBP_BUILD_LIBWEBPMUX "Build the libwebpmux." ON) # 增加此行选项,用于开启对libwebpmux的编译 option(WEBP_ENABLE_SIMD "Enable any SIMD optimization." ON) option(WEBP_BUILD_ANIM_UTILS "Build animation utilities." ON) ... if(WEBP_BUILD_GIF2WEBP OR WEBP_BUILD_IMG2WEBP OR WEBP_BUILD_LIBWEBPMUX) # 修改此行,增加 OR WEBP_BUILD_LIBWEBPMUX 此if为true就会编译libwebpmux parse_makefile_am(${CMAKE_CURRENT_SOURCE_DIR}/src/mux "WEBP_MUX_SRCS" "") add_library(libwebpmux ${WEBP_MUX_SRCS}) target_link_libraries(libwebpmux webp) ...
然后在 build-item.sh
中加入
cd /wasm/libwebp emconfigure cmake . -DWEBP_BUILD_WEBP_JS=ON -DZLIB_INCLUDE_DIR=/wasm/zlib -DZLIB_LIBRARY=/wasm/zlib -DPNG_LIBRARY=/wasm/libpng -DPNG_PNG_INCLUDE_DIR=/wasm/libpng -DJPEG_LIBRARY=/wasm/libjpeg -DJPEG_INCLUDE_DIR=/wasm/libjpeg emmake make
提示:
libwebp
在编译时会生成webp_wasm.js
和webp_wasm.wasm
,使用此模块能够使不支持webp的浏览器显示webp图片,我把此模块和官方示例也提取了出来,地址为 https://mk33mk333.github.io/wasm-im/webp-wasm.html,有兴趣的同学可以研究。
前面几个库相对比较小,编译比较简单,ImageMagick
的编译比较复杂。
首先下载源码,进入源码目录。ImageMagick
没有支持 CMake
,所以我们只能用 autotool
工具来编译。
autotool
比 CMake
复杂些,流程如下:
autotool生成流程
简单的说,就是
configure.ac
,执行 autoconf
生成 configure
Makefile.in
,执行./configure
生成 Makefile
make && make install
我们主要关注 configure.ac
中的配置。
在 configure.ac
文件的结尾处,我们可以看到 ImageMagick
的配置选项:
#ImageMagick/configure.ac ... # ============================================================================== # ImageMagick Configuration # ============================================================================== AC_MSG_NOTICE([ ============================================================================== ${PACKAGE_NAME} ${PACKAGE_VERSION}${PACKAGE_VERSION_ADDENDUM} is configured as follows. Please verify that this configuration matches your expectations. Host system type: $host Build system type: $build Option Value ------------------------------------------------------------------------------ Shared libraries --enable-shared=$enable_shared $libtool_build_shared_libs Static libraries --enable-static=$enable_static $libtool_build_static_libs Build utilities --with-utilities=$with_utilities $with_utilities Module support --with-modules=$build_modules $build_modules GNU ld --with-gnu-ld=$with_gnu_ld $lt_cv_prog_gnu_ld Quantum depth --with-quantum-depth=$with_quantum_depth $with_quantum_depth High Dynamic Range Imagery --enable-hdri=$enable_hdri $enable_hdri Install documentation: $wantdocs Memory allocation library: JEMalloc --with-jemalloc=$with_jemalloc $have_jemalloc TCMalloc --with-tcmalloc=$with_tcmalloc $have_tcmalloc UMem --with-umem=$with_umem $have_umem Delegate library configuration: BZLIB --with-bzlib=$with_bzlib $have_bzlib Autotrace --with-autotrace=$with_autotrace $have_autotrace DJVU --with-djvu=$with_djvu $have_djvu DPS --with-dps=$with_dps $have_dps FFTW --with-fftw=$with_fftw $have_fftw FLIF --with-flif=$with_flif $have_flif FlashPIX --with-fpx=$with_fpx $have_fpx FontConfig --with-fontconfig=$with_fontconfig $have_fontconfig FreeType --with-freetype=$with_freetype $have_freetype Ghostscript lib --with-gslib=$with_gslib $have_gslib Graphviz --with-gvc=$with_gvc $have_gvc HEIC --with-heic=$with_heic $have_heic JBIG --with-jbig=$with_jbig $have_jbig JPEG v1 --with-jpeg=$with_jpeg $have_jpeg JPEG XL --with-jxl=$with_jxl $have_jxl LCMS --with-lcms=$with_lcms $have_lcms LQR --with-lqr=$with_lqr $have_lqr LTDL --with-ltdl=$with_ltdl $have_ltdl LZMA --with-lzma=$with_lzma $have_lzma Magick++ --with-magick-plus-plus=$with_magick_plus_plus $have_magick_plus_plus OpenEXR --with-openexr=$with_openexr $have_openexr OpenJP2 --with-openjp2=$with_openjp2 $have_openjp2 PANGO --with-pango=$with_pango $have_pango PERL --with-perl=$with_perl $have_perl PNG --with-png=$with_png $have_png RAQM --with-raqm=$with_raqm $have_raqm RAW --with-raw=$with_raw $have_raw RSVG --with-rsvg=$with_rsvg $have_rsvg TIFF --with-tiff=$with_tiff $have_tiff WEBP --with-webp=$with_webp $have_webp WMF --with-wmf=$with_wmf $have_wmf X11 --with-x=$with_x $have_x XML --with-xml=$with_xml $have_xml ZLIB --with-zlib=$with_zlib $have_zlib ZSTD --with-zstd=$with_zstd $have_zstd Delegate program configuration: GhostPCL None $PCLDelegate ($PCLVersion) GhostXPS None $XPSDelegate ($XPSVersion) Ghostscript None $PSDelegate ($GSVersion) Font configuration: Apple fonts --with-apple-font-dir=$with_apple_font_dir $result_apple_font_dir Dejavu fonts --with-dejavu-font-dir=$with_dejavu_font_dir $result_dejavu_font_dir Ghostscript fonts --with-gs-font-dir=$with_gs_font_dir $result_ghostscript_font_dir URW-base35 fonts --with-urw-base35-font-dir=$with_urw_base35_font_dir $result_urw_base35_font_dir Windows fonts --with-windows-font-dir=$with_windows_font_dir $result_windows_font_dir X11 configuration: X_CFLAGS = $X_CFLAGS X_PRE_LIBS = $X_PRE_LIBS X_LIBS = $X_LIBS X_EXTRA_LIBS = $X_EXTRA_LIBS Options used to compile and link: PREFIX = $PREFIX_DIR EXEC-PREFIX = $EXEC_PREFIX_DIR VERSION = $PACKAGE_VERSION CC = $CC CFLAGS = $CFLAGS CPPFLAGS = $CPPFLAGS PCFLAGS = $PCFLAGS DEFS = $DEFS LDFLAGS = $LDFLAGS LIBS = $MAGICK_DEP_LIBS CXX = $CXX CXXFLAGS = $CXXFLAGS FEATURES = $MAGICK_FEATURES DELEGATES = $MAGICK_DELEGATES ============================================================================== ]) ...
如果需要更详细的说明,可以参考 advanced-unix-installation
(https://imagemagick.org/script/advanced-unix-installation.php)。
我们主要关注:
我们需要禁用掉没有编译的可选组件,并且添加 CPPFLAG
、 LDFLAG
环境变量。
CPPFLAG
:预编译参数,预编译主要涉及头文件的查找,因此我们要把之前编译的几个库的头文件路径添加进去。
LDFLAG
:链接器参数,链接需要找到所需的库文件和对象文件的位置,因此要把之前编译的几个库的库文件路径添加进去。
在 build-item.sh
中加入
export CPPFLAGS="-I/wasm/libpng -I/wasm/zlib -I/wasm/libjpeg -I/wasm/libwebp/src" export LDFLAGS="-L/wasm/zlib -L/wasm/libpng -L/wasm/libpng/.libs -L/wasm/libjpeg -L/wasm/libwebp"
除此之外,我们从 configure.ac
中可以发现,它使用 PKG_CHECK_MODULES()
来查找组件是否存在,例如:
#ImageMagick/configure.ac ... # # Check for ZLIB # AC_ARG_WITH([zlib], [AC_HELP_STRING([--without-zlib], [disable ZLIB support])], [with_zlib=$withval], [with_zlib='yes']) if test "$with_zlib" != 'yes'; then DISTCHECK_CONFIG_FLAGS="${DISTCHECK_CONFIG_FLAGS} --with-zlib=$with_zlib " fi have_zlib='no' ZLIB_CFLAGS="" ZLIB_LIBS="" ZLIB_PKG="" if test "x$with_zlib" = "xyes"; then AC_MSG_RESULT([-------------------------------------------------------------]) PKG_CHECK_MODULES(ZLIB,[zlib >= 1.0.0], have_zlib=yes, have_zlib=no) AC_MSG_RESULT([]) fi if test "$have_zlib" = 'yes'; then AC_DEFINE(ZLIB_DELEGATE,1,Define if you have ZLIB library) CFLAGS="$ZLIB_CFLAGS $CFLAGS" LIBS="$ZLIB_LIBS $LIBS" fi AM_CONDITIONAL(ZLIB_DELEGATE, test "$have_zlib" = 'yes') AC_SUBST(ZLIB_CFLAGS) AC_SUBST(ZLIB_LIBS) ...
PKG_CHECK_MODULES()
调用 pkg-config 工具查找格式为 *.pc 的配置文件,从中获取组件信息。我们可以看到,zlib
,libpng
等下面都有生成的 *.pc 文件。因此我们只要给 pkg-config
指定搜索路径就可以了,指定搜索路径的方式是添加参数 PKG_CONFIG_PATH
。
在 build-item.sh
中加入
cd /wasm/ImageMagick #进入源码目录 autoreconf -fi # 重新用configure.ac生成configure文件 # 指定PKG_CONFIG_PATH、禁用未编译的组件、不生成动态库 emconfigure ./configure --prefix=/ --disable-shared --without-threads --without-magick-plus-plus --without-perl --without-x --disable-largefile --disable-openmp --without-bzlib --without-dps --without-freetype --without-jbig --without-openjp2 --without-lcms --without-wmf --without-xml --without-fftw --without-flif --without-fpx --without-djvu --without-fontconfig --without-raqm --without-gslib --without-gvc --without-heic --without-lqr --without-openexr --without-pango --without-raw --without-rsvg --without-xml PKG_CONFIG_PATH="/wasm/libpng:/wasm/zlib:/wasm/libjpeg:/wasm/libwebp/src:/wasm/libwebp/src/mux:/wasm/libwebp/src/demux:" emmake make
make 以后,没有生成 *.a 文件,却生成了 *.la 文件,对于 *.la 文件,我们需要用 libtool 工具来处理,另外,我们最终需要一个能够执行的程序,而不只是一个库文件,因此,我们要把库文件和带有 main 方法的入口一起编译,最后生成我们可用的 wasm 模块。
如果我们想在 js 中像在 linux 中执行命令那样使用 ImageMagick
,需要有调用 main 方法的能力,按照官方文档 (https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html) 提供的方式,生成 wasm 模块的命令类似这样:
# 输出运行时方法,ccall和cwrap,输出模块里的全局方法int_sqrt ./emcc tests/hello_function.cpp -o function.js -s EXPORTED_FUNCTIONS='["_int_sqrt"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
因此,在 build-item.sh
中加入
# --tag 标明源码为C语言,--mode 表示此次动作为链接 # libMagickCore-7.Q16HDRI.la 为 ImageMagick 底层库 # libMagickWand-7.Q16HDRI.la 为 ImageMagick 上层库 # magick.o 为含有main方法和命令行调用逻辑的入口对象 # 在dist目录下生成wasm模块和js胶水文件,命名为wasm-im.js和wasm-im.wasm # 导出main方法 /bin/bash ./libtool --tag=CC --mode=link emcc $CFLAGS $LDFLAGS -o /wasm/dist/wasm-im.js -s EXTRA_EXPORTED_RUNTIME_METHODS='["callMain"]' utilities/magick.o MagickCore/libMagickCore-7.Q16HDRI.la MagickWand/libMagickWand-7.Q16HDRI.la
现在我们已经构建好的 wasm-im.wasm
,同时获得了一个 wasm-im.js
。此文件是一个胶水 js,封装了 wasm 模块的一些基本功能,我们可以直接使用它。
wasm-im.js
向外暴露了 Module 对象,我们对 wasm 模块的一切调用都可以通过 Module
对象完成。
Module
对象的官方说明在这里 (https://emscripten.org/docs/api_reference/module.html)。
我们可以在 Module.onRuntimeInitialized
的回调函数中使用 Module.callMain
来调用 ImageMagick
。
Module.onRuntimeInitialized = function () { // 调用main方法 Module.callMain(command) }
ImageMagick
处理图片,自然会涉及到文件的读取、写入,浏览器并没有广泛支持的文件系统API,浏览器本身有沙箱限制,也不能访问操作系统本地的文件系统。
WebAssembly
同样受到沙箱限制,因此提供了虚拟文件系统来适配C/C++程序对于文件系统的调用。
WebAssembly
提供了 MEMFS
,NODEFS
,IDBFS
三种虚拟文件系统,其中 NODEFS
专门供 Node.js 环境使用,直接调用本地文件系统。MEMFS
是在内存中建立的虚拟文件系统。IDBFS
是基于 IndexDB
的虚拟文件系统。胶水 js wasm-im.js
中同样包含了对虚拟文件系统的封装,默认使用 MEMFS
。我们可以使用 FS
对象来进行文件操作。例如:
FS.mkdir('/im');// mkdir im FS.currentPath = '/im'; // cd im FS.open('/im');// 打开文件夹 FS.readFile('animation.gif') // 读取文件内容
我们可以用官方的命令行实例 (https://imagemagick.org/Usage/anim_basics/#gif_anim)来验证测试wasm模块。
# 背景颜色:SkyBlue 间隔100ms 分别在位置5,10展示balloon.gif 35,30展示medical.gif 62,50展示present.gif 10,55展示shading.gif # 无限次循环 在当前目录下生成文件为 animation.gif convert -delay 100 -size 100x100 xc:SkyBlue \ -page +5+10 balloon.gif -page +35+30 medical.gif \ -page +62+50 present.gif -page +10+55 shading.gif \ -loop 0 animation.gif
我们知道main函数签名为 int main(int argc,char **argv)
,然后来看 Module.callMain
源码:
function callMain(args) { args = args || []; var argc = args.length + 1; var argv = stackAlloc((argc + 1) * 4); HEAP32[argv >> 2] = allocateUTF8OnStack(thisProgram); for (var i = 1; i < argc; i++) { HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]) } HEAP32[(argv >> 2) + argc] = 0; try { var ret = Module["_main"](argc, argv); exit(ret, true) } catch (e) { if (e instanceof ExitStatus) { return } else if (e == "SimulateInfiniteLoop") { noExitRuntime = true; return } else { var toLog = e; if (e && typeof e === "object" && e.stack) { toLog = [e, e.stack] } err("exception thrown: " + toLog); quit_(1, e) } } finally { calledMain = true } }
显然,我们只要把需要执行的命令用空格分隔成字符串数组作为 callMain
的参数即可。
// 把图片换成jpg var command = ['convert','-delay', '100', '-size', '100x100' ,'xc:SkyBlue', '-page','+5+10','1.jpg','-page','+35+30','2.jpg', '-page','+62+50','3.jpg','-page','+10+55','4.jpg', '-loop','0','animation.gif']
执行 calledMain
前,我们需要把四张原图在文件系统中准备好。
// 用fetch获取四张图片 FS.writeFile('1.jpg', new Uint8Array(await(await fetch("1.jpg")).arrayBuffer())) FS.writeFile('2.jpg', new Uint8Array(await(await fetch("2.jpg")).arrayBuffer())) FS.writeFile('3.jpg', new Uint8Array(await(await fetch("3.jpg")).arrayBuffer())) FS.writeFile('4.jpg', new Uint8Array(await(await fetch("4.jpg")).arrayBuffer()))
执行 calledMain
后,我们可以读取出生成的文件。
var gif = FS.readFile('animation.gif') var gifBlob = new Blob([gif]); var img = document.createElement('img'); img.src = URL.createObjectURL(gifBlob); document.body.appendChild(img)
对 ImageMagick
的调用很适合放到 worker
中执行。需要注意的是,postMessage
时如果传递了存放文件内容的 arrayBuffer
,需要将其放进 transferList,避免无谓的数据拷贝。
完整的示例在 https://mk33mk333.github.io/wasm-im/
Emscripten
工具链提供了 Emscripten Ports,内置了一批常用库,其中包括了 zlib
、 libpng
、 libjpeg
等。使用方式是在编译参数上增加对应的变量,比如想链接 libpng
,就添加-s USE_LIBPNG=1
。但是对于编译 libwebp
、 ImageMagick
这种成型库,要大幅修改构建脚本,有兴趣的同学可以尝试。
可用内置库如下:
Available ports: Boost headers v1.70.0 (USE_BOOST_HEADERS=1; Boost license) icu (USE_ICU=1; Unicode License) zlib (USE_ZLIB=1; zlib license) bzip2 (USE_BZIP2=1; BSD license) libjpeg (USE_LIBJPEG=1; BSD license) libpng (USE_LIBPNG=1; zlib license) SDL2 (USE_SDL=2; zlib license) SDL2_image (USE_SDL_IMAGE=2; zlib license) SDL2_gfx (zlib license) ogg (USE_OGG=1; zlib license) vorbis (USE_VORBIS=1; zlib license) SDL2_mixer (USE_SDL_MIXER=2; zlib license) bullet (USE_BULLET=1; zlib license) freetype (USE_FREETYPE=1; freetype license) harfbuzz (USE_HARFBUZZ=1; MIT license) SDL2_ttf (USE_SDL_TTF=2; zlib license) SDL2_net (zlib license) cocos2d regal (USE_REGAL=1; Regal license)
wasm兼容性
从图中可以看到,除了 IE 完全不支持,其他主流浏览器已经支持 WebAssembly
。
对于 IE,我们可以考虑使用 asm.js
或者其他降级方式,需要根据实际需求来决定。
本次我们把 ImageMagick
编译成 wasm
模块,并运行在浏览器中。但是我们只使用了最简单的功能:调用 main 方法。
没有写一行 C/C++ 代码,更没有涉及到 js/C++ 方法互调、js/C++ 对象绑定等更复杂的实践。
之后我们会深入研究更复杂的应用和实践。
如果对在浏览器中使用 ImageMagick
的成熟方案感兴趣,可以关注WASM-imageMagick (https://github.com/KnicKnic/WASM-ImageMagick),在 js 侧使用 Typescript
进行了完善的封装,提供了 Typescript API
。
WebAssembly MDN
(https://developer.mozilla.org/zh-CN/docs/WebAssembly)
emscripten
(https://emscripten.org/index.html)
RuntimeError: integer overflow when convert double to int in wasm mode
(https://github.com/emscripten-core/emscripten/issues/5404)
makefile编译选项CC与CXX/CPPFLAGS,CFLAGS与CXXFLAGS/LDFLAGS
(https://blog.csdn.net/lusic01/article/details/78645316)
pkg-config 详解
(https://blog.csdn.net/newchenxf/article/details/51750239)
WebAssembly进阶系列三:微信小程序支持webP的WebAssembly方案
(https://juejin.im/post/5d32e2c2f265da1b897b0b57)
ImageMagick中文站
(http://www.imagemagick.com.cn/)
本文分享自微信公众号 - WecTeam(Wec-Team),作者:刘辉
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2019-10-31
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句
用你 git 代码编出来的 wasm 对很多 jpg 支持不好,有的无法输出文件(convert: Unsupported marker type 0x06 `0' @ error/jpeg.c/JPEGErrorHandler/343),有的全黑(convert: Quantization table 0x00 was not defined `0' @ error/jpeg.c/JPEGErrorHandler/343)。按照你的流程自己整了一份,错误依旧。还是你的代码,把 build-item.sh 改一下,用 make 直接编出 Ubuntu 下的二进制程序,用起来没问题