SVGcode: eine PWA zum Konvertieren von Rasterbildern in SVG-Vektorgrafiken

SVGcode ist eine progressive Webanwendung, mit der Sie Rasterbilder wie JPG, PNG, GIF, WebP und AVIF in Vektorgrafiken im SVG-Format konvertieren können. Dabei werden die File System Access API, die Async Clipboard API, die File Handling API und die Anpassung der Window Controls Overlay API verwendet.

Wenn Sie lieber ein Video ansehen als zu lesen, ist dieser Artikel auch als Video verfügbar.

Von Raster zu Vektor

Haben Sie schon einmal ein Bild skaliert und das Ergebnis war pixelig und unbefriedigend? In diesem Fall haben Sie wahrscheinlich schon einmal mit einem Rasterbildformat wie WebP, PNG oder JPG gearbeitet.

Wenn Sie ein Rasterbild vergrößern, wird es pixelig.

Im Gegensatz dazu sind Vektorgrafiken Bilder, die durch Punkte in einem Koordinatensystem definiert sind. Diese Punkte werden durch Linien und Kurven verbunden, um Polygone und andere Formen zu bilden. Vektorgrafiken haben gegenüber Rastergrafiken den Vorteil, dass sie ohne Pixelung auf jede Auflösung skaliert werden können.

Skalieren eines Vektorbilds ohne Qualitätsverlust.

SVGcode

Ich habe eine PWA namens SVGcode entwickelt, mit der Sie Rasterbilder in Vektoren konvertieren können. Zuerst einmal: Ich habe diese Methode nicht erfunden. Bei SVGcode nutze ich einfach ein Befehlszeilentool namens Potrace von Peter Selinger, das ich in Web Assembly konvertiert habe, damit es in einer Webanwendung verwendet werden kann.

Screenshot der SVGcode-Anwendung
Die SVGcode App.

SVG-Code verwenden

Zuerst möchte ich Ihnen zeigen, wie Sie die App verwenden. Ich beginne mit dem Teaserbild für das Chrome Dev Summit, das ich vom Twitter-Kanal von ChromiumDev heruntergeladen habe. Das ist ein PNG-Rasterbild, das ich dann in die SVGcode App ziehe. Wenn ich die Datei ablege, wird das Bild farbweise nachgezeichnet, bis eine vektorisierte Version der Eingabe erscheint. Ich kann jetzt heranzoomen. Wie Sie sehen, bleiben die Kanten scharf. Wenn Sie jedoch heranzoomen, sehen Sie, dass die Verfolgung nicht perfekt war und insbesondere die Umrisse des Logos etwas ungleichmäßig sind. Ich kann das Ergebnis verbessern, indem ich die Streifen aus dem Tracing entferne, indem ich Streifen von bis zu fünf Pixeln unterdrücke.

Ein abgelegtes Bild in SVG konvertieren.

Posterisierung in SVG-Code

Ein wichtiger Schritt bei der Vektorisierung, insbesondere bei fotografischen Bildern, ist die Posterisierung des Eingabebilds, um die Anzahl der Farben zu reduzieren. Mit SVGcode kann ich das pro Farbkanal tun und das resultierende SVG sehen, während ich Änderungen vornehme. Wenn ich mit dem Ergebnis zufrieden bin, kann ich das SVG auf meiner Festplatte speichern und überall verwenden.

Posterisieren eines Bilds, um die Anzahl der Farben zu reduzieren.

In SVG-Code verwendete APIs

Nachdem Sie gesehen haben, was die App kann, möchte ich Ihnen einige der APIs zeigen, die dafür sorgen, dass alles so reibungslos funktioniert.

Progressive Web-App

SVGcode ist eine installierbare progressive Web-App und daher vollständig offlinefähig. Die App basiert auf der Vanilla-JS-Vorlage für Vite.js und verwendet das beliebte Vite-Plug-in PWA, das einen Service Worker erstellt, der Workbox.js verwendet. Workbox ist eine Reihe von Bibliotheken, die einen produktionsfertigen Service Worker für progressive Web-Apps unterstützen können. Dieses Muster funktioniert möglicherweise nicht für alle Apps, aber für den Anwendungsfall von SVGcode ist es hervorragend geeignet.

Overlay für Fenstersteuerelemente

Um die verfügbare Bildschirmfläche zu maximieren, verwendet SVGcode die Anpassung Window Controls Overlay, indem das Hauptmenü in den Bereich der Titelleiste verschoben wird. Sie sehen, dass diese Funktion am Ende des Installationsvorgangs aktiviert wird.

SVG-Code installieren und die Anpassung des Overlays für Fenstersteuerungen aktivieren.

File System Access API

Zum Öffnen von Eingabebilddateien und Speichern der resultierenden SVGs verwende ich die File System Access API. So kann ich einen Verweis auf zuvor geöffnete Dateien behalten und auch nach dem Neuladen einer App dort weitermachen, wo ich aufgehört habe. Jedes Mal, wenn ein Bild gespeichert wird, wird es über die svgo-Bibliothek optimiert. Je nach Komplexität des SVG kann dies einen Moment dauern. Das Dialogfeld zum Speichern von Dateien muss durch eine Nutzergeste geöffnet werden. Daher ist es wichtig, den Dateihandle vor der SVG-Optimierung abzurufen, damit die Nutzergeste nicht ungültig wird, bis die optimierte SVG-Datei bereit ist.

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

Drag-and-Drop

Zum Öffnen eines Eingabebilds kann ich entweder die Funktion zum Öffnen von Dateien verwenden oder, wie oben gezeigt, eine Bilddatei per Drag-and-drop in die App ziehen. Die Funktion zum Öffnen von Dateien ist ziemlich einfach, interessanter ist der Drag-and-drop-Fall. Besonders praktisch ist, dass Sie über die Methode getAsFileSystemHandle() einen Dateisystem-Handle vom Datenübertragungselement abrufen können. Wie bereits erwähnt, kann ich diesen Handle beibehalten, damit er bereit ist, wenn die App neu geladen wird.

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

Weitere Informationen finden Sie im Artikel zur File System Access API. Wenn Sie möchten, können Sie sich auch den SVG-Code in src/js/filesystem.js ansehen.

Async Clipboard API

SVG-Code ist außerdem über die Async Clipboard API vollständig in die Zwischenablage des Betriebssystems eingebunden. Sie können Bilder aus dem Dateimanager des Betriebssystems in die App einfügen, indem Sie entweder auf die Schaltfläche „Bild einfügen“ klicken oder auf der Tastatur die Tastenkombination „Befehl“ oder „Strg“ + „V“ drücken.

Ein Bild aus dem Dateimanager in SVG-Code einfügen.

Die Async Clipboard API kann seit Kurzem auch mit SVG-Bildern umgehen. Sie können also auch ein SVG-Bild kopieren und zur weiteren Verarbeitung in eine andere Anwendung einfügen.

Ein Bild aus SVGcode in SVGOMG kopieren.
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'));
});

Weitere Informationen finden Sie im Artikel Async Clipboard oder in der Datei src/js/clipboard.js.

Dateiverwaltung

Eine meiner Lieblingsfunktionen von SVG-Code ist, wie gut er sich in das Betriebssystem einfügt. Als installierte PWA kann sie ein Datei-Handler oder sogar der Standard-Datei-Handler für Bilddateien werden. Das bedeutet, dass ich auf meinem macOS-Computer im Finder mit der rechten Maustaste auf ein Bild klicken und es mit SVG-Code öffnen kann. Diese Funktion wird als Dateiverwaltung bezeichnet und basiert auf der Eigenschaft „file_handlers“ im Web-App-Manifest und der Startwarteschlange, über die die App die übergebene Datei verwenden kann.

Dateien über den Computer mit der installierten SVGcode App öffnen.
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;
    }
  }
});

Weitere Informationen finden Sie unter Installierte Webanwendungen als Datei-Handler verwenden. Den Quellcode finden Sie in src/js/filehandling.js.

Webfreigabe (Dateien)

Ein weiteres Beispiel für die Einbindung in das Betriebssystem ist die Freigabefunktion der App. Angenommen, ich möchte eine mit SVGcode erstellte SVG-Datei bearbeiten. Eine Möglichkeit dazu wäre, die Datei zu speichern, die SVG-Bearbeitungs-App zu starten und die SVG-Datei dort zu öffnen. Die Web Share API bietet jedoch einen einfacheren Ablauf, da Dateien direkt freigegeben werden können. Wenn die SVG-Bearbeitungs-App ein Freigabeziel ist, kann sie die Datei direkt ohne Abweichung empfangen.

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);
      }
    }
  }
});
Ein SVG-Bild in Gmail teilen.

Ziel für die Webfreigabe (Dateien)

Umgekehrt kann SVG-Code auch als Freigabeziel dienen und Dateien von anderen Apps empfangen. Damit dies funktioniert, muss die App dem Betriebssystem über die Web Share Target API mitteilen, welche Datentypen sie akzeptieren kann. Das geschieht über ein spezielles Feld im Web-App-Manifest.

{
  "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"]
        }
      ]
    }
  }
}

Der Pfad action existiert nicht wirklich, sondern wird ausschließlich im fetch-Handler des Service Workers verarbeitet, der empfangene Dateien dann zur tatsächlichen Verarbeitung in der App weiterleitet.

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);
      })(),
    );
  }
});
Einen Screenshot als SVG-Code teilen.

Fazit

Das war ein kurzer Überblick über einige der erweiterten App-Funktionen in SVG-Code. Ich hoffe, dass diese App neben anderen tollen Apps wie Squoosh oder SVGOMG ein unverzichtbares Tool für Ihre Bildverarbeitung wird.

SVGcode ist unter svgco.de verfügbar. Sehen Sie, was ich da gemacht habe? Sie können sich den Quellcode auf GitHub ansehen. Da Potrace unter der GPL-Lizenz steht, gilt dies auch für SVGcode. Viel Spaß beim Vektorisieren! Ich hoffe, dass SVGcode Ihnen nützlich ist und dass einige seiner Funktionen Sie zu Ihrer nächsten App inspirieren.

Danksagungen

Dieser Artikel wurde von Joe Medley überprüft.