کامپایل mkbitmap به WebAssembly

در WebAssembly چیست و از کجا آمده است؟ ، توضیح دادم که چگونه به WebAssembly امروز رسیدیم. در این مقاله، من روش خود را برای کامپایل کردن یک برنامه C موجود، mkbitmap ، به WebAssembly به شما نشان خواهم داد. از مثال hello world پیچیده‌تر است، زیرا شامل کار با فایل‌ها، برقراری ارتباط بین WebAssembly و جاوا اسکریپت و کشیدن روی بوم می‌شود، اما همچنان به اندازه‌ای قابل مدیریت است که شما را تحت تأثیر قرار ندهد.

این مقاله برای توسعه دهندگان وب نوشته شده است که می خواهند WebAssembly را یاد بگیرند و گام به گام نشان می دهد که اگر می خواهید چیزی مانند mkbitmap را در WebAssembly کامپایل کنید چگونه می توانید ادامه دهید. به عنوان یک هشدار منصفانه، دریافت نکردن یک برنامه یا کتابخانه برای کامپایل در اولین اجرا کاملاً طبیعی است، به همین دلیل است که برخی از مراحل شرح داده شده در زیر کار نمی‌کنند، بنابراین من باید به عقب برگردم و دوباره امتحان کنم. این مقاله فرمان کامپایل نهایی جادویی را به گونه‌ای نشان نمی‌دهد که انگار از آسمان افتاده است، بلکه پیشرفت واقعی من را توصیف می‌کند که شامل برخی ناامیدی‌ها نیز می‌شود.

درباره mkbitmap

برنامه mkbitmap C یک تصویر را می خواند و یک یا چند مورد از عملیات زیر را به ترتیب بر روی آن اعمال می کند: وارونگی، فیلتر بالاگذر، مقیاس گذاری و آستانه گذاری. هر عملیات را می توان به صورت جداگانه کنترل و روشن یا خاموش کرد. کاربرد اصلی mkbitmap تبدیل تصاویر رنگی یا خاکستری به فرمتی مناسب به عنوان ورودی برای برنامه های دیگر است، به ویژه ردیابی برنامه potrace که اساس SVGcode را تشکیل می دهد. به عنوان یک ابزار پیش پردازش، mkbitmap به ویژه برای تبدیل هنر خط اسکن شده، مانند کارتون یا متن دست نویس، به تصاویر دوسطحی با وضوح بالا مفید است.

شما از mkbitmap با ارسال تعدادی گزینه و یک یا چند نام فایل به آن استفاده می کنید. برای همه جزئیات، به صفحه مرد ابزار مراجعه کنید:

$ mkbitmap [options] [filename...]
تصویر کارتونی رنگی.
تصویر اصلی ( منبع ).
تصویر کارتونی پس از پیش پردازش به مقیاس خاکستری تبدیل شد.
ابتدا مقیاس، سپس آستانه: mkbitmap -f 2 -s 2 -t 0.48 ( منبع ).

کد را دریافت کنید

اولین قدم دریافت کد منبع mkbitmap است. می توانید آن را در وب سایت پروژه پیدا کنید. در زمان نگارش این مطلب، potrace-1.16.tar.gz آخرین نسخه است.

کامپایل و به صورت محلی نصب کنید

گام بعدی این است که ابزار را به صورت محلی کامپایل و نصب کنید تا احساس کنید که چگونه رفتار می کند. فایل INSTALL حاوی دستورالعمل های زیر است:

  1. cd به دایرکتوری حاوی کد منبع بسته وارد کنید و ./configure را تایپ کنید تا بسته را برای سیستم خود پیکربندی کنید.

    اجرای configure ممکن است کمی طول بکشد. در حین اجرا، پیام هایی را چاپ می کند که نشان می دهد کدام ویژگی ها را بررسی می کند.

  2. برای کامپایل کردن بسته، make تایپ کنید.

  3. به صورت اختیاری، make check را تایپ کنید تا آزمایش‌های خودکاری که با بسته ارائه می‌شود، اجرا شود، معمولاً با استفاده از باینری‌های حذف‌شده تازه ساخته‌شده.

  4. برای نصب برنامه ها و هر فایل داده و اسنادی، make install تایپ کنید. هنگام نصب در پیشوندی که متعلق به root است، توصیه می شود که بسته به عنوان یک کاربر معمولی پیکربندی و ساخته شود و فقط مرحله make install با حقوق ریشه اجرا شود.

با دنبال کردن این مراحل، باید به دو فایل اجرایی، potrace و mkbitmap برسید - مورد دوم تمرکز این مقاله است. می توانید با اجرای mkbitmap --version بررسی کنید که درست کار کرده است. در اینجا خروجی هر چهار مرحله از دستگاه من است که برای اختصار به شدت کوتاه شده است:

مرحله 1، ./configure :

 $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands

مرحله 2، make :

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.

مرحله 3، make check :

$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.

مرحله 4، sudo make install :

$ sudo make install
Password:
Making install in src
 .././install-sh -c -d '/usr/local/bin'
  /bin/sh ../libtool   --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.

برای بررسی اینکه آیا کار می کند، mkbitmap --version اجرا کنید:

$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

اگر جزئیات نسخه را دریافت کردید، mkbitmap با موفقیت کامپایل و نصب کرده اید. در مرحله بعد، معادل این مراحل را با WebAssembly کار کنید.

mkbitmap در WebAssembly کامپایل کنید

Emscripten ابزاری برای کامپایل برنامه های C/C++ در WebAssembly است. مستندات پروژه های ساختمانی Emscripten موارد زیر را بیان می کند:

ساخت پروژه های بزرگ با Emscripten بسیار آسان است. Emscripten دو اسکریپت ساده را ارائه می‌کند که فایل‌های ساخت شما را به گونه‌ای پیکربندی می‌کند که emcc به عنوان جایگزینی برای gcc استفاده کنند — در بیشتر موارد بقیه سیستم ساخت فعلی پروژه شما بدون تغییر باقی می‌ماند.

سپس مستندات ادامه می‌یابد (برای اختصار کمی ویرایش شده است):

حالتی را در نظر بگیرید که معمولاً با دستورات زیر می‌سازید:

./configure
make

برای ساخت با Emscripten، در عوض از دستورات زیر استفاده کنید:

emconfigure ./configure
emmake make

بنابراین اساسا ./configure تبدیل به emconfigure ./configure و make تبدیل به emmake make می شود. در زیر نحوه انجام این کار با mkbitmap را نشان می دهد.

مرحله 0، make clean :

$ make clean
Making clean in src
 rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo

مرحله 1، emconfigure ./configure :

$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands

مرحله 2، emmake make :

$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.

اگر همه چیز به خوبی پیش رفت، اکنون باید فایل های .wasm در جایی در دایرکتوری وجود داشته باشد. با اجرای find . -name "*.wasm" :

$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm

دو مورد آخر امیدوارکننده به نظر می رسند، بنابراین cd به دایرکتوری src/ وارد کنید. اکنون دو فایل متناظر جدید نیز وجود دارد، mkbitmap و potrace . برای این مقاله، فقط mkbitmap مرتبط است. این واقعیت که آنها پسوند .js ندارند کمی گیج کننده است، اما در واقع فایل های جاوا اسکریپت هستند که با یک head سریع قابل تأیید هستند:

$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)

با فراخوانی mv mkbitmap mkbitmap.js (و در صورت تمایل به ترتیب mv potrace potrace.js ) نام فایل جاوا اسکریپت را به mkbitmap.js تغییر دهید. اکنون نوبت اولین آزمایش است تا ببینیم با اجرای فایل با Node.js در خط فرمان با اجرای node mkbitmap.js --version کار می کند یا خیر:

$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

شما با موفقیت mkbitmap در WebAssembly کامپایل کردید. اکنون مرحله بعدی این است که آن را در مرورگر کار کنید.

mkbitmap با WebAssembly در مرورگر

فایل‌های mkbitmap.js و mkbitmap.wasm را در دایرکتوری جدیدی به نام mkbitmap کپی کنید و یک فایل HTML boilerplate index.html ایجاد کنید که فایل جاوا اسکریپت mkbitmap.js را بارگیری می‌کند.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <script src="mkbitmap.js"></script>
  </body>
</html>

یک سرور محلی که دایرکتوری mkbitmap را ارائه می دهد راه اندازی کنید و آن را در مرورگر خود باز کنید. باید اعلانی را ببینید که از شما ورودی می خواهد. این همان چیزی است که انتظار می رود، زیرا طبق صفحه man ابزار ، "[i]اگر هیچ آرگومان نام فایلی داده نشود، mkbitmap به عنوان یک فیلتر عمل می کند و از ورودی استاندارد می خواند" ، که برای Emscripten به طور پیش فرض یک prompt() است.

برنامه mkbitmap درخواستی را نشان می دهد که ورودی می خواهد.

جلوگیری از اجرای خودکار

برای توقف فوری اجرای mkbitmap و در عوض منتظر ماندن آن برای ورودی کاربر، باید شی Module Emscripten را درک کنید. Module یک شی جاوا اسکریپت جهانی با ویژگی هایی است که کد تولید شده توسط Emscripten در نقاط مختلف اجرای آن فراخوانی می کند. شما می توانید پیاده سازی Module برای کنترل اجرای کد ارائه دهید. هنگامی که یک برنامه Emscripten راه اندازی می شود، به مقادیر موجود در شی Module نگاه می کند و آنها را اعمال می کند.

در مورد mkbitmap ، Module.noInitialRun را روی true تنظیم کنید تا از اجرای اولیه که باعث ظاهر شدن درخواست شده است جلوگیری کنید. یک اسکریپت به نام script.js ایجاد کنید، آن را قبل از <script src="mkbitmap.js"></script> در index.html قرار دهید و کد زیر را به script.js اضافه کنید. هنگامی که اکنون برنامه را مجدداً بارگیری می کنید، اعلان باید ناپدید شود.

var Module = {
  // Don't run main() at page load
  noInitialRun: true,
};

یک ساخت مدولار با چند پرچم ساخت دیگر ایجاد کنید

برای ارائه ورودی به برنامه، می‌توانید از پشتیبانی سیستم فایل Emscripten در Module.FS استفاده کنید. در بخش پشتیبانی از فایل سیستم در اسناد آمده است:

Emscripten تصمیم می گیرد که آیا پشتیبانی سیستم فایل را به طور خودکار شامل شود یا خیر. بسیاری از برنامه‌ها به فایل‌ها نیازی ندارند و پشتیبانی از سیستم فایل از نظر اندازه ناچیز نیست، بنابراین Emscripten وقتی دلیلی برای این کار نمی‌بیند از قرار دادن آن اجتناب می‌کند. این بدان معناست که اگر کد C/C++ شما به فایل‌ها دسترسی نداشته باشد، شی FS و سایر APIهای سیستم فایل در خروجی گنجانده نمی‌شوند. و از سوی دیگر، اگر کد C/C++ شما از فایل‌ها استفاده می‌کند، پشتیبانی سیستم فایل به‌طور خودکار شامل می‌شود.

متأسفانه mkbitmap یکی از مواردی است که Emscripten به طور خودکار پشتیبانی از سیستم فایل را شامل نمی شود، بنابراین باید صریحاً به آن بگویید که این کار را انجام دهد. این بدان معنی است که شما باید مراحل emconfigure و emmake را که قبلا توضیح داده شد، با چند پرچم دیگر از طریق یک آرگومان CFLAGS دنبال کنید. پرچم‌های زیر ممکن است برای پروژه‌های دیگر نیز مفید باشند.

  • -sFILESYSTEM=1 را تنظیم کنید تا پشتیبانی سیستم فایل گنجانده شود.
  • -sEXPORTED_RUNTIME_METHODS=FS,callMain را تنظیم کنید تا Module.FS و Module.callMain صادر شوند.
  • -sMODULARIZE=1 و -sEXPORT_ES6 را برای تولید یک ماژول مدرن ES6 تنظیم کنید.
  • -sINVOKE_RUN=0 برای جلوگیری از اجرای اولیه که باعث ظاهر شدن درخواست شده است تنظیم کنید.

همچنین، در این مورد خاص، باید پرچم --host را روی wasm32 تنظیم کنید تا به اسکریپت configure که برای WebAssembly کامپایل می کنید بگویید.

دستور نهایی emconfigure به شکل زیر است:

$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'

فراموش نکنید که emmake make دوباره اجرا کنید و فایل های تازه ایجاد شده را در پوشه mkbitmap کپی کنید.

index.html طوری تغییر دهید که فقط ماژول ES script.js را بارگیری کند و سپس ماژول mkbitmap.js را از آن وارد کنید.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <!-- No longer load `mkbitmap.js` here -->
    <script src="script.js" type="module"></script>
  </body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  console.log(Module);
};

run();

هنگامی که اکنون برنامه را در مرورگر باز می کنید، باید شی Module را مشاهده کنید که در کنسول DevTools ثبت شده است، و اعلان از بین می رود، زیرا تابع main() mkbitmap دیگر در ابتدا فراخوانی نمی شود.

برنامه mkbitmap با یک صفحه سفید که شی ماژول وارد شده به کنسول DevTools را نشان می دهد.

تابع اصلی را به صورت دستی اجرا کنید

مرحله بعدی فراخوانی دستی تابع main() mkbitmap با اجرای Module.callMain() است. تابع callMain() آرایه‌ای از آرگومان‌ها را می‌گیرد که با آنچه در خط فرمان ارسال می‌کنید، یک به یک مطابقت دارند. اگر در خط فرمان mkbitmap -v را اجرا کنید، Module.callMain(['-v']) را در مرورگر فراخوانی کنید. این شماره نسخه mkbitmap را در کنسول DevTools ثبت می کند.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  Module.callMain(['-v']);
};

run();

برنامه mkbitmap با یک صفحه سفید که شماره نسخه mkbitmap ثبت شده در کنسول DevTools را نشان می دهد.

خروجی استاندارد را تغییر مسیر دهید

خروجی استاندارد ( stdout ) به طور پیش فرض کنسول است. با این حال، می توانید آن را به چیز دیگری هدایت کنید، به عنوان مثال، تابعی که خروجی را در یک متغیر ذخیره می کند. این بدان معنی است که می توانید با تنظیم ویژگی Module.print خروجی را به HTML اضافه کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  let consoleOutput = 'Powered by ';
  const Module = await loadWASM({
    print: (text) => (consoleOutput += text),
  });
  Module.callMain(['-v']);
  document.body.textContent = consoleOutput;
};

run();

برنامه mkbitmap شماره نسخه mkbitmap را نشان می دهد.

فایل ورودی را در سیستم فایل حافظه دریافت کنید

برای دریافت فایل ورودی به سیستم فایل حافظه، mkbitmap filename در خط فرمان نیاز دارید. برای درک نحوه برخورد من با این موضوع، ابتدا پیش زمینه ای در مورد اینکه چگونه mkbitmap ورودی خود را انتظار دارد و خروجی خود را ایجاد می کند، بپردازید.

فرمت های ورودی پشتیبانی شده mkbitmap PNM ( PBM ، PGM ، PPM ) و BMP هستند. فرمت های خروجی PBM برای نقشه های بیتی و PGM برای نقشه های خاکستری هستند. اگر آرگومان filename داده شود، mkbitmap به طور پیش فرض یک فایل خروجی ایجاد می کند که نام آن از نام فایل ورودی با تغییر پسوند آن به .pbm به دست می آید. به عنوان مثال، برای نام فایل ورودی example.bmp ، نام فایل خروجی example.pbm خواهد بود.

Emscripten یک سیستم فایل مجازی را ارائه می‌کند که سیستم فایل محلی را شبیه‌سازی می‌کند، به طوری که کد بومی با استفاده از APIهای فایل همزمان می‌تواند کامپایل شده و با تغییر کم یا بدون تغییر اجرا شود. برای اینکه mkbitmap بتواند یک فایل ورودی را بخواند که گویی به عنوان آرگومان خط فرمان filename ارسال شده است، باید از شی FS که Emscripten ارائه می دهد استفاده کنید.

شی FS توسط یک سیستم فایل در حافظه (که معمولاً به عنوان MEMFS نامیده می شود) پشتیبانی می شود و دارای تابع writeFile() است که از آن برای نوشتن فایل ها در سیستم فایل مجازی استفاده می کنید. همانطور که در نمونه کد زیر نشان داده شده است از writeFile() استفاده می کنید.

برای تایید عملکرد نوشتن فایل، تابع readdir() شی FS را با پارامتر '/' اجرا کنید. شما example.bmp و تعدادی فایل پیش فرض را خواهید دید که همیشه به صورت خودکار ایجاد می شوند .

توجه داشته باشید که تماس قبلی با Module.callMain(['-v']) برای چاپ شماره نسخه حذف شد. این به این دلیل است که Module.callMain() تابعی است که معمولاً انتظار دارد فقط یک بار اجرا شود.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp.

اولین اجرای واقعی

با همه چیز در جای خود، mkbitmap با اجرای Module.callMain(['example.bmp']) اجرا کنید. محتویات پوشه MEMFS ' '/' را وارد کنید، و باید فایل خروجی example.pbm تازه ایجاد شده را در کنار فایل ورودی example.bmp ببینید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp و example.pbm.

فایل خروجی را از سیستم فایل حافظه خارج کنید

تابع readFile() شی FS امکان خروج example.pbm ایجاد شده در آخرین مرحله از سیستم فایل حافظه را فراهم می کند. این تابع یک Uint8Array را برمی گرداند که شما آن را به یک شی File تبدیل کرده و در دیسک ذخیره می کنید، زیرا مرورگرها معمولاً فایل های PBM را برای مشاهده مستقیم در مرورگر پشتیبانی نمی کنند. (روش های ظریف تری برای ذخیره یک فایل وجود دارد، اما استفاده از <a download> ایجاد شده به صورت پویا بیشترین پشتیبانی را دارد.) هنگامی که فایل ذخیره شد، می توانید آن را در نمایشگر تصویر دلخواه خود باز کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
  const file = new File([output], 'example.pbm', {
    type: 'image/x-portable-bitmap',
  });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = file.name;
  a.click();
};

run();

macOS Finder با پیش نمایش فایل .bmp. ورودی و فایل pbm. خروجی.

یک رابط کاربری تعاملی اضافه کنید

تا این مرحله، فایل ورودی هاردکد شده و mkbitmap با پارامترهای پیش‌فرض اجرا می‌شود. مرحله آخر این است که به کاربر اجازه دهید به صورت پویا یک فایل ورودی را انتخاب کند، پارامترهای mkbitmap را تغییر دهد و سپس ابزار را با گزینه های انتخاب شده اجرا کند.

// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);

تجزیه فرمت تصویر PBM به خصوص سخت نیست، بنابراین با استفاده از کد جاوا اسکریپت ، حتی می توانید پیش نمایش تصویر خروجی را نشان دهید. کد منبع نسخه ی نمایشی تعبیه شده را برای یکی از راه های انجام این کار در زیر ببینید.

نتیجه گیری

تبریک می گوییم، شما با موفقیت mkbitmap در WebAssembly کامپایل کردید و آن را در مرورگر کار کردید! بن بست هایی وجود داشت و شما مجبور بودید بیش از یک بار ابزار را کامپایل کنید تا زمانی که کار کند، اما همانطور که در بالا نوشتم، این بخشی از تجربه است. همچنین اگر گیر کردید ، تگ webassembly StackOverflow را به خاطر بسپارید. تدوین مبارک!

قدردانی

این مقاله توسط سام کلگ و ریچل اندرو بررسی شده است.

،

در WebAssembly چیست و از کجا آمده است؟ ، توضیح دادم که چگونه به WebAssembly امروز رسیدیم. در این مقاله، من روش خود را برای کامپایل کردن یک برنامه C موجود، mkbitmap ، به WebAssembly به شما نشان خواهم داد. از مثال hello world پیچیده‌تر است، زیرا شامل کار با فایل‌ها، برقراری ارتباط بین WebAssembly و جاوا اسکریپت و کشیدن روی بوم می‌شود، اما همچنان به اندازه‌ای قابل مدیریت است که شما را تحت تأثیر قرار ندهد.

این مقاله برای توسعه دهندگان وب نوشته شده است که می خواهند WebAssembly را یاد بگیرند و گام به گام نشان می دهد که اگر می خواهید چیزی مانند mkbitmap در WebAssembly کامپایل کنید چگونه می توانید ادامه دهید. به عنوان یک هشدار منصفانه، دریافت نکردن یک برنامه یا کتابخانه برای کامپایل در اولین اجرا کاملاً طبیعی است، به همین دلیل است که برخی از مراحل شرح داده شده در زیر کار نمی‌کنند، بنابراین من باید به عقب برگردم و دوباره امتحان کنم. این مقاله فرمان کامپایل نهایی جادویی را به گونه‌ای نشان نمی‌دهد که انگار از آسمان افتاده است، بلکه پیشرفت واقعی من را توصیف می‌کند که شامل برخی ناامیدی‌ها نیز می‌شود.

درباره mkbitmap

برنامه mkbitmap C یک تصویر را می خواند و یک یا چند مورد از عملیات زیر را به ترتیب بر روی آن اعمال می کند: وارونگی، فیلتر بالاگذر، مقیاس گذاری و آستانه گذاری. هر عملیات را می توان به صورت جداگانه کنترل و روشن یا خاموش کرد. کاربرد اصلی mkbitmap تبدیل تصاویر رنگی یا خاکستری به فرمتی مناسب به عنوان ورودی برای برنامه های دیگر است، به ویژه ردیابی برنامه potrace که اساس SVGcode را تشکیل می دهد. به عنوان یک ابزار پیش پردازش، mkbitmap به ویژه برای تبدیل هنر خط اسکن شده، مانند کارتون یا متن دست نویس، به تصاویر دوسطحی با وضوح بالا مفید است.

شما از mkbitmap با ارسال تعدادی گزینه و یک یا چند نام فایل به آن استفاده می کنید. برای همه جزئیات، به صفحه مرد ابزار مراجعه کنید:

$ mkbitmap [options] [filename...]
تصویر کارتونی رنگی.
تصویر اصلی ( منبع ).
تصویر کارتونی پس از پیش پردازش به مقیاس خاکستری تبدیل شد.
ابتدا مقیاس، سپس آستانه: mkbitmap -f 2 -s 2 -t 0.48 ( منبع ).

کد را دریافت کنید

اولین قدم دریافت کد منبع mkbitmap است. می توانید آن را در وب سایت پروژه پیدا کنید. در زمان نگارش این مطلب، potrace-1.16.tar.gz آخرین نسخه است.

کامپایل و به صورت محلی نصب کنید

گام بعدی این است که ابزار را به صورت محلی کامپایل و نصب کنید تا احساس کنید که چگونه رفتار می کند. فایل INSTALL حاوی دستورالعمل های زیر است:

  1. cd به دایرکتوری حاوی کد منبع بسته وارد کنید و ./configure را تایپ کنید تا بسته را برای سیستم خود پیکربندی کنید.

    اجرای configure ممکن است کمی طول بکشد. در حین اجرا، پیام هایی را چاپ می کند که نشان می دهد کدام ویژگی ها را بررسی می کند.

  2. برای کامپایل کردن بسته، make تایپ کنید.

  3. به صورت اختیاری، make check را تایپ کنید تا آزمایش‌های خودکاری که با بسته ارائه می‌شود، اجرا شود، معمولاً با استفاده از باینری‌های حذف‌شده تازه ساخته‌شده.

  4. برای نصب برنامه ها و هر فایل داده و اسنادی، make install تایپ کنید. هنگام نصب در پیشوندی که متعلق به root است، توصیه می شود که بسته به عنوان یک کاربر معمولی پیکربندی و ساخته شود و فقط مرحله make install با حقوق ریشه اجرا شود.

با دنبال کردن این مراحل، باید به دو فایل اجرایی، potrace و mkbitmap برسید - مورد دوم تمرکز این مقاله است. می توانید با اجرای mkbitmap --version بررسی کنید که درست کار کرده است. در اینجا خروجی هر چهار مرحله از دستگاه من است که برای اختصار به شدت کوتاه شده است:

مرحله 1، ./configure :

 $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands

مرحله 2، make :

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.

مرحله 3، make check :

$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.

مرحله 4، sudo make install :

$ sudo make install
Password:
Making install in src
 .././install-sh -c -d '/usr/local/bin'
  /bin/sh ../libtool   --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.

برای بررسی اینکه آیا کار می کند، mkbitmap --version اجرا کنید:

$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

اگر جزئیات نسخه را دریافت کردید، mkbitmap با موفقیت کامپایل و نصب کرده اید. در مرحله بعد، معادل این مراحل را با WebAssembly کار کنید.

mkbitmap در WebAssembly کامپایل کنید

Emscripten ابزاری برای کامپایل برنامه های C/C++ در WebAssembly است. مستندات پروژه های ساختمانی Emscripten موارد زیر را بیان می کند:

ساخت پروژه های بزرگ با Emscripten بسیار آسان است. Emscripten دو اسکریپت ساده را ارائه می‌کند که فایل‌های ساخت شما را به گونه‌ای پیکربندی می‌کند که emcc به عنوان جایگزینی برای gcc استفاده کنند — در بیشتر موارد بقیه سیستم ساخت فعلی پروژه شما بدون تغییر باقی می‌ماند.

سپس مستندات ادامه می‌یابد (برای اختصار کمی ویرایش شده است):

حالتی را در نظر بگیرید که معمولاً با دستورات زیر می‌سازید:

./configure
make

برای ساخت با Emscripten، در عوض از دستورات زیر استفاده کنید:

emconfigure ./configure
emmake make

بنابراین اساسا ./configure تبدیل به emconfigure ./configure و make تبدیل به emmake make می شود. در زیر نحوه انجام این کار با mkbitmap را نشان می دهد.

مرحله 0، make clean :

$ make clean
Making clean in src
 rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo

مرحله 1، emconfigure ./configure :

$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands

مرحله 2، emmake make :

$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.

اگر همه چیز به خوبی پیش رفت، اکنون باید فایل های .wasm در جایی در دایرکتوری وجود داشته باشد. با اجرای find . -name "*.wasm" :

$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm

دو مورد آخر امیدوارکننده به نظر می رسند، بنابراین cd به دایرکتوری src/ وارد کنید. اکنون دو فایل متناظر جدید نیز وجود دارد، mkbitmap و potrace . برای این مقاله، فقط mkbitmap مرتبط است. این واقعیت که آنها پسوند .js ندارند کمی گیج کننده است، اما در واقع فایل های جاوا اسکریپت هستند که با یک head سریع قابل تأیید هستند:

$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)

با فراخوانی mv mkbitmap mkbitmap.js (و در صورت تمایل به ترتیب mv potrace potrace.js ) نام فایل جاوا اسکریپت را به mkbitmap.js تغییر دهید. اکنون نوبت اولین آزمایش است تا ببینیم با اجرای فایل با Node.js در خط فرمان با اجرای node mkbitmap.js --version کار می کند یا خیر:

$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

شما با موفقیت mkbitmap در WebAssembly کامپایل کردید. اکنون مرحله بعدی این است که آن را در مرورگر کار کنید.

mkbitmap با WebAssembly در مرورگر

فایل‌های mkbitmap.js و mkbitmap.wasm را در دایرکتوری جدیدی به نام mkbitmap کپی کنید و یک فایل HTML boilerplate index.html ایجاد کنید که فایل جاوا اسکریپت mkbitmap.js را بارگیری می‌کند.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <script src="mkbitmap.js"></script>
  </body>
</html>

یک سرور محلی که دایرکتوری mkbitmap را ارائه می دهد راه اندازی کنید و آن را در مرورگر خود باز کنید. باید اعلانی را ببینید که از شما ورودی می خواهد. این همان چیزی است که انتظار می رود، زیرا طبق صفحه man ابزار ، "[i]اگر هیچ آرگومان نام فایلی داده نشود، mkbitmap به عنوان یک فیلتر عمل می کند و از ورودی استاندارد می خواند" ، که برای Emscripten به طور پیش فرض یک prompt() است.

برنامه mkbitmap درخواستی را نشان می دهد که ورودی می خواهد.

جلوگیری از اجرای خودکار

برای توقف فوری اجرای mkbitmap و در عوض منتظر ماندن آن برای ورودی کاربر، باید شی Module Emscripten را درک کنید. Module یک شی جاوا اسکریپت جهانی با ویژگی هایی است که کد تولید شده توسط Emscripten در نقاط مختلف اجرای آن فراخوانی می کند. شما می توانید پیاده سازی Module برای کنترل اجرای کد ارائه دهید. هنگامی که یک برنامه Emscripten راه اندازی می شود، به مقادیر موجود در شی Module نگاه می کند و آنها را اعمال می کند.

در مورد mkbitmap ، Module.noInitialRun را روی true تنظیم کنید تا از اجرای اولیه که باعث ظاهر شدن درخواست شده است جلوگیری کنید. یک اسکریپت به نام script.js ایجاد کنید، آن را قبل از <script src="mkbitmap.js"></script> در index.html قرار دهید و کد زیر را به script.js اضافه کنید. هنگامی که اکنون برنامه را مجدداً بارگیری می کنید، اعلان باید ناپدید شود.

var Module = {
  // Don't run main() at page load
  noInitialRun: true,
};

یک ساخت مدولار با چند پرچم ساخت دیگر ایجاد کنید

برای ارائه ورودی به برنامه، می‌توانید از پشتیبانی سیستم فایل Emscripten در Module.FS استفاده کنید. در بخش پشتیبانی از فایل سیستم در اسناد آمده است:

Emscripten تصمیم می گیرد که آیا پشتیبانی سیستم فایل را به طور خودکار شامل شود یا خیر. بسیاری از برنامه‌ها به فایل‌ها نیازی ندارند و پشتیبانی از سیستم فایل از نظر اندازه ناچیز نیست، بنابراین Emscripten وقتی دلیلی برای این کار نمی‌بیند از قرار دادن آن اجتناب می‌کند. این بدان معناست که اگر کد C/C++ شما به فایل‌ها دسترسی نداشته باشد، شی FS و سایر APIهای سیستم فایل در خروجی گنجانده نمی‌شوند. و از سوی دیگر، اگر کد C/C++ شما از فایل‌ها استفاده می‌کند، پشتیبانی سیستم فایل به‌طور خودکار شامل می‌شود.

متأسفانه mkbitmap یکی از مواردی است که Emscripten به طور خودکار پشتیبانی از سیستم فایل را شامل نمی شود، بنابراین باید صریحاً به آن بگویید که این کار را انجام دهد. این بدان معنی است که شما باید مراحل emconfigure و emmake را که قبلا توضیح داده شد، با چند پرچم دیگر از طریق یک آرگومان CFLAGS دنبال کنید. پرچم‌های زیر ممکن است برای پروژه‌های دیگر نیز مفید باشند.

  • -sFILESYSTEM=1 را تنظیم کنید تا پشتیبانی سیستم فایل گنجانده شود.
  • -sEXPORTED_RUNTIME_METHODS=FS,callMain را تنظیم کنید تا Module.FS و Module.callMain صادر شوند.
  • -sMODULARIZE=1 و -sEXPORT_ES6 را برای تولید یک ماژول مدرن ES6 تنظیم کنید.
  • -sINVOKE_RUN=0 برای جلوگیری از اجرای اولیه که باعث ظاهر شدن درخواست شده است تنظیم کنید.

همچنین، در این مورد خاص، باید پرچم --host را روی wasm32 تنظیم کنید تا به اسکریپت configure که برای WebAssembly کامپایل می کنید بگویید.

دستور نهایی emconfigure به شکل زیر است:

$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'

فراموش نکنید که emmake make دوباره اجرا کنید و فایل های تازه ایجاد شده را در پوشه mkbitmap کپی کنید.

index.html طوری تغییر دهید که فقط ماژول ES script.js را بارگیری کند و سپس ماژول mkbitmap.js را از آن وارد کنید.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <!-- No longer load `mkbitmap.js` here -->
    <script src="script.js" type="module"></script>
  </body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  console.log(Module);
};

run();

هنگامی که اکنون برنامه را در مرورگر باز می کنید، باید شی Module را مشاهده کنید که در کنسول DevTools ثبت شده است، و اعلان از بین می رود، زیرا تابع main() mkbitmap دیگر در ابتدا فراخوانی نمی شود.

برنامه mkbitmap با یک صفحه سفید که شی ماژول وارد شده به کنسول DevTools را نشان می دهد.

تابع اصلی را به صورت دستی اجرا کنید

مرحله بعدی فراخوانی دستی تابع main() mkbitmap با اجرای Module.callMain() است. تابع callMain() آرایه‌ای از آرگومان‌ها را می‌گیرد که با آنچه در خط فرمان ارسال می‌کنید، یک به یک مطابقت دارند. اگر در خط فرمان mkbitmap -v را اجرا کنید، Module.callMain(['-v']) را در مرورگر فراخوانی کنید. این شماره نسخه mkbitmap را در کنسول DevTools ثبت می کند.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  Module.callMain(['-v']);
};

run();

برنامه mkbitmap با یک صفحه سفید که شماره نسخه mkbitmap ثبت شده در کنسول DevTools را نشان می دهد.

خروجی استاندارد را تغییر مسیر دهید

خروجی استاندارد ( stdout ) به طور پیش فرض کنسول است. با این حال، می توانید آن را به چیز دیگری هدایت کنید، به عنوان مثال، تابعی که خروجی را در یک متغیر ذخیره می کند. این بدان معنی است که می توانید با تنظیم ویژگی Module.print خروجی را به HTML اضافه کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  let consoleOutput = 'Powered by ';
  const Module = await loadWASM({
    print: (text) => (consoleOutput += text),
  });
  Module.callMain(['-v']);
  document.body.textContent = consoleOutput;
};

run();

برنامه mkbitmap شماره نسخه mkbitmap را نشان می دهد.

فایل ورودی را در سیستم فایل حافظه دریافت کنید

برای دریافت فایل ورودی به سیستم فایل حافظه، mkbitmap filename در خط فرمان نیاز دارید. برای درک نحوه برخورد من با این موضوع، ابتدا پیش زمینه ای در مورد اینکه چگونه mkbitmap ورودی خود را انتظار دارد و خروجی خود را ایجاد می کند، بپردازید.

فرمت های ورودی پشتیبانی شده mkbitmap PNM ( PBM ، PGM ، PPM ) و BMP هستند. فرمت های خروجی PBM برای نقشه های بیتی و PGM برای نقشه های خاکستری هستند. اگر آرگومان filename داده شود، mkbitmap به طور پیش فرض یک فایل خروجی ایجاد می کند که نام آن از نام فایل ورودی با تغییر پسوند آن به .pbm به دست می آید. به عنوان مثال، برای نام فایل ورودی example.bmp ، نام فایل خروجی example.pbm خواهد بود.

Emscripten یک سیستم فایل مجازی را ارائه می‌کند که سیستم فایل محلی را شبیه‌سازی می‌کند، به طوری که کد بومی با استفاده از APIهای فایل همزمان می‌تواند کامپایل شده و با تغییر کم یا بدون تغییر اجرا شود. برای اینکه mkbitmap بتواند یک فایل ورودی را بخواند که گویی به عنوان آرگومان خط فرمان filename ارسال شده است، باید از شی FS که Emscripten ارائه می دهد استفاده کنید.

شی FS توسط یک سیستم فایل در حافظه (که معمولاً به عنوان MEMFS نامیده می شود) پشتیبانی می شود و دارای تابع writeFile() است که از آن برای نوشتن فایل ها در سیستم فایل مجازی استفاده می کنید. همانطور که در نمونه کد زیر نشان داده شده است از writeFile() استفاده می کنید.

برای تایید عملکرد نوشتن فایل، تابع readdir() شی FS را با پارامتر '/' اجرا کنید. شما example.bmp و تعدادی فایل پیش فرض را خواهید دید که همیشه به صورت خودکار ایجاد می شوند .

توجه داشته باشید که تماس قبلی با Module.callMain(['-v']) برای چاپ شماره نسخه حذف شد. این به این دلیل است که Module.callMain() تابعی است که معمولاً انتظار دارد فقط یک بار اجرا شود.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp.

اولین اجرای واقعی

با همه چیز در جای خود، mkbitmap با اجرای Module.callMain(['example.bmp']) اجرا کنید. محتویات پوشه MEMFS ' '/' را وارد کنید، و باید فایل خروجی example.pbm تازه ایجاد شده را در کنار فایل ورودی example.bmp ببینید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  console.log(Module.FS.readdir('/'));
};

run();

برنامه mkbitmap آرایه ای از فایل ها را در سیستم فایل حافظه نشان می دهد، از جمله example.bmp و example.pbm.

فایل خروجی را از سیستم فایل حافظه خارج کنید

تابع readFile() شی FS امکان خروج example.pbm ایجاد شده در آخرین مرحله از سیستم فایل حافظه را فراهم می کند. این تابع یک Uint8Array را برمی گرداند که شما آن را به یک شی File تبدیل کرده و در دیسک ذخیره می کنید، زیرا مرورگرها معمولاً فایل های PBM را برای مشاهده مستقیم در مرورگر پشتیبانی نمی کنند. (روش های ظریف تری برای ذخیره یک فایل وجود دارد، اما استفاده از <a download> ایجاد شده به صورت پویا بیشترین پشتیبانی را دارد.) هنگامی که فایل ذخیره شد، می توانید آن را در نمایشگر تصویر دلخواه خود باز کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
  const file = new File([output], 'example.pbm', {
    type: 'image/x-portable-bitmap',
  });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = file.name;
  a.click();
};

run();

macOS Finder با پیش نمایش فایل .bmp. ورودی و فایل pbm. خروجی.

یک رابط کاربری تعاملی اضافه کنید

تا این مرحله، فایل ورودی هاردکد شده و mkbitmap با پارامترهای پیش‌فرض اجرا می‌شود. مرحله آخر این است که به کاربر اجازه دهید به صورت پویا یک فایل ورودی را انتخاب کند، پارامترهای mkbitmap را تغییر دهد و سپس ابزار را با گزینه های انتخاب شده اجرا کند.

// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);

تجزیه فرمت تصویر PBM به خصوص سخت نیست، بنابراین با استفاده از کد جاوا اسکریپت ، حتی می توانید پیش نمایش تصویر خروجی را نشان دهید. کد منبع نسخه ی نمایشی تعبیه شده را برای یکی از راه های انجام این کار در زیر ببینید.

نتیجه گیری

تبریک می گوییم، شما با موفقیت mkbitmap در WebAssembly کامپایل کردید و آن را در مرورگر کار کردید! بن بست هایی وجود داشت و شما مجبور بودید بیش از یک بار ابزار را کامپایل کنید تا زمانی که کار کند، اما همانطور که در بالا نوشتم، این بخشی از تجربه است. همچنین اگر گیر کردید ، تگ webassembly StackOverflow را به خاطر بسپارید. تدوین مبارک!

قدردانی

این مقاله توسط سام کلگ و ریچل اندرو بررسی شده است.

،

در WebAssembly چیست و از کجا آمده است؟ ، توضیح دادم که چگونه به WebAssembly امروز رسیدیم. در این مقاله، من روش خود را برای کامپایل کردن یک برنامه C موجود، mkbitmap ، به WebAssembly به شما نشان خواهم داد. از مثال hello world پیچیده‌تر است، زیرا شامل کار با فایل‌ها، برقراری ارتباط بین WebAssembly و جاوا اسکریپت و کشیدن روی بوم می‌شود، اما همچنان به اندازه‌ای قابل مدیریت است که شما را تحت تأثیر قرار ندهد.

این مقاله برای توسعه دهندگان وب نوشته شده است که می خواهند WebAssembly را یاد بگیرند و گام به گام نشان می دهد که اگر می خواهید چیزی مانند mkbitmap در WebAssembly کامپایل کنید چگونه می توانید ادامه دهید. به عنوان یک هشدار منصفانه، دریافت نکردن یک برنامه یا کتابخانه برای کامپایل در اولین اجرا کاملاً طبیعی است، به همین دلیل است که برخی از مراحل شرح داده شده در زیر کار نمی‌کنند، بنابراین من باید به عقب برگردم و دوباره امتحان کنم. این مقاله فرمان کامپایل نهایی جادویی را به گونه‌ای نشان نمی‌دهد که انگار از آسمان افتاده است، بلکه پیشرفت واقعی من را توصیف می‌کند که شامل برخی ناامیدی‌ها نیز می‌شود.

درباره mkbitmap

برنامه mkbitmap C یک تصویر را می خواند و یک یا چند مورد از عملیات زیر را به ترتیب بر روی آن اعمال می کند: وارونگی، فیلتر بالاگذر، مقیاس گذاری و آستانه گذاری. هر عملیات را می توان به صورت جداگانه کنترل و روشن یا خاموش کرد. کاربرد اصلی mkbitmap تبدیل تصاویر رنگی یا خاکستری به فرمتی مناسب به عنوان ورودی برای برنامه های دیگر است، به ویژه ردیابی برنامه potrace که اساس SVGcode را تشکیل می دهد. به عنوان یک ابزار پیش پردازش، mkbitmap به ویژه برای تبدیل هنر خط اسکن شده، مانند کارتون یا متن دست نویس، به تصاویر دوسطحی با وضوح بالا مفید است.

شما از mkbitmap با ارسال تعدادی گزینه و یک یا چند نام فایل به آن استفاده می کنید. برای همه جزئیات، به صفحه مرد ابزار مراجعه کنید:

$ mkbitmap [options] [filename...]
تصویر کارتونی رنگی.
تصویر اصلی ( منبع ).
تصویر کارتونی پس از پیش پردازش به مقیاس خاکستری تبدیل شد.
ابتدا مقیاس، سپس آستانه: mkbitmap -f 2 -s 2 -t 0.48 ( منبع ).

کد را دریافت کنید

اولین قدم دریافت کد منبع mkbitmap است. می توانید آن را در وب سایت پروژه پیدا کنید. در زمان نگارش این مطلب، potrace-1.16.tar.gz آخرین نسخه است.

کامپایل و به صورت محلی نصب کنید

گام بعدی این است که ابزار را به صورت محلی کامپایل و نصب کنید تا احساس کنید که چگونه رفتار می کند. فایل INSTALL حاوی دستورالعمل های زیر است:

  1. cd به دایرکتوری حاوی کد منبع بسته وارد کنید و ./configure را تایپ کنید تا بسته را برای سیستم خود پیکربندی کنید.

    اجرای configure ممکن است کمی طول بکشد. در حین اجرا، پیام هایی را چاپ می کند که نشان می دهد کدام ویژگی ها را بررسی می کند.

  2. برای کامپایل کردن بسته، make تایپ کنید.

  3. به صورت اختیاری، make check را تایپ کنید تا آزمایش‌های خودکاری که با بسته ارائه می‌شود، اجرا شود، معمولاً با استفاده از باینری‌های حذف‌شده تازه ساخته‌شده.

  4. برای نصب برنامه ها و هر فایل داده و اسنادی، make install تایپ کنید. هنگام نصب در پیشوندی که متعلق به root است، توصیه می شود که بسته به عنوان یک کاربر معمولی پیکربندی و ساخته شود و فقط مرحله make install با حقوق ریشه اجرا شود.

با دنبال کردن این مراحل، باید به دو فایل اجرایی، potrace و mkbitmap برسید - مورد دوم تمرکز این مقاله است. می توانید با اجرای mkbitmap --version بررسی کنید که درست کار کرده است. در اینجا خروجی هر چهار مرحله از دستگاه من است که برای اختصار به شدت کوتاه شده است:

مرحله 1، ./configure :

 $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands

مرحله 2، make :

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.

مرحله 3، make check :

$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.

مرحله 4، sudo make install :

$ sudo make install
Password:
Making install in src
 .././install-sh -c -d '/usr/local/bin'
  /bin/sh ../libtool   --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.

برای بررسی اینکه آیا کار می کند، mkbitmap --version اجرا کنید:

$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

اگر جزئیات نسخه را دریافت کردید، mkbitmap با موفقیت کامپایل و نصب کرده اید. در مرحله بعد، معادل این مراحل را با WebAssembly کار کنید.

mkbitmap به webassembly کامپایل کنید

Emscripten ابزاری برای تهیه برنامه های C/C ++ به WebAssembly است. مستندات پروژه های ساختمان Emscripten موارد زیر را بیان می کند:

ساختن پروژه های بزرگ با Emscriptten بسیار آسان است. Emscripten دو اسکریپت ساده را ارائه می دهد که MakeFiles شما را برای استفاده از emcc به عنوان جایگزینی قطره ای برای gcc پیکربندی می کند-در بیشتر موارد ، بقیه سیستم ساخت فعلی پروژه شما بدون تغییر باقی مانده است.

مستندات سپس ادامه می یابد (کمی برای کوتاه مدت ویرایش شده است):

موردی را در نظر بگیرید که به طور معمول با دستورات زیر می سازید:

./configure
make

برای ساخت با Emscripten ، در عوض از دستورات زیر استفاده می کنید:

emconfigure ./configure
emmake make

بنابراین اساساً ./configure تبدیل به emconfigure ./configure و make emmake make می شود. موارد زیر نحوه انجام این کار با mkbitmap را نشان می دهد.

مرحله 0 ، make clean :

$ make clean
Making clean in src
 rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo

مرحله 1 ، emconfigure ./configure :

$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands

مرحله 2 ، emmake make :

$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.

اگر همه چیز خوب پیش رفت ، اکنون باید پرونده های .wasm در جایی در فهرست وجود داشته باشد. با اجرای find . -name "*.wasm" :

$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm

این دو مورد آخر امیدوار کننده به نظر می رسند ، بنابراین cd در src/ Directory. هم اکنون دو پرونده جدید مربوطه ، mkbitmap و potrace نیز وجود دارد. برای این مقاله ، فقط mkbitmap مرتبط است. این واقعیت که آنها پسوند .js را ندارند ، کمی گیج کننده است ، اما آنها در واقع پرونده های JavaScript هستند که با یک تماس سریع head قابل اثبات هستند:

$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)

با تماس با mv mkbitmap mkbitmap.jsmv potrace potrace.js به ترتیب در صورت تمایل) پرونده javaScript را به mkbitmap.js تغییر نام دهید. اکنون زمان آن رسیده است که اولین تست ببینیم که آیا با اجرای پرونده با node.js در خط فرمان با اجرای node mkbitmap.js --version : کار کرده است:

$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

شما با موفقیت mkbitmap را به WebAssembly گردآوری کرده اید. اکنون مرحله بعدی این است که آن را در مرورگر کار کنید.

mkbitmap با WebAssembly در مرورگر

فایلهای mkbitmap.js و mkbitmap.wasm را در یک فهرست جدید به نام mkbitmap کپی کرده و یک پرونده index.html html boilerplate ایجاد کنید که پرونده mkbitmap.js javaScript را بارگیری می کند.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <script src="mkbitmap.js"></script>
  </body>
</html>

یک سرور محلی را شروع کنید که به دایرکتوری mkbitmap خدمت کند و آن را در مرورگر خود باز کنید. شما باید یک سریع را ببینید که از شما درخواست ورودی می کند. این همانطور که انتظار می رود ، از آنجا که ، مطابق با صفحه مرد ابزار ، "[من] هیچ آرگومان نام پرونده ارائه نمی شود ، سپس MKBitMap به عنوان یک فیلتر عمل می کند ، از ورودی استاندارد می خواند" ، که برای emscripten به طور پیش فرض prompt() .

برنامه MKBitMap سریع نشان می دهد که درخواست ورودی را می دهد.

از اجرای خودکار جلوگیری کنید

برای جلوگیری از اجرای بلافاصله mkbitmap و در عوض ، آن را منتظر ورودی کاربر هستید ، باید شیء Module Emscripten را درک کنید. Module یک شیء جهانی جاوا اسکریپت است و دارای ویژگی هایی است که کد تولید شده توسط emscripten را در نقاط مختلف اجرای آن فراخوانی می کند. برای کنترل اجرای کد می توانید یک Module را ارائه دهید. هنگامی که یک برنامه emscripten شروع به کار می کند ، مقادیر موجود در شیء Module را نگاه می کند و آنها را اعمال می کند.

در مورد mkbitmap ، Module.noInitialRun true برای جلوگیری از اجرای اولیه که باعث ظاهر شدن سریع شد. یک اسکریپت به نام script.js ایجاد کنید ، آن را قبل از <script src="mkbitmap.js"></script> در index.html درج کنید و کد زیر را به script.js اضافه کنید. وقتی اکنون برنامه را بارگیری مجدد می کنید ، سریع باید از بین برود.

var Module = {
  // Don't run main() at page load
  noInitialRun: true,
};

با برخی از پرچم های ساخت بیشتر یک ساخت ماژولار ایجاد کنید

برای ارائه ورودی به برنامه ، می توانید از پشتیبانی سیستم فایل Emscripten در Module.FS استفاده کنید. بخش پشتیبانی سیستم پرونده از جمله مستندات آمده است:

Emscripten تصمیم می گیرد که آیا پشتیبانی سیستم فایل را بطور خودکار درج می کند. بسیاری از برنامه ها به پرونده ها احتیاج ندارند و پشتیبانی از سیستم فایل در اندازه ناچیز نیست ، بنابراین Emscripten از جمله آن جلوگیری می کند که دلیل آن را نمی بیند. این بدان معناست که اگر کد C/C ++ شما به پرونده ها دسترسی پیدا نکند ، آنگاه FS Object و API های سیستم فایل دیگر در خروجی قرار نمی گیرند. و از طرف دیگر ، اگر کد C/C ++ شما از پرونده ها استفاده می کند ، پشتیبانی سیستم پرونده به طور خودکار گنجانده می شود.

متأسفانه mkbitmap یکی از مواردی است که Emscripten به طور خودکار شامل پشتیبانی سیستم فایل نیست ، بنابراین شما باید صریحاً برای انجام این کار بگویید. این بدان معناست که شما باید مراحل emconfigure و emmake که قبلاً شرح داده شده بود ، دنبال کنید ، با این که چند پرچم دیگر از طریق یک آرگومان CFLAGS تنظیم شده است. پرچم های زیر ممکن است برای سایر پروژه ها نیز مفید باشد.

همچنین ، در این مورد خاص ، شما باید پرچم --host را روی wasm32 تنظیم کنید تا به اسکریپت configure بگویید که برای WebAnsembly تهیه می کنید.

دستور نهایی emconfigure به این شکل است:

$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'

فراموش نکنید که دوباره emmake make اجرا کنید و پرونده های تازه ایجاد شده را در پوشه mkbitmap کپی کنید.

index.html به گونه ای اصلاح کنید که فقط script.js ماژول ES را بارگذاری کند ، که از آن می توانید ماژول mkbitmap.js را وارد کنید.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <!-- No longer load `mkbitmap.js` here -->
    <script src="script.js" type="module"></script>
  </body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  console.log(Module);
};

run();

وقتی برنامه را اکنون در مرورگر باز می کنید ، باید شیء Module را که به کنسول DevTools وارد شده است ، مشاهده کنید ، و سریع از بین می رود ، زیرا عملکرد main() mkbitmap دیگر در ابتدا فراخوانی نمی شود.

برنامه MKBITMAP با یک صفحه نمایش سفید ، شیء ماژول را که به کنسول DevTools وارد شده است نشان می دهد.

عملکرد اصلی را به صورت دستی اجرا کنید

مرحله بعدی این است که با اجرای Module.callMain() main() ) mkbitmap تماس بگیرید. تابع callMain() مجموعه ای از آرگومان ها را می گیرد ، که یک به یک با یک آنچه را که می توانید از خط فرمان عبور دهید مطابقت دارد. اگر در خط فرمان mkbitmap -v را اجرا کنید ، می توانید Module.callMain(['-v']) را در مرورگر صدا کنید. این شماره نسخه mkbitmap را به کنسول DevTools وارد می کند.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  Module.callMain(['-v']);
};

run();

برنامه MKBITMAP با یک صفحه نمایش سفید ، شماره نسخه MKBITMAP را که به کنسول DevTools وارد شده است ، نشان می دهد.

بازده استاندارد را هدایت کنید

خروجی استاندارد ( stdout ) به طور پیش فرض کنسول است. با این حال ، می توانید آن را به چیز دیگری هدایت کنید ، به عنوان مثال ، عملکردی که خروجی را به یک متغیر ذخیره می کند. این بدان معنی است که می توانید با تنظیم ویژگی Module.print ، خروجی را به HTML اضافه کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  let consoleOutput = 'Powered by ';
  const Module = await loadWASM({
    print: (text) => (consoleOutput += text),
  });
  Module.callMain(['-v']);
  document.body.textContent = consoleOutput;
};

run();

برنامه MKBITMAP شماره نسخه MKBITMAP را نشان می دهد.

پرونده ورودی را به سیستم پرونده حافظه دریافت کنید

برای دریافت پرونده ورودی به سیستم پرونده حافظه ، به معادل mkbitmap filename در خط فرمان نیاز دارید. برای درک چگونگی نزدیک شدن به این ، ابتدا پیش زمینه ای در مورد چگونگی انتظار mkbitmap از ورودی خود و ایجاد خروجی خود را ایجاد می کند.

قالبهای ورودی پشتیبانی شده mkbitmap عبارتند از PNM ( PBM ، PGM ، PPM ) و BMP . قالب های خروجی PBM برای Bitmaps و PGM برای Graymaps هستند. اگر یک آرگومان filename ارائه شود ، mkbitmap به طور پیش فرض یک فایل خروجی ایجاد می کند که نام آن از نام پرونده ورودی با تغییر پسوند خود به .pbm بدست می آید. به عنوان مثال ، برای example.bmp نام پرونده ورودی. BMP ، نام پرونده خروجی example.pbm خواهد بود.

Emscripten یک سیستم فایل مجازی را ارائه می دهد که سیستم فایل محلی را شبیه سازی می کند ، به طوری که کد بومی با استفاده از API های فایل همزمان می تواند با تغییر کم و یا تغییر کند. برای اینکه mkbitmap یک فایل ورودی را بخواند ، گویی که به عنوان یک آرگومان خط فرمان filename منتقل شده است ، باید از شیء FS که Emscripten ارائه می دهد استفاده کنید.

شیء FS توسط یک سیستم پرونده در حافظه (که معمولاً به آن MEMF ها گفته می شود) پشتیبانی می شود و دارای یک تابع writeFile() است که شما برای نوشتن پرونده ها به سیستم فایل مجازی استفاده می کنید. شما از writeFile() همانطور که در نمونه کد زیر نشان داده شده است استفاده می کنید.

برای تأیید عملکرد نوشتن پرونده ، readdir() FS Object را با پارامتر '/' اجرا کنید. example.bmp و تعدادی از پرونده های پیش فرض که همیشه به طور خودکار ایجاد می شوند را مشاهده خواهید کرد.

توجه داشته باشید که تماس قبلی به Module.callMain(['-v']) برای چاپ شماره نسخه حذف شد. این به این دلیل است که Module.callMain() تابعی است که به طور کلی انتظار دارد فقط یک بار اجرا شود.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  console.log(Module.FS.readdir('/'));
};

run();

برنامه MKBITMAP آرایه ای از پرونده ها را در سیستم پرونده حافظه ، از جمله مثال.BMP نشان می دهد.

اولین اجرای واقعی

با همه چیز در جای خود ، mkbitmap با اجرای Module.callMain(['example.bmp']) اجرا کنید. محتویات پوشه MEMFS ' '/' را وارد کنید ، و باید پرونده خروجی جدید به example.pbm ایجاد شده را در کنار پرونده ورودی به example.bmp مشاهده کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  console.log(Module.FS.readdir('/'));
};

run();

برنامه MKBITMAP مجموعه ای از پرونده ها را در سیستم فایل حافظه ، از جمله مثال.BMP و مثال.PBM نشان می دهد.

پرونده خروجی را از سیستم پرونده حافظه دریافت کنید

عملکرد FS Object's readFile() دریافت example.pbm در آخرین مرحله از سیستم پرونده حافظه ایجاد می کند. این تابع یک Uint8Array را که به یک شیء File تبدیل می کنید باز می گرداند و در دیسک ذخیره می شود ، زیرا مرورگرها به طور کلی از فایلهای PBM برای مشاهده مستقیم مرورگر پشتیبانی نمی کنند. (روش های ظریف تری برای ذخیره یک فایل وجود دارد ، اما استفاده از یک دینامیکی ایجاد شده <a download> بسیار پشتیبانی شده است.) پس از ذخیره فایل ، می توانید آن را در بیننده تصویر مورد علاقه خود باز کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
  const file = new File([output], 'example.pbm', {
    type: 'image/x-portable-bitmap',
  });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = file.name;
  a.click();
};

run();

MacOS Finder با پیش نمایش پرونده .bmp ورودی و پرونده خروجی .pbm.

UI تعاملی اضافه کنید

برای این مرحله ، پرونده ورودی به سختی کد شده و mkbitmap با پارامترهای پیش فرض اجرا می شود. مرحله آخر این است که به کاربر اجازه دهید به صورت پویا یک فایل ورودی را انتخاب کند ، پارامترهای mkbitmap را تغییر داده و سپس ابزار را با گزینه های انتخاب شده اجرا کنید.

// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);

فرمت تصویر PBM به ویژه تجزیه نیست ، بنابراین با برخی از کد JavaScript ، حتی می توانید پیش نمایش تصویر خروجی را نشان دهید. برای یک روش برای انجام این کار ، کد منبع نسخه ی نمایشی تعبیه شده را در زیر مشاهده کنید.

نتیجه گیری

تبریک می گویم ، شما با موفقیت mkbitmap به WebAssembly گردآوری کرده اید و آن را در مرورگر کار کرده اید! برخی از بن بست ها وجود داشت و شما باید بیش از یک بار ابزار را تا زمانی که کار کند ، کامپایل کنید ، اما همانطور که در بالا نوشتم ، این بخشی از تجربه است. همچنین اگر گیر کردید ، برچسب webassembly StackoverFlow را به خاطر بسپارید. کامپایل مبارک!

قدردانی

این مقاله توسط سام کلگ و راشل اندرو بررسی شده است.

،

در WebAssembly چیست و از کجا آمده است؟ ، من توضیح دادم که چگونه ما با WebAssembly امروز به پایان رسیدیم. در این مقاله ، من رویکرد خود را برای تهیه یک برنامه C موجود ، mkbitmap ، به WebAssembly به شما نشان خواهم داد. این پیچیده تر از مثال Hello World است ، زیرا شامل کار با پرونده ها ، برقراری ارتباط بین WebAnsembly و سرزمین های JavaScript و ترسیم به یک بوم است ، اما هنوز هم به اندازه کافی قابل کنترل است که شما را تحت الشعاع قرار ندهد.

این مقاله برای توسعه دهندگان وب نوشته شده است که می خواهند WebAssembly را یاد بگیرند و به صورت گام به گام نشان می دهد که اگر می خواهید چیزی مانند mkbitmap را به WebAnsembly کامپایل کنید ، چگونه می توانید ادامه دهید. به عنوان یک هشدار عادلانه ، عدم دریافت برنامه یا کتابخانه برای تهیه در اولین اجرا کاملاً طبیعی است ، به همین دلیل برخی از مراحل شرح داده شده در زیر به پایان نرسیده است ، بنابراین من نیاز به عقب نشینی و دوباره امتحان کردن دوباره دارم. این مقاله دستور تالیف نهایی جادویی را نشان نمی دهد که گویی از آسمان افتاده است ، بلکه پیشرفت واقعی من را توصیف می کند ، برخی از ناامیدی ها شامل می شود.

درباره mkbitmap

برنامه mkbitmap C یک تصویر را می خواند و یک یا چند مورد از عملیات زیر را در این ترتیب به کار می برد: وارونگی ، فیلتر Highpass ، مقیاس گذاری و آستانه. هر عملیاتی را می توان به صورت جداگانه کنترل کرد و روشن یا خاموش کرد. استفاده اصلی از mkbitmap تبدیل تصاویر رنگ یا مقیاس خاکستری به فرمت مناسب به عنوان ورودی برای سایر برنامه ها ، به ویژه برنامه ردیابی potrace که اساس SVGCode را تشکیل می دهد. به عنوان یک ابزار پیش پردازش ، mkbitmap به ویژه برای تبدیل هنر خط اسکن شده مانند کارتون یا متن دست نوشته به تصاویر صفراوی با وضوح بالا مفید است.

شما با عبور از تعدادی از گزینه ها و یک یا چند نام پرونده از mkbitmap استفاده می کنید. برای تمام جزئیات ، به صفحه مرد ابزار مراجعه کنید:

$ mkbitmap [options] [filename...]
تصویر کارتونی به رنگ.
تصویر اصلی ( منبع ).
تصویر کارتونی پس از پیش پردازش به مقیاس خاکستری تبدیل شد.
ابتدا مقیاس بندی شده ، سپس آستانه: mkbitmap -f 2 -s 2 -t 0.48 ( منبع ).

کد را دریافت کنید

اولین قدم به دست آوردن کد منبع mkbitmap است. می توانید آن را در وب سایت پروژه پیدا کنید. در زمان این نوشتار ، POTRACE-1.16.TAR.GZ آخرین نسخه است.

کامپایل و نصب محلی

مرحله بعدی تهیه و نصب ابزار به صورت محلی است تا احساس رفتار خود را بدست آورید. پرونده INSTALL شامل دستورالعمل های زیر است:

  1. cd به فهرست حاوی کد منبع بسته و نوع ./configure برای پیکربندی بسته برای سیستم خود.

    configure در حال اجرا ممکن است مدتی طول بکشد. در حین اجرای ، برخی از پیام ها را چاپ می کند که می گوید کدام ویژگی ها را بررسی می کند.

  2. برای کامپایل کردن بسته ، تایپ make .

  3. به صورت اختیاری ، برای اجرای هرگونه تست خود که با بسته بندی همراه است ، به طور کلی با استفاده از باینری های حذف نشده درست ساخته شده ، نوع خود make check .

  4. برای نصب برنامه ها و هر پرونده و اسناد داده ، make install . هنگام نصب در پیشوند متعلق به Root ، توصیه می شود که این بسته به عنوان یک کاربر معمولی تنظیم و ساخته شود و فقط مرحله make install با امتیازات ریشه ای اجرا شود.

با دنبال کردن این مراحل ، شما باید با دو اجرایی ، potrace و mkbitmap به پایان برسید - دومی مورد توجه این مقاله است. می توانید با اجرای mkbitmap --version تأیید کنید که به درستی کار کرده است. در اینجا خروجی هر چهار مرحله از دستگاه من است که به شدت برای کوتاه بودن بریده شده است:

مرحله 1 ، ./configure :

 $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
[…]
config.status: executing libtool commands

مرحله 2 ، make :

$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
clang -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all-am'.

مرحله 3 ، make check :

$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in doc
make[1]: Nothing to be done for `check'.
[…]
============================================================================
Testsuite summary for potrace 1.16
============================================================================
# TOTAL: 8
# PASS:  8
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[1]: Nothing to be done for `check-am'.

مرحله 4 ، sudo make install :

$ sudo make install
Password:
Making install in src
 .././install-sh -c -d '/usr/local/bin'
  /bin/sh ../libtool   --mode=install /usr/bin/install -c potrace mkbitmap '/usr/local/bin'
[…]
make[2]: Nothing to be done for `install-data-am'.

برای بررسی اینکه آیا کار کرده است ، mkbitmap --version اجرا کنید:

$ mkbitmap --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

اگر جزئیات نسخه را دریافت کردید ، mkbitmap با موفقیت کامپایل و نصب کرده اید. در مرحله بعد ، معادل این مراحل با WebAssembly کار کنید.

mkbitmap به webassembly کامپایل کنید

Emscripten ابزاری برای تهیه برنامه های C/C ++ به WebAssembly است. مستندات پروژه های ساختمان Emscripten موارد زیر را بیان می کند:

ساختن پروژه های بزرگ با Emscriptten بسیار آسان است. Emscripten دو اسکریپت ساده را ارائه می دهد که MakeFiles شما را برای استفاده از emcc به عنوان جایگزینی قطره ای برای gcc پیکربندی می کند-در بیشتر موارد ، بقیه سیستم ساخت فعلی پروژه شما بدون تغییر باقی مانده است.

مستندات سپس ادامه می یابد (کمی برای کوتاه مدت ویرایش شده است):

موردی را در نظر بگیرید که به طور معمول با دستورات زیر می سازید:

./configure
make

برای ساخت با Emscripten ، در عوض از دستورات زیر استفاده می کنید:

emconfigure ./configure
emmake make

بنابراین اساساً ./configure تبدیل به emconfigure ./configure و make emmake make می شود. موارد زیر نحوه انجام این کار با mkbitmap را نشان می دهد.

مرحله 0 ، make clean :

$ make clean
Making clean in src
 rm -f potrace mkbitmap
test -z "" || rm -f
rm -rf .libs _libs
[…]
rm -f *.lo

مرحله 1 ، emconfigure ./configure :

$ emconfigure ./configure
configure: ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
[…]
config.status: executing libtool commands

مرحله 2 ، emmake make :

$ emmake make
make: make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive
Making all in src
/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
[…]
make[2]: Nothing to be done for `all'.

اگر همه چیز خوب پیش رفت ، اکنون باید پرونده های .wasm در جایی در فهرست وجود داشته باشد. با اجرای find . -name "*.wasm" :

$ find . -name "*.wasm"
./a.wasm
./src/mkbitmap.wasm
./src/potrace.wasm

این دو مورد آخر امیدوار کننده به نظر می رسند ، بنابراین cd در src/ Directory. هم اکنون دو پرونده جدید مربوطه ، mkbitmap و potrace نیز وجود دارد. برای این مقاله ، فقط mkbitmap مرتبط است. این واقعیت که آنها پسوند .js را ندارند ، کمی گیج کننده است ، اما آنها در واقع پرونده های JavaScript هستند که با یک تماس سریع head قابل اثبات هستند:

$ cd src/
$ head -n 20 mkbitmap
// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module != 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)

با تماس با mv mkbitmap mkbitmap.jsmv potrace potrace.js به ترتیب در صورت تمایل) پرونده javaScript را به mkbitmap.js تغییر نام دهید. اکنون زمان آن رسیده است که اولین تست ببینیم که آیا با اجرای پرونده با node.js در خط فرمان با اجرای node mkbitmap.js --version : کار کرده است:

$ node mkbitmap.js --version
mkbitmap 1.16. Copyright (C) 2001-2019 Peter Selinger.

شما با موفقیت mkbitmap را به WebAssembly گردآوری کرده اید. اکنون مرحله بعدی این است که آن را در مرورگر کار کنید.

mkbitmap با WebAssembly در مرورگر

فایلهای mkbitmap.js و mkbitmap.wasm را در یک فهرست جدید به نام mkbitmap کپی کرده و یک پرونده index.html html boilerplate ایجاد کنید که پرونده mkbitmap.js javaScript را بارگیری می کند.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <script src="mkbitmap.js"></script>
  </body>
</html>

یک سرور محلی را شروع کنید که به دایرکتوری mkbitmap خدمت کند و آن را در مرورگر خود باز کنید. شما باید یک سریع را ببینید که از شما درخواست ورودی می کند. این همانطور که انتظار می رود ، از آنجا که ، مطابق با صفحه مرد ابزار ، "[من] هیچ آرگومان نام پرونده ارائه نمی شود ، سپس MKBitMap به عنوان یک فیلتر عمل می کند ، از ورودی استاندارد می خواند" ، که برای emscripten به طور پیش فرض prompt() .

برنامه MKBitMap سریع نشان می دهد که درخواست ورودی را می دهد.

از اجرای خودکار جلوگیری کنید

برای جلوگیری از اجرای بلافاصله mkbitmap و در عوض ، آن را منتظر ورودی کاربر هستید ، باید شیء Module Emscripten را درک کنید. Module یک شیء جهانی جاوا اسکریپت است و دارای ویژگی هایی است که کد تولید شده توسط emscripten را در نقاط مختلف اجرای آن فراخوانی می کند. برای کنترل اجرای کد می توانید یک Module را ارائه دهید. هنگامی که یک برنامه emscripten شروع به کار می کند ، مقادیر موجود در شیء Module را نگاه می کند و آنها را اعمال می کند.

در مورد mkbitmap ، Module.noInitialRun true برای جلوگیری از اجرای اولیه که باعث ظاهر شدن سریع شد. یک اسکریپت به نام script.js ایجاد کنید ، آن را قبل از <script src="mkbitmap.js"></script> در index.html درج کنید و کد زیر را به script.js اضافه کنید. وقتی اکنون برنامه را بارگیری مجدد می کنید ، سریع باید از بین برود.

var Module = {
  // Don't run main() at page load
  noInitialRun: true,
};

با برخی از پرچم های ساخت بیشتر یک ساخت ماژولار ایجاد کنید

برای ارائه ورودی به برنامه ، می توانید از پشتیبانی سیستم فایل Emscripten در Module.FS استفاده کنید. بخش پشتیبانی سیستم پرونده از جمله مستندات آمده است:

Emscripten تصمیم می گیرد که آیا پشتیبانی سیستم فایل را بطور خودکار درج می کند. بسیاری از برنامه ها به پرونده ها احتیاج ندارند و پشتیبانی از سیستم فایل در اندازه ناچیز نیست ، بنابراین Emscripten از جمله آن جلوگیری می کند که دلیل آن را نمی بیند. این بدان معناست که اگر کد C/C ++ شما به پرونده ها دسترسی پیدا نکند ، آنگاه FS Object و API های سیستم فایل دیگر در خروجی قرار نمی گیرند. و از طرف دیگر ، اگر کد C/C ++ شما از پرونده ها استفاده می کند ، پشتیبانی سیستم پرونده به طور خودکار گنجانده می شود.

متأسفانه mkbitmap یکی از مواردی است که Emscripten به طور خودکار شامل پشتیبانی سیستم فایل نیست ، بنابراین شما باید صریحاً برای انجام این کار بگویید. این بدان معناست که شما باید مراحل emconfigure و emmake که قبلاً شرح داده شده بود ، دنبال کنید ، با این که چند پرچم دیگر از طریق یک آرگومان CFLAGS تنظیم شده است. پرچم های زیر ممکن است برای سایر پروژه ها نیز مفید باشد.

همچنین ، در این مورد خاص ، شما باید پرچم --host را روی wasm32 تنظیم کنید تا به اسکریپت configure بگویید که برای WebAnsembly تهیه می کنید.

دستور نهایی emconfigure به این شکل است:

$ emconfigure ./configure --host=wasm32 CFLAGS='-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0'

فراموش نکنید که دوباره emmake make اجرا کنید و پرونده های تازه ایجاد شده را در پوشه mkbitmap کپی کنید.

index.html به گونه ای اصلاح کنید که فقط script.js ماژول ES را بارگذاری کند ، که از آن می توانید ماژول mkbitmap.js را وارد کنید.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>mkbitmap</title>
  </head>
  <body>
    <!-- No longer load `mkbitmap.js` here -->
    <script src="script.js" type="module"></script>
  </body>
</html>
// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  console.log(Module);
};

run();

وقتی برنامه را اکنون در مرورگر باز می کنید ، باید شیء Module را که به کنسول DevTools وارد شده است ، مشاهده کنید ، و سریع از بین می رود ، زیرا عملکرد main() mkbitmap دیگر در ابتدا فراخوانی نمی شود.

برنامه MKBITMAP با یک صفحه نمایش سفید ، شیء ماژول را که به کنسول DevTools وارد شده است نشان می دهد.

عملکرد اصلی را به صورت دستی اجرا کنید

مرحله بعدی این است که با اجرای Module.callMain() main() ) mkbitmap تماس بگیرید. تابع callMain() مجموعه ای از آرگومان ها را می گیرد ، که یک به یک با یک آنچه را که می توانید از خط فرمان عبور دهید مطابقت دارد. اگر در خط فرمان mkbitmap -v را اجرا کنید ، می توانید Module.callMain(['-v']) را در مرورگر صدا کنید. این شماره نسخه mkbitmap را به کنسول DevTools وارد می کند.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  Module.callMain(['-v']);
};

run();

برنامه MKBITMAP با یک صفحه نمایش سفید ، شماره نسخه MKBITMAP را که به کنسول DevTools وارد شده است ، نشان می دهد.

بازده استاندارد را هدایت کنید

خروجی استاندارد ( stdout ) به طور پیش فرض کنسول است. با این حال ، می توانید آن را به چیز دیگری هدایت کنید ، به عنوان مثال ، عملکردی که خروجی را به یک متغیر ذخیره می کند. این بدان معنی است که می توانید با تنظیم ویژگی Module.print ، خروجی را به HTML اضافه کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  let consoleOutput = 'Powered by ';
  const Module = await loadWASM({
    print: (text) => (consoleOutput += text),
  });
  Module.callMain(['-v']);
  document.body.textContent = consoleOutput;
};

run();

برنامه MKBITMAP شماره نسخه MKBITMAP را نشان می دهد.

پرونده ورودی را به سیستم پرونده حافظه دریافت کنید

برای دریافت پرونده ورودی به سیستم پرونده حافظه ، به معادل mkbitmap filename در خط فرمان نیاز دارید. برای درک چگونگی نزدیک شدن به این ، ابتدا پیش زمینه ای در مورد چگونگی انتظار mkbitmap از ورودی خود و ایجاد خروجی خود را ایجاد می کند.

قالبهای ورودی پشتیبانی شده mkbitmap عبارتند از PNM ( PBM ، PGM ، PPM ) و BMP . قالب های خروجی PBM برای Bitmaps و PGM برای Graymaps هستند. اگر یک آرگومان filename ارائه شود ، mkbitmap به طور پیش فرض یک فایل خروجی ایجاد می کند که نام آن از نام پرونده ورودی با تغییر پسوند خود به .pbm بدست می آید. به عنوان مثال ، برای example.bmp نام پرونده ورودی. BMP ، نام پرونده خروجی example.pbm خواهد بود.

Emscripten یک سیستم فایل مجازی را ارائه می دهد که سیستم فایل محلی را شبیه سازی می کند ، به طوری که کد بومی با استفاده از API های فایل همزمان می تواند با تغییر کم و یا تغییر کند. برای اینکه mkbitmap یک فایل ورودی را بخواند ، گویی که به عنوان یک آرگومان خط فرمان filename منتقل شده است ، باید از شیء FS که Emscripten ارائه می دهد استفاده کنید.

شیء FS توسط یک سیستم پرونده در حافظه (که معمولاً به آن MEMF ها گفته می شود) پشتیبانی می شود و دارای یک تابع writeFile() است که شما برای نوشتن پرونده ها به سیستم فایل مجازی استفاده می کنید. شما از writeFile() همانطور که در نمونه کد زیر نشان داده شده است استفاده می کنید.

برای تأیید عملکرد نوشتن پرونده ، readdir() FS Object را با پارامتر '/' اجرا کنید. example.bmp و تعدادی از پرونده های پیش فرض که همیشه به طور خودکار ایجاد می شوند را مشاهده خواهید کرد.

توجه داشته باشید که تماس قبلی به Module.callMain(['-v']) برای چاپ شماره نسخه حذف شد. این به این دلیل است که Module.callMain() تابعی است که به طور کلی انتظار دارد فقط یک بار اجرا شود.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  console.log(Module.FS.readdir('/'));
};

run();

برنامه MKBITMAP آرایه ای از پرونده ها را در سیستم پرونده حافظه ، از جمله مثال.BMP نشان می دهد.

اولین اجرای واقعی

با همه چیز در جای خود ، mkbitmap با اجرای Module.callMain(['example.bmp']) اجرا کنید. محتویات پوشه MEMFS ' '/' را وارد کنید ، و باید پرونده خروجی جدید به example.pbm ایجاد شده را در کنار پرونده ورودی به example.bmp مشاهده کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  console.log(Module.FS.readdir('/'));
};

run();

برنامه MKBITMAP مجموعه ای از پرونده ها را در سیستم فایل حافظه ، از جمله مثال.BMP و مثال.PBM نشان می دهد.

پرونده خروجی را از سیستم پرونده حافظه دریافت کنید

عملکرد FS Object's readFile() دریافت example.pbm در آخرین مرحله از سیستم پرونده حافظه ایجاد می کند. این تابع یک Uint8Array را که به یک شیء File تبدیل می کنید باز می گرداند و در دیسک ذخیره می شود ، زیرا مرورگرها به طور کلی از فایلهای PBM برای مشاهده مستقیم مرورگر پشتیبانی نمی کنند. (روش های ظریف تری برای ذخیره یک فایل وجود دارد ، اما استفاده از یک دینامیکی ایجاد شده <a download> بسیار پشتیبانی شده است.) پس از ذخیره فایل ، می توانید آن را در بیننده تصویر مورد علاقه خود باز کنید.

// This is `script.js`.
import loadWASM from './mkbitmap.js';

const run = async () => {
  const Module = await loadWASM();
  const buffer = await fetch('https://example.com/example.bmp').then((res) => res.arrayBuffer());
  Module.FS.writeFile('example.bmp', new Uint8Array(buffer));
  Module.callMain(['example.bmp']);
  const output = Module.FS.readFile('example.pbm', { encoding: 'binary' });
  const file = new File([output], 'example.pbm', {
    type: 'image/x-portable-bitmap',
  });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = file.name;
  a.click();
};

run();

MacOS Finder با پیش نمایش پرونده .bmp ورودی و پرونده خروجی .pbm.

UI تعاملی اضافه کنید

برای این مرحله ، پرونده ورودی به سختی کد شده و mkbitmap با پارامترهای پیش فرض اجرا می شود. مرحله آخر این است که به کاربر اجازه دهید به صورت پویا یک فایل ورودی را انتخاب کند ، پارامترهای mkbitmap را تغییر داده و سپس ابزار را با گزینه های انتخاب شده اجرا کنید.

// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.
Module.callMain(['-o', 'output.pbm', 'input.bmp', '-s', '8', '-3', '-f', '4', '-t', '0.45']);

فرمت تصویر PBM به ویژه تجزیه نیست ، بنابراین با برخی از کد JavaScript ، حتی می توانید پیش نمایش تصویر خروجی را نشان دهید. برای یک روش برای انجام این کار ، کد منبع نسخه ی نمایشی تعبیه شده را در زیر مشاهده کنید.

نتیجه گیری

تبریک می گویم ، شما با موفقیت mkbitmap به WebAssembly گردآوری کرده اید و آن را در مرورگر کار کرده اید! برخی از بن بست ها وجود داشت و شما باید بیش از یک بار ابزار را تا زمانی که کار کند ، کامپایل کنید ، اما همانطور که در بالا نوشتم ، این بخشی از تجربه است. همچنین اگر گیر کردید ، برچسب webassembly StackoverFlow را به خاطر بسپارید. کامپایل مبارک!

قدردانی

این مقاله توسط سام کلگ و راشل اندرو بررسی شده است.