SVGcode 是一款渐进式 Web 应用,可让您将 JPG、PNG、GIF、WebP、AVIF 等光栅图片转换为 SVG 格式的矢量图形。它使用 File System Access API、Async Clipboard API、File Handling API 以及窗口控件叠加层自定义。
从光栅图像到矢量图像
您是否曾遇到过这样的情况:在缩放图像后,图像出现像素化且令人失望的情况?如果是这样,那么您可能已处理过 WebP、PNG 或 JPG 等光栅图片格式。
相比之下,矢量图形则是由坐标系中的点定义的图像。这些点通过线条和曲线连接,形成多边形和其他形状。与光栅图形相比,矢量图形的优势在于,可以放大或缩小到任何分辨率,而不会出现像素化问题。
SVGcode 简介
我构建了一款名为 SVGcode 的 PWA,它可以帮助您将光栅图片转换为矢量。应注明信用的出处:不是我发明的。有了 SVGcode,我就站在由 Peter Selinger 开发的一个名为 Potrace 的命令行工具的旁边,该工具已转换为 Web Assembly,因此可以在 Web 应用中使用。
使用 SVGcode
首先,我想向大家展示该应用的使用方法。首先来看 我从 ChromiumDev Twitter 频道下载的 Chrome 开发者峰会的宣传片图片这是一张 PNG 光栅图片,然后我将其拖动到 SVGcode 应用上。当我拖放文件时,应用会按颜色追踪图片的颜色,直到显示输入的矢量化版本。现在放大图片 可以看到,边缘保持清晰但是放大 Chrome 徽标,您会发现轨迹呈现不完美,特别是徽标的轮廓看起来有点起斑。我可以通过抑制多达 5 个像素的斑点来消除轨迹,从而改善结果。
SVGcode 中的海报化
对于矢量化(尤其是摄影图片),一个重要的步骤是对输入图片进行后化处理,以减少颜色数量。SVGcode 允许我按颜色通道执行此操作,并在我做出更改时查看生成的 SVG。如果对结果感到满意,我可以将 SVG 保存到硬盘,并在任何地方使用。
SVGcode 中使用的 API
现在,您已经了解了该应用的功能,接下来我将向您展示一些有助于实现这一神奇功能的 API。
渐进式 Web 应用
SVGcode 是一个可安装的渐进式 Web 应用,因此完全离线。该应用基于 Vite.js 的 Vanilla JS 模板,并使用广受欢迎的 Vite 插件 PWA,后者在后台创建一个使用 Workbox.js 的 Service Worker。Workbox 是一组库,可为渐进式 Web 应用提供可正式投入使用的 Service Worker。此模式未必适用于所有应用,但对于 SVGcode 的用例而言非常棒。
窗口控件叠加层
为了最大限度地利用可用的屏幕空间,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 的文章;如果您有兴趣,不妨研究一下 src/js/filesystem.js
中的 SVGcode 源代码。
Async Clipboard API
SVGcode 还通过 Async Clipboard API 与操作系统的剪贴板完全集成。您可以将图片从操作系统的文件资源管理器粘贴到应用中,方法是点击“粘贴图片”按钮,或者按键盘上的 Command 键或 Ctrl + v 键。
Async Clipboard API 最近也获得了处理 SVG 图片的功能,因此您也可以复制 SVG 图片并将其粘贴到其他应用,以供进一步处理。
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 计算机上的“访达”中,右键点击图片,然后使用 SVG 代码打开它。此功能称为“文件处理”,基于 Web 应用清单中的 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;
}
}
});
如需了解详情,请参阅让已安装的 Web 应用成为文件处理程序,并在 src/js/filehandling.js
中查看源代码。
网络共享(文件)
与操作系统混合的另一个示例是应用的分享功能。假设我想对使用 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 应用清单中的专用字段来实现。
{
"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);
})(),
);
}
});
总结
好的,这里简要介绍了 SVGcode 中的一些高级应用功能。我希望此应用可以与其他出色的应用(例如 Squoosh 或 SVGOMG)一起成为满足您图片处理需求的必备工具。
SVGcode 可在 svgco.de 上找到。看看我做了什么?您可以在 GitHub 上查看其源代码。请注意,由于 Potrace 已获得 GPL 许可,因此 SVGcode 也已获得 GPL 许可。好了,祝您矢量化工作一切顺利!希望 SVGcode 能对您有所帮助,其中一些功能可以为您的下一款应用提供灵感。
致谢
本文由 Joe Medley 审核。