SVGcode: una PWA per convertire le immagini raster in grafica vettoriale SVG

SVGcode è un'applicazione web progressiva che consente di convertire immagini raster come JPG, PNG, GIF, WebP, AVIF e così via in grafica vettoriale in formato SVG. Utilizza l'API File System Access, l'API Async Clipboard, l'API File handling e la personalizzazione dell'overlay dei controlli di finestra.

Se preferisci guardare più che leggere, questo articolo è disponibile anche come video.)

Dal raster al vettore

Ti è mai capitato di ridimensionare un'immagine e il risultato era pixelato e insoddisfacente? In tal caso, probabilmente hai utilizzato un formato di immagine raster, ad esempio WebP, PNG o JPG.

Aumentando la dimensione di un'immagine raster, questa risulterà pixelata.

Al contrario, le grafiche vettoriali sono immagini definite da punti in un sistema di coordinate. Questi punti sono collegati da linee e curve per formare poligoni e altre forme. Le immagini vettoriali hanno un vantaggio rispetto a quelle raster, in quanto possono essere ridimensionate a qualsiasi risoluzione senza pixelizzazione.

Ridimensionamento di un'immagine vettoriale senza perdita di qualità.

Introduzione a SVGcode

Ho creato una PWA chiamata SVGcode, che può aiutarti a convertire le immagini raster in vettori. Crediti a cui bisogna cedere il credito: non l'ho inventato. Con SVGcode, mi trovo sulle spalle di uno strumento a riga di comando chiamato Potrace di Peter Selinger che ho convertito in Web Assembly per poterlo utilizzare in un'app web.

Screenshot dell'applicazione SVGcode.
L'app SVGcode.

Utilizzo di SVGcode

Innanzitutto, ti mostro come usare l'app. Inizio con l'immagine teaser del Chrome Dev Summit, che ho scaricato dal canale Twitter di ChromiumDev. Questa è un'immagine raster PNG che poi trascino sull'app SVGcode. Quando trascino il file, l'app traccia il colore dell'immagine in base al colore, fino a una versione vettoriale dell'input. Ora posso aumentare lo zoom sull'immagine e, come vedi, i bordi restano nitidi. Aumentando lo zoom sul logo di Chrome, però, puoi vedere che il tracciamento non era perfetto, in particolare i contorni del logo erano leggermente maculati. Posso migliorare i risultati rimuovendo le macchie dal tracciamento, eliminando macchie fino a, ad esempio, cinque pixel.

Conversione di un'immagine rilasciata in SVG.

Posterizzazione in codice SVG

Un passaggio importante per la vettorializzazione, soprattutto per le immagini fotografiche, è la posterizzazione dell'immagine di input per ridurre il numero di colori. SVGcode mi consente di farlo per canale colore e di vedere il risultato SVG risultante mentre apporto le modifiche. Se il risultato mi soddisfa, posso salvare il file SVG sul mio disco rigido e utilizzarlo dove voglio.

Poster di un'immagine per ridurre il numero di colori.

API utilizzate in SVGcode

Ora che hai visto le funzionalità dell'app, vediamo alcune API che contribuiscono a realizzare la magia.

App web progressiva

SVGcode è un'app web progressiva installabile, quindi completamente offline. L'app si basa sul modello Vanilla JS per Vite.js e utilizza il popolare plug-in Vite PWA, che crea un service worker che utilizza Workbox.js in background. Workbox è un insieme di librerie che possono supportare un service worker pronto per la produzione per le app web progressive. Questo pattern potrebbe non funzionare necessariamente per tutte le app, ma nel caso d'uso di SVGcode è perfetto.

Overlay controlli finestra

Per massimizzare lo spazio disponibile sullo schermo, SVGcode utilizza la personalizzazione dell'overlay dei controlli delle finestre spostando il menu principale verso l'alto nell'area della barra del titolo. Potrai verificarlo che verrà attivato al termine del flusso di installazione.

Installazione di SVGcode e attivazione della personalizzazione dell'overlay dei controlli delle finestre.

API File System Access

Per aprire i file delle immagini di input e salvare i risultanti SVG, utilizzo l'API File System Access. Questo mi consente di mantenere un riferimento ai file aperti in precedenza e di continuare da dove avevo interrotto, anche dopo aver ricaricato l'app. Ogni volta che un'immagine viene salvata, l'immagine viene ottimizzata tramite la libreria svgo, che può richiedere alcuni istanti, a seconda della complessità del file SVG. Per mostrare la finestra di dialogo per il salvataggio del file è necessario un gesto dell'utente. È quindi importante ottenere l'handle del file prima dell'ottimizzazione SVG, in modo che il gesto dell'utente non venga invalidato quando l'SVG ottimizzato è 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);
}

Trascina

Per aprire un'immagine di input, posso utilizzare la funzionalità di apertura file o, come hai visto sopra, trascinare un file immagine nell'app. La funzionalità di apertura file è piuttosto semplice, più interessante è la casella di trascinamento. L'aspetto particolarmente interessante è che puoi ottenere un handle di file system dall'elemento di trasferimento di dati tramite il metodo getAsFileSystemHandle(). Come accennato in precedenza, posso mantenere questo handle, in modo che sia pronto quando l'app viene ricaricata.

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

Per maggiori dettagli, consulta l'articolo sull'API File System Access e, se ti interessa, studia il codice sorgente SVGcode in src/js/filesystem.js.

API Async Clipboard

SVGcode è inoltre completamente integrato con gli appunti del sistema operativo tramite l'API Async Clipboard. Puoi incollare le immagini dall'Explorer file del sistema operativo nell'app facendo clic sul pulsante Incolla immagine o premendo Comando o Controllo più V sulla tastiera.

Incollamento di un'immagine da Esplora file in SVGcode.

L'API Async Clipboard ha recentemente acquisito la capacità di gestire anche le immagini SVG, pertanto è possibile anche copiare un'immagine SVG e incollarla in un'altra applicazione per un'ulteriore elaborazione.

Copia di un'immagine da SVGcode 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'));
});

Per ulteriori informazioni, leggi l'articolo Appunti asinc o consulta il file src/js/clipboard.js.

Gestione dei file

Una delle mie funzionalità preferite di SVGcode è la perfetta integrazione con il sistema operativo. In quanto PWA installata, può diventare un gestore di file o anche il gestore di file predefinito per i file immagine. Ciò significa che quando mi trovo nel Finder sul mio MacOS, posso fare clic con il tasto destro del mouse su un'immagine e aprirla con SVGcode. Questa funzionalità, denominata Gestione file, funziona in base alla proprietà file_handlers nel manifest dell'app web e alla coda di avvio, che consente all'app di utilizzare il file trasferito.

Apertura di un file dal desktop con l'app SVGcode installata.
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;
    }
  }
});

Per maggiori informazioni, consulta Consentire alle applicazioni web installate di essere gestori di file e visualizza il codice sorgente in src/js/filehandling.js.

Condivisione web (file)

Un altro esempio di integrazione con il sistema operativo è la funzionalità di condivisione dell'app. Supponendo di voler modificare un file SVG creato con SVGcode, un modo per risolvere questo problema è salvare il file, avviare l'app di modifica SVG e aprire il file SVG da lì. Tuttavia, un flusso più fluido consiste nell'utilizzare l'API Web Share, che consente la condivisione diretta dei file. Pertanto, se l'app di modifica SVG è una destinazione di condivisione, può ricevere direttamente il file senza deviazioni.

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);
      }
    }
  }
});
Condivisione di un'immagine SVG su Gmail.

Destinazione condivisione web (file)

Al contrario, il codice SVGcode può anche fungere da obiettivo per la condivisione e ricevere file da altre app. Per far funzionare questa operazione, l'app deve comunicare al sistema operativo tramite l'API Web Share Target quali tipi di dati può accettare. Ciò avviene tramite un campo apposito nel manifest dell'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 route action in realtà non esiste, ma viene gestita esclusivamente nel gestore fetch del service worker, che a sua volta trasmette i file ricevuti per l'effettiva elaborazione nell'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);
      })(),
    );
  }
});
Condivisione di uno screenshot nel formato SVGcode.

Conclusione

Bene, questo è un breve tour di alcune delle funzionalità avanzate dell'app in SVGcode. Spero che questa app possa diventare uno strumento essenziale per le tue esigenze di elaborazione delle immagini insieme ad altre fantastiche app come Squoosh o SVGOMG.

Il codice SVGcode è disponibile all'indirizzo svgco.de. Vedi cosa ho fatto lì? Puoi esaminare il codice sorgente su GitHub. Tieni presente che Potrace ha una licenza GPL, così come lo è SVGcode. E con questo, buona vettorialità! Spero che il codice SVGcode ti sia utile e che alcune delle sue funzionalità ti ispirano per la tua prossima app.

Ringraziamenti

Questo articolo è stato esaminato da Joe Medley.