使用Node.js的Sharp转换含外部图片的SVG为PNG遇内存错误
解决Sharp转换含外部图片的SVG到PNG时的内存分配失败问题
问题背景
在Node.js环境下使用Sharp库将包含外部图片链接的SVG转换为PNG时,尝试通过将外部图片转为Base64嵌入SVG的方式处理,但因Base64字符串过长,Sharp抛出内存分配失败的XML解析错误,即使压缩图片也无法解决。
原代码
export const convertSvgToPng = async (svgCode: string) => { try { // Convert the SVG code to a PNG buffer const pngBuffer = await embedImagesInSvg(svgCode); return pngBuffer; } catch (error) { console.error(`Error converting SVG to PNG:`, error); } }; // Utility function to download image and convert to Base64 async function fetchImageAsBase64(url: string) { const response = await axios.get(url, { responseType: 'arraybuffer' }); // Compress the image using sharp const image = sharp(response.data); const compressedImageBuffer = await image.png({ compressionLevel: 9 }).toBuffer(); const base64Image = compressedImageBuffer.toString('base64'); const mimeType = response.headers['content-type']; return `data:${mimeType};base64,${base64Image}`; } // Function to modify SVG by embedding multiple base64 images async function embedImagesInSvg(svgCode: string) { // let svgContent = fs.readFileSync(svgFilePath, 'utf8'); // Regex to match all xlink:href occurrences const regex = /xlink:href="([^"]+)"/g; let match; const imagePromises = []; // Collect all image URLs while ((match = regex.exec(svgCode)) !== null) { const imageUrl = match[1]; imagePromises.push(fetchImageAsBase64(imageUrl)); } try { // Wait for all images to be fetched and converted to base64 const base64Images = await Promise.all(imagePromises); // Replace each xlink:href with the base64-encoded image svgCode = svgCode.replace(regex, () => { const base64Image = base64Images.shift(); return `xlink:href="${base64Image}"`; }); // Convert the modified SVG to PNG using sharp return sharp(Buffer.from(svgCode)).png().toBuffer(); } catch (error) { console.error('Error fetching one or more external images:', error); } }
报错信息
Error converting SVG to PNG: Error: Input buffer has corrupt header: glib: XML parse error: fatal error code=2 (1) in (null):1:10003752: Memory allocation failed : Huge input lookup
解决方案
1. 放弃Base64嵌入,改用本地临时文件引用
Base64会让SVG体积暴增,触发XML解析时的内存限制。改为将外部图片下载到本地临时文件,修改SVG的图片链接指向本地路径,让Sharp直接解析带本地路径的SVG,避免内存过载。
先安装临时文件管理库:
npm install tmp
2. 修改后的完整代码
import sharp from 'sharp'; import axios from 'axios'; import tmp from 'tmp'; import fs from 'fs/promises'; // 配置临时文件自动清理 tmp.setGracefulCleanup(); export const convertSvgToPng = async (svgCode: string) => { try { const pngBuffer = await embedImagesInSvg(svgCode); return pngBuffer; } catch (error) { console.error(`Error converting SVG to PNG:`, error); throw error; } }; // 下载图片到临时文件并返回文件路径 async function fetchImageToTempFile(url: string) { const response = await axios.get(url, { responseType: 'arraybuffer' }); // 创建临时文件 const tempFile = tmp.fileSync({ postfix: '.png' }); await fs.writeFile(tempFile.name, response.data); // 压缩图片(可选,根据需求保留) await sharp(tempFile.name) .png({ compressionLevel: 9 }) .toFile(tempFile.name); return tempFile; } async function embedImagesInSvg(svgCode: string) { const regex = /xlink:href="([^"]+)"/g; const matches = [...svgCode.matchAll(regex)]; const tempFiles: tmp.FileResult[] = []; try { // 下载所有图片到临时文件 const imagePaths = await Promise.all( matches.map(async (match) => { const tempFile = await fetchImageToTempFile(match[1]); tempFiles.push(tempFile); return tempFile.name; }) ); // 替换SVG中的图片链接为本地临时文件路径 let modifiedSvg = svgCode; matches.forEach((match, index) => { modifiedSvg = modifiedSvg.replace( match[0], `xlink:href="${imagePaths[index]}"` ); }); // 转换SVG到PNG return sharp(Buffer.from(modifiedSvg)).png().toBuffer(); } finally { // 清理所有临时文件 tempFiles.forEach(file => file.removeCallback()); } }
关键优化点
- 避免Base64膨胀:用本地文件路径替代Base64字符串,大幅减少SVG体积,绕过XML解析的内存限制。
- 可靠的匹配替换:用
matchAll收集所有匹配项,再逐个替换,避免原正则全局替换时的索引异常。 - 自动清理临时文件:利用tmp库的自动清理机制,防止临时文件堆积。
内容的提问来源于stack exchange,提问作者Muhammad Junaid
相关产品推荐
相关产品推荐

