SVGcode: một PWA để chuyển đổi hình ảnh đường quét thành đồ họa vectơ SVG

SVGcode là một Ứng dụng web tiến bộ cho phép bạn chuyển đổi hình ảnh đường quét như JPG, PNG, GIF, WebP, AVIF, v.v. sang đồ họa vectơ ở định dạng SVG. Tính năng này sử dụng API Truy cập hệ thống tệp, API Bảng nhớ tạm không đồng bộ, API Xử lý tệp và tuỳ chỉnh Lớp phủ chế độ điều khiển cửa sổ.

(Nếu bạn thích xem hơn so với nội dung đọc, thì bài viết này cũng được cung cấp dưới dạng video.)

Từ đường quét đến vectơ

Bạn đã bao giờ điều chỉnh tỷ lệ một hình ảnh và kết quả hình ảnh bị phân thành điểm ảnh và khiến bạn cảm thấy không hài lòng chưa? Nếu có, thì có thể bạn đã xử lý được một định dạng hình ảnh đường quét như WebP, PNG hoặc JPG.

Việc tăng tỷ lệ hình ảnh đường quét sẽ làm cho hình ảnh đó trông bị phân thành điểm ảnh.

Ngược lại, đồ hoạ vectơ là hình ảnh được xác định bởi các điểm trong hệ toạ độ. Các điểm này được nối với nhau bằng các đường thẳng và đường cong để tạo thành đa giác và hình dạng khác. Đồ hoạ vectơ có lợi thế hơn đồ hoạ đường quét ở chỗ đồ hoạ này có thể được tăng hoặc giảm tỷ lệ theo bất kỳ độ phân giải nào mà không bị vỡ ảnh.

Tăng tỷ lệ ảnh vectơ mà không làm giảm chất lượng.

Giới thiệu về mã SVG

Tôi đã tạo một PWA có tên là SVGcode. API này có thể giúp bạn chuyển đổi hình ảnh đường quét thành vectơ. Phần tín dụng đến hạn: Tôi không phát minh ra điều này. Với SVGcode, tôi chỉ sử dụng công cụ dòng lệnh có tên Potrace của Peter Selinger mà tôi đã chuyển đổi thành Web mã hoá để có thể sử dụng công cụ này trong ứng dụng web.

Ảnh chụp màn hình ứng dụng SVGcode.
Ứng dụng SVGcode.

Sử dụng SVGcode

Trước tiên, tôi muốn cho bạn thấy cách sử dụng ứng dụng. Tôi bắt đầu với hình ảnh giới thiệu cho Hội nghị Nhà phát triển Chrome mà tôi đã tải xuống từ kênh Twitter ChromiumDev. Đây là hình ảnh đường quét PNG mà sau đó tôi kéo vào ứng dụng SVGcode. Khi tôi thả tệp, ứng dụng sẽ theo dõi màu hình ảnh theo màu, cho đến khi phiên bản vectơ hoá của đầu vào xuất hiện. Bây giờ, tôi có thể phóng to hình ảnh và như bạn thấy, các cạnh vẫn sắc nét. Tuy nhiên, khi phóng to biểu trưng Chrome, bạn có thể thấy rằng quá trình theo dõi chưa hoàn hảo và đặc biệt là các đường viền của biểu trưng trông hơi lốm đốm. Tôi có thể cải thiện kết quả bằng cách bỏ dấu vết theo dõi bằng cách loại bỏ các vết ố bẩn có kích thước tối đa là 5 pixel.

Chuyển đổi hình ảnh bị thả sang SVG.

Áp phích trong mã SVG

Một bước quan trọng trong quá trình vectơ hoá, đặc biệt là đối với ảnh chụp, là áp phích ảnh đầu vào để giảm số lượng màu. SVGcode cho phép tôi làm việc này trên mỗi kênh màu và xem SVG tạo ra khi tôi thực hiện thay đổi. Khi hài lòng với kết quả, tôi có thể lưu SVG vào ổ đĩa cứng và sử dụng ở bất cứ nơi nào tôi muốn.

Áp phích hình ảnh để giảm số lượng màu sắc.

API dùng trong mã SVG

Giờ bạn đã biết khả năng của ứng dụng, hãy để tôi chỉ cho bạn một số API giúp làm cho điều kỳ diệu xảy ra.

Ứng dụng web tiến bộ

SVGcode là một Ứng dụng web tiến bộ có thể cài đặt và do đó được bật hoàn toàn ngoại tuyến. Ứng dụng này dựa trên mẫuVanilla JS cho Vite.js và sử dụng PWA Vite trình bổ trợ phổ biến để tạo một trình chạy dịch vụ sử dụng Workbox.js để nâng cao. Workbox là một tập hợp thư viện có thể hỗ trợ trình chạy dịch vụ sẵn sàng phát hành cho Ứng dụng web tiến bộ. Mẫu này có thể không nhất thiết hoạt động cho tất cả các ứng dụng, nhưng đối với trường hợp sử dụng của SVGcode thì thật tuyệt.

Lớp phủ chế độ điều khiển cửa sổ

Để tối đa hoá không gian màn hình hiện có, SVGcode sử dụng tính năng tuỳ chỉnh Lớp phủ điều khiển cửa sổ bằng cách di chuyển trình đơn chính lên trên khu vực thanh tiêu đề. Bạn có thể thấy mã này được kích hoạt ở cuối quy trình cài đặt.

Cài đặt SVGcode và kích hoạt chế độ tuỳ chỉnh Lớp phủ chế độ điều khiển cửa sổ.

API Truy cập hệ thống tệp

Để mở tệp hình ảnh đầu vào và lưu các SVG thu được, tôi sử dụng File System Access API (API Truy cập hệ thống tệp). Điều này cho phép tôi giữ lại thông tin tham chiếu đến các tệp đã mở trước đó và tiếp tục từ nơi tôi đã dừng lại, ngay cả sau khi ứng dụng tải lại. Bất cứ khi nào được lưu, hình ảnh sẽ được tối ưu hoá thông qua thư viện svgo. Quá trình này có thể mất một chút thời gian, tuỳ thuộc vào độ phức tạp của SVG. Việc hiển thị hộp thoại lưu tệp yêu cầu cử chỉ của người dùng. Do đó, điều quan trọng là phải có được tên người dùng tệp trước khi quá trình tối ưu hoá SVG diễn ra, để cử chỉ của người dùng không bị vô hiệu hoá vào thời điểm SVG được tối ưu hoá sẵn sàng.

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

Kéo và thả

Để mở hình ảnh nhập, tôi có thể sử dụng tính năng mở tệp hoặc như bạn đã thấy ở trên, chỉ cần kéo và thả tệp hình ảnh vào ứng dụng. Tính năng mở tệp khá đơn giản, thú vị hơn là kéo và thả. Điều đặc biệt hay ở đây là bạn có thể nhận xử lý hệ thống tệp từ mục chuyển dữ liệu thông qua phương thức getAsFileSystemHandle(). Như đã đề cập trước đó, tôi có thể duy trì tên người dùng này để ứng dụng sẵn sàng khi ứng dụng được tải lại.

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

Để biết thêm thông tin chi tiết, hãy xem bài viết về API Truy cập hệ thống tệp và nếu bạn quan tâm, hãy nghiên cứu mã nguồn SVGcode trong src/js/filesystem.js.

API Bảng nhớ tạm không đồng bộ

Mã SVG cũng được tích hợp đầy đủ với bảng nhớ tạm của hệ điều hành thông qua API Bảng nhớ tạm Async. Bạn có thể dán hình ảnh từ trình khám phá tệp của hệ điều hành vào ứng dụng bằng cách nhấp vào nút dán hình ảnh hoặc nhấn tổ hợp phím lệnh hoặc tổ hợp phím Control + v trên bàn phím.

Dán hình ảnh từ trình khám phá tệp vào mã SVG.

API Bảng nhớ tạm không đồng bộ gần đây cũng đã có khả năng xử lý hình ảnh SVG, vì vậy, bạn cũng có thể sao chép hình ảnh SVG và dán vào một ứng dụng khác để xử lý thêm.

Sao chép hình ảnh từ mã SVG vào 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'));
});

Để tìm hiểu thêm, hãy đọc bài viết về Bảng nhớ tạm không đồng bộ hoặc xem tệp src/js/clipboard.js.

Xử lý tệp

Một trong những tính năng yêu thích của tôi của SVGcode là khả năng kết hợp tốt với hệ điều hành. Dưới dạng một PWA đã cài đặt, PWA có thể trở thành trình xử lý tệp hoặc thậm chí là trình xử lý tệp mặc định đối với các tệp hình ảnh. Điều này có nghĩa là khi đang ở trong Finder trên máy macOS, tôi có thể nhấp chuột phải vào một hình ảnh và mở hình ảnh đó bằng SVGcode. Tính năng này có tên là Xử lý tệp và hoạt động dựa trên thuộc tính file_handlers trong Tệp kê khai ứng dụng web và hàng đợi khởi chạy, cho phép ứng dụng dùng tệp đã truyền.

Mở một tệp trên máy tính bằng ứng dụng SVGcode đã cài đặt.
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;
    }
  }
});

Để biết thêm thông tin, hãy xem phần Cho phép các ứng dụng web đã cài đặt làm trình xử lý tệp và xem mã nguồn trong src/js/filehandling.js.

Chia sẻ trên web (Tệp)

Một ví dụ khác về việc kết hợp với hệ điều hành là tính năng chia sẻ của ứng dụng. Giả sử tôi muốn chỉnh sửa tệp SVG được tạo bằng SVGcode, một cách để xử lý vấn đề này sẽ là lưu tệp, khởi chạy ứng dụng chỉnh sửa SVG, sau đó mở tệp SVG từ đó. Tuy nhiên, để suôn sẻ hơn thì việc sử dụng API Chia sẻ web sẽ cho phép chia sẻ trực tiếp các tệp. Vì vậy, nếu ứng dụng chỉnh sửa SVG là mục tiêu chia sẻ, thì ứng dụng đó có thể trực tiếp nhận tệp mà không bị lệch.

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);
      }
    }
  }
});
Chia sẻ hình ảnh SVG với Gmail.

Mục tiêu chia sẻ trên web (Tệp)

Ngược lại, mã SVG cũng có thể đóng vai trò là mục tiêu chia sẻ và nhận tệp từ các ứng dụng khác. Để làm được việc này, ứng dụng cần cho hệ điều hành biết thông qua Web Share Target API về những loại dữ liệu có thể chấp nhận. Việc này diễn ra thông qua một trường chuyên dụng trong Tệp kê khai ứng dụng 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"]
        }
      ]
    }
  }
}

Tuyến action không thực sự tồn tại, nhưng chỉ được xử lý trong trình xử lý fetch của trình chạy dịch vụ, tuyến này sau đó sẽ truyền các tệp đã nhận để xử lý thực tế trong ứng dụng.

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);
      })(),
    );
  }
});
Chia sẻ ảnh chụp màn hình sang SVGcode.

Kết luận

Được rồi. Đây là hướng dẫn nhanh về một số tính năng nâng cao của ứng dụng trong SVGcode. Tôi hy vọng ứng dụng này có thể trở thành một công cụ thiết yếu cho nhu cầu xử lý hình ảnh của bạn cùng với các ứng dụng tuyệt vời khác như Squoosh hoặc SVGOMG.

Mã SVG có sẵn tại svgco.de. Xem những gì tôi đã làm ở đó? Bạn có thể xem mã nguồn của mã trên GitHub. Xin lưu ý rằng vì Potrace được cấp phép GPL, nên SVGcode cũng vậy. Vậy là bạn đã có thể sử dụng các biểu đồ vectơ hiệu quả! Tôi hy vọng SVGcode sẽ hữu ích và một số tính năng của nó có thể truyền cảm hứng cho ứng dụng tiếp theo của bạn.

Xác nhận

Bài viết này đã được Joe Medley đánh giá.