กำลังคอมไพล์ mkbitmap ไปยัง WebAssembly

ในบทความWebAssembly คืออะไรและมาจากไหน เราได้อธิบายว่า WebAssembly ในปัจจุบันเกิดขึ้นได้อย่างไร บทความนี้จะแสดงแนวทางการคอมไพล์โปรแกรม C ที่มีอยู่ mkbitmap เป็น WebAssembly ตัวอย่างนี้มีความซับซ้อนกว่าตัวอย่าง hello world เนื่องจากมีการทำงานกับไฟล์ การติดต่อสื่อสารระหว่าง WebAssembly กับ JavaScript และวาดภาพลงในผืนผ้าใบ แต่ก็ยังจัดการได้อยู่

บทความนี้เขียนขึ้นสำหรับนักพัฒนาเว็บที่ต้องการเรียนรู้ WebAssembly และแสดงวิธีดำเนินการทีละขั้นตอนหากต้องการคอมไพล์ mkbitmap เป็น WebAssembly ขอเตือนไว้ก่อนว่า การไม่ได้รับแอปหรือไลบรารีที่คอมไพล์เมื่อเรียกใช้ครั้งแรกนั้นเป็นเรื่องปกติ จึงเป็นเหตุผลที่ขั้นตอนบางขั้นตอนที่อธิบายไว้ด้านล่างใช้ไม่ได้ผล ฉันจึงต้องทำการย้อนกลับและลองอีกครั้ง บทความนี้ไม่ได้แสดงคำสั่งการคอมไพล์ครั้งสุดท้ายราวกับว่าเครื่องตกลงไปจากท้องฟ้า แต่จะอธิบายความคืบหน้าที่แท้จริงของข้าพเจ้า ซึ่งทำให้เกิดความหงุดหงิดบ้าง

เกี่ยวกับ mkbitmap

โปรแกรม C ของ mkbitmap จะอ่านรูปภาพและใช้การดำเนินการต่อไปนี้อย่างน้อย 1 รายการกับรูปภาพตามลำดับ ได้แก่ การกลับค่า การกรอง Highpass การปรับขนาด และการกําหนดเกณฑ์ แต่ละการดำเนินการสามารถควบคุมและเปิดหรือปิดแยกกันได้ การใช้งานหลักของ mkbitmap คือแปลงรูปภาพสีหรือสีเทาเป็นรูปแบบที่เหมาะสำหรับใช้เป็นอินพุตสำหรับโปรแกรมอื่นๆ โดยเฉพาะโปรแกรมการลากเส้น potrace ที่เป็นพื้นฐานของ SVGcode mkbitmap เป็นเครื่องมือเตรียมข้อมูลขั้นต้นที่มีประโยชน์อย่างยิ่งในการแปลงภาพวาดเส้นที่สแกน เช่น การ์ตูนหรือข้อความที่เขียนด้วยลายมือ เป็นรูปภาพแบบ 2 ระดับที่มีความละเอียดสูง

คุณใช้ mkbitmap ได้โดยส่งตัวเลือกและชื่อไฟล์อย่างน้อย 1 รายการ โปรดดูรายละเอียดทั้งหมดที่หน้าหน้าแมนของเครื่องมือ

$ 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 เพื่อติดตั้งโปรแกรม รวมถึงไฟล์ข้อมูลและเอกสารประกอบ เมื่อติดตั้งลงในคำนำหน้าที่เป็นของรูท เราขอแนะนำให้กำหนดค่าและสร้างแพ็กเกจให้เป็นผู้ใช้ทั่วไป และเฉพาะเฟส make install ที่ดำเนินการด้วยสิทธิ์รูทเท่านั้น

เมื่อทำตามขั้นตอนเหล่านี้แล้ว คุณควรได้ไฟล์ปฏิบัติการ 2 ไฟล์ ได้แก่ potrace และ mkbitmap ซึ่งอย่างหลังคือจุดโฟกัสของบทความนี้ คุณสามารถตรวจสอบว่าทำงานได้อย่างถูกต้องโดยเรียกใช้ mkbitmap --version เอาต์พุตของทั้ง 4 ขั้นตอนจากเครื่องของฉันมีดังนี้ โดยตัดให้สั้นลงมาก

ขั้นตอนที่ 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 มีสคริปต์ง่ายๆ 2 รายการที่จะกำหนดค่าไฟล์ make ให้ใช้ 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

2 รายการสุดท้ายดูดี โปรดcdย้ายไปยังไดเรกทอรี src/ ตอนนี้ยังมีไฟล์ใหม่ที่เกี่ยวข้อง 2 ไฟล์ ได้แก่ 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)

เปลี่ยนชื่อไฟล์ JavaScript เป็น mkbitmap.js โดยเรียก mv mkbitmap mkbitmap.js (และ mv potrace potrace.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 ที่ใช้บ่อยซึ่งโหลดไฟล์ JavaScript 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 คือออบเจ็กต์ JavaScript ร่วมที่มีแอตทริบิวต์ที่โค้ดที่ 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,
};

สร้างบิลด์แบบโมดูลด้วย Flag การสร้างเพิ่มเติม

หากต้องการป้อนข้อมูลไปยังแอป คุณสามารถใช้การรองรับระบบไฟล์ของ Emscripten ใน Module.FS ส่วนรวมการรองรับระบบไฟล์ของเอกสารจะระบุข้อมูลต่อไปนี้

Emscripten จะเป็นผู้ตัดสินใจว่าจะรวมการรองรับระบบไฟล์โดยอัตโนมัติหรือไม่ โปรแกรมจำนวนมากไม่จําเป็นต้องใช้ไฟล์ และการรองรับระบบไฟล์มีขนาดใหญ่พอสมควร Emscripten จึงหลีกเลี่ยงการรวมไว้เมื่อไม่จําเป็น ซึ่งหมายความว่าหากโค้ด C/C++ ของคุณไม่ได้เข้าถึงไฟล์ ระบบจะไม่รวมออบเจ็กต์ FS และ API ระบบไฟล์อื่นๆ ไว้ในเอาต์พุต ในทางกลับกัน หากโค้ด C/C++ ใช้ไฟล์ ระบบจะรวมการรองรับระบบไฟล์ไว้โดยอัตโนมัติ

ขออภัย mkbitmap เป็นหนึ่งในกรณีที่ Emscripten ไม่ได้รวมการรองรับระบบไฟล์โดยอัตโนมัติ คุณจึงต้องบอกให้ดำเนินการดังกล่าวอย่างชัดเจน ซึ่งหมายความว่าคุณต้องทําตามขั้นตอน emconfigure และ emmake ที่อธิบายไว้ก่อนหน้านี้ พร้อมกับตั้งค่า Flag อีก 2 รายการผ่านอาร์กิวเมนต์ CFLAGS แฟล็กต่อไปนี้อาจมีประโยชน์สำหรับโปรเจ็กต์อื่นๆ ด้วย

  • ตั้งค่า -sFILESYSTEM=1 เพื่อให้ระบบรองรับระบบไฟล์
  • ตั้งค่า -sEXPORTED_RUNTIME_METHODS=FS,callMain เพื่อให้ระบบส่งออก Module.FS และ Module.callMain
  • ตั้งค่า -sMODULARIZE=1 และ -sEXPORT_ES6 เพื่อสร้างโมดูล ES6 สมัยใหม่
  • ตั้งค่า -sINVOKE_RUN=0 เพื่อป้องกันการเรียกใช้ครั้งแรกที่ทําให้ข้อความแจ้งปรากฏขึ้น

นอกจากนี้ ในกรณีนี้ คุณจะต้องตั้งค่า Flag --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 ที่มีหน้าจอสีขาวแสดงออบเจ็กต์โมดูลที่บันทึกไว้ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

เรียกใช้ฟังก์ชันหลักด้วยตนเอง

ขั้นตอนถัดไปคือการเรียกใช้ฟังก์ชัน 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) โดยค่าเริ่มต้นคือคอนโซล แต่คุณสามารถเปลี่ยนเส้นทางไปยังอย่างอื่นได้ เช่น ฟังก์ชันที่จัดเก็บเอาต์พุตไปยังตัวแปร ซึ่งหมายความว่าคุณสามารถเพิ่มเอาต์พุตลงใน HTML ได้โดยการตั้งค่าพร็อพเพอร์ตี้ Module.print

// 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();

Finder ของ macOS พร้อมตัวอย่างไฟล์ .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 และทำให้ใช้งานได้ในเบราว์เซอร์เรียบร้อยแล้ว บางครั้งก็เจอทางตันและคุณต้องคอมไพล์เครื่องมือมากกว่า 1 ครั้งกว่าที่จะใช้งานได้ แต่อย่างที่ได้เขียนไว้ข้างต้น ปัญหาเหล่านี้เป็นส่วนหนึ่งของประสบการณ์ นอกจากนี้ อย่าลืมใช้แท็ก webassembly ของ StackOverflow หากพบปัญหา ขอให้สนุกกับการคอมไพล์

ขอขอบคุณ

บทความนี้ได้รับการตรวจสอบโดย Sam Clegg และ Rachel Andrew