PerfectVector
लेखक: Irene Kim

SVG से Canvas: PNG निर्यात से पहले कलाकृति लोड करें

Image.decode से पूरा SVG लोड करें, चुने bitmap आकार पर ड्रॉ करें, CORS व PNG रीडिंग जाँचें और संपादन योग्य स्रोत निर्यातित पिक्सेल से अलग रखें।

9 मिनट का पठन
इस पेज पर

पूरा SVG canvas पर ड्रॉ करने के लिए उसे चित्र की तरह लोड करें, image.decode() की प्रतीक्षा करें और चित्र drawImage() को दें। फिर PNG निर्यात अलग जाँचें: चित्र canvas पर दिख सकता है, फिर भी ब्राउज़र उसके पिक्सेल पढ़ना या सहेजना रोक सकता है।

यह रास्ता SVG दस्तावेज़ को चित्र की तरह पेंट करता है। Canvas को संपादन योग्य SVG पाथ नहीं देता। चुनिंदा रूपरेखाएँ फिर बनानी हों तो Path2D SVG गाइड लें। प्रदर्शन का कोई भी रास्ता चुनें, मूल SVG रखें।

डिकोडिंग, पेंटिंग और निर्यात अलग रखें

HTMLImageElement, canvas drawImage() का स्वीकार्य इनपुट है। गंतव्य चौड़ाई और ऊँचाई पेंट आकार नियंत्रित करती हैं। Image.decode() ऐसी promise देता है जो चित्र डिकोड होने पर resolve होती है; विफल अनुरोध या डिकोड समस्या उसे reject कर सकती है।

सफल डिकोड पहली जाँच ही है। CORS अनुमति के बिना लोड cross-origin चित्र ड्रॉ हो सकता है, लेकिन canvas दूषित कर देता है। फिर पिक्सेल रीडिंग और निर्यात विफल होते हैं। MDN की CORS-सक्षम चित्र गाइड यह अंतर बताती है।

स्रोत रखें, पिक्सेल जाँचें
स्रोत रूपरेखाओं के रूप में खोखले हीरे और वृत्त, चिकनी रेंडर कलाकृति और पिक्सेल-ग्रिड आउटपुट का वैचारिक चित्रण
चित्रण: स्रोत रूपरेखाएँ और पेंट पिक्सेल अलग निरूपण हैं। फ़्रेम, तीर और मोटे ग्रिड वैचारिक हैं; नीचे के ब्राउज़र परिणाम स्वतंत्र चलाए मूल परीक्षण से हैं।

स्वयंपूर्ण मूल SVG बनाएँ

इसे master.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 image renderer से दस्तावेज़ पेंट कराता है।

उदाहरण जानबूझकर स्वयंपूर्ण है। चित्र के रूप में SVG पर बाहरी संसाधन और स्क्रिप्ट समेत सीमाएँ हैं; MDN की SVG-as-image गाइड देखें। इस छोटी फ़ाइल का सफल डिकोड इस बात का प्रमाण नहीं कि कोई भी SVG बाहरी फ़ॉन्ट, शैली या लिंक कलाकृति लोड करेगा। डिलीवरी चुनाव के लिए inline SVG और image एलिमेंट तुलना करें।

अलग CORS उत्तर वाले दो स्थानीय origin चलाएँ

Origin में पोर्ट भी है। यह केवल-स्थानीय सर्वर वही SVG बाइट दो origin पर देता है। दूसरे origin पर /cors/master.svg में Access-Control-Allow-Origin: * है; /plain/master.svg में नहीं। इससे उत्तर की अनुमति कलाकृति से अलग होती है।

इसे SVG और नीचे के HTML के पास serve-proof.py सहेजें। python3 serve-proof.py चलाकर http://127.0.0.1:8770/starter.html खोलें। दोनों पोर्ट खाली होने चाहिए; बदलें तो सर्वर और HTML साथ बदलें। दोनों सर्वर रोकने के लिए टर्मिनल में Enter दबाएँ।

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

यह खुली अनुमति वाला हेडर अपनी कलाकृति देने वाले स्थानीय परीक्षण का है। उत्पादन image host पर अपने नियंत्रण वाले सर्वर में उचित allowed origin सेट करें। चित्र गुण सेट करने से दूसरे सर्वर की सामग्री का अधिकार नहीं मिलता।

ड्रॉ करने से पहले चित्र डिकोड करें

पूरा पेज starter.html सहेजें। हर मामले का अपना canvas है, इसलिए दूषित मामला सफल मामलों को दूषित नहीं करता। CORS वाले अनुरोधों में src से पहले crossOrigin रखें, जैसा MDN का crossOrigin संदर्भ बताता है।

Canvas bitmap स्पष्ट 480 × 320 है; CSS प्रदर्शन आकार 240 × 160 है। मूल चित्र के intrinsic आयाम 240 × 160 हैं और drawImage() उसे चुने bitmap आयामों तक स्केल करता है। ये चुने आयाम हैं, device-pixel-ratio माप नहीं।

<!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 और null blob दोनों सँभालता है। Canvas.toBlob() PNG देता है और canvas origin-clean न हो तो SecurityError फेंक सकता है। दो सफल मामलों में Save PNG लिंक जुड़ता है; दिखाई देने वाली रिपोर्ट base64 payload नहीं दिखाती।

SVG बदलने से पहले विफलता का चरण पढ़ें

हमने ठीक यही उदाहरण एक Chrome ब्राउज़र परिवेश में चलाया, फिर दोनों सहेजे PNG दोबारा खोले। अवलोकन मूल स्वयंपूर्ण फ़ाइल और दो स्थानीय origin के हैं।

मामलाचित्र अनुरोधदेखा ड्रॉ/रीडिंग/निर्यात
समान originस्थानीय SVG, CORS गुण नहीं240 × 160 पर डिकोड; पेंट; रीडिंग और PNG सफल
Cross origin, अनुमति मिलीanonymous और response headerडिकोड; पेंट; रीडिंग और PNG सफल
Cross origin, CORS अनुरोध नहींगुण नहीं, response header नहींडिकोड और पेंट; रीडिंग व निर्यात ने SecurityError फेंका
Cross origin, अनुमति नहींanonymous, response header नहींEncodingError से डिकोड reject; ड्रॉ की कोशिश नहीं
फ़ाइल गायबSame-origin 404EncodingError से डिकोड reject; ड्रॉ की कोशिश नहीं

बिना-CORS मामले में कलाकृति दिखती थी। दिखना पढ़ने या निर्यात की अनुमति नहीं था। CORS-अस्वीकृत और गायब फ़ाइल पहले विफल हुए, इसलिए ज्यामिति दोबारा लिखना किसी अनुरोध विफलता को नहीं सुधारेगा।

दोनों निर्यात PNG 480 × 320 पर फिर खुले। इस रन में वे बाइट-समान थे, प्रत्येक 10,509 बाइट। Bitmap निर्देशांक (192,160) पर खुला भाग RGBA (0,0,0,0) था; कोने का अल्फ़ा भी शून्य था। (192,80) पर टील नमूना (23,107,91,255) था। ये इस फ़ाइल का खुला भाग और भरण जाँचते हैं, हर किनारा पिक्सेल या हर SVG सुविधा नहीं।

ब्राउज़र पेज के सफ़ेद आयत canvas के पीछे CSS बैकग्राउंड हैं। PNG में जोड़ा सफ़ेद पेंट नहीं। अपारदर्शी बैकग्राउंड चाहिए तो चित्र ड्रॉ करने से पहले उसे स्पष्ट पेंट करें और निर्यात फ़ाइल फिर जाँचें।

संपादन योग्य मास्टर को PNG से अलग रखें

सहेजे PNG में पिक्सेल हैं। मूल पाथ, वृत्त या संपादन योग्य भीतरी रूपरेखा नहीं। master.svg वेक्टर स्रोत रखें और सच में PNG चाहिए उस गंतव्य के लिए canvas bitmap आयाम चुनें। मौजूदा PNG बाद में बड़ा करने से स्रोत ज्यामिति वापस नहीं आती।

केवल SVG फ़ाइलनाम भी यह प्रमाण नहीं कि भीतर की सारी कलाकृति वेक्टर है; लिंक या एम्बेडेड चित्र हों तो एम्बेडेड रास्टर निरीक्षण गाइड लें।

स्रोत कलाकृति केवल रास्टर हो और संपादन योग्य रूपरेखाएँ चाहिए तो PerfectVector की image-to-vector प्रक्रिया SVG मास्टर तैयार कर सकती है। इस लोडिंग में लेने से पहले बाहरी रेखा, रंग और खुले भाग जाँचें। वेक्टराइज़ेशन CORS हेडर नहीं सुधारता और canvas रीडिंग अनुमति नहीं देता।

अक्सर पूछे जाने वाले प्रश्न

क्या SVG मार्कअप सीधे drawImage() को दे सकते हैं? नहीं। यह उदाहरण SVG फ़ाइल HTMLImageElement में लोड करता है और डिकोड चित्र drawImage() को देता है। मार्कअप स्ट्रिंग वह चित्र ऑब्जेक्ट नहीं। दस्तावेज़ लोडिंग को Path2D पाथ-डेटा निर्माण से अलग रखें।

चित्र दिखता है लेकिन PNG निर्यात क्यों विफल होता है? CORS अनुमति के बिना लोड cross-origin चित्र ड्रॉ होने पर canvas दूषित कर सकता है। हमारे बिना-CORS मामले में पेंट सफल था, लेकिन पिक्सेल रीडिंग और निर्यात दोनों ने SecurityError फेंका। SVG ज्यामिति टूटी मानने के बजाय अनुरोध मोड और सर्वर उत्तर जाँचें।

क्या crossOrigin anonymous हर चित्र URL ठीक करता है? नहीं। src से पहले सेट करें और चित्र सर्वर को संगत CORS उत्तर देना होगा। वही कलाकृति अनुमति हेडर के बिना endpoint से anonymous अनुरोध करने पर डिकोड में विफल हुई।

क्या canvas को PNG निर्यात करना संपादन योग्य SVG पाथ बचाता है? नहीं। PNG रास्टर आउटपुट है। पाथ, आकृति या खुले भाग संपादित करने हों तो मूल SVG अलग रखें और PNG इच्छित डिलीवरी आकार पर जाँचें।

स्रोत

  1. MDN: CanvasRenderingContext2D.drawImage() — स्वीकार्य चित्र ऑब्जेक्ट, गंतव्य आकार और intrinsic आयाम।
  2. MDN: HTMLImageElement.decode() — चित्र तैयार होने की promise और अनुरोध/डिकोड reject।
  3. MDN: CORS-सक्षम चित्र — ड्रॉ चित्र पढ़ने और निर्यात की origin-clean सीमाएँ।
  4. MDN: HTMLImageElement.crossOrigin — चित्र लोड का CORS अनुरोध मोड।
  5. MDN: HTMLCanvasElement.toBlob() — PNG blob, null आउटपुट और SecurityError।
  6. MDN: चित्र के रूप में SVG — SVG image संदर्भ और बाहरी संसाधन सीमाएँ।

रास्टर मूल से संपादन योग्य कलाकृति चाहिए? PerfectVector से SVG मास्टर बनाएँ, बाहरी रेखा व खुले भाग जाँचें, फिर canvas रेंडर और PNG निर्यात अलग जाँचते समय मास्टर रखें।

Blog से और

PerfectVector

ऐसे साफ़ SVG से शुरुआत करें
जिसे एडिट करना आसान हो

क्रेडिट कार्ड की ज़रूरत नहीं