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

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

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

เกี่ยวกับ mkbitmap

โปรแกรม mkbitmap C จะอ่านรูปภาพและนำการดำเนินการต่อไปนี้อย่างน้อย 1 อย่างไปใช้กับรูปภาพนั้น โดยเรียงตามลำดับดังนี้ การกลับ การกรองไฮพาส การปรับขนาด และการกำหนดเกณฑ์ คุณควบคุมและเปิดหรือปิดการดำเนินการแต่ละรายการแยกกันได้ การใช้งานหลักของ 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 ซึ่งเป็นเนื้อหาสำคัญของบทความนี้ คุณสามารถยืนยันว่า URL ทำงานได้อย่างถูกต้องโดยเรียกใช้ 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 สคริปต์ที่กำหนดค่า Makefile เพื่อใช้ 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 เรียบร้อยแล้ว ขั้นตอนถัดไปคือทำให้ Chrome ใช้งานได้ในเบราว์เซอร์

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 และเปิดในเบราว์เซอร์ คุณควรเห็นข้อความแจ้งที่ขอให้คุณป้อนข้อมูล กรณีนี้เป็นไปตามที่คาดไว้ เนื่องจากอ้างอิงจากหน้าหลักของเครื่องมือ "[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,
};

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

หากต้องการให้อินพุตแก่แอป คุณสามารถใช้การรองรับระบบไฟล์ของ 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 ของ StackOverflow หากพบปัญหา ขอให้สนุกกับการคอมไพล์

ข้อความแสดงการยอมรับ

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