SVGcode: PWA untuk mengonversi gambar raster menjadi grafik vektor SVG

SVGcode adalah Progressive Web App yang memungkinkan Anda mengonversi gambar raster seperti JPG, PNG, GIF, WebP, AVIF, dll. ke grafik vektor dalam format SVG. Aplikasi ini menggunakan File System Access API, Async Clipboard API, File Handling API, dan penyesuaian Window Controls Overlay.

(Jika Anda lebih suka menonton daripada membaca, artikel ini juga tersedia sebagai video.)

Dari raster ke vektor

Pernahkah Anda menskalakan gambar dan hasilnya menjadi pecah dan tidak memuaskan? Jika begitu, Anda mungkin telah menangani format gambar raster seperti WebP, PNG, atau JPG.

Menskalakan gambar raster akan membuatnya terlihat pecah.

Sebaliknya, grafik vektor adalah gambar yang ditentukan oleh titik dalam sistem koordinat. Titik-titik ini dihubungkan oleh garis dan kurva untuk membentuk poligon dan bentuk lainnya. Grafik vektor memiliki keunggulan dibandingkan grafik raster karena dapat ditingkatkan atau diturunkan ke resolusi apa pun tanpa pikselasi.

Menskalakan gambar vektor tanpa kehilangan kualitas.

Memperkenalkan SVGcode

Saya telah membuat PWA bernama SVGcode yang dapat membantu Anda mengonversi gambar raster menjadi vektor. Saya tidak menciptakannya: Dengan SVGcode, saya hanya menggunakan alat command line yang disebut Potrace oleh Peter Selinger yang telah saya konversi ke Web Assembly, sehingga dapat digunakan di aplikasi Web.

Screenshot aplikasi SVGcode.
Aplikasi SVGcode.

Menggunakan SVGcode

Pertama, saya ingin menunjukkan cara menggunakan aplikasi. Saya memulai dengan gambar teaser untuk Chrome Dev Summit yang saya download dari channel Twitter ChromiumDev. Ini adalah gambar raster PNG yang kemudian saya tarik ke aplikasi SVGcode. Saat saya meletakkan file, aplikasi akan melacak warna gambar satu per satu, hingga versi vektor input muncul. Sekarang saya dapat memperbesar gambar, dan seperti yang dapat Anda lihat, tepinya tetap tajam. Namun, saat memperbesar logo Chrome, Anda dapat melihat bahwa pelacakannya tidak sempurna, dan terutama garis luar logo terlihat sedikit berbintik. Saya dapat meningkatkan hasilnya dengan menghilangkan speckle pada rekaman aktivitas dengan menekan speckle hingga, misalnya, lima piksel.

Mengonversi gambar yang dilepas ke SVG.

Posterisasi di SVGcode

Langkah penting untuk vektorisasi, terutama untuk gambar fotografi, adalah posterisasi gambar input untuk mengurangi jumlah warna. SVGcode memungkinkan saya melakukannya per saluran warna, dan melihat SVG yang dihasilkan saat saya membuat perubahan. Jika sudah puas dengan hasilnya, saya dapat menyimpan SVG ke hard disk dan menggunakannya di mana pun saya suka.

Mem-poster gambar untuk mengurangi jumlah warna.

API yang digunakan di SVGcode

Setelah Anda melihat kemampuan aplikasi, izinkan saya menunjukkan beberapa API yang membantu melakukannya.

Progressive Web App

SVGcode adalah Progressive Web App yang dapat diinstal dan karenanya diaktifkan sepenuhnya secara offline. Aplikasi ini didasarkan pada template Vanilla JS untuk Vite.js dan menggunakan PWA plugin Vite yang populer, yang membuat pekerja layanan yang menggunakan Workbox.js di balik layar. Workbox adalah kumpulan library yang dapat mendukung pekerja layanan siap produksi untuk Progressive Web App. Pola ini mungkin tidak selalu berfungsi untuk semua aplikasi, tetapi pola ini bagus untuk kasus penggunaan SVGcode.

Overlay Kontrol Jendela

Untuk memaksimalkan ruang layar yang tersedia, SVGcode menggunakan penyesuaian Overlay Kontrol Jendela dengan memindahkan menu utamanya ke atas dalam area titlebar. Anda dapat melihatnya diaktifkan di akhir alur penginstalan.

Menginstal SVGcode dan mengaktifkan penyesuaian Overlay Kontrol Jendela.

File System Access API

Untuk membuka file gambar input dan menyimpan SVG yang dihasilkan, saya menggunakan File System Access API. Hal ini memungkinkan saya menyimpan referensi ke file yang sebelumnya dibuka dan melanjutkan dari posisi terakhir, bahkan setelah aplikasi dimuat ulang. Setiap kali gambar disimpan, gambar akan dioptimalkan melalui library svgo, yang mungkin memerlukan waktu, bergantung pada kompleksitas SVG. Menampilkan dialog simpan file memerlukan gestur pengguna. Oleh karena itu, penting untuk mendapatkan handle file sebelum pengoptimalan SVG terjadi, sehingga gestur pengguna tidak menjadi tidak valid pada saat SVG yang dioptimalkan siap.

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

Tarik lalu lepas

Untuk membuka gambar input, saya dapat menggunakan fitur buka file, atau, seperti yang telah Anda lihat di atas, cukup tarik lalu lepas file gambar ke aplikasi. Fitur buka file cukup mudah, yang lebih menarik adalah kasus tarik lalu lepas. Menariknya, Anda bisa mendapatkan penanganan sistem file dari item transfer data melalui metode getAsFileSystemHandle(). Seperti yang disebutkan sebelumnya, saya dapat mempertahankan nama sebutan ini, sehingga siap saat aplikasi dimuat ulang.

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

Untuk mengetahui detail selengkapnya, lihat artikel tentang File System Access API dan, jika Anda tertarik, pelajari kode sumber SVGcode di src/js/filesystem.js.

Async Clipboard API

SVGcode juga sepenuhnya terintegrasi dengan papan klip sistem operasi melalui Async Clipboard API. Anda dapat menempelkan gambar dari file explorer sistem operasi ke dalam aplikasi dengan mengklik tombol tempel gambar atau dengan menekan command atau control plus v di keyboard.

Menempelkan gambar dari penjelajah file ke SVGcode.

Async Clipboard API baru-baru ini juga mendapatkan kemampuan untuk menangani gambar SVG, sehingga Anda juga dapat menyalin gambar SVG dan menempelkannya ke aplikasi lain untuk pemrosesan lebih lanjut.

Menyalin gambar dari SVGcode ke 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'));
});

Untuk mempelajari lebih lanjut, baca artikel Async Clipboard, atau lihat file src/js/clipboard.js.

Penanganan File

Salah satu fitur favorit saya dari SVGcode adalah betapa baik menyatu dengan sistem operasi. Sebagai PWA yang terinstal, PWA dapat menjadi pengendali file, atau bahkan pengendali file default, untuk file gambar. Artinya, saat berada di Finder di komputer macOS, saya dapat mengklik kanan gambar dan membukanya dengan SVGcode. Fitur ini disebut Penanganan File dan berfungsi berdasarkan properti file_handlers di Manifes Aplikasi Web dan antrean peluncuran, yang memungkinkan aplikasi menggunakan file yang diteruskan.

Membuka file dari desktop dengan aplikasi SVGcode yang diinstal.
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;
    }
  }
});

Untuk mengetahui informasi selengkapnya, lihat Mengizinkan aplikasi web terinstal menjadi pengendali file, dan melihat kode sumber di src/js/filehandling.js.

Berbagi Web (File)

Contoh lain dari penggabungan dengan sistem operasi adalah fitur berbagi aplikasi. Dengan asumsi saya ingin melakukan pengeditan pada SVG yang dibuat dengan SVGcode, salah satu cara untuk mengatasinya adalah dengan menyimpan file, meluncurkan aplikasi pengeditan SVG, lalu membuka file SVG dari sana. Namun, alur yang lebih lancar adalah menggunakan Web Share API, yang memungkinkan file dibagikan secara langsung. Jadi, jika aplikasi pengeditan SVG adalah target berbagi, aplikasi tersebut dapat langsung menerima file tanpa penyimpangan.

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);
      }
    }
  }
});
Membagikan gambar SVG ke Gmail.

Target Berbagi Web (File)

Sebaliknya, SVGcode juga dapat bertindak sebagai target berbagi dan menerima file dari aplikasi lain. Agar dapat berfungsi, aplikasi harus memberi tahu sistem operasi melalui Web Share Target API jenis data yang dapat diterimanya. Ini terjadi melalui kolom khusus di Manifes Aplikasi 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"]
        }
      ]
    }
  }
}

Rute action sebenarnya tidak ada, tetapi ditangani sepenuhnya di pengendali fetch pekerja layanan, yang kemudian meneruskan file yang diterima untuk pemrosesan sebenarnya di aplikasi.

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);
      })(),
    );
  }
});
Membagikan screenshot ke SVGcode.

Kesimpulan

Baiklah, ini adalah tur singkat tentang beberapa fitur aplikasi lanjutan di SVGcode. Semoga aplikasi ini dapat menjadi alat penting untuk kebutuhan pemrosesan gambar Anda bersama aplikasi luar biasa lainnya seperti Squoosh atau SVGOMG.

SVGcode tersedia di svgco.de. Lihat apa yang saya lakukan di sana? Anda dapat meninjau kode sumbernya di GitHub. Perhatikan bahwa karena Potrace memiliki lisensi GPL, SVGcode juga demikian. Dengan demikian, selamat melakukan vektorisasi. Saya harap SVGcode bermanfaat, dan beberapa fiturnya dapat menginspirasi aplikasi Anda berikutnya.

Ucapan terima kasih

Artikel ini ditinjau oleh Joe Medley.