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 審查。