SVGcode: um PWA para converter imagens rasterizadas em gráficos vetoriais SVG

O SVGcode é um Progressive Web App que permite converter imagens rasterizadas, como JPG, PNG, GIF, WebP, AVIF etc., em gráficos vetoriais no formato SVG. Ela usa a API File System Access, a API Async Clipboard, a API File Handling e a personalização de sobreposição de controles de janela.

.
Se você prefere assistir em vez de ler, este artigo também está disponível como vídeo.

De rasterização para vetor

Você já dimensionou uma imagem e o resultado foi pixelado e insatisfatório? Se Você provavelmente já usou um formato de imagem rasterizado, como WebP, PNG ou JPG.

Ampliar uma imagem rasterizada faz com que ela pareça pixelada.

Por outro lado, gráficos vetoriais são imagens definidas por pontos em um sistema de coordenadas. Esses os pontos são conectados por linhas e curvas para formar polígonos e outras formas. Os gráficos vetoriais têm vantagem sobre os gráficos rasterizados, pois eles podem ser dimensionados para qualquer resolução sem pixelização.

Escalonar verticalmente uma imagem vetorial sem perda de qualidade.

Introdução ao SVGcode

Eu criei um PWA chamado SVGcode, que ajuda a converter imagens rasterizadas vetores. Crédito, onde o crédito é devido: eu não inventei isso. Com SVGcode, eu apenas fico no usando uma ferramenta de linha de comando chamada Potrace, Peter Selinger, que tenho convertido para Web Assembly, para que possa ser usado em um App da Web.

Captura de tela do aplicativo SVGcode.
O app SVGcode.

Usar SVGcode

Primeiro, quero mostrar como usar o app. Começo com a imagem do teaser do Chrome Dev Summit que baixei do canal ChromiumDev no Twitter. Esta é uma imagem de rasterização PNG que eu arraste para o aplicativo SVGcode. Quando derrubo o arquivo, o app rastreia a imagem cor por cor, até aparecer uma versão vetorizada da entrada. Agora posso aplicar zoom na imagem e, como você pode ver, que as bordas fiquem nítidas. Mas, ao aumentar o zoom no logotipo do Chrome, podemos notar que o rastro não era perfeitos e, especialmente, os contornos do logotipo parecem um pouco salgados. Posso melhorar o resultado suprima manchas de até cinco pixels, por exemplo.

Converter uma imagem solta em SVG.

Posterização em SVGcode

Uma etapa importante para a vetorização, especialmente no caso de imagens fotográficas, é a pôster da entrada imagem para reduzir o número de cores. O SVGcode permite fazer isso por canal de cor e ver o SVG resultante conforme eu faço as alterações. Quando estiver satisfeito com o resultado, posso salvar o SVG no meu disco rígido. e usá-lo onde eu quiser.

Posterizar uma imagem para reduzir o número de cores.

APIs usadas em SVGcode

Agora que você já sabe do que o aplicativo é capaz, vou mostrar algumas das APIs que ajudam a tornar a mágica acontece.

App Web Progressivo

O SVGcode é um Progressive Web App instalável e, portanto, totalmente ativado off-line. O app é baseado em no(a) Modelo do Vanilla para JS para Vite.js e usa a famosa Vite plugin PWA, que cria um service worker que usa Workbox.js internamente. A caixa de trabalho é um conjunto de bibliotecas que podem potencializar um service worker pronto para produção para Progressive Web Apps, esse padrão pode não funcionar necessariamente para todos os apps, mas para o caso de uso do SVGcode é ótimo.

Sobreposição dos controles da janela

Para maximizar o espaço disponível na tela, o SVGcode usa a personalização da Sobreposição de controles da janela, movendo o menu principal para cima em na área da barra de títulos. Ele é ativado no fim do fluxo de instalação.

Instalando o SVGcode e ativando a personalização da sobreposição de controles da janela.

API File System Access

Para abrir arquivos de imagem de entrada e salvar os SVGs resultantes, uso o comando API File System Access. Isso me permite manter uma referência aos arquivos abertos e continuar de onde parei, mesmo depois de recarregar o aplicativo. Sempre que uma imagem é salvo, ele é otimizado pela biblioteca svgo, o que pode levar alguns instantes, dependendo da complexidade do SVG. A exibição da caixa de diálogo para salvar arquivo exige um gesto do usuário. É é importante ter o identificador do arquivo antes da otimização do SVG, para que o usuário o gesto não será invalidado quando o SVG otimizado estiver pronto.

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);
}

Arrastar e soltar

Para abrir uma imagem de entrada, posso usar o recurso de abertura de arquivo ou, como você viu acima, apenas arraste e solte um arquivo de imagem no app. O recurso de abrir arquivos é bem objetivo, interessante é o caso de arrastar e soltar. O que é particularmente bom nisso é que você pode obtenha um identificador do sistema de arquivos no item de transferência de dados usando o getAsFileSystemHandle() . Como mencionado anteriormente, posso manter esse identificador para que ele esteja pronto quando o app for recarregado.

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);
  }
});

Para mais detalhes, confira o artigo sobre a API File System Access e se estiver interessado, estude o código-fonte SVGcode em src/js/filesystem.js.

API Async Clipboard

O SVGcode também é totalmente integrado à área de transferência do sistema operacional pela API Async Clipboard. Você pode colar imagens do explorador de arquivos do sistema operacional no aplicativo clicando no o botão para colar imagem ou pressionando Command ou Control + V no teclado.

Colando uma imagem do explorador de arquivos no SVGcode.

Recentemente, a API Async Clipboard passou a processar também imagens SVG. copiar uma imagem SVG e colá-la em outro aplicativo para processamento adicional.

Copiar uma imagem do SVGcode para o 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'));
});

Para saber mais, leia o artigo Async Clipboard ou veja o arquivo. src/js/clipboard.js.

Gerenciamento de arquivos

Um dos meus recursos favoritos do SVGcode é o quão bem ele se integra ao sistema operacional. Como PWA instalado, pode se tornar um gerenciador de arquivos ou até mesmo o gerenciador padrão de arquivos de imagem. Isso significa que, quando estou no Finder da minha máquina macOS, posso clicar com o botão direito do mouse em uma imagem e abri-la com SVGcode. Esse recurso é chamado Manipulação de arquivos e funciona com base na propriedade file_handlers no Manifesto do app da Web e a fila de inicialização, que permite que o app consuma o arquivo transmitido.

Abrindo um arquivo na área de trabalho com o app SVGcode instalado.
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;
    }
  }
});

Para mais informações, consulte Permitir que os aplicativos da Web instalados sejam gerenciadores de arquivos e veja o código-fonte em src/js/filehandling.js.

Compartilhamento na Web (arquivos)

Outro exemplo de combinação com o sistema operacional é o recurso de compartilhamento do app. Supondo que eu queira para editar um SVG criado com SVGcode, uma forma de lidar com isso seria salvar o arquivo, inicie o app de edição SVG e, em seguida, abra o arquivo SVG. Um fluxo mais suave, no entanto, é usar a API Web Share, que permite compartilhar arquivos diretamente. Então, se o app de edição de SVG é um alvo de compartilhamento, pode receber o arquivo diretamente sem desvio.

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);
      }
    }
  }
});
Compartilhar uma imagem SVG no Gmail.

Destino de compartilhamento na Web (arquivos)

Por outro lado, o SVG também pode atuar como um alvo de compartilhamento e receber arquivos de outros apps. Para fazer isso funcionar, o app precisa informar ao sistema operacional por meio do API Web Share Target e quais tipos de dados ela pode aceitar. Isso acontece por meio de uma campo dedicado no manifesto do app da 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"]
        }
      ]
    }
  }
}

A rota action realmente não existe, mas é gerenciada puramente no fetch do service worker. que transmite os arquivos recebidos para o processamento real no aplicativo.

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);
      })(),
    );
  }
});
Compartilhando uma captura de tela com SVGcode.

Conclusão

Certo, este foi um tour rápido sobre alguns dos recursos avançados de apps em SVGcode. Espero que este app pode se tornar uma ferramenta essencial para suas necessidades de processamento de imagens, junto com outros aplicativos incríveis como Squoosh ou SVGOMG.

O SVGcode está disponível em svgco.de. Vê o que eu fiz lá? Você pode analise o código-fonte dela no GitHub. Como o Potrace é com licença GPL, assim como o SVGcode. Com isso, desejamos uma boa vetorização! Espero que o SVGcode seja útil, e alguns dos recursos dele podem inspirar seu próximo aplicativo.

Agradecimentos

Este artigo foi revisado por Joe Medley.