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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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);
}
}
}
});
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);
})(),
);
}
});
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.