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

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

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

เกี่ยวกับ mkbitmap

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

คุณใช้ 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 เพื่อติดตั้งโปรแกรมและไฟล์ข้อมูลใดๆ และ เอกสารประกอบ เมื่อติดตั้งลงในคำนำหน้าที่เป็นของรูท ขอแนะนำให้กำหนดค่าและสร้างแพ็กเกจตามปกติ ผู้ใช้ และเฉพาะเฟส 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 รายการที่กำหนดค่าไฟล์ให้คุณใช้ 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 แล้วสร้างไฟล์ต้นแบบ HTML index.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 ที่อธิบายไว้ก่อนหน้านี้ และตั้งค่าแฟล็กเพิ่มเติม 2 รายการผ่านอาร์กิวเมนต์ 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 บันทึกไว้ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บและข้อความแจ้งจะหายไปเนื่องจากไม่มีการเรียกใช้ฟังก์ชัน main() ของ mkbitmap ในตอนเริ่มต้นแล้ว

แอป mkbitmap ที่มีหน้าจอสีขาวแสดงออบเจ็กต์โมดูลที่บันทึกไว้ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

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

ขั้นตอนถัดไปคือการเรียกฟังก์ชัน main() ของ mkbitmap ด้วยตนเองโดยการเรียกใช้ Module.callMain() ฟังก์ชัน callMain() จะใช้อาร์เรย์ของอาร์กิวเมนต์ ซึ่งตรงกับสิ่งที่คุณจะส่งผ่านบรรทัดคำสั่งไปทีละรายการ หากในบรรทัดคำสั่งที่คุณจะเรียกใช้ mkbitmap -v คุณจะเรียกใช้ Module.callMain(['-v']) ในเบราว์เซอร์ โดยจะบันทึกหมายเลขเวอร์ชัน mkbitmap ไปยังคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

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

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

run();

แอป mkbitmap ที่มีหน้าจอสีขาวแสดงหมายเลขเวอร์ชัน mkbitmap ที่บันทึกไว้ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

เปลี่ยนเส้นทางเอาต์พุตมาตรฐาน

เอาต์พุตมาตรฐาน (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();

เครื่องมือค้นหา 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 ของสแต็คโอเวอร์โฟลว์ หากพบปัญหา ขอให้สนุกกับการคอมไพล์

กิตติกรรมประกาศ

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