SVG auf Canvas: Grafik vor dem PNG-Export laden
Lade ein vollständiges SVG mit Image.decode, zeichne es in gewählter Bitmapgröße, prüfe CORS und PNG-Auslesung und halte die bearbeitbare Quelle von exportierten Pixeln getrennt.
Auf dieser Seite
Um ein vollständiges SVG auf Canvas zu zeichnen, lade es als Bild, warte auf image.decode() und übergib dieses Bild an drawImage(). Prüfe anschließend den PNG-Export separat. Ein Bild kann auf Canvas erscheinen, während der Browser das Lesen oder Speichern seiner Pixel blockiert.
Dieser Weg zeichnet das SVG-Dokument als Bild. Er gibt dem Canvas keine bearbeitbaren SVG-Pfade. Möchtest du stattdessen ausgewählte Konturen rekonstruieren, verwende den Path2D-SVG-Leitfaden. Behalte das ursprüngliche SVG unabhängig vom gewählten Darstellungsweg.
Trenne Dekodierung, Zeichnen und Export
Ein HTMLImageElement ist eine akzeptierte Eingabe für Canvas-drawImage(). Seine Zielbreite und -höhe steuern die gezeichnete Größe. Image.decode() gibt ein Promise zurück, das nach dem Dekodieren aufgelöst wird. Ein fehlgeschlagener Abruf oder ein Dekodierungsproblem kann es zurückweisen.
Erfolgreiches Dekodieren ist nur die erste Prüfung. Ein Bild einer anderen Herkunft, das ohne CORS-Erlaubnis geladen wurde, kann weiterhin gezeichnet werden, verunreinigt aber das Canvas. Pixelauslesung und Export scheitern dann. MDNs Leitfaden zu CORS-freigegebenen Bildern erklärt diesen Unterschied.

Erstelle ein eigenständiges SVG
Speichere dies als master.svg. Es enthält einen Pfad und einen Kreis, ohne eingebettetes Bild, Schrift oder externe Abhängigkeit. Die innere Raute und die umgebende Zeichenfläche sind in der Quelle transparent.
<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>Wir verwenden die vollständige Datei als Bild, einschließlich beider Formen und ihrer viewBox. Anders als beim Extrahieren nur des d-Werts eines Pfads bitten wir den SVG-Bildrenderer des Browsers, das Dokument zu zeichnen.
Das Beispiel ist bewusst eigenständig. Für SVG als Bild gelten Einschränkungen, unter anderem für externe Ressourcen und Skripte. Siehe MDNs Leitfaden zu SVG als Bild. Betrachte das erfolgreiche Dekodieren dieser kleinen Datei nicht als Beleg dafür, dass ein beliebiges SVG externe Schriften, Stile oder verknüpfte Grafiken lädt. Vergleiche für die Ausgabe Inline-SVG und Bildelemente.
Betreibe zwei lokale Herkünfte mit unterschiedlichen CORS-Antworten
Zu einer Herkunft gehört der Port. Der folgende rein lokale Server stellt dieselben SVG-Bytes über zwei Herkünfte bereit. An der zweiten Herkunft enthält /cors/master.svg den Header Access-Control-Allow-Origin: *; /plain/master.svg nicht. Das isoliert die Antwortberechtigung von der Grafik.
Speichere dies als serve-proof.py neben dem SVG und dem folgenden HTML. Führe python3 serve-proof.py aus und öffne dann http://127.0.0.1:8770/starter.html. Beide Ports müssen frei sein. Änderst du sie, aktualisiere Server und HTML gemeinsam. Drücke im Terminal die Eingabetaste, um beide Server zu stoppen.
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()Dieser freizügige Header gehört zu einer lokalen Testumgebung, die unsere eigene Grafik bereitstellt. Konfiguriere für einen produktiven Bildhost die passende erlaubte Herkunft auf einem von dir kontrollierten Server. Ein Bildattribut zu setzen gewährt keinen Zugriff auf Inhalte eines anderen Servers.
Dekodiere das Bild vor dem Zeichnen
Speichere diese vollständige Seite als starter.html. Jeder Fall bekommt ein eigenes Canvas. Der verunreinigte Fall kann deshalb die erfolgreichen Fälle nicht beeinträchtigen. Setze bei CORS-Anfragen crossOrigin vor src, wie MDNs crossOrigin-Referenz beschreibt.
Die Canvas-Bitmap ist ausdrücklich 480 × 320 groß; ihre CSS-Darstellungsgröße beträgt 240 × 160. Die intrinsischen Abmessungen des ursprünglichen Bilds sind 240 × 160. drawImage() skaliert es auf die gewählten Bitmapabmessungen. Das sind gewählte Abmessungen, keine Messung des Geräte-Pixelverhältnisses.
<!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>Die Exportfunktion behandelt sowohl eine geworfene Ausnahme als auch ein null-Blob-Ergebnis. Canvas.toBlob() unterstützt PNG-Ausgabe und kann SecurityError werfen, wenn das Canvas nicht herkunftsrein ist. Die beiden erfolgreichen Fälle ergänzen einen Link zum Speichern des PNGs. Der sichtbare Bericht lässt die Base64-Nutzlast weg.
Lies die Fehlerstufe, bevor du das SVG änderst
Wir führten genau diese Vorlage in einer Chrome-Browserumgebung aus und öffneten anschließend beide gespeicherten PNGs erneut. Diese Beobachtungen betreffen die ursprüngliche eigenständige Datei und diese beiden lokalen Herkünfte.
| Fall | Bildanfrage | Beobachtetes Zeichnen/Auslesen/Exportieren |
|---|---|---|
| Gleiche Herkunft | Lokales SVG, kein CORS-Attribut | Mit 240 × 160 dekodiert; gezeichnet; Auslesung und PNG erfolgreich |
| Andere Herkunft, Erlaubnis vorhanden | anonymous plus Antwortheader | Dekodiert; gezeichnet; Auslesung und PNG erfolgreich |
| Andere Herkunft, keine CORS-Anfrage | Kein Attribut, kein Antwortheader | Dekodiert und gezeichnet; Auslesung und Export warfen SecurityError |
| Andere Herkunft, Erlaubnis verweigert | anonymous, kein Antwortheader | Dekodierung mit EncodingError zurückgewiesen; kein Zeichenversuch |
| Fehlende Datei | 404 derselben Herkunft | Dekodierung mit EncodingError zurückgewiesen; kein Zeichenversuch |
Der Fall ohne CORS zeigte die Grafik sichtbar an. Dieses Aussehen belegte keine Erlaubnis zum Lesen oder Exportieren. Der Fall mit verweigertem CORS und die fehlende Datei scheiterten früher. Eine neue Geometrie würde daher keinen dieser Anfragefehler beheben.
Beide exportierten PNGs ließen sich mit 480 × 320 erneut öffnen. In diesem Lauf waren sie byteidentisch und jeweils 10509 Bytes groß. An der Bitmapkoordinate (192,160) war die Öffnung RGBA (0,0,0,0). Auch die Ecke hatte Alpha null. Die blaugrüne Probe bei (192,80) war (23,107,91,255). Diese Proben prüfen Öffnung und Füllung dieser Datei, nicht jeden Kantenpixel oder jede SVG-Funktion.
Die weißen Rechtecke auf der Browserseite sind CSS-Hintergründe hinter den Canvas-Elementen. Sie sind keine dem PNG hinzugefügte weiße Farbe. Braucht dein Ergebnis einen undurchsichtigen Hintergrund, zeichne ihn ausdrücklich vor dem Bild und prüfe die exportierte Datei erneut.
Halte bearbeitbare Masterdatei und PNG getrennt
Das gespeicherte PNG enthält Pixel. Es enthält nicht den ursprünglichen Pfad, Kreis oder bearbeitbaren Innenumriss. Behalte master.svg als Vektorquelle und wähle die Canvas-Bitmapabmessungen für das Ziel, das tatsächlich ein PNG braucht. Ein vorhandenes PNG später zu vergrößern stellt seine Quellgeometrie nicht wieder her.
Ein SVG-Dateiname allein beweist auch nicht, dass alle enthaltene Grafik Vektorgeometrie ist. Nutze den Leitfaden zur Prüfung eingebetteter Rasterbilder, wenn eine Datei verknüpfte oder eingebettete Bilder enthält.
Existiert deine Quellgrafik nur als Rasterbild und brauchst du bearbeitbare Konturen, kann PerfectVectors Bild-zu-Vektor-Workflow eine SVG-Masterdatei vorbereiten. Prüfe Umriss, Farben und Öffnungen, bevor du sie in diesem Ladeablauf verwendest. Vektorisierung repariert keine CORS-Header und gewährt keine Canvas-Ausleseberechtigung.
Häufige Fragen
Kann ich SVG-Markup direkt an drawImage() übergeben? Nein. Dieses Beispiel lädt eine SVG-Datei in ein HTMLImageElement und übergibt das dekodierte Bild an drawImage(). Eine Markup-Zeichenfolge ist nicht dieses Bildobjekt. Halte Dokumentladen und Path2D-Konstruktion aus Pfaddaten getrennt.
Warum erscheint das Bild, aber der PNG-Export scheitert? Ein Bild einer anderen Herkunft, das ohne CORS-Erlaubnis geladen wird, kann das Canvas nach dem Zeichnen verunreinigen. In unserem Fall ohne CORS gelang das Zeichnen, aber sowohl Pixelauslesung als auch Export warfen SecurityError. Prüfe Anfragemodus und Serverantwort, statt eine beschädigte SVG-Geometrie anzunehmen.
Repariert crossOrigin anonymous jede Bild-URL? Nein. Setze es vor src. Der Bildserver muss eine kompatible CORS-Antwort liefern. Dieselbe Grafik scheiterte beim Dekodieren, als anonymous am Endpunkt ohne Erlaubnisheader angefragt wurde.
Erhält der Canvas-Export als PNG bearbeitbare SVG-Pfade? Nein. PNG ist Rasterausgabe. Behalte das ursprüngliche SVG separat, wenn du Pfade, Formen oder Öffnungen bearbeiten musst, und prüfe das PNG bei seiner vorgesehenen Ausgabegröße.
Quellen
- MDN: CanvasRenderingContext2D.drawImage() – Akzeptierte Bildobjekte, Zielgrößen und intrinsische Abmessungen.
- MDN: HTMLImageElement.decode() – Promise zur Bildbereitschaft und Zurückweisung bei Abruf-/Dekodierungsfehlern.
- MDN: CORS-freigegebene Bilder – Herkunftsreinheitsbeschränkungen beim Lesen und Exportieren gezeichneter Bilder.
- MDN: HTMLImageElement.crossOrigin – CORS-Anfragemodus beim Laden von Bildern.
- MDN: HTMLCanvasElement.toBlob() – PNG-Blob-Erzeugung, null-Ausgabe und SecurityError.
- MDN: SVG als Bild – SVG-Bildkontexte und Beschränkungen für externe Ressourcen.
Du arbeitest mit einem Rasteroriginal und brauchst bearbeitbare Grafik? Erstelle mit PerfectVector eine SVG-Masterdatei, prüfe Umriss und Öffnungen und behalte diese Masterdatei, während du Canvas-Darstellung und exportiertes PNG separat prüfst.
Mehr aus dem Blog

SVG-Icons in MapLibre: Pixel und Symbolgröße prüfen
Dekodiere eigene SVG-Grafik für eine MapLibre-Symbolebene, steuere Bilddichte und Icongröße und behalte eine bearbeitbare Masterdatei. Prüfe dargestellte Form und Öffnung.

SVG-Bilder in p5.js: Laden, skalieren und Quelle behalten
Lade ein SVG in p5.js 2.3.4 mit asynchronem setup, prüfe seine transparente Öffnung bei drei Canvas-Größen und halte bearbeitbare Pfade vom angezeigten Bild getrennt.
