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 czy AVIF, na grafikę wektorową w formacie SVG. Używa do tego interfejsu File System Access API, Async Clipboard API, File handling API oraz nakładki Window Controls.

(jeśli wolisz zamiast czytać, ten artykuł jest dostępny jako film).

Od rastrowania do wektorowego

Czy zdarzyło Ci się kiedyś przeskalować obraz i uzyskać efekt pikselizacji, który nie był satysfakcjonujący? Jeśli tak, najprawdopodobniej korzystasz z formatu rastrowego, takiego jak WebP, PNG lub JPG.

Skalowanie obrazu rastrowego w górę sprawia, że wygląda on na pikselizację.

Grafika wektorowa to natomiast obrazy zdefiniowane przez punkty w układzie współrzędnych. Te punkty są połączone liniami i krzywymi, co tworzą wielokąty i inne kształty. Grafika wektorowa ma przewagę nad grafiką rastrową, ponieważ można ją skalować w górę lub w dół do dowolnej rozdzielczości bez pikseli.

Skalowanie obrazu wektorowego bez utraty jakości.

Przedstawiamy SVGcode

Mam utworzoną przeze mnie aplikację PWA o nazwie SVGcode, która pomoże Ci konwertować obrazy rastrowe na wektory. Środki przeznaczone na kredyt: nie został on wynaleziony przeze mnie. Dzięki kodowi SVG po prostu znajdę narzędzie wiersza poleceń o nazwie Potrace autorstwa Petera Selingera, które zostało przekonwertowane na Web Assembly i można go używać w aplikacji internetowej.

Zrzut ekranu aplikacji SVGcode.
Aplikacja SVGcode.

Korzystanie z kodu SVGcode

Najpierw pokażę Ci, jak korzystać z tej aplikacji. Zaczynam od obrazu zwiastuna Chrome Dev Summit, który został pobrany z kanału ChromiumDev na Twitterze. Jest to obraz rastrowy PNG, który przeciągam następnie do aplikacji SVGcode. Po upuszczeniu pliku aplikacja śledzi kolor obrazu według koloru, aż pojawi się wersja wektorowa danych wejściowych. Mogę teraz powiększyć obraz i jak widzicie, krawędzie pozostają ostre. Po powiększeniu logo Chrome widać, że śledzenie nie jest perfekcyjne, a jego kontury są nieco niewyraźne. Mogę poprawić wynik, usuwając zniekształcenia śledzenia przez eliminowanie zniekształceń nawet do pięciu pikseli.

Konwertuję upuszczony obraz do SVG.

Plakat w SVGcode

Ważnym etapem wektorizacji, zwłaszcza w przypadku zdjęć fotograficznych, jest plakaty z obrazem wejściowym, by zmniejszyć liczbę kolorów. Dzięki kodowi SVG mogę to zrobić dla każdego kanału koloru i widzieć wynikowy SVG po wprowadzeniu zmian. Jeśli wynik będzie zadowalający, mogę zapisać plik SVG na dysku twardym i używać go, gdzie tylko zechcesz.

Podawanie obrazu za plakatem w celu zmniejszenia liczby kolorów.

Interfejsy API używane w kodzie SVG

Skoro już wiesz, do czego służy ta aplikacja, pokażę Ci kilka interfejsów API, dzięki którym ta magia może działać.

Progresywna aplikacja internetowa

SVGcode to progresywna aplikacja internetowa, którą można zainstalować, a tym samym w pełni dostępna w trybie offline. Aplikacja opiera się na szablonie Vanilla JS dla Vite.js i korzysta z popularnej wtyczki PWA, która tworzy skrypt service worker, który wykorzystuje pod maską instancję Workbox.js. Workbox to zestaw bibliotek, które mogą obsługiwać skrypt service worker gotowy do wykorzystania w progresywnych aplikacjach internetowych. Ten wzorzec może niekoniecznie działać we wszystkich aplikacjach, ale w przypadku kodu SVG na pewno świetnie się sprawdza.

Nakładka z elementami sterującymi okna

Aby zmaksymalizować dostępne miejsce na ekranie, SVGcode wykorzystuje dostosowanie nakładki elementów sterujących oknami, przenosząc menu główne do obszaru paska tytułu. Taka aktywacja będzie widoczna na końcu procesu instalacji.

Instaluję kod SVG i aktywuję dostosowanie nakładki elementów sterujących oknami.

File System Access API

Aby otworzyć wejściowe pliki obrazów i zapisać wynikowe pliki SVG, używam interfejsu File System Access API. Dzięki temu mogę zachować odniesienie do wcześniej otwartych plików i kontynuować od tego samego miejsca, nawet po ponownym załadowaniu aplikacji. Po każdym zapisaniu obraz jest optymalizowany za pomocą biblioteki svgo, co może zająć trochę czasu w zależności od złożoności obrazu SVG. Wyświetlanie okna zapisywania plików wymaga gestu użytkownika. Dlatego ważne jest, aby uzyskać uchwyt pliku przed rozpoczęciem optymalizacji SVG. Dzięki temu gest użytkownika nie zostanie unieważniony, gdy zoptymalizowany plik SVG będzie gotowy.

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ść

Do otwarcia obrazu wejściowego mogę użyć funkcji otwierania plików lub, jak już wiesz, przeciągnij i upuść plik obrazu do aplikacji. Otwieranie plików jest całkiem proste, ciekawsze jest przeciąganie i upuszczanie pliku. Szczególną zaletą jest możliwość uzyskania uchwytu systemu plików z elementu Przenoszenia danych za pomocą metody getAsFileSystemHandle(). Jak już wspomnieliśmy, mogę zachować ten nick, aby był gotowy po ponownym załadowaniu aplikacji.

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

Aby dowiedzieć się więcej, przeczytaj artykuł o interfejsie File System Access API. Jeśli Cię to interesuje, przeczytaj kod źródłowy SVG w src/js/filesystem.js.

Async Clipboard API

Kod 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 albo naciskając na klawiaturze Command lub Control + V.

Wklejenie obrazu z eksploratora plików do kodu SVG.

Interfejs Async Clipboard API niedawno uzyskał również możliwość obsługi obrazów SVG, dzięki czemu możesz też skopiować obraz SVG i wkleić go do innej aplikacji, aby kontynuować przetwarzanie.

Skopiowanie 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 w pliku src/js/clipboard.js.

Obsługa plików

Jedną z moich ulubionych funkcji SVGcode jest to, że wpasowuje się on w system operacyjny. Jako zainstalowana aplikacja PWA może stać się modułem obsługi plików, a nawet domyślnym modułem obsługi plików w przypadku plików graficznych. Oznacza to, że gdy jestem w programie Finder na komputerze z macOS, mogę kliknąć obraz prawym przyciskiem myszy i otworzyć go za pomocą kodu SVG. Ta funkcja nosi nazwę Obsługa plików i działa na podstawie właściwości file_handlers w pliku manifestu aplikacji internetowej oraz kolejki uruchamiania, która umożliwia aplikacji użycie przekazanego pliku.

Otwieranie pliku z pulpitu za pomocą 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 Zezwalanie zainstalowanym aplikacjom internetowym na moduły obsługi plików i wyświetlanie kodu źródłowego w src/js/filehandling.js.

Udostępnianie w internecie (pliki)

Innym przykładem łączenia się z systemem operacyjnym jest funkcja udostępniania aplikacji. Zakładając, że chcę wprowadzić zmiany w elemencie SVG utworzonym za pomocą kodu SVG. Aby rozwiązać ten problem, zapisz plik, uruchom aplikację do edycji SVG, a potem otwórz plik SVG. Płynniejszy proces polega jednak na użyciu interfejsu Web Share API, który umożliwia bezpośrednie udostępnianie plików. Jeśli więc aplikacja do edycji SVG służy do udostępniania plików, może bezpośrednio odbierać plik bez żadnych zmian.

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.

Cel udostępniania w internecie (pliki)

Z drugiej strony kod SVG może też służyć jako obiekt do udostępniania i odbierać pliki z innych aplikacji. Aby było to możliwe, aplikacja musi za pomocą interfejsu Web Share Target API poinformować system operacyjny, jakie typy danych akceptuje. Odbywa 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"]
        }
      ]
    }
  }
}

Trasa action w rzeczywistości nie istnieje, ale jest obsługiwana wyłącznie w module obsługi fetch skryptu service worker, który przekazuje odebrane pliki do rzeczywistego przetwarzania 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 w kodzie SVG.

Podsumowanie

To była krótka prezentacja niektórych zaawansowanych funkcji aplikacji w SVGcode. Mam nadzieję, że ta aplikacja (np. Squoosh czy SVGOMG) może stać się narzędziem do przetwarzania obrazów również w przypadku innych świetnych aplikacji.

Kod SVG jest dostępny na stronie svgco.de. Zobacz, co udało mi się zrobić. Kod źródłowy aplikacji znajdziesz na GitHubie. Potrace ma licencję GPL, więc jest to SVGcode. I życzę udanego publikowania wektorów. Mam nadzieję, że kod SVG Ci się przyda, a niektóre jego funkcje będą inspiracją do tworzenia kolejnych aplikacji.

Podziękowania

Autor artykułu: Joe Medley.