SVGcode: PWA เพื่อแปลงรูปภาพแรสเตอร์เป็นกราฟิกเวกเตอร์ SVG

SVGcode เป็น Progressive Web App ที่ให้คุณแปลงรูปภาพแรสเตอร์ เช่น JPG, PNG, GIF, WebP, AVIF ฯลฯ เป็นกราฟิกเวกเตอร์ในรูปแบบ SVG โดยใช้ File System Access API, Async Clipboard API, File Handling API และการปรับแต่ง Window Controls Overlay

(หากต้องการดูมากกว่าการอ่าน บทความนี้มีให้บริการเป็นวิดีโอด้วย)

จากแรสเตอร์เป็นเวกเตอร์

คุณเคยปรับขนาดรูปภาพแล้วรูปภาพแตกเป็นพิกเซลและไม่น่าพอใจไหม หากใช่ คุณน่าจะต้องใช้รูปแบบรูปภาพแรสเตอร์ เช่น WebP, PNG หรือ JPG

การปรับขนาดรูปภาพแรสเตอร์ทำให้รูปภาพดูเป็นแบบพิกเซล

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

การปรับขนาดรูปภาพเวกเตอร์โดยไม่สูญเสียคุณภาพของภาพ

ขอแนะนำ SVGcode

เราได้สร้าง PWA ชื่อ SVGcode ที่ช่วยแปลงรูปภาพแรสเตอร์เป็นเวกเตอร์ได้ เครดิตที่ต้องชำระ: ผมไม่ได้คิดค้นสิ่งนี้ขึ้นมา การใช้ SVGcode จะช่วยให้เราสามารถดำเนินการกับเครื่องมือบรรทัดคำสั่งที่เรียกว่า Potrace โดย Peter Selinger ซึ่งได้แปลงเป็น Web Assembly เพื่อให้ใช้ได้ในเว็บแอป

ภาพหน้าจอของแอปพลิเคชัน SVGcode
แอป SVGcode

การใช้โค้ด SVG

ก่อนอื่น ผมจะสาธิตวิธีใช้แอปโดยเริ่มจากภาพทีเซอร์ งาน Chrome Dev Summit ที่ฉันดาวน์โหลดจากช่อง Twitter ของ ChromiumDev นี่คือรูปภาพแรสเตอร์ PNG ที่ผมลากไปวางในแอป SVGcode เมื่อวางไฟล์ แอปจะติดตามสีรูปภาพตามสี จนกว่าอินพุตที่เป็นเวกเตอร์จะปรากฏขึ้น ผมซูมเข้าไปได้แล้ว คุณจะเห็นว่าขอบยังคงคมชัด แต่เมื่อซูมเข้าไปที่โลโก้ Chrome คุณจะเห็นว่าการติดตามนั้นไม่สมบูรณ์แบบ และโดยเฉพาะอย่างยิ่งขอบโลโก้บนรอยขีดข่วนเล็กน้อย ผมสามารถปรับปรุงผลลัพธ์โดย ลดการกระตุกจากรอยขีดข่วนโดยการเก็บแต้มไว้ไม่เกิน 5 พิกเซล

การแปลงรูปภาพที่วางไปแล้วเป็น SVG

การทำโปสเตอร์ในโค้ด SVG

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

การโปสเตอร์รูปภาพเพื่อลดจำนวนสี

API ที่ใช้ใน SVGcode

ตอนนี้คุณก็ได้เห็นแล้วว่าแอปมีความสามารถอย่างไร ผมจะขอแนะนำ API ที่จะช่วยสร้างเวทมนตร์ให้คุณดู

Progressive Web App

SVGcode เป็น Progressive Web App ที่ติดตั้งได้ ดังนั้นจึงเปิดใช้แบบออฟไลน์โดยสมบูรณ์ได้ แอปจะอิงตามเทมเพลต Vanilla JS สำหรับ Vite.js และใช้ PWA ปลั๊กอิน Vite ยอดนิยม ซึ่งจะสร้าง Service Worker ที่ใช้ Workbox.js ไว้ภายใน Workbox คือชุดไลบรารีที่สามารถขับเคลื่อน Service Worker ที่พร้อมสำหรับการใช้งานจริงสำหรับ Progressive Web App รูปแบบนี้อาจไม่สามารถใช้ได้กับบางแอป แต่สำหรับ Use Case ของ SVGcode

การวางซ้อนการควบคุมหน้าต่าง

เพื่อเพิ่มพื้นที่หน้าจอที่มีอยู่ให้ได้สูงสุด SVGcode ใช้การปรับแต่งการวางซ้อนการควบคุมหน้าต่างโดยการย้ายเมนูหลักของแอปไปไว้บริเวณแถบชื่อ คุณจะเห็นว่ามีการเปิดใช้งานตัวเลือกนี้เมื่อขั้นตอนการติดตั้งสิ้นสุดลง

การติดตั้งโค้ด SVG และเปิดใช้งานการปรับแต่งการวางซ้อนการควบคุมหน้าต่าง

File System Access API

หากต้องการเปิดไฟล์ภาพอินพุตและบันทึก SVG ที่ได้มา ฉันจะใช้ File System Access API วิธีนี้ช่วยให้ผมเก็บการอ้างอิงไฟล์ที่เปิดก่อนหน้านี้ และทำต่อจากจุดที่ทำค้างไว้ได้ แม้ว่าจะโหลดแอปซ้ำแล้วก็ตาม เมื่อใดก็ตามที่ระบบบันทึกภาพไว้ ระบบจะเพิ่มประสิทธิภาพผ่านไลบรารี svgo ซึ่งอาจใช้เวลาสักครู่ทั้งนี้ขึ้นอยู่กับความซับซ้อนของ SVG การแสดงกล่องโต้ตอบการบันทึกไฟล์ต้องใช้ท่าทางสัมผัสของผู้ใช้ คุณจึงต้องหาแฮนเดิลไฟล์ก่อนที่การเพิ่มประสิทธิภาพ SVG จะเกิดขึ้น เพื่อไม่ให้ท่าทางสัมผัสของผู้ใช้ใช้งานไม่ได้เมื่อ SVG ที่เพิ่มประสิทธิภาพพร้อมใช้งาน

try {
  let svg = svgOutput.innerHTML;
  let handle = null;
  // To not consume the user gesture obtain the handle before preparing the
  // blob, which may take longer.
  if (supported) {
    handle = await showSaveFilePicker({
      types: [{description: 'SVG file', accept: {'image/svg+xml': ['.svg']}}],
    });
  }
  showToast(i18n.t('optimizingSVG'), Infinity);
  svg = await optimizeSVG(svg);
  showToast(i18n.t('savedSVG'));
  const blob = new Blob([svg], {type: 'image/svg+xml'});
  await fileSave(blob, {description: 'SVG file'}, handle);
} catch (err) {
  console.error(err.name, err.message);
  showToast(err.message);
}

ลากและวาง

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

document.addEventListener('drop', async (event) => {
  event.preventDefault();
  dropContainer.classList.remove('dropenter');
  const item = event.dataTransfer.items[0];
  if (item.kind === 'file') {
    inputImage.addEventListener(
      'load',
      () => {
        URL.revokeObjectURL(blobURL);
      },
      {once: true},
    );
    const handle = await item.getAsFileSystemHandle();
    if (handle.kind !== 'file') {
      return;
    }
    const file = await handle.getFile();
    const blobURL = URL.createObjectURL(file);
    inputImage.src = blobURL;
    await set(FILE_HANDLE, handle);
  }
});

ดูรายละเอียดเพิ่มเติมได้ในบทความ File System Access API และหากสนใจ ให้ศึกษาซอร์สโค้ด SVG ใน src/js/filesystem.js

Async Clipboard API

SVGcode ยังผสานรวมกับคลิปบอร์ดของระบบปฏิบัติการผ่าน Async Clipboard API ได้เป็นอย่างดี คุณสามารถวางรูปภาพจากโปรแกรมสำรวจไฟล์ของระบบปฏิบัติการลงในแอปได้โดยคลิกปุ่มวางรูปภาพ หรือโดยกดแป้นคำสั่งหรือ Control Plus v บนแป้นพิมพ์

การวางรูปภาพจากเครื่องมือสำรวจไฟล์ลงในโค้ด SVG

เมื่อเร็วๆ นี้ Async Clipboard API เพิ่งสามารถจัดการกับรูปภาพ SVG ด้วย คุณจึงคัดลอกรูปภาพ SVG แล้ววางลงในแอปพลิเคชันอื่นเพื่อประมวลผลเพิ่มเติมได้

กำลังคัดลอกรูปภาพจาก SVGcode ไปยัง SVGOMG
copyButton.addEventListener('click', async () => {
  let svg = svgOutput.innerHTML;
  showToast(i18n.t('optimizingSVG'), Infinity);
  svg = await optimizeSVG(svg);
  const textBlob = new Blob([svg], {type: 'text/plain'});
  const svgBlob = new Blob([svg], {type: 'image/svg+xml'});
  navigator.clipboard.write([
    new ClipboardItem({
      [svgBlob.type]: svgBlob,
      [textBlob.type]: textBlob,
    }),
  ]);
  showToast(i18n.t('copiedSVG'));
});

หากต้องการดูข้อมูลเพิ่มเติม โปรดอ่านบทความคลิปบอร์ดแบบไม่พร้อมกัน หรือดูไฟล์ src/js/clipboard.js

การจัดการไฟล์

ฟีเจอร์หนึ่งของ SVGcode ที่ผมชอบคือ สามารถทำงานร่วมกับระบบปฏิบัติการได้อย่างดีเยี่ยม โดยเป็น PWA ที่ติดตั้งมาเป็นเครื่องจัดการไฟล์ หรือแม้กระทั่งเป็นตัวแฮนเดิลไฟล์เริ่มต้นสำหรับไฟล์ภาพ ซึ่งหมายความว่าเมื่อฉันอยู่ใน Finder ในเครื่อง macOS ก็จะคลิกขวาที่รูปภาพและเปิดด้วย SVGcode ได้ ฟีเจอร์นี้เรียกว่า File Handling และจะทำงานตามพร็อพเพอร์ตี้ file_handlers ในไฟล์ Manifest ของเว็บแอปและคิวการเปิดตัว ซึ่งทำให้แอปใช้ไฟล์ที่ส่งผ่านได้

การเปิดไฟล์จากเดสก์ท็อปด้วยแอป SVGcode ที่ติดตั้งไว้
window.launchQueue.setConsumer(async (launchParams) => {
  if (!launchParams.files.length) {
    return;
  }
  for (const handle of launchParams.files) {
    const file = await handle.getFile();
    if (file.type.startsWith('image/')) {
      const blobURL = URL.createObjectURL(file);
      inputImage.addEventListener(
        'load',
        () => {
          URL.revokeObjectURL(blobURL);
        },
        {once: true},
      );
      inputImage.src = blobURL;
      await set(FILE_HANDLE, handle);
      return;
    }
  }
});

ดูข้อมูลเพิ่มเติมได้ที่อนุญาตให้เว็บแอปพลิเคชันที่ติดตั้งไว้เป็นตัวแฮนเดิลไฟล์ และดูซอร์สโค้ดใน src/js/filehandling.js

การแชร์เว็บ (ไฟล์)

อีกตัวอย่างหนึ่งของการผสานกับระบบปฏิบัติการคือฟีเจอร์การแชร์ของแอป สมมติว่าผมต้องการแก้ไข SVG ที่สร้างด้วย SVGcode วิธีหนึ่งในการจัดการกับปัญหานี้คือการบันทึกไฟล์ เปิดแอปแก้ไข SVG แล้วเปิดไฟล์ SVG จากที่นั่น ขั้นตอนที่ราบรื่นขึ้นคือการใช้ Web Share API ซึ่งช่วยให้แชร์ไฟล์ได้โดยตรง ดังนั้นหากแอปแก้ไข SVG เป็นเป้าหมายการแชร์ แอปจะได้รับไฟล์โดยตรงโดยไม่มีการเบี่ยงเบน

shareSVGButton.addEventListener('click', async () => {
  let svg = svgOutput.innerHTML;
  svg = await optimizeSVG(svg);
  const suggestedFileName =
    getSuggestedFileName(await get(FILE_HANDLE)) || 'Untitled.svg';
  const file = new File([svg], suggestedFileName, { type: 'image/svg+xml' });
  const data = {
    files: [file],
  };
  if (navigator.canShare(data)) {
    try {
      await navigator.share(data);
    } catch (err) {
      if (err.name !== 'AbortError') {
        console.error(err.name, err.message);
      }
    }
  }
});
การแชร์รูปภาพ SVG ไปยัง Gmail

เป้าหมายการแชร์เว็บ (ไฟล์)

นอกจากนี้ SVGcode ยังสามารถทำหน้าที่เป็นเป้าหมายในการแชร์และรับไฟล์จากแอปอื่นๆ ได้อีกด้วย หากต้องการให้ทำงานนี้ แอปต้องแจ้งให้ระบบปฏิบัติการทราบผ่าน Web Share Target API ว่าจะยอมรับข้อมูลประเภทใดบ้าง ซึ่งจะเกิดขึ้นผ่านช่องเฉพาะในไฟล์ Manifest ของเว็บแอป

{
  "share_target": {
    "action": "https://svgco.de/share-target/",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "image",
          "accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
        }
      ]
    }
  }
}

เส้นทาง action ไม่มีอยู่จริง แต่ได้รับการจัดการในเครื่องจัดการ fetch ของผู้ปฏิบัติงานบริการ ซึ่งจะส่งผ่านไฟล์ที่ได้รับเพื่อประมวลผลจริงในแอป

self.addEventListener('fetch', (fetchEvent) => {
  if (
    fetchEvent.request.url.endsWith('/share-target/') &&
    fetchEvent.request.method === 'POST'
  ) {
    return fetchEvent.respondWith(
      (async () => {
        const formData = await fetchEvent.request.formData();
        const image = formData.get('image');
        const keys = await caches.keys();
        const mediaCache = await caches.open(
          keys.filter((key) => key.startsWith('media'))[0],
        );
        await mediaCache.put('shared-image', new Response(image));
        return Response.redirect('./?share-target', 303);
      })(),
    );
  }
});
การแชร์ภาพหน้าจอไปยังโค้ด SVG

บทสรุป

นี่เป็นการทัวร์ชมสั้นๆ เกี่ยวกับฟีเจอร์แอปขั้นสูงใน SVGcode เราหวังว่าแอปนี้จะกลายเป็นเครื่องมือสำคัญสำหรับความต้องการด้านการประมวลผลรูปภาพควบคู่ไปกับแอปที่ยอดเยี่ยมอื่นๆ อย่าง Squoosh หรือ SVGOMG

SVGcode มีให้ใช้งานที่ svgco.de ดูสิ่งที่ฉันทำ คุณตรวจสอบซอร์สโค้ดของ GitHub ได้ โปรดทราบว่าเนื่องจาก Potrace ได้รับใบอนุญาต GPL เช่นเดียวกับ SVG และเช่นนั้น ขอให้สนุกกับการทำเวกเตอร์! เราหวังว่าโค้ด SVG จะเป็นประโยชน์และ ฟีเจอร์บางอย่างของ SVG จะเป็นแรงบันดาลใจในการสร้างแอปต่อไป

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

บทความนี้ได้รับการตรวจสอบโดย Joe Medley