SVGcode:用于将光栅图片转换为 SVG 矢量图形的 PWA

SVGcode 是一个渐进式 Web 应用,可让您将 JPG、PNG、GIF、WebP、AVIF 等的光栅图片转换为 SVG 格式的矢量图形。它使用 File System Access API、Async Clipboard API、File Handling API 和窗口控件叠加层自定义功能。

。 <ph type="x-smartling-placeholder">
</ph>
(如果您更喜欢观看而不是阅读,还可以通过视频阅读本文。)

从光栅图像到矢量图像

您是否曾遇到过因缩放图片而导致图片像素化且令人不满意的情况?如果 因此,您使用的可能是光栅图片格式,例如 WebP、PNG 或 JPG。

<ph type="x-smartling-placeholder">
</ph>
放大光栅图片会使其看起来像素化。

相比之下,矢量图形是由坐标系中的点定义的图像。这些 这些点通过线条和曲线连接形成多边形和其他形状。矢量图形具有 与光栅图形相比, 而且不会呈现像素化效果

<ph type="x-smartling-placeholder">
</ph>
在不损失质量的情况下放大矢量图像。

SVGcode 简介

我开发了一款名为 SVGcode 的 PWA,它可以帮助您将光栅图片转换为 进行训练。致谢名单:这项技术不是我发明的。有了 SVGcode,我就站在 Potrace 命令行工具 我收到的Peter Selinger 转换成了 Web Assembly,这样它就可以在 Web 应用。

<ph type="x-smartling-placeholder">
</ph> SVGcode 应用屏幕截图。 <ph type="x-smartling-placeholder">
</ph> SVGcode 应用。

使用 SVGcode

首先,我想向大家展示如何使用这款应用。首先是 Chrome 开发者峰会的宣传片图片 是我从 ChromiumDev Twitter 渠道下载的。这是一个 PNG 光栅图像 拖动到 SVGcode 应用上。当我拖放文件时,应用会按颜色跟踪图片颜色, 直到出现输入的矢量化版本。现在我可以放大图片 边缘会保持清晰但是放大 Chrome 徽标可以看出, 尤其要注意,徽标的轮廓看起来有点儿斑点。我可以通过以下方式改善搜索结果: 通过抑制最多(如 5 个像素)的斑点,对跟踪进行去散斑处理。

<ph type="x-smartling-placeholder">
</ph>
将放置的图片转换为 SVG。

SVGcode 中的海报化

向量化(尤其是对于摄影图片)的一个重要步骤是对输入进行海报化处理。 减少颜色数量。通过 SVGcode,我可以按颜色通道执行此操作, 生成 SVG 文件对结果感到满意后,就可以将 SVG 保存到硬盘上 并随时随地使用

<ph type="x-smartling-placeholder">
</ph>
对图片进行色调化处理以减少颜色数量。

SVGcode 中使用的 API

现在,您已经了解了该应用的功能,下面我来介绍一些 API, 就会产生奇迹

渐进式 Web 应用

SVGcode 是一个可安装的 Progressive Web App,因此完全离线启用。该应用基于 在 普通 JS 模板 (适用于 Vite.js),并使用热门 Vite 插件 PWA,用于创建一个 Service Worker, 在后台使用 Workbox.js。Workbox 是一个 支持 Progressive Web App 的生产就绪型 Service Worker 的库,这种模式 不一定适用于所有应用,但对于 SVGcode 用例,这一点非常实用。

窗口控件叠加层

为了最大限度地利用可用的屏幕空间,SVGcode 使用 窗口控件叠加层自定义:将其主菜单上移至 标题栏区域您可以看到,此扩展程序在安装流程结束时激活。

<ph type="x-smartling-placeholder">
</ph>
安装 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

Async Clipboard API

SVGcode 还通过 Async Clipboard API 与操作系统的剪贴板完全集成。 您可以将操作系统的文件资源管理器中的图片粘贴到该应用中,方法是点击 粘贴图片按钮,或者按键盘上的 Command 键或 Control + V 键。

<ph type="x-smartling-placeholder">
</ph>
将文件资源管理器中的图片粘贴到 SVGcode 中。

Async Clipboard API 最近也获得了处理 SVG 图片的功能,因此您可以 还可以复制 SVG 图片,并将其粘贴到其他应用中,以便进一步处理。

<ph type="x-smartling-placeholder">
</ph>
将图片从 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 计算机上的“访达”中时,可以右键点击某个图片,然后使用 SVGcode。此功能称为“文件处理”,基于 网络应用清单和启动队列,可让应用使用传递的文件。

<ph type="x-smartling-placeholder">
</ph>
从已安装 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;
    }
  }
});

有关详情,请参阅让已安装的 Web 应用成为文件处理程序,并在 src/js/filehandling.js

网络共享(文件)

另一个与操作系统融为一体的例子是应用的分享功能。假设我想 对使用 SVGcode 创建的 SVG 进行修改时,一种方法是保存文件, 启动 SVG 编辑应用,然后从中打开 SVG 文件。而更流畅的流程则是 使用 Web Share API,该 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 接受哪些类型的数据。这是通过 专用字段。

{
  "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。

总结

好的,下面我们简单介绍了 SVGcode 中的一些高级应用功能。希望这个应用 可以成为满足图片处理需求的重要工具,与其他优质应用(例如 SquooshSVGOMG

您可以在 svgco.de 上找到 SVGcode。看看我在这里做了什么?您可以 在 GitHub 上查看其源代码。请注意,由于 Potrace 已获得 GPL 许可,SVGcode 也是如此。至此,你可以轻松实现矢量化!希望 SVGcode 对您有所帮助。 其中一些功能可以为您的下一款应用提供灵感。

致谢

本文由 Joe Medley 审核。