如何在Node.js中重构canvas.toBlob()返回的PNG数据
I'm using Puppeteer to draw content on a browser Canvas, then trying to transfer the image data to Node.js context and save it as a PNG with OpenCV. The toDataURL() approach works fine, but when I use toBlob() and convert the Blob to a string for transfer, the reconstructed Buffer in Node.js isn't a valid PNG—OpenCV's imdecode can't create a Mat from it.
Here's my current code:
const puppeteer = require('puppeteer'); const cv = require('opencv4nodejs'); const pup = puppeteer.launch({ headless: false, args: [ '--use-gl=swiftshader', '--no-sandbox', '--enable-surface-synchronization', '--disable-web-security', ] }).then(async browser => { const page = (await browser.pages())[0]; page.on('console', msg => console.log(msg.text())); page.on("pageerror", function(err) { const theTempValue = err.toString(); console.log("Page error: " + theTempValue); }); page.on("error", function (err) { const theTempValue = err.toString(); console.log("Error: " + theTempValue); }); await page.exposeFunction('_setupCompleted', async () => { await page.evaluate(async () => { const c = document.getElementById("myCanvas"); const dataUrl = c.toDataURL("image/png"); const txt = await new Promise(resolve => c.toBlob((blb) => blb.text().then(resolve), "image/png")); await window._saveMat(dataUrl, txt); }); await browser.close(); }); await page.exposeFunction('_saveMat', async (dataUrl, txt) => { dataUrl = dataUrl.replace(/^data:image\/png;base64,/, ""); const dataUrlBuffer = Buffer.from(dataUrl, 'base64'); const dataUrlMat = cv.imdecode(dataUrlBuffer, -1); cv.imwrite(`./dataUrl.png`, dataUrlMat); const txtBuffer = Buffer.from(txt, 'utf8'); const txtMat = cv.imdecode(txtBuffer, -1); cv.imwrite(`./txtMat.png`, txtMat); }); await page.goto('http://127.0.0.1:8887/index.html', { waitUntil: 'networkidle2', }); });
The toDataURL flow works, but the toBlob -> text() -> Node.js Buffer flow produces an invalid PNG. How can I properly reconstruct a valid buffer from the Blob data in Node.js?
Solution: Avoid Blob.text()—Use Base64 Encoding for Binary Data
The core issue here is that Blob.text() decodes the binary PNG data as a UTF-8 string, which corrupts the data. PNG files contain byte sequences that aren't valid UTF-8 characters, so the decoder replaces invalid bytes with replacement characters (�), making the string impossible to convert back to the original binary buffer.
Instead, you should convert the Blob's raw binary data to a base64 string (just like toDataURL does internally) for transfer. Here's how to adjust your code:
Step 1: Modify the Browser-side Code to Convert Blob to Base64
Instead of using blb.text(), read the Blob as an ArrayBuffer, convert it to a Uint8Array, then encode that to a base64 string:
await page.evaluate(async () => { const c = document.getElementById("myCanvas"); const dataUrl = c.toDataURL("image/png"); // Convert Blob to base64 string instead of UTF-8 text const blobBase64 = await new Promise(resolve => { c.toBlob(async (blb) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result.split(',')[1]); // Extract base64 part reader.readAsDataURL(blb); }, "image/png"); }); await window._saveMat(dataUrl, blobBase64); });
Alternatively, if you prefer using ArrayBuffer directly:
const blobBase64 = await new Promise(resolve => { c.toBlob(async (blb) => { const buffer = await blb.arrayBuffer(); const uint8Array = new Uint8Array(buffer); // Convert Uint8Array to base64 string const base64 = btoa(String.fromCharCode(...uint8Array)); resolve(base64); }, "image/png"); });
Step 2: Update the Node-side Code to Decode the Base64 String
Your _saveMat function can now handle the Blob-derived base64 string the same way it handles the toDataURL result:
await page.exposeFunction('_saveMat', async (dataUrl, blobBase64) => { // Existing toDataURL handling stays the same dataUrl = dataUrl.replace(/^data:image\/png;base64,/, ""); const dataUrlBuffer = Buffer.from(dataUrl, 'base64'); const dataUrlMat = cv.imdecode(dataUrlBuffer, -1); cv.imwrite(`./dataUrl.png`, dataUrlMat); // Decode the Blob-derived base64 string to buffer const blobBuffer = Buffer.from(blobBase64, 'base64'); const blobMat = cv.imdecode(blobBuffer, -1); cv.imwrite(`./blobMat.png`, blobMat); });
Why This Works
Base64 encoding converts binary data into a safe, ASCII-compatible string that preserves all original byte information. Unlike UTF-8 decoding (used by Blob.text()), base64 doesn't alter any bytes during encoding or decoding, so you get an exact copy of the original PNG binary data in Node.js.
内容的提问来源于stack exchange,提问作者Mehran

