SVGcode: una AWP para convertir imágenes de trama en gráficos vectoriales SVG

SVGcode es una app web progresiva que te permite convertir imágenes de trama, como JPG, PNG, GIF, WebP, AVIF, etc., en gráficos vectoriales en formato SVG. Usa la API de File System Access, la API de Async Clipboard, la API de File Handling y la personalización de Window Controls Overlay.

Si prefieres mirar videos en lugar de leer, este artículo también está disponible como video.

De trama a vector

¿Alguna vez escalaste una imagen y el resultado fue pixelado y poco satisfactorio? Si es así, es probable que hayas trabajado con un formato de imagen de trama, como WebP, PNG o JPG.

Si se aumenta el tamaño de una imagen de trama, esta se verá pixelada.

Por el contrario, los gráficos vectoriales son imágenes que se definen por puntos en un sistema de coordenadas. Estos puntos se conectan con líneas y curvas para formar polígonos y otras formas. Los gráficos vectoriales tienen una ventaja sobre los gráficos de trama, ya que se pueden agrandar o achicar a cualquier resolución sin pixelación.

Se agranda una imagen vectorial sin pérdida de calidad.

Presentamos SVGcode

Compilé una AWP llamada SVGcode que puede ayudarte a convertir imágenes raster en vectores. Crédito donde corresponde: No inventé esto. Con SVGcode, me apoyo en una herramienta de línea de comandos llamada Potrace de Peter Selinger que convertí a Web Assembly para que se pueda usar en una app web.

Captura de pantalla de la aplicación de SVGcode.
La app de SVGcode.

Cómo usar SVGcode

Primero, quiero mostrarte cómo usar la app. Empiezo con la imagen de vista previa de la Cumbre de desarrolladores de Chrome que descargué del canal de Twitter de ChromiumDev. Esta es una imagen de trama PNG que luego arrastro a la app de SVGcode. Cuando dejo caer el archivo, la app traza la imagen color por color hasta que aparece una versión vectorizada de la entrada. Ahora puedo acercar la imagen y, como puedes ver, los bordes se mantienen nítidos. Sin embargo, si acercas el logotipo de Chrome, puedes ver que el trazado no fue perfecto, en especial, los contornos del logotipo se ven un poco salpicados. Para mejorar el resultado, puedo quitar las manchas del trazo suprimiendo manchas de hasta, por ejemplo, cinco píxeles.

Conversión de una imagen colocada en SVG.

Posterización en SVGcode

Un paso importante para la vectorización, especialmente para las imágenes fotográficas, es posterizar la imagen de entrada para reducir la cantidad de colores. SVGcode me permite hacer esto por canal de color y ver el SVG resultante a medida que hago cambios. Cuando esté conforme con el resultado, puedo guardar el SVG en mi disco duro y usarlo donde quiera.

Posterizar una imagen para reducir la cantidad de colores.

APIs que se usan en SVGcode

Ahora que viste lo que puede hacer la app, te mostraré algunas de las APIs que ayudan a hacer que la magia suceda.

App web progresiva

SVGcode es una app web progresiva instalable y, por lo tanto, está completamente habilitada para funcionar sin conexión. La app se basa en la plantilla de Vanilla JS para Vite.js y usa el popular complemento PWA de Vite, que crea un trabajador de servicio que usa Workbox.js en segundo plano. Workbox es un conjunto de bibliotecas que pueden potenciar un servicio de trabajo listo para producción para Progressive Web Apps. Es posible que este patrón no funcione para todas las apps, pero es excelente para el caso de uso de SVGcode.

Superposición de controles de la ventana

Para maximizar el espacio disponible en la pantalla, SVGcode usa la personalización de Window Controls Overlay moviendo su menú principal hacia arriba en el área de la barra de título. Puedes ver que se activa al final del flujo de instalación.

Instala SVGcode y activa la personalización de la superposición de controles de ventana.

API de File System Access

Para abrir archivos de imagen de entrada y guardar los SVG resultantes, uso la API de File System Access. Esto me permite mantener una referencia a los archivos abiertos anteriormente y continuar desde donde lo dejé, incluso después de que se vuelva a cargar una app. Cada vez que se guarda una imagen, se optimiza a través de la biblioteca svgo, lo que puede tardar un momento, según la complejidad del SVG. Para mostrar el diálogo de guardado de archivos, se requiere un gesto del usuario. Por lo tanto, es importante obtener el identificador de archivo antes de que se realice la optimización de SVG, de modo que el gesto del usuario no se invalide cuando el SVG optimizado esté listo.

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

Arrastrar y soltar

Para abrir una imagen de entrada, puedo usar la función de apertura de archivos o, como viste anteriormente, simplemente arrastrar y soltar un archivo de imagen en la app. La función de apertura de archivos es bastante sencilla, pero lo más interesante es el caso de arrastrar y soltar. Lo que es particularmente bueno de esto es que puedes obtener un identificador de sistema de archivos del elemento de transferencia de datos a través del método getAsFileSystemHandle(). Como se mencionó antes, puedo conservar este identificador para que esté listo cuando se vuelva a cargar la app.

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 obtener más detalles, consulta el artículo sobre la API de acceso al sistema de archivos y, si te interesa, estudia el código fuente de SVGcode en src/js/filesystem.js.

API de Async Clipboard

SVGcode también está completamente integrado en el portapapeles del sistema operativo a través de la API de Async Clipboard. Puedes pegar imágenes del explorador de archivos del sistema operativo en la app haciendo clic en el botón para pegar imágenes o presionando Comando o Control + V en el teclado.

Pegado de una imagen del explorador de archivos en SVGcode.

Recientemente, la API de Async Clipboard también adquirió la capacidad de manejar imágenes SVG, por lo que también puedes copiar una imagen SVG y pegarla en otra aplicación para su procesamiento posterior.

Se copia una imagen de SVGcode en 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 obtener más información, lee el artículo Async Clipboard o consulta el archivo src/js/clipboard.js.

Manejo de archivos

Una de mis funciones favoritas de SVGcode es lo bien que se integra en el sistema operativo. Como una AWP instalada, puede convertirse en un controlador de archivos, o incluso en el controlador de archivos predeterminado, para archivos de imagen. Esto significa que, cuando estoy en Finder en mi máquina macOS, puedo hacer clic con el botón derecho en una imagen y abrirla con SVGcode. Esta función se llama Control de archivos y funciona en función de la propiedad file_handlers en el manifiesto de la app web y la cola de inicio, lo que permite que la app consuma el archivo pasado.

Apertura de un archivo desde el escritorio con la app de SVGcode instalada.
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 obtener más información, consulta Permite que las aplicaciones web instaladas sean controladores de archivos y consulta el código fuente en src/js/filehandling.js.

Compartir en la Web (archivos)

Otro ejemplo de integración en el sistema operativo es la función de compartir de la app. Si supongo que quiero editar un SVG creado con SVGcode, una forma de hacerlo sería guardar el archivo, iniciar la app de edición de SVG y, luego, abrir el archivo SVG desde allí. Sin embargo, un flujo más fluido es usar la API de Web Share, que permite compartir archivos directamente. Por lo tanto, si la app de edición de SVG es un destino de uso compartido, puede recibir el archivo directamente sin desviación.

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);
      }
    }
  }
});
Compartir una imagen SVG en Gmail.

Web Share Target (Files)

Al revés, SVGcode también puede actuar como destino de uso compartido y recibir archivos de otras apps. Para que esto funcione, la app debe informar al sistema operativo a través de la API de Web Share Target qué tipos de datos puede aceptar. Esto se hace a través de un campo dedicado en el manifiesto de la app 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"]
        }
      ]
    }
  }
}

La ruta action no existe en realidad, pero se controla solo en el controlador fetch del trabajador de servicio, que luego pasa los archivos recibidos para el procesamiento real en la app.

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);
      })(),
    );
  }
});
Se comparte una captura de pantalla en SVGcode.

Conclusión

Bien, este fue un recorrido rápido por algunas de las funciones avanzadas de la app en SVGcode. Espero que esta app se convierta en una herramienta esencial para tus necesidades de procesamiento de imágenes junto con otras apps increíbles, como Squoosh o SVGOMG.

SVGcode está disponible en svgco.de. ¿Viste lo que hice? Puedes revisar su código fuente en GitHub. Ten en cuenta que, como Potrace tiene licencia GPL, SVGcode también la tiene. Y con eso, ¡feliz vectorización! Espero que SVGcode te resulte útil y que algunas de sus funciones puedan inspirar tu próxima app.

Agradecimientos

Joe Medley revisó este artículo.