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'));
});

หากต้องการดูข้อมูลเพิ่มเติม โปรดอ่านบทความ Async Clipboard หรือดูไฟล์ 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