> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# SVG sang Canvas: tải hình trước khi xuất PNG

> Tải toàn bộ SVG qua Image.decode, vẽ ở kích thước bitmap đã chọn, thử CORS và đọc lại PNG, đồng thời giữ nguồn chỉnh sửa được riêng với các pixel đã xuất.

Source: https://perfectvector.com/vi/blog/svg-canvas-image-export

Language: vi
Author: Irene Kim
Published: 2026-10-06T08:00:00+09:00
Updated: 2026-10-06T08:00:00+09:00

Để vẽ toàn bộ SVG lên canvas, tải nó dưới dạng ảnh, chờ `image.decode()` và truyền ảnh đó cho `drawImage()`. Sau đó kiểm tra xuất PNG riêng: ảnh có thể xuất hiện trên canvas trong khi trình duyệt chặn việc đọc hoặc lưu pixel.

Cách này vẽ tài liệu SVG như một ảnh. Nó không cung cấp đường dẫn SVG chỉnh sửa được cho canvas. Nếu thay vào đó bạn muốn dựng lại các đường bao được chọn, dùng [hướng dẫn SVG Path2D](https://perfectvector.com/vi/blog/canvas-path2d-svg). Giữ SVG gốc dù chọn cách hiển thị nào.

## Tách giải mã, vẽ và xuất

`HTMLImageElement` là đầu vào được chấp nhận của [canvas drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). Chiều rộng và chiều cao đích kiểm soát kích thước được vẽ. [Image.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) trả về promise hoàn tất khi ảnh được giải mã; yêu cầu thất bại hay vấn đề giải mã có thể khiến nó bị từ chối.

Giải mã thành công chỉ là bước kiểm tra đầu. Ảnh khác nguồn được tải không có quyền CORS vẫn có thể vẽ, nhưng làm canvas bị nhiễm nguồn. Đọc lại pixel và xuất sau đó thất bại. [Hướng dẫn ảnh có CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image) của MDN giải thích sự phân biệt này.

**Giữ nguồn, kiểm tra pixel**

![Minh họa khái niệm hình thoi rỗng và hình tròn dưới dạng đường bao nguồn, hình được kết xuất mượt và đầu ra lưới pixel](https://perfectvector.com/blog/svg-canvas-image-export/image-and-png.webp)

*Minh họa: đường bao nguồn và pixel được vẽ là các cách biểu diễn khác nhau. Khung, mũi tên và lưới thô mang tính khái niệm; kết quả trình duyệt bên dưới đến từ mẫu gốc được chạy độc lập.*

## Tạo SVG gốc độc lập

Lưu thành `master.svg`. Nó chứa một đường dẫn và một hình tròn, không có ảnh nhúng, phông chữ hay phụ thuộc bên ngoài. Hình thoi bên trong và vùng vẽ xung quanh trong suốt trong nguồn.

```svg
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="160" viewBox="0 0 240 160">
  <path d="M96 24 L152 80 L96 136 L40 80 Z M96 58 L118 80 L96 102 L74 80 Z" fill="#176b5b" fill-rule="evenodd"/>
  <circle cx="200" cy="80" r="24" fill="#d4a53a"/>
</svg>
```

Chúng tôi dùng toàn bộ tệp làm ảnh, gồm cả hai hình và `viewBox`. Khác với chỉ trích xuất giá trị `d` của một đường dẫn, cách này yêu cầu bộ kết xuất ảnh SVG của trình duyệt vẽ tài liệu.

Ví dụ chủ đích độc lập. SVG dùng làm ảnh có các hạn chế, gồm tài nguyên ngoài và tập lệnh; xem [hướng dẫn SVG làm ảnh của MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image). Đừng xem giải mã thành công tệp nhỏ này là bằng chứng mọi SVG sẽ tải phông chữ, kiểu hay hình liên kết bên ngoài. Với lựa chọn bàn giao, so sánh [SVG nội tuyến với phần tử ảnh](https://perfectvector.com/vi/blog/inline-svg-vs-img).

## Chạy hai nguồn cục bộ có phản hồi CORS khác nhau

Một nguồn bao gồm cả cổng. Máy chủ chỉ dùng cục bộ sau cung cấp cùng các byte SVG qua hai nguồn. Ở nguồn thứ hai, `/cors/master.svg` có `Access-Control-Allow-Origin: *`; `/plain/master.svg` thì không. Điều này tách quyền trong phản hồi khỏi hình.

Lưu thành `serve-proof.py` cạnh SVG và HTML bên dưới. Chạy `python3 serve-proof.py`, rồi mở `http://127.0.0.1:8770/starter.html`. Cả hai cổng phải còn trống; nếu đổi, cập nhật máy chủ và HTML cùng nhau. Nhấn Enter trong terminal để dừng cả hai máy chủ.

```python
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from threading import Thread

root = Path(__file__).parent
class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(root), **kwargs)
    def do_GET(self):
        if self.path in ['/cors/master.svg', '/plain/master.svg']:
            payload = (root / 'master.svg').read_bytes()
            self.send_response(200)
            self.send_header('Content-Type', 'image/svg+xml')
            self.send_header('Cache-Control', 'no-store')
            if self.path.startswith('/cors/'):
                self.send_header('Access-Control-Allow-Origin', '*')
            self.end_headers()
            self.wfile.write(payload)
        else:
            super().do_GET()

servers = [ThreadingHTTPServer(('127.0.0.1', port), Handler) for port in (8770, 8783)]
for server in servers:
    Thread(target=server.serve_forever, daemon=True).start()
try:
    input('Original SVG proof on 127.0.0.1:8770 and :8783. Enter to stop.\n')
finally:
    for server in servers:
        server.shutdown()
        server.server_close()
```

Tiêu đề cho phép rộng này thuộc mẫu cục bộ phục vụ hình của chính chúng tôi. Với máy chủ ảnh thực tế, cấu hình nguồn được phép phù hợp trên máy chủ bạn kiểm soát. Đặt thuộc tính ảnh không cấp quyền truy cập nội dung máy chủ khác.

## Giải mã ảnh trước khi vẽ

Lưu trang hoàn chỉnh này thành `starter.html`. Mỗi trường hợp có canvas riêng, nên trường hợp bị nhiễm không thể làm nhiễm những trường hợp thành công. Đặt `crossOrigin` trước `src` cho yêu cầu cần CORS, như [tài liệu tham khảo crossOrigin của MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin) mô tả.

Bitmap canvas được đặt tường minh `480 × 320`; kích thước hiển thị CSS là `240 × 160`. Kích thước nội tại của ảnh gốc là `240 × 160`, và `drawImage()` co giãn nó vào kích thước bitmap đã chọn. Đây là kích thước được chọn, không phải phép đo tỷ lệ pixel thiết bị.

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Original SVG to canvas export</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  main { display: grid; grid-template-columns: repeat(2, 280px); gap: 20px; }
  figure { margin: 0; }
  canvas { display: block; width: 240px; height: 160px; background: white; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Decode, paint, then check PNG access</h1>
<main id="cases"></main>
<pre id="report"></pre>
<script>
  function pngBlob(canvas) {
    return new Promise((resolve, reject) => {
      try { canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Empty PNG')), 'image/png'); }
      catch (error) { reject(error); }
    });
  }
  function dataURL(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });
  }
  async function render(test) {
    const figure = document.createElement('figure');
    const canvas = document.createElement('canvas');
    canvas.width = 480;
    canvas.height = 320;
    const caption = document.createElement('figcaption');
    figure.append(canvas, caption);
    document.getElementById('cases').append(figure);
    const result = { id: test.id, url: new URL(test.src, location.href).href,
      crossOrigin: test.crossOrigin ?? null, canvasWidth: canvas.width, canvasHeight: canvas.height };
    const image = new Image();
    if (test.crossOrigin) image.crossOrigin = test.crossOrigin;
    image.src = result.url;
    try { await image.decode(); }
    catch (error) {
      result.stage = 'decode';
      result.error = error.name;
      caption.textContent = test.id + ': decode ' + error.name;
      return result;
    }
    result.naturalWidth = image.naturalWidth;
    result.naturalHeight = image.naturalHeight;
    const context = canvas.getContext('2d');
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    result.stage = 'painted';
    try {
      result.opening = [...context.getImageData(192, 160, 1, 1).data];
      result.fill = [...context.getImageData(192, 80, 1, 1).data];
      result.corner = [...context.getImageData(0, 0, 1, 1).data];
    } catch (error) { result.readbackError = error.name; }
    try {
      const blob = await pngBlob(canvas);
      result.pngType = blob.type;
      result.pngBytes = blob.size;
      result.pngDataURL = await dataURL(blob);
      result.export = 'ok';
      const download = document.createElement('a');
      download.href = result.pngDataURL;
      download.download = test.id + '.png';
      download.textContent = 'Save PNG';
      figure.append(download);
    } catch (error) { result.export = 'failed'; result.exportError = error.name; }
    caption.textContent = test.id + ': ' + result.stage + ', PNG ' + result.export;
    return result;
  }
  async function run() {
    const tests = [
      { id: 'same-origin', src: 'master.svg' },
      { id: 'cors-allowed', src: 'http://127.0.0.1:8783/cors/master.svg', crossOrigin: 'anonymous' },
      { id: 'no-cors-image', src: 'http://127.0.0.1:8783/plain/master.svg' },
      { id: 'cors-denied', src: 'http://127.0.0.1:8783/plain/master.svg', crossOrigin: 'anonymous' },
      { id: 'missing-file', src: 'missing.svg' },
    ];
    const results = [];
    for (const test of tests) results.push(await render(test));
    document.getElementById('report').textContent = JSON.stringify({ pageOrigin: location.origin, results }, (key, value) => key === 'pngDataURL' ? undefined : value, 2);
  }
  run();
</script>
</html>
```

Hàm xuất xử lý cả ngoại lệ được ném và kết quả blob null. [Canvas.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) hỗ trợ đầu ra PNG và có thể ném `SecurityError` khi canvas không origin-clean. Hai trường hợp thành công thêm liên kết Lưu PNG; báo cáo hiển thị bỏ phần dữ liệu base64.

## Đọc giai đoạn thất bại trước khi sửa SVG

Chúng tôi chạy đúng mã khởi đầu này trong một môi trường trình duyệt Chrome, rồi mở lại cả hai PNG đã lưu. Những quan sát này liên quan đến tệp gốc độc lập và hai nguồn cục bộ đó.

| Trường hợp                     | Yêu cầu ảnh                              | Vẽ/đọc lại/xuất quan sát được                        |
| ------------------------------ | ---------------------------------------- | ---------------------------------------------------- |
| Cùng nguồn                     | SVG cục bộ, không có thuộc tính CORS     | Giải mã ở 240 × 160; vẽ; đọc lại và PNG thành công   |
| Khác nguồn, được phép          | `anonymous` cùng tiêu đề phản hồi        | Giải mã; vẽ; đọc lại và PNG thành công               |
| Khác nguồn, không yêu cầu CORS | Không thuộc tính, không tiêu đề phản hồi | Giải mã và vẽ; đọc lại và xuất ném `SecurityError`   |
| Khác nguồn, bị từ chối         | `anonymous`, không tiêu đề phản hồi      | Giải mã bị từ chối với `EncodingError`; không thử vẽ |
| Thiếu tệp                      | 404 cùng nguồn                           | Giải mã bị từ chối với `EncodingError`; không thử vẽ |

Trường hợp không CORS hiển thị hình rõ ràng. Hình thức đó không xác lập quyền đọc hay xuất. Trường hợp CORS bị từ chối và thiếu tệp thất bại sớm hơn, nên viết lại hình học sẽ không xử lý được lỗi yêu cầu nào trong hai trường hợp.

Cả hai PNG đã xuất mở lại ở `480 × 320`. Trong lần chạy này chúng giống hệt từng byte, mỗi tệp 10.509 byte. Ở tọa độ bitmap `(192,160)`, khoảng trống là `RGBA (0,0,0,0)`; góc cũng có alpha 0. Mẫu màu xanh mòng két ở `(192,80)` là `(23,107,91,255)`. Các mẫu kiểm tra khoảng trống và màu tô của tệp này, không phải mọi pixel cạnh hay mọi tính năng SVG.

Các hình chữ nhật trắng trên trang trình duyệt là nền CSS phía sau canvas. Chúng không phải màu trắng được thêm vào PNG. Nếu kết quả cần nền đục, hãy vẽ nền đó tường minh trước ảnh và xác minh lại tệp đã xuất.

## Giữ bản chính có thể chỉnh sửa riêng với PNG

PNG đã lưu chứa pixel. Nó không mang theo đường dẫn gốc, hình tròn hay đường bao trong có thể chỉnh sửa. Giữ `master.svg` làm nguồn vector và chọn kích thước bitmap canvas cho đích thực sự cần PNG. Phóng to PNG có sẵn về sau không khôi phục hình học nguồn.

Chỉ tên tệp SVG cũng không chứng minh toàn bộ hình bên trong là hình học vector; dùng [hướng dẫn kiểm tra raster nhúng](https://perfectvector.com/vi/blog/svg-contains-raster-image) khi tệp chứa ảnh liên kết hay nhúng.

Nếu hình nguồn chỉ tồn tại dưới dạng ảnh raster và bạn cần đường bao có thể chỉnh sửa, [quy trình ảnh thành vector của PerfectVector](https://perfectvector.com/vi/image-to-vector) có thể chuẩn bị bản chính SVG. Kiểm tra đường viền, màu và khoảng trống trước khi dùng trong quy trình tải này. Vector hóa không sửa tiêu đề CORS hay cấp quyền đọc lại canvas.

## Câu hỏi thường gặp

**Tôi có thể truyền mã đánh dấu SVG trực tiếp cho drawImage() không?**
Không. Ví dụ này tải tệp SVG vào HTMLImageElement và truyền ảnh đã giải mã cho drawImage(). Chuỗi mã đánh dấu không phải đối tượng ảnh đó. Giữ việc tải tài liệu khác với tạo dữ liệu đường dẫn Path2D.

**Tại sao hình xuất hiện nhưng xuất PNG thất bại?**
Ảnh khác nguồn được tải không có quyền CORS có thể làm canvas bị nhiễm sau khi vẽ. Trong trường hợp không CORS của chúng tôi, vẽ thành công nhưng cả đọc lại pixel và xuất đều ném SecurityError. Kiểm tra chế độ yêu cầu và phản hồi máy chủ thay vì cho rằng hình học SVG bị hỏng.

**crossOrigin anonymous có sửa mọi URL ảnh không?**
Không. Đặt nó trước src và máy chủ ảnh phải cung cấp phản hồi CORS tương thích. Cùng hình của chúng tôi giải mã thất bại khi yêu cầu anonymous từ điểm cuối không có tiêu đề cấp quyền.

**Xuất canvas thành PNG có giữ đường dẫn SVG chỉnh sửa được không?**
Không. PNG là đầu ra raster. Giữ SVG gốc riêng nếu cần sửa đường dẫn, hình hay khoảng trống, và kiểm tra PNG ở kích thước bàn giao dự kiến.

## Nguồn tham khảo

1. [MDN: CanvasRenderingContext2D.drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) — Đối tượng ảnh được chấp nhận, kích thước đích và kích thước nội tại.
2. [MDN: HTMLImageElement.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) — Promise sẵn sàng của ảnh và từ chối yêu cầu/giải mã.
3. [MDN: Ảnh có CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image) — Hạn chế origin-clean với đọc và xuất ảnh đã vẽ.
4. [MDN: HTMLImageElement.crossOrigin](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin) — Chế độ yêu cầu CORS để tải ảnh.
5. [MDN: HTMLCanvasElement.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) — Tạo blob PNG, đầu ra null và SecurityError.
6. [MDN: SVG làm ảnh](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image) — Ngữ cảnh ảnh SVG và hạn chế tài nguyên ngoài.

***

Bạn đang dùng bản gốc raster nhưng cần hình chỉnh sửa được? [Tạo bản chính SVG bằng PerfectVector](https://perfectvector.com/vi/image-to-vector), kiểm tra đường viền và khoảng trống, rồi giữ bản chính trong khi kiểm tra kết xuất canvas và PNG đã xuất riêng.

## Explore

- [Read the HTML article](https://perfectvector.com/vi/blog/svg-canvas-image-export)
- [Biểu tượng SVG MapLibre: pixel và kích thước ký hiệu](https://perfectvector.com/vi/blog/maplibre-svg-symbol-image.md)
- [Ảnh SVG trong p5.js: tải, co giãn và giữ nguồn](https://perfectvector.com/vi/blog/p5js-svg-artwork.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
