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

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

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

เกี่ยวกับ mkbitmap

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

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

$ 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 คุณยืนยันได้ว่าการดำเนินการทำงานอย่างถูกต้องโดยเรียกใช้ 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 แล้วเปิดในเบราว์เซอร์ คุณควรเห็นข้อความแจ้งที่ขอให้คุณป้อนข้อมูล ซึ่งเป็นไปตามที่คาดไว้ เนื่องจากตามหน้าคู่มือของเครื่องมือ "[i]f no filename arguments are given, then mkbitmap acts as a filter, reading from standard input" ซึ่งสำหรับ 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 ที่มีหน้าจอสีขาว ซึ่งแสดงออบเจ็กต์โมดูลที่บันทึกไว้ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ

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

ขั้นตอนถัดไปคือการเรียกใช้ฟังก์ชัน mkbitmap's main() ด้วยตนเองโดยการเรียกใช้ 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 สำหรับ Graymap หากมีการระบุอาร์กิวเมนต์ 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 ครั้งจนกว่าจะใช้งานได้ แต่ดังที่ฉันเขียนไว้ข้างต้น นั่นเป็นส่วนหนึ่งของประสบการณ์การใช้งาน นอกจากนี้ อย่าลืมแท็ก StackOverflow's webassembly หากคุณติดขัด ขอให้สนุกกับการรวบรวม

การรับทราบ

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