SVG ke Canvas: Muat Gambar Sebelum Mengekspor PNG
Muat SVG lengkap melalui Image.decode, gambar pada ukuran bitmap pilihan, uji CORS dan pembacaan PNG, serta pisahkan sumber yang bisa diedit dari piksel hasil ekspor.
Di halaman ini
Untuk menggambar SVG lengkap ke canvas, muat sebagai gambar, tunggu image.decode(), lalu teruskan gambar itu ke drawImage(). Kemudian periksa ekspor PNG secara terpisah: gambar bisa muncul pada canvas sementara peramban memblokir pembacaan atau penyimpanan pikselnya.
Jalur ini melukis dokumen SVG sebagai gambar. Jalur ini tidak memberikan path SVG yang bisa diedit kepada canvas. Jika ingin merekonstruksi kontur pilihan, gunakan panduan SVG Path2D. Simpan SVG asli apa pun jalur tampilan yang dipilih.
Pisahkan dekode, penggambaran, dan ekspor
HTMLImageElement merupakan masukan yang diterima canvas drawImage(). Lebar dan tinggi tujuannya mengendalikan ukuran yang digambar. Image.decode() mengembalikan promise yang selesai ketika gambar telah didekode; permintaan gagal atau masalah dekode bisa membuatnya ditolak.
Dekode berhasil hanyalah pemeriksaan pertama. Gambar lintas origin yang dimuat tanpa izin CORS masih bisa digambar, tetapi mencemari canvas. Pembacaan piksel dan ekspor kemudian gagal. Panduan gambar berkemampuan CORS MDN menjelaskan perbedaannya.

Buat SVG asli yang mandiri
Simpan sebagai master.svg. Berkas berisi satu path dan satu lingkaran, tanpa gambar tertanam, font, atau dependensi eksternal. Wajik dalam dan kanvas di sekelilingnya transparan pada sumber.
<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>Kami memakai berkas lengkap sebagai gambar, termasuk kedua bentuk dan viewBox-nya. Berbeda dari hanya mengekstrak nilai d sebuah path, ini meminta renderer gambar SVG peramban untuk melukis dokumen.
Contoh ini sengaja mandiri. SVG yang dipakai sebagai gambar memiliki pembatasan, termasuk sumber daya eksternal dan skrip; lihat panduan SVG sebagai gambar MDN. Jangan menganggap dekode berhasil pada berkas kecil ini membuktikan bahwa SVG sembarang akan memuat font, gaya, atau gambar tertaut eksternal. Untuk pilihan penyerahan, bandingkan SVG inline dengan elemen gambar.
Jalankan dua origin lokal dengan respons CORS berbeda
Origin mencakup port-nya. Server khusus lokal berikut menyajikan byte SVG yang sama melalui dua origin. Pada origin kedua, /cors/master.svg menyertakan Access-Control-Allow-Origin: *; /plain/master.svg tidak. Ini memisahkan izin respons dari gambar.
Simpan sebagai serve-proof.py di samping SVG dan HTML di bawah. Jalankan python3 serve-proof.py, lalu buka http://127.0.0.1:8770/starter.html. Kedua port harus kosong; jika diubah, perbarui server dan HTML bersama-sama. Tekan Enter di terminal untuk menghentikan kedua server.
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()Header permisif ini milik contoh uji lokal yang menyajikan gambar kami sendiri. Untuk host gambar produksi, konfigurasikan origin yang diizinkan sesuai kebutuhan pada server yang Anda kendalikan. Menetapkan atribut gambar tidak memberikan akses ke konten server lain.
Dekode gambar sebelum menggambarnya
Simpan halaman lengkap ini sebagai starter.html. Setiap kasus memiliki canvas sendiri, sehingga kasus tercemar tidak bisa mencemari kasus yang berhasil. Tetapkan crossOrigin sebelum src untuk permintaan yang membutuhkan CORS, seperti dijelaskan referensi crossOrigin MDN.
Bitmap canvas secara eksplisit 480 × 320; ukuran tampilan CSS-nya 240 × 160. Dimensi intrinsik gambar asli adalah 240 × 160, dan drawImage() menskalakannya ke dimensi bitmap pilihan. Ini dimensi yang dipilih, bukan pengukuran rasio piksel perangkat.
<!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>Fungsi ekspor menangani pengecualian yang dilempar maupun hasil blob null. Canvas.toBlob() mendukung keluaran PNG dan bisa melempar SecurityError saat canvas tidak origin-clean. Kedua kasus berhasil menambahkan tautan Simpan PNG; laporan yang terlihat menghilangkan payload base64.
Baca tahap kegagalan sebelum mengubah SVG
Kami menjalankan kode awal ini persis dalam satu lingkungan peramban Chrome, lalu membuka kembali kedua PNG tersimpan. Pengamatan ini berkaitan dengan berkas asli mandiri dan kedua origin lokal tersebut.
| Kasus | Permintaan gambar | Hasil gambar/pembacaan/ekspor yang diamati |
|---|---|---|
| Origin sama | SVG lokal, tanpa atribut CORS | Didekode pada 240 × 160; digambar; pembacaan dan PNG berhasil |
| Lintas origin, izin diberikan | anonymous ditambah header respons | Didekode; digambar; pembacaan dan PNG berhasil |
| Lintas origin, tanpa permintaan CORS | Tanpa atribut, tanpa header respons | Didekode dan digambar; pembacaan serta ekspor melempar SecurityError |
| Lintas origin, izin ditolak | anonymous, tanpa header respons | Dekode ditolak dengan EncodingError; penggambaran tidak dicoba |
| Berkas tidak ada | 404 dari origin sama | Dekode ditolak dengan EncodingError; penggambaran tidak dicoba |
Kasus tanpa CORS memperlihatkan gambar secara nyata. Tampilan itu tidak membuktikan izin untuk membaca atau mengekspornya. Kasus CORS ditolak dan berkas hilang gagal lebih awal, sehingga menulis ulang geometri tidak akan mengatasi kedua kegagalan permintaan.
Kedua PNG hasil ekspor dibuka kembali pada 480 × 320. Dalam eksekusi ini keduanya identik per byte, masing-masing 10.509 byte. Pada koordinat bitmap (192,160), bukaannya RGBA (0,0,0,0); sudut juga memiliki alfa nol. Sampel hijau kebiruan pada (192,80) adalah (23,107,91,255). Sampel ini memeriksa bukaan dan fill berkas ini, bukan setiap piksel tepi atau setiap fitur SVG.
Persegi panjang putih di halaman peramban merupakan latar CSS di belakang canvas. Itu bukan cat putih yang ditambahkan ke PNG. Jika hasil memerlukan latar buram, gambar latar tersebut secara eksplisit sebelum menggambar citra dan verifikasi lagi berkas hasil ekspor.
Pisahkan master yang bisa diedit dari PNG
PNG tersimpan berisi piksel. PNG tidak membawa path asli, lingkaran, atau kontur dalam yang bisa diedit. Simpan master.svg sebagai sumber vektor dan pilih dimensi bitmap canvas untuk tujuan yang benar-benar membutuhkan PNG. Memperbesar PNG yang ada nantinya tidak memulihkan geometri sumbernya.
Nama berkas SVG saja juga tidak membuktikan semua gambar di dalamnya berupa geometri vektor; gunakan panduan pemeriksaan raster tertanam saat berkas berisi gambar tertaut atau tertanam.
Jika gambar sumber hanya tersedia sebagai raster dan Anda membutuhkan kontur yang bisa diedit, alur gambar-ke-vektor PerfectVector dapat menyiapkan master SVG. Periksa kontur, warna, dan bukaannya sebelum memakainya dalam prosedur pemuatan ini. Vektorisasi tidak memperbaiki header CORS atau memberikan izin pembacaan canvas.
Pertanyaan umum
Bisakah saya meneruskan markup SVG langsung ke drawImage()? Tidak. Contoh ini memuat berkas SVG ke HTMLImageElement dan meneruskan gambar yang sudah didekode ke drawImage(). String markup bukan objek gambar itu. Bedakan pemuatan dokumen dari pembuatan data path Path2D.
Mengapa gambar muncul tetapi ekspor PNG gagal? Gambar lintas origin yang dimuat tanpa izin CORS dapat mencemari canvas setelah digambar. Dalam kasus tanpa CORS kami, penggambaran berhasil tetapi pembacaan piksel dan ekspor sama-sama melempar SecurityError. Periksa mode permintaan dan respons server alih-alih menganggap geometri SVG rusak.
Apakah crossOrigin anonymous memperbaiki setiap URL gambar? Tidak. Tetapkan sebelum src, dan server gambar harus menyediakan respons CORS yang cocok. Gambar kami yang sama gagal didekode saat anonymous diminta dari endpoint tanpa header izin.
Apakah mengekspor canvas sebagai PNG mempertahankan path SVG yang bisa diedit? Tidak. PNG adalah keluaran raster. Simpan SVG asli secara terpisah jika perlu mengedit path, bentuk, atau bukaan, dan periksa PNG pada ukuran penyerahan yang dimaksud.
Sumber
- MDN: CanvasRenderingContext2D.drawImage() — Objek gambar yang diterima, ukuran tujuan, dan dimensi intrinsik.
- MDN: HTMLImageElement.decode() — Promise kesiapan gambar dan penolakan permintaan/dekode.
- MDN: Gambar berkemampuan CORS — Pembatasan origin-clean pada pembacaan dan ekspor gambar yang digambar.
- MDN: HTMLImageElement.crossOrigin — Mode permintaan CORS untuk pemuatan gambar.
- MDN: HTMLCanvasElement.toBlob() — Pembuatan blob PNG, keluaran null, dan SecurityError.
- MDN: SVG sebagai gambar — Konteks gambar SVG dan pembatasan sumber daya eksternal.
Bekerja dari raster asli yang membutuhkan gambar yang bisa diedit? Buat master SVG dengan PerfectVector, periksa kontur dan bukaannya, lalu simpan master itu sambil memeriksa rendering canvas serta PNG hasil ekspor secara terpisah.
Lebih banyak dari blog

Ikon SVG MapLibre: Periksa Piksel dan Ukuran Simbol
Dekode gambar SVG khusus untuk layer simbol MapLibre, kendalikan kepadatan gambar dan ukuran ikon, serta simpan master yang bisa diedit. Verifikasi bentuk dan bukaan yang ditampilkan.

Gambar SVG p5.js: Muat, Skalakan, dan Simpan Sumber
Muat SVG di p5.js 2.3.4 dengan setup asinkron, periksa bukaan transparannya pada tiga ukuran Canvas, dan pisahkan path yang bisa diedit dari gambar yang ditampilkan.
