SVGcode: PWA для преобразования растровых изображений в векторную графику SVG.

SVGcode — это прогрессивное веб-приложение, позволяющее преобразовывать растровые изображения, такие как JPG, PNG, GIF, WebP, AVIF и т. д., в векторную графику в формате SVG. Оно использует API доступа к файловой системе, API асинхронного буфера обмена, API обработки файлов и настройку наложения элементов управления окнами.

(Если вы предпочитаете смотреть, а не читать, эта статья также доступна в виде видео .)

Из растра в вектор

Вы когда-нибудь масштабировали изображение, а результат был пикселизированным и неудовлетворительным? Если да, то вы, вероятно, имели дело с растровым форматом изображения, таким как WebP, PNG или JPG.

Увеличение масштаба растрового изображения делает его пикселизированным.

Напротив, векторная графика — это изображения, которые определяются точками в системе координат. Эти точки соединены линиями и кривыми, образуя многоугольники и другие фигуры. Векторная графика имеет преимущество перед растровой графикой в ​​том, что ее можно масштабировать до любого разрешения без пикселизации.

Масштабирование векторного изображения без потери качества.

Знакомство с SVGcode

Я создал PWA под названием SVGcode , который может помочь вам преобразовать растровые изображения в векторные. Благодарность там, где это необходимо: я не изобрел это. С SVGcode я просто стою на плечах инструмента командной строки под названием Potrace Питера Селинджера , который я преобразовал в Web Assembly , чтобы его можно было использовать в веб-приложении.

Скриншот приложения SVGcode.
Приложение SVGcode .

Использование SVG-кода

Сначала я хочу показать вам, как пользоваться приложением. Я начну с тизерного изображения для Chrome Dev Summit, которое я скачал с канала ChromiumDev в Twitter. Это растровое изображение PNG, которое я затем перетаскиваю в приложение SVGcode. Когда я перетаскиваю файл, приложение трассирует изображение цвет за цветом, пока не появится векторизованная версия ввода. Теперь я могу увеличить изображение, и, как вы видите, края остаются четкими. Но увеличив масштаб логотипа Chrome, вы увидите, что трассировка была неидеальной, и особенно контуры логотипа выглядят немного пятнистыми. Я могу улучшить результат, удалив пятнистость трассировки, подавив пятнистость размером, скажем, до пяти пикселей.

Преобразование перетащенного изображения в SVG.

Постеризация в SVGcode

Важным шагом векторизации, особенно для фотографических изображений, является постеризация входного изображения для уменьшения количества цветов. SVGcode позволяет мне делать это по каждому цветовому каналу и видеть полученный SVG по мере внесения изменений. Когда я буду доволен результатом, я могу сохранить SVG на жестком диске и использовать его где угодно.

Постеризация изображения для уменьшения количества цветов.

API, используемые в SVGcode

Теперь, когда вы увидели, на что способно приложение, позвольте мне показать вам некоторые API, которые помогают творить чудеса.

Прогрессивное веб-приложение

SVGcode — это устанавливаемое Progressive Web App, поэтому оно полностью офлайн-доступно. Приложение основано на шаблоне Vanilla JS для Vite.js и использует популярный плагин Vite PWA , который создает service worker, использующий Workbox.js под капотом. Workbox — это набор библиотек, которые могут обеспечить работу service worker для Progressive Web Apps, готового к производству. Этот шаблон может не обязательно работать для всех приложений, но для варианта использования SVGcode он великолепен.

Наложение элементов управления окнами

Чтобы максимально использовать доступное экранное пространство, SVGcode использует настройку Window Controls Overlay , перемещая свое главное меню вверх в область заголовка. Вы можете увидеть, как это активируется в конце процесса установки.

Установка SVGcode и активация настройки наложения элементов управления окнами.

API доступа к файловой системе

Чтобы открыть файлы входных изображений и сохранить полученные SVG, я использую API File System Access . Это позволяет мне сохранять ссылку на ранее открытые файлы и продолжать с того места, на котором я остановился, даже после перезагрузки приложения. Всякий раз, когда изображение сохраняется, оно оптимизируется с помощью библиотеки svgo , что может занять некоторое время в зависимости от сложности SVG. Для отображения диалогового окна сохранения файла требуется жест пользователя. Поэтому важно получить дескриптор файла до того, как произойдет оптимизация SVG, чтобы жест пользователя не стал недействительным к моменту готовности оптимизированного 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);
}

Перетащите и отпустите

Для открытия входного изображения я могу либо использовать функцию открытия файла, либо, как вы видели выше, просто перетащить файл изображения в приложение. Функция открытия файла довольно проста, более интересен случай перетаскивания. Что особенно приятно в этом, так это то, что вы можете получить дескриптор файловой системы из элемента передачи данных через метод getAsFileSystemHandle() . Как упоминалось ранее, я могу сохранить этот дескриптор, чтобы он был готов, когда приложение перезагрузится.

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

Более подробную информацию можно найти в статье File System Access API и, если вам интересно, изучите исходный код SVGcode в src/js/filesystem.js .

API асинхронного буфера обмена

SVGcode также полностью интегрирован с буфером обмена операционной системы через API Async Clipboard. Вы можете вставлять изображения из проводника файлов операционной системы в приложение, нажав кнопку вставки изображения или нажав command или control плюс v на клавиатуре.

Вставка изображения из проводника в SVGcode.

API асинхронного буфера обмена недавно получил возможность работать и с изображениями SVG, поэтому вы также можете скопировать изображение SVG и вставить его в другое приложение для дальнейшей обработки.

Копирование изображения из SVGcode в 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'));
});

Чтобы узнать больше, прочитайте статью Async Clipboard или посмотрите файл src/js/clipboard.js .

Обработка файлов

Одна из моих любимых функций SVGcode — это то, как хорошо он вписывается в операционную систему. Как установленный PWA, он может стать обработчиком файлов или даже обработчиком файлов по умолчанию для файлов изображений. Это означает, что когда я нахожусь в Finder на своей машине macOS, я могу щелкнуть правой кнопкой мыши по изображению и открыть его с помощью SVGcode. Эта функция называется File Handling и работает на основе свойства file_handlers в Web App Manifest и очереди запуска, что позволяет приложению использовать переданный файл.

Открытие файла с рабочего стола с установленным приложением 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;
    }
  }
});

Для получения дополнительной информации см. статью «Пусть установленные веб-приложения будут обработчиками файлов » и просмотрите исходный код в src/js/filehandling.js .

Веб-общение (файлы)

Другим примером слияния с операционной системой является функция общего доступа приложения. Если я хочу внести изменения в SVG, созданный с помощью SVGcode, одним из способов решения этой проблемы будет сохранение файла, запуск приложения для редактирования SVG, а затем открытие файла SVG оттуда. Однако более плавный процесс — использовать API Web Share , который позволяет делиться файлами напрямую. Таким образом, если приложение для редактирования SVG является целью общего доступа, оно может напрямую получать файл без отклонений.

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);
      }
    }
  }
});
Отправка изображения SVG в Gmail.

Веб-ресурс Share Target (файлы)

С другой стороны, SVGcode также может выступать в качестве цели общего доступа и получать файлы из других приложений. Чтобы это работало, приложению необходимо сообщить операционной системе через API Web Share Target, какие типы данных оно может принимать. Это происходит через специальное поле в 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"]
        }
      ]
    }
  }
}

Маршрут action на самом деле не существует, а обрабатывается исключительно в обработчике fetch Service Worker, который затем передает полученные файлы для фактической обработки в приложении.

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);
      })(),
    );
  }
});
Публикация снимка экрана в SVGcode.

Заключение

Хорошо, это был краткий обзор некоторых расширенных функций приложения в SVGcode. Надеюсь, это приложение станет важным инструментом для ваших потребностей в обработке изображений наряду с другими замечательными приложениями, такими как Squoosh или SVGOMG .

SVGcode доступен на svgco.de . Посмотрите, что я там сделал? Вы можете просмотреть его исходный код на GitHub . Обратите внимание, что поскольку Potrace имеет лицензию GPL, то и SVGcode тоже. И с этим, счастливой векторизации! Надеюсь, SVGcode будет полезен, и некоторые из его функций могут вдохновить ваше следующее приложение.

Благодарности

Эту статью рецензировал Джо Медли .