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

SVGcode è un'app 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 di Window Controls Overlay.

Se preferisci guardare piuttosto che leggere, questo articolo è disponibile anche come video.

Da raster a vettoriale

Hai mai ridimensionato un'immagine e il risultato è stato sfocato e insoddisfacente? Se così è, probabilmente hai utilizzato un formato di immagine raster come WebP, PNG o JPG.

Se aumenti le dimensioni di un'immagine raster, questa diventa pixellata.

Al contrario, le immagini 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 alle immagini raster in quanto possono essere ridimensionate in base a qualsiasi risoluzione senza pixelizzazione.

Aumento della dimensione 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. A ognuno il giusto merito: non l'ho inventato io. Con SVGcode, mi appoggio su uno strumento a riga di comando chiamato Potrace di Peter Selinger che ho convertito in Web Assembly, in modo che possa essere utilizzato in un'app web.

Screenshot dell'applicazione SVGcode.
L'app SVGcode.

Utilizzo di SVGcode

Innanzitutto, voglio mostrarti come utilizzare l'app. Comincio con l'immagine teaser per il Chrome Dev Summit che ho scaricato dal canale Twitter di ChromiumDev. Si tratta di un'immagine raster PNG che poi trascino nell'app SVGcode. Quando rilascio il file, l'app traccia l'immagine colore per colore, fino a quando non viene visualizzata una versione vettorizzata dell'input. Ora posso aumentare lo zoom dell'immagine e, come puoi vedere, i bordi rimangono nitidi. Tuttavia, se aumenti lo zoom sul logo di Chrome, puoi vedere che il rilevamento non è stato perfetto e, in particolare, i contorni del logo sembrano un po' macchiati. Posso migliorare il risultato eliminando gli aloni dal tracciato sopprimendo gli aloni fino a, ad esempio, cinque pixel.

Conversione di un'immagine incollata in SVG.

Posterizzazione in SVGcode

Un passaggio importante per la vettorizzazione, in particolare per le immagini fotografiche, è la posterizzazione dell'immagine di input per ridurre il numero di colori. SVGcode mi consente di farlo per canale di colore e di visualizzare l'SVG risultante man mano che apporto modifiche. Quando il risultato mi soddisfa, posso salvare il file SVG sul mio disco rigido e utilizzarlo dove voglio.

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

API utilizzate in SVGcode

Ora che hai visto di cosa è capace l'app, ti mostro alcune delle API che contribuiscono a creare la magia.

App web progressiva

SVGcode è un'app web progressiva installabile e quindi completamente offline. L'app si basa sul modello Vanilla JS per Vite.js e utilizza il popolare plug-in PWA di Vite, che crea un servizio worker che utilizza Workbox.js sotto il cofano. 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 è ottimo per il caso d'uso di SVGcode.

Overlay controlli finestra

Per massimizzare lo spazio sullo schermo disponibile, SVGcode utilizza la personalizzazione di Window Controls Overlay spostando il menu principale nell'area della barra del titolo. Puoi vedere questa opzione attivata al termine del flusso di installazione.

Installazione del codice SVG e attivazione della personalizzazione dell'overlay dei controlli della finestra.

API File System Access

Per aprire i file immagine di input e salvare gli SVG risultanti, utilizzo l'API File System Access. In questo modo posso mantenere un riferimento ai file aperti in precedenza e continuare da dove avevo interrotto, anche dopo il ricaricamento di un'app. Ogni volta che un'immagine viene salvata, viene ottimizzata tramite la libreria svgo, il che potrebbe richiedere un istante, a seconda della complessità dell'SVG. La visualizzazione della finestra di dialogo di salvataggio del file richiede un gesto dell'utente. È quindi importante ottenere l'handle del file prima dell'ottimizzazione dell'SVG, in modo che il gesto dell'utente non venga invalidato al momento del completamento dell'SVG ottimizzato.

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 del file oppure, come hai visto sopra, semplicemente trascinare un file immagine nell'app. La funzionalità di apertura del file è piuttosto semplice, ma più interessante è il caso del trascinamento. La cosa particolarmente interessante è che puoi recuperare un handle del file system dall'elemento di trasferimento 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 di 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'esploratore file del sistema operativo nell'app facendo clic sul pulsante Incolla immagine o premendo Comando o Controllo + V sulla tastiera.

Incolla un'immagine da Esplora file in SVGcode.

Di recente l'API Async Clipboard ha acquisito la capacità di gestire anche le immagini SVG, quindi puoi anche copiare un'immagine SVG e incollarla in un'altra applicazione per ulteriori elaborazioni.

Copia di un'immagine da SVGcode in 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 scoprire di più, leggi l'articolo Appunti sincronici o consulta il file src/js/clipboard.js.

Gestione dei file

Una delle mie funzionalità preferite di SVGcode è la sua perfetta integrazione con il sistema operativo. Come PWA installata, può diventare un gestore di file o addirittura il gestore di file predefinito per i file immagine. Ciò significa che quando sono nel Finder sul mio computer macOS, posso fare clic con il tasto destro del mouse su un'immagine e aprirla con SVGcode. Questa funzionalità è chiamata Gestione file e funziona in base alla proprietà file_handlers nel manifest dell'app web e nella coda di lancio, che consente all'app di utilizzare il file passato.

Apertura di un file dal computer 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 ulteriori informazioni, vedi 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 che voglia apportare modifiche a un file SVG creato con SVGcode, un modo per risolvere il problema è salvare il file, avviare l'app di modifica SVG e aprirlo da lì. Un flusso più semplice, però, è utilizzare l'API Web Share, che consente di condividere direttamente i 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 con Gmail.

Destinazione condivisione web (file)

Viceversa, SVGcode può anche fungere da destinazione di 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 i tipi di dati che può accettare. Ciò avviene tramite un campo dedicato nel file 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 non esiste realmente, ma viene gestita esclusivamente nell'handler fetch del service worker, che poi trasmette i file ricevuti per l'elaborazione effettiva 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 in SVGcode.

Conclusione

Bene, questo è stato 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 app straordinarie come Squoosh o SVGOMG.

SVGcode è disponibile all'indirizzo svgco.de. Hai capito cosa ho fatto? Puoi esaminare il codice sorgente su GitHub. Tieni presente che, poiché Potrace è concesso in licenza GPL, lo è anche SVGcode. Buona trasformazione in vettori. Spero che SVGcode ti sia utile e che alcune delle sue funzionalità possano ispirare la tua prossima app.

Ringraziamenti

Questo articolo è stato esaminato da Joe Medley.