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

การใช้ SVGcode

ก่อนอื่น เราขอแสดงวิธีใช้แอป โดยเริ่มจากภาพตัวอย่างสำหรับ 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 ใช้การปรับแต่ง Window Controls Overlay เพื่อเพิ่มพื้นที่บนหน้าจอให้มากที่สุดโดยย้ายเมนูหลักขึ้นไปไว้ในพื้นที่แถบชื่อ คุณจะเห็นการเปิดใช้งานนี้เมื่อสิ้นสุดขั้นตอนการติดตั้ง

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

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 และหากสนใจ ให้ศึกษาซอร์สโค้ด SVGcode ใน src/js/filesystem.js

Async Clipboard API

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

การวางรูปภาพจาก File Explorer ลงในโค้ด 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_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 handler ของ Service Worker เท่านั้น ซึ่งจะส่งต่อไฟล์ที่ได้รับเพื่อประมวลผลจริงในแอป

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);
      })(),
    );
  }
});
การแชร์ภาพหน้าจอไปยัง SVGcode

บทสรุป

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

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

ขอขอบคุณ

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