SVGcode:將光柵圖片轉換為 SVG 向量圖形的 PWA

SVGcode 是漸進式網頁應用程式,可將光柵圖片 (例如 JPG、PNG、GIF、WebP、AVIF 等) 轉換為 SVG 格式的向量圖形。這個應用程式會使用 File System Access API、Async Clipboard API、File Handling API 和 Window Controls Overlay 自訂功能。

(如果您偏好觀看影片,這篇文章也有影片版本。)

從光柵轉換為向量

你是否曾經縮放圖片,但結果出現象素化現象,讓你不滿意?如果是這樣,您可能處理的是光柵圖形格式,例如 WebP、PNG 或 JPG。

放大點陣圖會使其看起來像是像素化。

相較之下,向量圖形是指由座標系統中的點定義的圖片。這些點會透過線條和曲線連接,形成多邊形和其他形狀。向量圖形相較於點陣圖,優點在於可縮放至任何解析度,且不會產生像素化現象。

放大向量圖片,且不會損失品質。

隆重推出 SVGcode

我已建構名為 SVGcode 的 PWA,可協助您將點陣圖轉換為向量圖。把功勞歸給應得的項目:這不是我發明的概念。在 SVGcode 中,我只是站在 Peter Selinger 所開發的 Potrace 指令列工具肩上,而我已將該工具轉換為 Web 組合,因此可用於 Web 應用程式。

SVGcode 應用程式螢幕截圖。
SVGcode 應用程式。

使用 SVGcode

首先,我想向您展示如何使用應用程式。我會從 Chrome 開發人員大會的預告圖片開始,這些圖片是我從 ChromiumDev Twitter 頻道下載的。這是一張 PNG 光柵圖片,我將它拖曳到 SVGcode 應用程式上。當我放置檔案時,應用程式會逐一追蹤圖片的顏色,直到輸入內容的向量版本顯示為止。我現在可以放大圖片,您可以看到邊緣仍保持清晰。不過,放大檢視 Chrome 標誌,您會發現追蹤結果並不完美,尤其是標誌的輪廓看起來有點斑駁。我可以透過抑制多達五個像素的斑點,讓追蹤結果更精確。

將已放置的圖片轉換為可擴充向量圖形。

在 SVG 程式碼中使用海報化

向量化 (尤其是相片) 的重要步驟,就是將輸入圖片做成海報,以減少顏色數量。SVGcode 可讓我針對每個色彩管道執行這項操作,並在進行變更時查看產生的 SVG。當我滿意結果時,可以將 SVG 儲存到硬碟,並在任何地方使用。

將圖片轉為海報風格,以減少顏色數量。

SVGcode 中使用的 API

您已經瞭解應用程式的功能,接下來我將介紹一些 API,協助您發揮應用程式的魔力。

漸進式網頁應用程式

SVGcode 是可安裝的漸進式網頁應用程式,因此可完全離線使用。這個應用程式以 Vanilla JS 範本為基礎,適用於 Vite.js,並使用熱門的 Vite 外掛程式 PWA,可建立服務工作站,在幕後使用 Workbox.js。Workbox 是一組程式庫,可為漸進式網頁應用程式提供可正式發布的服務工作管理員。這個模式不一定適用於所有應用程式,但對於 SVGcode 的用途來說,它非常實用。

視窗控制項重疊

為盡量擴大可用的螢幕空間,SVGcode 使用 Window Controls Overlay 自訂功能,將主選單移至標題列區域。您可以在安裝流程結束時看到這項功能已啟用。

安裝 SVGcode 並啟用「Window Controls Overlay」自訂功能。

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 相關文章,並視需要研究 src/js/filesystem.js 中的 SVGcode 原始碼。

Async Clipboard API

SVGcode 也透過 Async Clipboard API 與作業系統的剪貼簿完全整合。您可以按一下「貼上圖片」按鈕,或按下鍵盤上的 Command 或 Control 鍵加 V 鍵,將作業系統檔案總管中的圖片貼到應用程式中。

將檔案總管中的圖片貼到 SVGcode 中。

Async 剪貼簿 API 最近也能處理可擴充向量圖形圖片,因此您也可以複製可擴充向量圖形圖片,並將其貼到其他應用程式中進行後續處理。

將圖片從 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,因此可以成為圖片檔案的檔案處理常式,甚至是預設檔案處理常式。也就是說,當我在 macOS 電腦的 Finder 中使用時,可以按一下圖片的滑鼠右鍵,然後使用 SVGcode 開啟圖片。這項功能稱為「檔案處理」,會根據網頁應用程式資訊清單和啟動佇列中的 file_handlers 屬性運作,讓應用程式使用傳入的檔案。

使用已安裝的 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 中的原始碼。

Web Share (檔案)

應用程式的分享功能也是與作業系統融合的另一個例子。假設我想對使用 SVGcode 建立的 SVG 進行編輯,解決方法之一是儲存檔案、啟動 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,讓作業系統知道可接受哪些類型的資料。這項操作會透過 Web App 資訊清單中的專屬欄位執行。

{
  "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);
      })(),
    );
  }
});
將螢幕截圖分享至 SVGcode。

結論

好了,這就是 SVGcode 中部分進階應用程式功能的快速導覽。我希望這個應用程式能成為您處理圖片時必備的工具,與 SquooshSVGOMG 等其他出色的應用程式並駕齊驅。

您可以在 svgco.de 下載 SVGcode。你看我做了什麼?您可以在 GitHub 上查看原始碼。請注意,由於 Potrace 是 GPL 授權,因此 SVGcode 也是。祝您順利轉向向量化!希望 SVGcode 對您有所助益,且其中部分功能能為您日後的應用程式帶來靈感。

特別銘謝

本文由 Joe Medley 審查。