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

SVGcode es una aplicación web progresiva que te permite convertir imágenes de trama, como JPG, PNG, GIF, WebP, AVIF, etc., a gráficos vectoriales en formato SVG. Utiliza 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 el contenido en lugar de leerlo, este artículo también está disponible como un video).

De trama a vectores

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

Escalar una imagen de trama hace que parezca pixelada.

En cambio, los gráficos vectoriales son imágenes que se definen por puntos en un sistema de coordenadas. Estos puntos están conectados por líneas y curvas para formar polígonos y otras formas. Los gráficos vectoriales tienen una ventaja en comparación con los gráficos de trama, ya que se pueden aumentar o reducir su escala a cualquier resolución sin pixelar.

Escalar verticalmente una imagen vectorial sin pérdida de calidad

Presentamos SVGcode

Creé una AWP llamada SVGcode que puede ayudarte a convertir imágenes de trama en vectores. Crédito donde se debe crédito: Yo no inventé esto. Con SVGcode, solo me pongo sobre los hombros de una herramienta de línea de comandos llamada Potrace de Peter Selinger, que convertí a Web Assembly, para que pueda usarse en una app web.

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

Usa código SVG

En primer lugar, quiero mostrarte cómo usar la app. Comienzo con la imagen de avance de la Chrome Dev Summit que descargué del canal de Twitter de ChromiumDev. Esta es una imagen de trama PNG que arrastro a la app de SVGcode. Cuando suelto el archivo, la app rastrea el color de la imagen hasta que aparece una versión vectorizada de la entrada. Ahora puedo acercar la imagen y, como puedes ver, los bordes permanecen nítidos. Sin embargo, si acercas el logotipo de Chrome, puedes ver que el trazado no era perfecto y, en especial, los contornos del logotipo se veían un poco moteados. Puedo mejorar el resultado quitando penetraciones del seguimiento suprimiendo las motas de hasta cinco píxeles, por ejemplo.

Conversión de una imagen soltada a SVG.

Posterización en código SVG

Un paso importante para la vectorización, en especial para las imágenes fotográficas, es postergar 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 estoy 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 código SVG

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

App web progresiva

SVGcode es una app web progresiva instalable y, por lo tanto, está completamente habilitada sin conexión. La app se basa en la plantilla Vainilla JS para Vite.js y usa el popular AWP del complemento Vite, que crea un service worker que usa Workbox.js de forma interna. Workbox es un conjunto de bibliotecas que puede potenciar un service worker listo para la producción en apps web progresivas. Este patrón puede no funcionar necesariamente en todas las apps, pero en el caso de uso de SVGcode, es fantástico.

Superposición de controles de ventana

Para maximizar el espacio disponible de la pantalla, SVGcode usa la personalización de Window Controls Overlay moviendo el 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.

Instalación de código SVG y activación de la personalización de Window Controls Overlay.

API de File System Access

Para abrir los archivos de imagen de entrada y guardar los SVG resultantes, uso la API de File System Access. Esto me permite conservar una referencia a los archivos abiertos con anterioridad y continuar donde lo dejé, incluso después de volver a cargar la app. Cada vez que se guarda una imagen, se optimiza mediante la biblioteca svgo, lo que puede tardar un momento, según la complejidad del SVG. Se requiere un gesto del usuario para mostrar el diálogo para guardar el archivo. Por lo tanto, es importante obtener el controlador del archivo antes de que se realice la optimización del 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 abrir archivo o, como viste arriba, solo arrastrar y soltar un archivo de imagen en la app. La función de abrir archivos es bastante sencilla; más interesante es la posibilidad de arrastrar y soltar. Lo interesante sobre esto es que puedes obtener un controlador del sistema de archivos desde el elemento de transferencia de datos a través del método getAsFileSystemHandle(). Como se mencionó antes, puedo conservar este controlador 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 código SVG 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 desde el explorador de archivos del sistema operativo en la app. Para ello, haz clic en el botón Pegar imagen o presiona Comando o Control + V en tu teclado.

Se pega una imagen desde el explorador de archivos en código SVG.

Recientemente, la API de Async Clipboard también tiene 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.

Copiando una imagen de código SVG a 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 Portapapeles asíncrono o consulta el archivo src/js/clipboard.js.

Manejo de archivos

Una de mis funciones favoritas del código SVG es la eficacia con la que se integra en el sistema operativo. Como 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 el Finder en mi máquina macOS, puedo hacer clic con el botón derecho en una imagen y abrirla con código SVG. Esta función se llama Manejo de archivos y funciona según la propiedad file_handlers en el manifiesto de apps web y la cola de inicio, que permite que la app consuma el archivo que se pasó.

Abrir un archivo desde la computadora de escritorio con la app de código SVG 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 Cómo permitir que las aplicaciones web instaladas sean controladores de archivos y consulta el código fuente en src/js/filehandling.js.

Uso compartido en la Web (archivos)

Otro ejemplo de integración con el sistema operativo es la función para compartir de la app. Suponiendo que quiero editar un SVG creado con SVGcode, una forma de solucionarlo es 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 de forma directa. Por lo tanto, si la app de edición SVG es un objetivo de uso compartido, puede recibir directamente el archivo sin desvíos.

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

Objetivo de uso compartido en la Web (archivos)

Por el contrario, el código SVG también puede actuar como objetivo de uso compartido y recibir archivos de otras apps. Para ello, la app debe informar al sistema operativo a través de la API de Web Share Target qué tipos de datos puede aceptar. Esto sucede a través de un campo dedicado en el manifiesto de apps 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, pero se controla solo en el controlador fetch del service worker, que luego pasa los archivos recibidos para su 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 compartirá una captura de pantalla en código SVG.

Conclusión

De acuerdo, este fue un recorrido rápido por algunas de las funciones avanzadas de la app en código SVG. Espero que esta app pueda convertirse 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. ¿Ves lo que hice allí? Puedes revisar su código fuente en GitHub. Ten en cuenta que, dado que Potrace tiene licencia de GPL, también lo es el código SVG. Y con eso, ¡feliz vectorización! Espero que este código sea útil y que algunas de sus funciones inspiren tu próxima app.

Agradecimientos

Joe Medley revisó este artículo.