Export SVG as PNG Programmatically: Complete Developer Guide
Master programmatic SVG to PNG export with Node.js solutions, browser automation, batch processing techniques, and complete API examples for automated conversion workflows.
Programmatic SVG to PNG export enables automated workflows, batch processing, and server-side image generation. Whether you're building automated systems to export graphics from our AI SVG generator or processing custom designs at scale, this comprehensive guide covers Node.js implementations, browser automation, API development, and production-ready solutions for converting SVG graphics to PNG programmatically.
⚡ Need instant programmatic conversion?
Start with our SVG to PNG API converter for rapid prototyping, then implement custom solutions using the techniques below.
Overview of Programmatic Approaches
Programmatic SVG to PNG export can be implemented using several different approaches, each with specific advantages and use cases. Before processing, consider using our SVG optimizer to prepare graphics for optimal conversion results:
Approach | Environment | Performance | Quality | Best For |
---|---|---|---|---|
Sharp + librsvg | Node.js | Excellent | High | High-performance batch processing |
Puppeteer | Node.js | Moderate | Excellent | Complex SVGs, web fonts |
Canvas API | Browser/Node.js | Good | Good | Client-side automation |
svg2img | Node.js | Good | Good | Simple automation scripts |
Node.js with Sharp and librsvg
Installation and Setup
// Install required packages
npm install sharp
npm install @resvg/resvg-js
// For systems with librsvg
// Ubuntu/Debian: sudo apt-get install librsvg2-dev
// macOS: brew install librsvg
// Windows: Use @resvg/resvg-js (pure JavaScript alternative)
High-Performance SVG Converter
const sharp = require('sharp');
const { Resvg } = require('@resvg/resvg-js');
const fs = require('fs').promises;
const path = require('path');
class HighPerformanceSvgConverter {
constructor(options = {}) {
this.defaultOptions = {
width: 800,
height: 600,
density: 300,
format: 'png',
quality: 90,
background: { r: 255, g: 255, b: 255, alpha: 0 }
};
this.options = { ...this.defaultOptions, ...options };
}
async convertSvgFile(inputPath, outputPath, customOptions = {}) {
try {
const svgBuffer = await fs.readFile(inputPath);
const result = await this.convertSvgBuffer(svgBuffer, customOptions);
await fs.writeFile(outputPath, result.buffer);
return {
inputPath,
outputPath,
inputSize: svgBuffer.length,
outputSize: result.buffer.length,
dimensions: result.info,
compressionRatio: result.buffer.length / svgBuffer.length
};
} catch (error) {
throw new Error(`Failed to convert ${inputPath}: ${error.message}`);
}
}
async convertSvgString(svgString, customOptions = {}) {
const svgBuffer = Buffer.from(svgString, 'utf-8');
return this.convertSvgBuffer(svgBuffer, customOptions);
}
async convertSvgBuffer(svgBuffer, customOptions = {}) {
const options = { ...this.options, ...customOptions };
try {
// Use Resvg for initial SVG rendering
const resvg = new Resvg(svgBuffer, {
background: 'rgba(0, 0, 0, 0)',
fitTo: {
mode: 'width',
value: options.width
},
font: {
loadSystemFonts: true
}
});
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
// Use Sharp for final processing and optimization
let sharpInstance = sharp(pngBuffer);
// Resize if needed
if (options.width || options.height) {
sharpInstance = sharpInstance.resize(options.width, options.height, {
fit: 'contain',
background: options.background
});
}
// Set density for print quality
if (options.density) {
sharpInstance = sharpInstance.density(options.density);
}
// Convert to final format
switch (options.format) {
case 'jpeg':
case 'jpg':
sharpInstance = sharpInstance.jpeg({
quality: options.quality,
progressive: true
});
break;
case 'webp':
sharpInstance = sharpInstance.webp({
quality: options.quality,
lossless: false
});
break;
case 'png':
default:
sharpInstance = sharpInstance.png({
compressionLevel: 6,
adaptiveFiltering: true
});
break;
}
const { data: buffer, info } = await sharpInstance.toBuffer({ resolveWithObject: true });
return {
buffer,
info,
format: options.format
};
} catch (error) {
throw new Error(`SVG conversion failed: ${error.message}`);
}
}
// Batch conversion with concurrency control
async convertBatch(inputFiles, outputDir, options = {}) {
const config = {
concurrency: 4,
preserveStructure: true,
...options
};
// Ensure output directory exists
await fs.mkdir(outputDir, { recursive: true });
const results = [];
const semaphore = new Semaphore(config.concurrency);
const conversionPromises = inputFiles.map(async (inputFile) => {
return semaphore.acquire(async () => {
try {
const outputFile = this.generateOutputPath(inputFile, outputDir, config);
const result = await this.convertSvgFile(inputFile, outputFile, config);
results.push({
...result,
status: 'success'
});
} catch (error) {
results.push({
inputPath: inputFile,
status: 'error',
error: error.message
});
}
});
});
await Promise.all(conversionPromises);
return {
total: inputFiles.length,
successful: results.filter(r => r.status === 'success').length,
failed: results.filter(r => r.status === 'error').length,
results: results
};
}
generateOutputPath(inputFile, outputDir, options) {
const basename = path.basename(inputFile, path.extname(inputFile));
const extension = options.format === 'jpeg' ? 'jpg' : options.format;
if (options.preserveStructure) {
const relativePath = path.dirname(inputFile);
const fullOutputDir = path.join(outputDir, relativePath);
return path.join(fullOutputDir, `${basename}.${extension}`);
}
return path.join(outputDir, `${basename}.${extension}`);
}
// Generate multiple sizes
async generateResponsiveSizes(svgInput, outputDir, sizes = []) {
const defaultSizes = [
{ width: 320, height: 240, suffix: 'small' },
{ width: 800, height: 600, suffix: 'medium' },
{ width: 1600, height: 1200, suffix: 'large' },
{ width: 3200, height: 2400, suffix: 'xlarge' }
];
const targetSizes = sizes.length ? sizes : defaultSizes;
const results = [];
for (const size of targetSizes) {
try {
const outputPath = path.join(outputDir, `output_${size.suffix}.${this.options.format}`);
let result;
if (typeof svgInput === 'string' && svgInput.includes('<svg')) {
result = await this.convertSvgString(svgInput, size);
await fs.writeFile(outputPath, result.buffer);
} else {
result = await this.convertSvgFile(svgInput, outputPath, size);
}
results.push({
size: size.suffix,
dimensions: `${size.width}x${size.height}`,
outputPath,
fileSize: result.buffer ? result.buffer.length : result.outputSize,
status: 'success'
});
} catch (error) {
results.push({
size: size.suffix,
status: 'error',
error: error.message
});
}
}
return results;
}
}
// Semaphore for concurrency control
class Semaphore {
constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency;
this.currentConcurrency = 0;
this.queue = [];
}
async acquire(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.tryNext();
});
}
tryNext() {
if (this.currentConcurrency < this.maxConcurrency && this.queue.length > 0) {
this.currentConcurrency++;
const { task, resolve, reject } = this.queue.shift();
task()
.then(resolve)
.catch(reject)
.finally(() => {
this.currentConcurrency--;
this.tryNext();
});
}
}
}
// Usage examples
async function demonstrateSharpConverter() {
const converter = new HighPerformanceSvgConverter({
width: 1200,
height: 800,
density: 300,
format: 'png'
});
// Single file conversion
const result = await converter.convertSvgFile('input.svg', 'output.png');
console.log('Conversion result:', result);
// Batch conversion
const svgFiles = ['file1.svg', 'file2.svg', 'file3.svg'];
const batchResult = await converter.convertBatch(svgFiles, './output', {
concurrency: 2,
format: 'png'
});
console.log('Batch conversion:', batchResult);
// Responsive sizes
const responsiveResult = await converter.generateResponsiveSizes('input.svg', './responsive');
console.log('Responsive images:', responsiveResult);
}
module.exports = { HighPerformanceSvgConverter };
Browser Automation with Puppeteer
Advanced Puppeteer Implementation
const puppeteer = require('puppeteer');
const fs = require('fs').promises;
const path = require('path');
class PuppeteerSvgConverter {
constructor(options = {}) {
this.browserOptions = {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-web-security',
'--disable-features=VizDisplayCompositor'
],
...options.browser
};
this.defaultPageOptions = {
width: 800,
height: 600,
deviceScaleFactor: 2,
format: 'png',
quality: 90,
background: 'transparent',
waitTime: 1000, // Time to wait for fonts/animations
...options.page
};
this.browser = null;
}
async initialize() {
if (!this.browser) {
this.browser = await puppeteer.launch(this.browserOptions);
}
return this.browser;
}
async close() {
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
async convertSvgString(svgString, options = {}) {
const config = { ...this.defaultPageOptions, ...options };
const browser = await this.initialize();
const page = await browser.newPage();
try {
// Set viewport
await page.setViewport({
width: config.width,
height: config.height,
deviceScaleFactor: config.deviceScaleFactor
});
// Create HTML with embedded SVG
const html = this.createHtmlWrapper(svgString, config);
await page.setContent(html, { waitUntil: 'networkidle0' });
// Wait for fonts and animations
await page.waitForTimeout(config.waitTime);
// Take screenshot
const screenshotOptions = {
type: config.format,
clip: {
x: 0,
y: 0,
width: config.width,
height: config.height
},
omitBackground: config.background === 'transparent'
};
if (config.format === 'jpeg') {
screenshotOptions.quality = config.quality;
}
const buffer = await page.screenshot(screenshotOptions);
return {
buffer,
width: config.width,
height: config.height,
format: config.format,
fileSize: buffer.length
};
} finally {
await page.close();
}
}
async convertSvgFile(inputPath, outputPath, options = {}) {
const svgContent = await fs.readFile(inputPath, 'utf-8');
const result = await this.convertSvgString(svgContent, options);
await fs.writeFile(outputPath, result.buffer);
return {
inputPath,
outputPath,
...result
};
}
createHtmlWrapper(svgString, config) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 0;
background: ${config.background === 'transparent' ? 'transparent' : config.background};
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
svg {
width: 100%;
height: 100%;
display: block;
}
.container {
width: ${config.width}px;
height: ${config.height}px;
overflow: hidden;
}
</style>
<!-- Font preloads would go here -->
</head>
<body>
<div class="container">
${svgString}
</div>
</body>
</html>
`;
}
generateFontPreloads() {
// Preload common web fonts for better rendering
return `
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
`;
}
// Batch conversion with worker pool
async convertBatch(svgFiles, outputDir, options = {}) {
const config = {
concurrency: 3, // Number of browser pages
...options
};
await fs.mkdir(outputDir, { recursive: true });
const browser = await this.initialize();
const results = [];
// Create worker pool
const workers = Array(config.concurrency).fill().map(() => this.createWorker(browser));
// Queue management
const queue = svgFiles.map((file, index) => ({ file, index }));
const workPromises = workers.map(worker => this.processQueue(worker, queue, outputDir, config, results));
await Promise.all(workPromises);
return {
total: svgFiles.length,
successful: results.filter(r => r.status === 'success').length,
failed: results.filter(r => r.status === 'error').length,
results: results.sort((a, b) => a.index - b.index)
};
}
async createWorker(browser) {
const page = await browser.newPage();
return {
page,
busy: false,
close: () => page.close()
};
}
async processQueue(worker, queue, outputDir, options, results) {
while (queue.length > 0) {
const job = queue.shift();
if (!job) break;
worker.busy = true;
try {
const inputPath = job.file;
const outputPath = path.join(outputDir, this.generateOutputFilename(inputPath, options));
const result = await this.convertSvgFileWithWorker(worker.page, inputPath, outputPath, options);
results.push({
index: job.index,
status: 'success',
...result
});
} catch (error) {
results.push({
index: job.index,
status: 'error',
inputPath: job.file,
error: error.message
});
}
worker.busy = false;
}
await worker.close();
}
async convertSvgFileWithWorker(page, inputPath, outputPath, options) {
const config = { ...this.defaultPageOptions, ...options };
const svgContent = await fs.readFile(inputPath, 'utf-8');
await page.setViewport({
width: config.width,
height: config.height,
deviceScaleFactor: config.deviceScaleFactor
});
const html = this.createHtmlWrapper(svgContent, config);
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.waitForTimeout(config.waitTime);
const screenshotOptions = {
path: outputPath,
type: config.format,
clip: {
x: 0,
y: 0,
width: config.width,
height: config.height
},
omitBackground: config.background === 'transparent'
};
if (config.format === 'jpeg') {
screenshotOptions.quality = config.quality;
}
await page.screenshot(screenshotOptions);
const stats = await fs.stat(outputPath);
return {
inputPath,
outputPath,
width: config.width,
height: config.height,
format: config.format,
fileSize: stats.size
};
}
generateOutputFilename(inputPath, options) {
const basename = path.basename(inputPath, '.svg');
const extension = options.format === 'jpeg' ? 'jpg' : options.format;
return `${basename}.${extension}`;
}
// Animation frame capture
async captureAnimationFrames(svgString, options = {}) {
const config = {
frameCount: 10,
duration: 1000, // milliseconds
...this.defaultPageOptions,
...options
};
const browser = await this.initialize();
const page = await browser.newPage();
try {
await page.setViewport({
width: config.width,
height: config.height,
deviceScaleFactor: config.deviceScaleFactor
});
const html = this.createAnimationHtml(svgString, config);
await page.setContent(html, { waitUntil: 'networkidle0' });
const frames = [];
const frameInterval = config.duration / config.frameCount;
for (let i = 0; i < config.frameCount; i++) {
const timeMs = i * frameInterval;
// Set animation time
await page.evaluate((time) => {
const svg = document.querySelector('svg');
if (svg && svg.pauseAnimations) {
svg.pauseAnimations();
svg.setCurrentTime(time / 1000);
}
}, timeMs);
await page.waitForTimeout(100); // Allow rendering
const buffer = await page.screenshot({
type: config.format,
clip: {
x: 0,
y: 0,
width: config.width,
height: config.height
},
omitBackground: config.background === 'transparent'
});
frames.push({
frame: i,
timeMs: timeMs,
buffer: buffer
});
}
return frames;
} finally {
await page.close();
}
}
createAnimationHtml(svgString, config) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 0;
background: ${config.background === 'transparent' ? 'transparent' : config.background};
}
svg {
width: ${config.width}px;
height: ${config.height}px;
display: block;
}
</style>
</head>
<body>
${svgString}
</body>
</html>
`;
}
}
// Usage examples
async function demonstratePuppeteerConverter() {
const converter = new PuppeteerSvgConverter({
browser: {
headless: true
},
page: {
width: 1200,
height: 800,
format: 'png',
deviceScaleFactor: 2
}
});
try {
// Single conversion
const svgString = `
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#f0f0f0"/>
<circle cx="200" cy="150" r="100" fill="#4CAF50"/>
<text x="200" y="160" text-anchor="middle" font-family="Arial" font-size="16">Hello World</text>
</svg>
`;
const result = await converter.convertSvgString(svgString);
console.log('Conversion result:', result);
// Batch conversion
const svgFiles = ['file1.svg', 'file2.svg', 'file3.svg'];
const batchResult = await converter.convertBatch(svgFiles, './output', {
concurrency: 2,
format: 'png',
quality: 95
});
console.log('Batch result:', batchResult);
// Animation frames
const animatedSvg = `
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="50" fill="red">
<animate attributeName="r" values="20;80;20" dur="2s" repeatCount="indefinite"/>
</circle>
</svg>
`;
const frames = await converter.captureAnimationFrames(animatedSvg, {
frameCount: 8,
duration: 2000
});
console.log(`Captured ${frames.length} animation frames`);
} finally {
await converter.close();
}
}
module.exports = { PuppeteerSvgConverter };
RESTful API for SVG Conversion
Express.js API Implementation
const express = require('express');
const multer = require('multer');
const rateLimit = require('express-rate-limit');
const { body, validationResult } = require('express-validator');
const { HighPerformanceSvgConverter } = require('./high-performance-converter');
const { PuppeteerSvgConverter } = require('./puppeteer-converter');
const app = express();
// Middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Rate limiting
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests, please try again later.',
retryAfter: 15 * 60
}
});
app.use('/api/', apiLimiter);
// File upload configuration
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 10
},
fileFilter: (req, file, cb) => {
if (file.mimetype === 'image/svg+xml' || file.originalname.endsWith('.svg')) {
cb(null, true);
} else {
cb(new Error('Only SVG files are allowed'));
}
}
});
// Initialize converters
const sharpConverter = new HighPerformanceSvgConverter();
const puppeteerConverter = new PuppeteerSvgConverter();
// Validation middleware
const conversionValidation = [
body('width').optional().isInt({ min: 1, max: 5000 }),
body('height').optional().isInt({ min: 1, max: 5000 }),
body('format').optional().isIn(['png', 'jpeg', 'webp']),
body('quality').optional().isInt({ min: 1, max: 100 }),
body('background').optional().isHexColor(),
body('method').optional().isIn(['sharp', 'puppeteer'])
];
// API Routes
// Convert SVG string to PNG
app.post('/api/convert/string', conversionValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { svgString, ...options } = req.body;
if (!svgString) {
return res.status(400).json({
success: false,
error: 'SVG string is required'
});
}
const method = options.method || 'sharp';
const converter = method === 'puppeteer' ? puppeteerConverter : sharpConverter;
const result = await converter.convertSvgString(svgString, {
width: options.width || 800,
height: options.height || 600,
format: options.format || 'png',
quality: options.quality || 90,
background: options.background || 'transparent'
});
// Return as base64 data URL
const base64 = result.buffer.toString('base64');
const dataUrl = `data:image/${result.format};base64,${base64}`;
res.json({
success: true,
data: {
dataUrl: dataUrl,
format: result.format,
width: result.width || result.info?.width,
height: result.height || result.info?.height,
fileSize: result.buffer.length,
method: method
}
});
} catch (error) {
console.error('String conversion error:', error);
res.status(500).json({
success: false,
error: 'Conversion failed',
details: error.message
});
}
});
// Convert uploaded SVG file
app.post('/api/convert/file', upload.single('svg'), conversionValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
if (!req.file) {
return res.status(400).json({
success: false,
error: 'SVG file is required'
});
}
const svgString = req.file.buffer.toString('utf-8');
const options = req.body;
const method = options.method || 'sharp';
const converter = method === 'puppeteer' ? puppeteerConverter : sharpConverter;
const result = await converter.convertSvgString(svgString, {
width: parseInt(options.width) || 800,
height: parseInt(options.height) || 600,
format: options.format || 'png',
quality: parseInt(options.quality) || 90,
background: options.background || 'transparent'
});
// Set appropriate headers for file download
const filename = `converted.${result.format}`;
res.set({
'Content-Type': `image/${result.format}`,
'Content-Length': result.buffer.length,
'Content-Disposition': `attachment; filename="${filename}"`
});
res.send(result.buffer);
} catch (error) {
console.error('File conversion error:', error);
res.status(500).json({
success: false,
error: 'Conversion failed',
details: error.message
});
}
});
// Batch conversion
app.post('/api/convert/batch', upload.array('svgs', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
error: 'At least one SVG file is required'
});
}
const options = req.body;
const method = options.method || 'sharp';
const converter = method === 'puppeteer' ? puppeteerConverter : sharpConverter;
const conversionPromises = req.files.map(async (file, index) => {
try {
const svgString = file.buffer.toString('utf-8');
const result = await converter.convertSvgString(svgString, {
width: parseInt(options.width) || 800,
height: parseInt(options.height) || 600,
format: options.format || 'png',
quality: parseInt(options.quality) || 90,
background: options.background || 'transparent'
});
const base64 = result.buffer.toString('base64');
const dataUrl = `data:image/${result.format};base64,${base64}`;
return {
index: index,
filename: file.originalname,
status: 'success',
dataUrl: dataUrl,
fileSize: result.buffer.length
};
} catch (error) {
return {
index: index,
filename: file.originalname,
status: 'error',
error: error.message
};
}
});
const results = await Promise.all(conversionPromises);
const successful = results.filter(r => r.status === 'success').length;
const failed = results.filter(r => r.status === 'error').length;
res.json({
success: true,
data: {
total: results.length,
successful: successful,
failed: failed,
results: results
}
});
} catch (error) {
console.error('Batch conversion error:', error);
res.status(500).json({
success: false,
error: 'Batch conversion failed',
details: error.message
});
}
});
// Generate responsive sizes
app.post('/api/convert/responsive', conversionValidation, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { svgString, sizes, ...options } = req.body;
if (!svgString) {
return res.status(400).json({
success: false,
error: 'SVG string is required'
});
}
const defaultSizes = [
{ width: 320, height: 240, suffix: 'small' },
{ width: 800, height: 600, suffix: 'medium' },
{ width: 1600, height: 1200, suffix: 'large' }
];
const targetSizes = sizes || defaultSizes;
const method = options.method || 'sharp';
const converter = method === 'puppeteer' ? puppeteerConverter : sharpConverter;
const results = [];
for (const size of targetSizes) {
try {
const result = await converter.convertSvgString(svgString, {
...size,
format: options.format || 'png',
quality: options.quality || 90,
background: options.background || 'transparent'
});
const base64 = result.buffer.toString('base64');
const dataUrl = `data:image/${result.format};base64,${base64}`;
results.push({
size: size.suffix || `${size.width}x${size.height}`,
width: size.width,
height: size.height,
dataUrl: dataUrl,
fileSize: result.buffer.length,
status: 'success'
});
} catch (error) {
results.push({
size: size.suffix || `${size.width}x${size.height}`,
status: 'error',
error: error.message
});
}
}
res.json({
success: true,
data: {
results: results,
method: method
}
});
} catch (error) {
console.error('Responsive conversion error:', error);
res.status(500).json({
success: false,
error: 'Responsive conversion failed',
details: error.message
});
}
});
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
success: true,
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error('Unhandled error:', error);
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
success: false,
error: 'File too large',
maxSize: '5MB'
});
}
}
res.status(500).json({
success: false,
error: 'Internal server error'
});
});
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down gracefully...');
await puppeteerConverter.close();
process.exit(0);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`SVG Conversion API running on port ${PORT}`);
});
module.exports = app;
CLI Tool for Batch Processing
Command-Line Interface
#!/usr/bin/env node
const { Command } = require('commander');
const fs = require('fs').promises;
const path = require('path');
const glob = require('glob');
const chalk = require('chalk');
const ora = require('ora');
const { HighPerformanceSvgConverter } = require('./high-performance-converter');
const program = new Command();
program
.name('svg2png')
.description('Convert SVG files to PNG with various options')
.version('1.0.0');
program
.command('convert')
.description('Convert SVG files to PNG')
.argument('<input>', 'Input SVG file or directory')
.option('-o, --output <path>', 'Output directory or file')
.option('-w, --width <number>', 'Output width in pixels', '800')
.option('-h, --height <number>', 'Output height in pixels', '600')
.option('-f, --format <format>', 'Output format (png, jpeg, webp)', 'png')
.option('-q, --quality <number>', 'Quality for lossy formats (1-100)', '90')
.option('-d, --density <number>', 'DPI for print quality', '300')
.option('-b, --background <color>', 'Background color (hex)', 'transparent')
.option('-c, --concurrency <number>', 'Concurrent conversions', '4')
.option('--recursive', 'Process directories recursively')
.option('--overwrite', 'Overwrite existing files')
.action(async (input, options) => {
const spinner = ora('Initializing converter...').start();
try {
const converter = new HighPerformanceSvgConverter({
width: parseInt(options.width),
height: parseInt(options.height),
format: options.format,
quality: parseInt(options.quality),
density: parseInt(options.density),
background: options.background === 'transparent' ?
{ r: 255, g: 255, b: 255, alpha: 0 } :
parseColor(options.background)
});
const inputFiles = await findSvgFiles(input, options.recursive);
if (inputFiles.length === 0) {
spinner.fail('No SVG files found');
return;
}
spinner.text = `Found ${inputFiles.length} SVG files`;
const outputDir = options.output || path.dirname(input);
await fs.mkdir(outputDir, { recursive: true });
spinner.text = 'Converting files...';
const result = await converter.convertBatch(inputFiles, outputDir, {
concurrency: parseInt(options.concurrency),
format: options.format,
overwrite: options.overwrite
});
spinner.succeed(`Conversion completed: ${result.successful} successful, ${result.failed} failed`);
// Display results
console.log('\n' + chalk.bold('Conversion Results:'));
result.results.forEach(item => {
if (item.status === 'success') {
console.log(chalk.green('✓'), item.inputPath, '→', item.outputPath);
console.log(` Size: ${(item.outputSize / 1024).toFixed(2)}KB`);
} else {
console.log(chalk.red('✗'), item.inputPath);
console.log(` Error: ${item.error}`);
}
});
} catch (error) {
spinner.fail(`Conversion failed: ${error.message}`);
process.exit(1);
}
});
program
.command('responsive')
.description('Generate responsive image sizes')
.argument('<input>', 'Input SVG file')
.option('-o, --output <path>', 'Output directory')
.option('-s, --sizes <sizes>', 'Comma-separated sizes (e.g., "320x240,800x600")')
.option('-f, --format <format>', 'Output format (png, jpeg, webp)', 'png')
.option('-q, --quality <number>', 'Quality for lossy formats', '90')
.action(async (input, options) => {
const spinner = ora('Generating responsive sizes...').start();
try {
const converter = new HighPerformanceSvgConverter();
const sizes = options.sizes ?
parseSizes(options.sizes) :
[
{ width: 320, height: 240, suffix: 'small' },
{ width: 800, height: 600, suffix: 'medium' },
{ width: 1600, height: 1200, suffix: 'large' }
];
const outputDir = options.output || path.dirname(input);
const results = await converter.generateResponsiveSizes(input, outputDir, sizes);
spinner.succeed(`Generated ${results.filter(r => r.status === 'success').length} responsive images`);
console.log('\n' + chalk.bold('Generated Sizes:'));
results.forEach(result => {
if (result.status === 'success') {
console.log(chalk.green('✓'), result.size, '-', result.dimensions);
console.log(` Path: ${result.outputPath}`);
console.log(` Size: ${(result.fileSize / 1024).toFixed(2)}KB`);
} else {
console.log(chalk.red('✗'), result.size);
console.log(` Error: ${result.error}`);
}
});
} catch (error) {
spinner.fail(`Generation failed: ${error.message}`);
process.exit(1);
}
});
program
.command('watch')
.description('Watch directory for SVG changes and auto-convert')
.argument('<input>', 'Directory to watch')
.option('-o, --output <path>', 'Output directory')
.option('-w, --width <number>', 'Output width', '800')
.option('-h, --height <number>', 'Output height', '600')
.option('-f, --format <format>', 'Output format', 'png')
.action(async (input, options) => {
const chokidar = require('chokidar');
console.log(chalk.blue('👀 Watching for SVG changes in:'), input);
const converter = new HighPerformanceSvgConverter({
width: parseInt(options.width),
height: parseInt(options.height),
format: options.format
});
const outputDir = options.output || input;
const watcher = chokidar.watch(path.join(input, '**/*.svg'), {
persistent: true,
ignoreInitial: false
});
watcher.on('add', async (filePath) => {
console.log(chalk.green('Added:'), filePath);
await convertFile(converter, filePath, outputDir);
});
watcher.on('change', async (filePath) => {
console.log(chalk.yellow('Changed:'), filePath);
await convertFile(converter, filePath, outputDir);
});
process.on('SIGINT', () => {
console.log('\n' + chalk.blue('👋 Stopping watcher...'));
watcher.close();
process.exit(0);
});
});
// Helper functions
async function findSvgFiles(input, recursive = false) {
const stats = await fs.stat(input);
if (stats.isFile()) {
return input.endsWith('.svg') ? [input] : [];
}
const pattern = recursive ?
path.join(input, '**/*.svg') :
path.join(input, '*.svg');
return new Promise((resolve, reject) => {
glob(pattern, (err, files) => {
if (err) reject(err);
else resolve(files);
});
});
}
function parseSizes(sizesString) {
return sizesString.split(',').map(size => {
const [width, height] = size.split('x').map(n => parseInt(n.trim()));
return {
width: width,
height: height,
suffix: `${width}x${height}`
};
});
}
function parseColor(colorString) {
if (colorString.startsWith('#')) {
const hex = colorString.slice(1);
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return { r, g, b, alpha: 1 };
}
return { r: 255, g: 255, b: 255, alpha: 1 };
}
async function convertFile(converter, inputPath, outputDir) {
try {
const outputPath = path.join(outputDir, path.basename(inputPath, '.svg') + '.png');
await converter.convertSvgFile(inputPath, outputPath);
console.log(chalk.green('✓ Converted:'), outputPath);
} catch (error) {
console.log(chalk.red('✗ Failed:'), inputPath, '-', error.message);
}
}
program.parse();
Docker Container for Scalable Processing
Dockerfile and Container Setup
# Dockerfile
FROM node:18-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
libvips-dev \
librsvg2-dev \
chromium \
fonts-liberation \
fonts-roboto \
fonts-open-sans \
&& rm -rf /var/lib/apt/lists/*
# Set Puppeteer to use installed Chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
# Create app directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Create directories
RUN mkdir -p /app/input /app/output /app/temp
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
# Start application
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:
svg-converter:
build: .
ports:
- "3000:3000"
volumes:
- ./input:/app/input
- ./output:/app/output
- ./temp:/app/temp
environment:
- NODE_ENV=production
- MAX_CONCURRENT_CONVERSIONS=4
- TEMP_DIR=/app/temp
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
worker:
build: .
command: node worker.js
volumes:
- ./input:/app/input
- ./output:/app/output
- ./temp:/app/temp
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
depends_on:
- redis
deploy:
replicas: 2
restart: unless-stopped
# worker.js - Queue-based processing
const Queue = require('bull');
const { HighPerformanceSvgConverter } = require('./high-performance-converter');
const conversionQueue = new Queue('svg conversion', process.env.REDIS_URL || 'redis://localhost:6379');
const converter = new HighPerformanceSvgConverter();
conversionQueue.process(async (job, done) => {
try {
const { svgString, options, outputPath } = job.data;
console.log(`Processing job ${job.id}: ${outputPath}`);
const result = await converter.convertSvgString(svgString, options);
// Save to file system or cloud storage
await require('fs').promises.writeFile(outputPath, result.buffer);
done(null, {
outputPath: outputPath,
fileSize: result.buffer.length,
width: result.info?.width,
height: result.info?.height
});
} catch (error) {
console.error(`Job ${job.id} failed:`, error);
done(error);
}
});
conversionQueue.on('completed', (job, result) => {
console.log(`Job ${job.id} completed: ${result.outputPath}`);
});
conversionQueue.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err.message);
});
console.log('Worker started, waiting for jobs...');
Cloud Deployment Examples
AWS Lambda Function
// lambda-handler.js
const { Resvg } = require('@resvg/resvg-js');
exports.handler = async (event) => {
try {
const { svgString, width = 800, height = 600 } = JSON.parse(event.body);
if (!svgString) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'SVG string is required' })
};
}
// Convert SVG to PNG
const resvg = new Resvg(Buffer.from(svgString), {
background: 'rgba(0, 0, 0, 0)',
fitTo: {
mode: 'width',
value: width
}
});
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
// Return base64 encoded result
const base64 = pngBuffer.toString('base64');
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
success: true,
data: {
image: `data:image/png;base64,${base64}`,
width: width,
height: height,
fileSize: pngBuffer.length
}
})
};
} catch (error) {
console.error('Conversion error:', error);
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: 'Conversion failed',
details: error.message
})
};
}
};
// serverless.yml
service: svg-to-png-converter
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
timeout: 30
memorySize: 1024
functions:
convert:
handler: lambda-handler.handler
events:
- http:
path: convert
method: post
cors: true
plugins:
- serverless-webpack
custom:
webpack:
webpackConfig: 'webpack.config.js'
includeModules: true
packager: 'npm'
Conclusion
Programmatic SVG to PNG export enables powerful automation workflows for web applications, content management systems, and design tools. Whether you're automating exports from our AI icon generator or processing animated graphics from our animation tool, the choice of implementation depends on your specific requirements:
- Sharp + Resvg: Best performance for high-volume processing
- Puppeteer: Highest quality for complex SVGs with web fonts
- Canvas API: Client-side automation and real-time conversion
- Cloud Functions: Serverless scaling for occasional use
For immediate programmatic conversion needs, start with our SVG to PNG API and scale with custom implementations as your requirements grow. Explore our complete conversion toolkit for more automation options.
Related Implementation Guides
- SVG to PNG JavaScript - Client-side implementation details
- Quality Settings Guide - Optimization techniques
- SVG to Raster Best Practices - Format selection and optimization
- SVG to PNG Converter - Online conversion tool