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

# SVG เป็น Canvas: โหลดภาพก่อนส่งออก PNG

> โหลด SVG ทั้งไฟล์ด้วย Image.decode วาดตามขนาดบิตแมปที่เลือก ทดสอบ CORS และอ่าน PNG แล้วเก็บต้นฉบับแก้ไขได้แยกจากพิกเซลส่งออก

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

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

หากต้องการวาด SVG ทั้งไฟล์บน canvas ให้โหลดเป็นภาพ รอ `image.decode()` แล้วส่งภาพเข้า `drawImage()` จากนั้นตรวจส่งออก PNG แยก ภาพอาจเห็นบน canvas แม้เบราว์เซอร์บล็อกการอ่านหรือบันทึกพิกเซล

วิธีนี้ระบายเอกสาร SVG เป็นภาพ ไม่ให้เส้น SVG แก้ไขได้กับ canvas หากต้องการสร้างบางเส้นขอบใหม่ ใช้[คู่มือ Path2D SVG](https://perfectvector.com/th/blog/canvas-path2d-svg) เก็บ SVG ต้นฉบับไม่ว่าจะเลือกทางแสดงใด

## แยกถอดรหัส ระบาย และส่งออก

`HTMLImageElement` เป็นอินพุตที่ [canvas drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) รับ ความกว้างสูงปลายทางควบคุมขนาดระบาย [Image.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) คืน promise ที่สำเร็จเมื่อภาพถอดรหัสแล้ว คำขอล้มเหลวหรือปัญหาถอดรหัสอาจทำให้ถูกปฏิเสธ

ถอดรหัสสำเร็จเป็นเพียงการตรวจแรก ภาพข้ามต้นทางที่โหลดโดยไม่มีสิทธิ์ CORS ยังวาดได้ แต่ทำให้ canvas ปนเปื้อน การอ่านพิกเซลและส่งออกจะล้มเหลว [คู่มือภาพเปิด CORS ของ MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image) อธิบายความต่าง

**เก็บต้นฉบับ ตรวจพิกเซล**

![ภาพแนวคิดของข้าวหลามตัดโปร่งกลางและวงกลมเป็นเส้นต้นฉบับ ภาพเรนเดอร์เรียบ และผลกริดพิกเซล](https://perfectvector.com/blog/svg-canvas-image-export/image-and-png.webp)

*ภาพอธิบาย: เส้นขอบต้นฉบับกับพิกเซลที่ระบายเป็นการแทนคนละแบบ กรอบ ลูกศร และกริดหยาบเป็นแนวคิด ผลเบราว์เซอร์ด้านล่างมาจากไฟล์ต้นฉบับที่ทดลองอิสระ*

## สร้าง SVG ต้นฉบับครบในตัว

บันทึกเป็น `master.svg` มีหนึ่งเส้นและหนึ่งวงกลม ไม่มีภาพฝัง ฟอนต์ หรือการพึ่งภายนอก รูปข้าวหลามตัดด้านในและพื้นที่รอบโปร่งใสในต้นฉบับ

```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>
```

เราใช้ทั้งไฟล์เป็นภาพ รวมสองรูปและ `viewBox` ต่างจากดึงเฉพาะค่า `d` ของเส้น วิธีนี้ให้ตัวเรนเดอร์ภาพ SVG เบราว์เซอร์ระบายเอกสาร

ตัวอย่างตั้งใจครบในตัว SVG ที่ใช้เป็นภาพมีข้อจำกัด รวมทรัพยากรภายนอกและสคริปต์ ดู[คู่มือ SVG เป็นภาพของ MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image) อย่าถือว่าถอดรหัสไฟล์เล็กนี้สำเร็จพิสูจน์ว่า SVG ใด ๆ จะโหลดฟอนต์ สไตล์ หรือภาพเชื่อมภายนอกได้ สำหรับทางส่งงาน เทียบ [SVG inline กับองค์ประกอบภาพ](https://perfectvector.com/th/blog/inline-svg-vs-img)

## รันสองต้นทางในเครื่องที่ตอบ CORS ต่างกัน

ต้นทางรวมพอร์ตด้วย เซิร์ฟเวอร์เฉพาะในเครื่องต่อไปให้ไบต์ SVG เดียวกันผ่านสองต้นทาง ต้นทางที่สอง `/cors/master.svg` มี `Access-Control-Allow-Origin: *` ส่วน `/plain/master.svg` ไม่มี จึงแยกสิทธิ์คำตอบออกจากตัวภาพ

บันทึกเป็น `serve-proof.py` ข้าง SVG กับ HTML ด้านล่าง รัน `python3 serve-proof.py` แล้วเปิด `http://127.0.0.1:8770/starter.html` ทั้งสองพอร์ตต้องว่าง หากเปลี่ยนให้อัปเดตเซิร์ฟเวอร์และ HTML พร้อมกัน กด Enter ในเทอร์มินัลเพื่อหยุดทั้งสองเซิร์ฟเวอร์

```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()
```

เฮดเดอร์เปิดกว้างนี้เป็นของการทดสอบในเครื่องที่ให้ภาพของเราเอง สำหรับโฮสต์ภาพใช้งานจริง ให้ตั้งต้นทางที่อนุญาตอย่างเหมาะสมบนเซิร์ฟเวอร์ที่คุณควบคุม การตั้งแอตทริบิวต์ภาพไม่ได้ให้สิทธิ์เนื้อหาอีกเซิร์ฟเวอร์

## ถอดรหัสภาพก่อนวาด

บันทึกหน้าครบนี้เป็น `starter.html` แต่ละกรณีมี canvas ของตน จึงไม่ให้กรณีปนเปื้อนส่งผลต่อกรณีสำเร็จ ตั้ง `crossOrigin` ก่อน `src` สำหรับคำขอที่ต้องใช้ CORS ตาม[เอกสาร crossOrigin ของ MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin)

บิตแมป canvas ระบุ `480 × 320` ส่วนขนาดแสดง CSS คือ `240 × 160` ภาพต้นฉบับมีขนาดธรรมชาติ `240 × 160` และ `drawImage()` ปรับลงขนาดบิตแมปที่เลือก นี่เป็นขนาดที่เลือก ไม่ใช่การวัดอัตราส่วนพิกเซลอุปกรณ์

```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>
```

ฟังก์ชันส่งออกจัดการทั้ง exception ที่โยนและ blob ว่าง [Canvas.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) รองรับ PNG และอาจโยน `SecurityError` เมื่อ canvas ไม่สะอาดด้านต้นทาง สองกรณีสำเร็จเพิ่มลิงก์ Save PNG รายงานที่เห็นละข้อมูล base64

## อ่านขั้นที่ล้มเหลวก่อนเปลี่ยน SVG

เรารันตัวอย่างนี้ตรง ๆ ในสภาพแวดล้อม Chrome หนึ่งชุด แล้วเปิด PNG ที่บันทึกทั้งคู่ใหม่ ข้อสังเกตเกี่ยวกับไฟล์ต้นฉบับครบในตัวและสองต้นทางในเครื่องนี้

| กรณี                  | คำขอภาพ                             | ผลวาด/อ่าน/ส่งออกที่เห็น                            |
| --------------------- | ----------------------------------- | --------------------------------------------------- |
| ต้นทางเดียว           | SVG ในเครื่อง ไม่มีแอตทริบิวต์ CORS | ถอดรหัส 240 × 160 ระบาย อ่านและ PNG สำเร็จ          |
| ข้ามต้นทาง อนุญาต     | `anonymous` บวกเฮดเดอร์ตอบ          | ถอดรหัส ระบาย อ่านและ PNG สำเร็จ                    |
| ข้ามต้นทาง ไม่ขอ CORS | ไม่มีแอตทริบิวต์และเฮดเดอร์ตอบ      | ถอดรหัสและระบาย แต่อ่านกับส่งออกโยน `SecurityError` |
| ข้ามต้นทาง ไม่อนุญาต  | `anonymous` ไม่มีเฮดเดอร์ตอบ        | ถอดรหัสถูกปฏิเสธด้วย `EncodingError` ไม่ได้ลองวาด   |
| ไฟล์หาย               | 404 ต้นทางเดียว                     | ถอดรหัสถูกปฏิเสธด้วย `EncodingError` ไม่ได้ลองวาด   |

กรณีไม่มี CORS เห็นภาพจริง การเห็นไม่ได้ยืนยันสิทธิ์อ่านหรือส่งออก กรณี CORS ไม่อนุญาตและไฟล์หายล้มก่อน การเขียนรูปทรงใหม่จึงไม่แก้ความล้มเหลวของคำขอเหล่านั้น

PNG ส่งออกทั้งคู่เปิดใหม่ที่ `480 × 320` ในการรันนี้ไบต์ตรงกัน แต่ละไฟล์ 10,509 ไบต์ ที่พิกัดบิตแมป `(192,160)` ช่องคือ `RGBA (0,0,0,0)` มุมก็มีอัลฟาศูนย์ ตัวอย่างสีเขียวน้ำทะเลที่ `(192,80)` คือ `(23,107,91,255)` ตัวอย่างเหล่านี้ตรวจช่องกับสีเติมของไฟล์นี้ ไม่ใช่ทุกพิกเซลขอบหรือทุกคุณสมบัติ SVG

สี่เหลี่ยมขาวบนหน้าเบราว์เซอร์คือพื้นหลัง CSS หลัง canvas ไม่ใช่สีขาวเพิ่มใน PNG หากต้องการพื้นทึบ ให้ระบายพื้นนั้นชัดเจนก่อนวาดภาพ แล้วตรวจไฟล์ส่งออกอีกครั้ง

## เก็บต้นฉบับแก้ไขได้แยกจาก PNG

PNG ที่บันทึกมีพิกเซล ไม่ได้บรรจุเส้น วงกลม หรือขอบในที่แก้ไขได้เดิม เก็บ `master.svg` เป็นต้นทางเวกเตอร์ และเลือกขนาดบิตแมป canvas สำหรับปลายทางที่ต้องใช้ PNG จริง การขยาย PNG เดิมภายหลังไม่คืนรูปทรงต้นฉบับ

ชื่อ SVG อย่างเดียวก็ไม่พิสูจน์ว่าภายในทั้งหมดเป็นเวกเตอร์ ใช้[คู่มือตรวจแรสเตอร์ฝังตัว](https://perfectvector.com/th/blog/svg-contains-raster-image) เมื่อไฟล์มีภาพเชื่อมหรือฝัง

หากต้นฉบับมีเพียงแรสเตอร์และต้องการเส้นขอบแก้ไขได้ [ขั้นตอนภาพเป็นเวกเตอร์ของ PerfectVector](https://perfectvector.com/th/image-to-vector) เตรียมต้นฉบับ SVG ได้ ตรวจเส้น สี และช่องก่อนใช้ในรูทีนโหลดนี้ การแปลงเวกเตอร์ไม่แก้เฮดเดอร์ CORS หรือให้สิทธิ์อ่าน canvas

## คำถามที่พบบ่อย

**ส่งมาร์กอัป SVG ตรงให้ drawImage() ได้หรือไม่?**
ไม่ได้ ตัวอย่างนี้โหลดไฟล์ SVG เป็น HTMLImageElement และส่งภาพถอดรหัสให้ drawImage() สตริงมาร์กอัปไม่ใช่วัตถุภาพนั้น แยกการโหลดเอกสารออกจากสร้างข้อมูลเส้น Path2D

**ทำไมเห็นภาพแต่ส่งออก PNG ไม่ได้?**
ภาพข้ามต้นทางที่โหลดโดยไม่มีสิทธิ์ CORS อาจทำ canvas ปนเปื้อนหลังวาด ในกรณีไม่มี CORS ของเรา ระบายสำเร็จแต่อ่านพิกเซลและส่งออกโยน SecurityError ตรวจโหมดคำขอกับคำตอบเซิร์ฟเวอร์แทนคิดว่ารูปทรง SVG เสีย

**crossOrigin anonymous แก้ทุก URL ภาพหรือไม่?**
ไม่ ตั้งก่อน src และเซิร์ฟเวอร์ภาพต้องตอบ CORS ที่เข้ากัน ภาพเดียวกันของเราถอดรหัสล้มเมื่อขอ anonymous จากปลายทางไม่มีเฮดเดอร์สิทธิ์

**ส่งออก canvas เป็น PNG เก็บเส้น SVG แก้ไขได้หรือไม่?**
ไม่ PNG เป็นผลแรสเตอร์ เก็บ SVG ต้นฉบับแยกหากต้องแก้เส้น รูป หรือช่อง และตรวจ PNG ที่ขนาดส่งงานเป้าหมาย

## แหล่งข้อมูล

1. [MDN: CanvasRenderingContext2D.drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) — วัตถุภาพที่รับ ขนาดปลายทาง และขนาดธรรมชาติ
2. [MDN: HTMLImageElement.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) — Promise ภาพพร้อมและการปฏิเสธคำขอ/ถอดรหัส
3. [MDN: ภาพเปิด CORS](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image) — ข้อจำกัดต้นทางสะอาดในการอ่านและส่งออกภาพวาด
4. [MDN: HTMLImageElement.crossOrigin](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin) — โหมดคำขอ CORS สำหรับโหลดภาพ
5. [MDN: HTMLCanvasElement.toBlob()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) — สร้าง PNG blob ผลว่าง และ SecurityError
6. [MDN: SVG เป็นภาพ](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image) — บริบทภาพ SVG และข้อจำกัดทรัพยากรภายนอก

***

ทำงานจากแรสเตอร์เดิมที่ต้องการภาพแก้ไขได้หรือไม่? [สร้างต้นฉบับ SVG ด้วย PerfectVector](https://perfectvector.com/th/image-to-vector) ตรวจเส้นขอบและช่อง แล้วเก็บต้นฉบับขณะตรวจ canvas เรนเดอร์กับ PNG ส่งออกแยก

## Explore

- [Read the HTML article](https://perfectvector.com/th/blog/svg-canvas-image-export)
- [ไอคอน SVG ใน MapLibre: ตรวจพิกเซลและขนาดสัญลักษณ์](https://perfectvector.com/th/blog/maplibre-svg-symbol-image.md)
- [ภาพ SVG ใน p5.js: โหลด ปรับขนาด และเก็บต้นฉบับ](https://perfectvector.com/th/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)
