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

SVGcode 是漸進式網頁應用程式,可將光柵圖片 (例如 JPG、PNG、GIF、WebP、AVIF 等) 轉換為 SVG 格式的向量圖形。它使用 File System Access API、Async Clipboard API、File Handling API 以及視窗控制項疊加層自訂功能。

(如果您偏好看重閱讀,也可以以影片的形式閱讀這篇文章。)

從光柵轉為向量

你是否曾經縮放過圖片,導致圖片經過像素化且不符合要求?若是如此,您可能使用了光柵圖片格式,例如 WebP、PNG 或 JPG。

放大光柵圖片,就能產生像素化的影像。

相對地,向量圖形則是由座標系統中點定義的圖片。這些點會以線條和曲線連接,形成多邊形和其他形狀。向量圖形優於光柵圖形,用途為放大或縮小至任何解析度,且未經像素化。

放大向量圖片,同時維持畫質。

SVG 程式碼簡介

我已建構一個名為 SVGcode 的 PWA,可協助您將光柵圖片轉換為向量。功勞歸功於 - 我沒有發明這一點。使用 SVG 程式碼時,我只要站在 Peter Selinger 名為 Potrace 的指令列工具的肩上,而我已轉換為 Web Assembly,好在網頁應用程式中使用。

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

使用 SVG 程式碼

首先,我會示範如何使用應用程式。首先,我會使用從 Chromium 開發人員 Twitter 頻道下載的 Chrome 開發人員高峰會前導圖片。這是一張 PNG 光柵圖片,將其拖曳到 SVGcode 應用程式。捨棄檔案時,應用程式會按顏色追蹤圖片顏色,直到輸入的向量化版本出現為止。我現在可以放大圖片 而如你所見,邊緣保持清晰但放大 Chrome 標誌後,即可看到追蹤記錄並未完美,尤其標誌外框看起來有點模糊。我可以藉由抑制最多 5 個像素的光譜,抑制追蹤記錄,以改善追蹤結果。

將放置的圖片轉換成 SVG。

SVG 程式碼的海報

向量化的重要步驟 (特別是用於攝影的圖片) 是將輸入圖片分開,以減少色彩數量。可擴充向量圖形程式碼可讓我依色彩管道執行這項作業,並在進行變更時查看最終產生的 SVG。對結果感到滿意後,就能將 SVG 檔案儲存至硬碟,隨時隨地使用。

將圖片張貼來減少顏色數量。

SVG 程式碼中使用的 API

現在您已瞭解應用程式的功能,接下來將介紹一些有助於實現魔法的 API。

漸進式網頁應用程式

SVGcode 是可安裝的漸進式網頁應用程式,因此可完全離線啟用。應用程式是以 Vite.jsVanilla JS 範本為基礎,並使用熱門的 Vite 外掛程式 PWA,它會在背景建立使用 Workbox.js 的 Service Worker。Workbox 是一套程式庫,可為實作體驗的漸進式網頁應用程式服務工作站提供支援。這個模式不一定適用於所有應用程式,但對 SVGcode 的用途是最佳選擇。

視窗控制項重疊

為了盡可能增加可用的螢幕空間,SVG 程式碼使用了「視窗控制項疊加層」自訂功能,將主選單移至標題列區域。安裝流程結束時,您就會看到啟用狀態。

安裝 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 的文章。如有需要,請在 src/js/filesystem.js 中研究 SVG 程式碼原始碼。

Async Clipboard API

您也可以透過 Async 剪貼簿 API 將 SVG 程式碼與作業系統的剪貼簿完全整合。按一下「貼上圖片」按鈕,或是按下鍵盤上的 Command 鍵或 Control + v 鍵,即可將圖片從作業系統的檔案探索工具貼入應用程式。

將檔案探索工具中的圖片貼到 SVG 程式碼。

Async Clipboard API 最近也能處理 SVG 圖片,因此您也可以複製 SVG 圖片,並貼到其他應用程式中,以便進行後續處理。

將圖片從 SVG 程式碼複製到 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 中時,可以在圖片上按一下滑鼠右鍵,然後使用 SVG 程式碼開啟圖片。這項功能稱為「檔案處理」,根據網頁應用程式資訊清單中的 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 中查看原始碼。

網頁分享 (檔案)

另一個融入作業系統的例子,就是應用程式的分享功能。假設我想編輯使用 SVG 程式碼建立的 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);
      }
    }
  }
});
將可擴充向量圖形圖片分享到 Gmail。

網頁分享目標 (檔案)

可擴充的 SVG 程式碼也能做為共用目標,並接收其他應用程式的檔案。為此,應用程式需要透過 Web Share Target API,通知作業系統可接受哪些類型的資料。這項操作會透過網頁應用程式資訊清單中的專用欄位進行。

{
  "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 路徑實際上不存在,但只會在 Service Worker 的 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 程式碼。

結論

本簡短導覽介紹 SVG 程式碼的部分進階應用程式功能。希望這個應用程式能夠成為 SquooshSVGOMG 等出色應用程式處理圖片的重要工具。

您可以在 svgco.de 取得 SVG 程式碼。查看我在那裡做什麼?您可以在 GitHub 上查看原始碼。請注意,由於 Potrace 已取得 GPL 授權,因此是 SVGcode。完成這個步驟,祝你向量作業愉快!希望 SVG 程式碼能派上用場 其中一些功能可以激發您下一個應用程式靈感

特別銘謝

本文是由 Joe Medley 審查過的。