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
首先,我想向您展示如何使用應用程式。我會從 Chrome 開發人員大會的預告圖片開始,這些圖片是我從 ChromiumDev Twitter 頻道下載的。這是一張 PNG 光柵圖片,我將它拖曳到 SVGcode 應用程式上。當我放置檔案時,應用程式會逐一追蹤圖片的顏色,直到輸入內容的向量版本顯示為止。我現在可以放大圖片,如您所見,邊緣仍保持清晰。不過,放大檢視 Chrome 標誌,您會發現追蹤結果並不完美,尤其是標誌的輪廓看起來有點斑駁。我可以透過抑制高達 5 個像素的斑點,讓追蹤結果更精確。
在 SVG 程式碼中使用海報化效果
向量化 (尤其是相片) 的重要步驟,就是將輸入圖片做成海報,以減少顏色數量。SVGcode 可讓我針對每個色彩管道執行這項操作,並在變更時查看產生的 SVG。當我滿意結果時,可以將 SVG 儲存到硬碟,並在任何地方使用。
SVGcode 中使用的 API
您已經瞭解應用程式的功能,接下來我將介紹一些 API,協助您發揮應用程式的魔力。
漸進式網頁應用程式
SVGcode 是可安裝的漸進式網頁應用程式,因此可完全離線運作。這個應用程式以 Vanilla JS 範本為基礎,適用於 Vite.js,並使用熱門的 Vite 外掛程式 PWA,後者會建立使用 Workbox.js 的服務工作站。Workbox 是一組程式庫,可為漸進式網頁應用程式提供可正式發布的服務工作管理員。這個模式不一定適用於所有應用程式,但對於 SVGcode 的用途來說,它非常實用。
視窗控制項重疊
為盡量擴大可用的螢幕空間,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 鍵,將作業系統檔案總管中的圖片貼到應用程式中。
Async 剪貼簿 API 最近也能處理可擴充向量圖形圖片,因此您也可以複製可擴充向量圖形圖片,並將其貼到其他應用程式中進行後續處理。
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 屬性運作,讓應用程式使用傳入的檔案。
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);
}
}
}
});
網路分享目標 (檔案)
反之,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 中部分進階應用程式功能的快速導覽。我希望這個應用程式能成為您處理圖片時必備的工具,與 Squoosh 或 SVGOMG 等其他出色的應用程式並駕齊驅。
您可以在 svgco.de 下載 SVGcode。你看我做了什麼?您可以在 GitHub 上查看原始碼。請注意,由於 Potrace 是 GPL 授權,因此 SVGcode 也是。祝您順利轉向向量化!希望 SVGcode 對您有所助益,且其中部分功能能為您下一個應用程式帶來靈感。
特別銘謝
本文由 Joe Medley 審查。