SVGcode: aplikacja PWA do konwertowania obrazów rastrowych na grafiki wektorowe SVG.

SVGcode to progresywna aplikacja internetowa, która umożliwia konwertowanie obrazów rastrowych, takich jak JPG, PNG, GIF, WebP, AVIF itp., na grafikę wektorową w formacie SVG. Aplikacja korzysta z interfejsów File System Access API, Async Clipboard API, File Handling API i Window Controls Overlay.

(Jeśli wolisz oglądać niż czytać, ten artykuł jest też dostępny w formie filmu).

Z rastera na wektor

Czy zdarzyło Ci się kiedykolwiek skalować obraz, a efekt był ziarnisty i niesatysfakcjonujący? Jeśli tak, prawdopodobnie masz do czynienia z formatem obrazu rastrowego, takim jak WebP, PNG lub JPG.

Powiększenie obrazu rastrowego powoduje, że staje się on pikselowaty.

Grafika wektorowa to natomiast obrazy zdefiniowane przez punkty w układzie współrzędnych. Punkty te są połączone liniami i krzywiznami, tworząc wielokąty i inne kształty. Grafika wektorowa ma tę zaletę nad grafiką rastrową, że można ją powiększyć lub zmniejszyć do dowolnej rozdzielczości bez utraty jakości.

Zwiększanie obrazu wektorowego bez utraty jakości.

Przedstawiamy SVGcode

Stworzyłem aplikację PWA o nazwie SVGcode, która może pomóc w konwertowaniu obrazów rastrowych na wektory. Odpowiednie przypisywanie udziału w konwersji: nie ja wymyśliłem tego rozwiązania. W przypadku SVGcode korzystam z narzędzi wiersza poleceń o nazwie Potrace stworzonego przez Petera Selingera. Konwertuję je na Web Assembly, aby można było z niego korzystać w aplikacji internetowej.

Zrzut ekranu aplikacji SVGcode
Aplikacja SVGcode.

Za pomocą SVGcode

Najpierw pokażę, jak korzystać z aplikacji. Zaczynam od teasera Chrome Dev Summit, który pobrałem z kanału ChromiumDev na Twitterze. To rastrowy obraz PNG, który przeciągam do aplikacji SVGcode. Gdy upuszczę plik, aplikacja będzie śledzić kolory obrazu, aż pojawi się wektorowa wersja danych wejściowych. Teraz mogę powiększyć obraz i jak widzisz, krawędzie pozostają ostre. Po zbliżeniu logo Chrome widać jednak, że śledzenie nie było idealne, zwłaszcza kontury logo wyglądają nieco nierówno. Mogę poprawić wynik, usuwając z wykresu plamki o rozmiary do 5 pikseli.

Konwertowanie przeciągniętego obrazu na plik SVG.

Posteryzacja w SVGcode

Ważnym krokiem w procesie wektoryzacji, zwłaszcza w przypadku obrazów fotograficznych, jest posteryzacja wejściowego obrazu w celu zmniejszenia liczby kolorów. SVGcode pozwala mi to robić w przypadku każdego kanału kolorów i oglądać wynikowy plik SVG w miarę wprowadzania zmian. Gdy uzyskam zadowalający efekt, mogę zapisać plik SVG na dysku twardym i używać go gdziekolwiek zechcę.

Posteryzacja obrazu w celu zmniejszenia liczby kolorów.

Interfejsy API używane w SVGcode

Teraz, gdy już wiesz, na co stać aplikację, pokażę Ci kilka interfejsów API, które sprawiają, że wszystko działa jak należy.

Progresywna aplikacja internetowa

SVGcode to instalowana progresywna aplikacja internetowa, która działa w trybie offline. Aplikacja opiera się na szablonie Vanilla JS dla Vite.js i korzysta z popularnej wtyczki VITE PWA, która tworzy usługę workera, która pod spodem korzysta z Workbox.js. Workbox to zbiór bibliotek, które mogą służyć do tworzenia skryptów service worker dostępnych w wersji produkcyjnej w progresywnych aplikacjach internetowych. Ten wzór może nie działać w przypadku wszystkich aplikacji, ale w przypadku SVGcode jest świetny.

Nakładka z elementami sterującymi okna

Aby zmaksymalizować dostępną przestrzeń na ekranie, SVGcode używa nakładki z elementami sterującymi okna, przenosząc główne menu do obszaru paska tytułu. Możesz zobaczyć, jak to działa, na końcu procesu instalacji.

Instalowanie kodu SVG i aktywowanie funkcji dostosowywania nakładki elementów sterujących oknem.

File System Access API

Aby otwierać pliki obrazów wejściowych i zapisywać powstałe pliki SVG, używam interfejsu File System Access API. Pozwala mi to zachować odniesienie do wcześniej otwartych plików i kontynuować pracę w miejscu, w którym ją przerwałem, nawet po ponownym załadowaniu aplikacji. Za każdym razem, gdy obraz zostanie zapisany, zostanie zoptymalizowany za pomocą biblioteki svgo. Może to chwilę potrwać, w zależności od złożoności pliku SVG. Wyświetlenie okna zapisywania pliku wymaga gestu użytkownika. Dlatego ważne jest, aby uzyskać uchwyt pliku przed optymalizacją SVG, aby gest użytkownika nie został unieważniony do czasu, gdy gotowy będzie zoptymalizowany plik SVG.

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

Przeciągnij i upuść

Aby otworzyć obraz wejściowy, mogę użyć funkcji otwierania plików lub, jak widać powyżej, przeciągnąć i upuścić plik obrazu w aplikacji. Funkcja otwierania plików jest dość prosta, ale ciekawszy jest przypadek przeciągania i upuszczania. Jest to szczególnie przydatne, ponieważ możesz uzyskać uchwyt systemu plików z elementu przesyłania danych za pomocą metody getAsFileSystemHandle(). Jak już wspomniałem, mogę zachować ten identyfikator, aby był gotowy, gdy aplikacja zostanie ponownie załadowana.

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

Więcej informacji znajdziesz w artykule o interfejsie File System Access API. Jeśli chcesz, możesz też zapoznać się z kodem źródłowym SVGcode w src/js/filesystem.js.

Async Clipboard API

SVGcode jest też w pełni zintegrowany ze schowkiem systemu operacyjnego za pomocą interfejsu Async Clipboard API. Obrazy z eksploratora plików systemu operacyjnego możesz wkleić do aplikacji, klikając przycisk wklejania obrazu lub naciskając Command lub Control + V na klawiaturze.

Wklejanie obrazu z Eksploratora plików do kodu SVG.

Interfejs Async Clipboard API został niedawno rozszerzony o obsługę obrazów SVG, dzięki czemu możesz kopiować obrazy SVG i wklejać je w innej aplikacji w celu dalszego przetwarzania.

Kopiowanie obrazu z kodu SVG do 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'));
});

Więcej informacji znajdziesz w artykule Schowek asynchroniczny lub pliku src/js/clipboard.js.

Obsługa plików

Jedną z moich ulubionych funkcji SVGcode jest to, jak dobrze komponuje się on z systemem operacyjnym. Jako zainstalowana PWA może ona obsługiwać pliki, a nawet być domyślną aplikacją do obsługi plików graficznych. Oznacza to, że gdy jestem w Finderze na komputerze z systemem macOS, mogę kliknąć obraz prawym przyciskiem myszy i otworzyć go za pomocą SVGcode. Ta funkcja nosi nazwę obsługi plików i działa na podstawie właściwości file_handlers w pliku manifestu aplikacji internetowej i kole zadań, co pozwala aplikacji na wykorzystanie przekazanego pliku.

Otwieranie pliku z pulpitu przy zainstalowanej aplikacji SVGcode.
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;
    }
  }
});

Więcej informacji znajdziesz w artykule Ustawianie zainstalowanych aplikacji internetowych jako modułów obsługi plików. Kod źródłowy możesz wyświetlić w sekcji src/js/filehandling.js.

Udostępnianie przez internet (pliki)

Innym przykładem dopasowania do systemu operacyjnego jest funkcja udostępniania w aplikacji. Załóżmy, że chcę wprowadzić zmiany w pliku SVG utworzonym za pomocą SVGcode. W tym celu mogę zapisać plik, uruchomić aplikację do edycji SVG, a potem otworzyć plik SVG. Bardziej płynny proces to jednak użycie interfejsu Web Share API, który umożliwia bezpośrednie udostępnianie plików. Jeśli więc aplikacja do edycji plików SVG jest celem udostępniania, może bezpośrednio odbierać plik bez odchyleń.

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);
      }
    }
  }
});
Udostępnianie obrazu SVG w Gmailu.

Docelowy adres udostępniania w przeglądarce (pliki)

Odwrotnie, SVGcode może też działać jako docelowa aplikacja do udostępniania i odbierać pliki z innych aplikacji. Aby to działało, aplikacja musi poinformować system operacyjny za pomocą interfejsu Web Share Target API, jakie typy danych może akceptować. Dzieje się to za pomocą specjalnego pola w pliku manifestu aplikacji internetowej.

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

Ścieżka action nie istnieje, ale jest obsługiwana wyłącznie w obiekcie fetch w ramach usługi workera, który następnie przekazuje otrzymane pliki do przetworzenia w aplikacji.

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);
      })(),
    );
  }
});
Udostępnianie zrzutu ekranu do SVGcode.

Podsumowanie

To była krótka prezentacja zaawansowanych funkcji aplikacji SVGcode. Mam nadzieję, że ta aplikacja stanie się podstawowym narzędziem do przetwarzania obrazów, obok innych świetnych aplikacji, takich jak Squoosh czy SVGOMG.

SVGcode jest dostępny na stronie svgco.de. Widzisz, co zrobiłem? Kod źródłowy znajdziesz na GitHubie. Pamiętaj, że ponieważ Potrace jest objęty licencją GPL, tak samo jest w przypadku SVGcode. Życzę przyjemnej pracy z wektorami! Mam nadzieję, że SVGcode okaże się przydatny, a jego funkcje zainspirują Cię do stworzenia kolejnej aplikacji.

Podziękowania

Ten artykuł został sprawdzony przez Joe Medley.