Skip to content

Instantly share code, notes, and snippets.

@isair
Last active July 20, 2020 11:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isair/aabe5ac37ea4ee92a900157acda7d4a3 to your computer and use it in GitHub Desktop.
Save isair/aabe5ac37ea4ee92a900157acda7d4a3 to your computer and use it in GitHub Desktop.
ts-node script for converting all SVG files in a directory to @1x @2x @3x PNG files, files are processed in parallel for optimal performance
import path from 'path';
import { exec } from 'child_process';
import fse from 'fs-extra';
import glob from 'glob-promise';
import pLimit from 'p-limit';
const imagesPath = path.resolve('./assets/svg');
const outputPath = path.resolve('./assets/images');
const concurrency = 2; // File count only. Actual parallelism is three times this.
function execAsync(command: string): Promise<string> {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(error.message);
} else if (stderr) {
reject(stderr);
} else {
resolve(stdout);
}
});
});
}
async function convertSvgToPng(svgPath: string, scale: number) {
const svgBuffer = await fse.readFile(svgPath);
const svgString = svgBuffer.toString();
const widthMatches = svgString.match(/ width="(\d+)(px)*"/);
const heightMatches = svgString.match(/ height="(\d+)(px)*"/);
const newWidth =
widthMatches && widthMatches.length > 1
? Number(widthMatches[1]) * scale
: 300;
const newHeight =
heightMatches && heightMatches.length > 1
? Number(heightMatches[1]) * scale
: 300;
const svgPathWithoutExtension = svgPath.slice(0, -4);
const svgName = svgPathWithoutExtension.substr(
svgPathWithoutExtension.lastIndexOf('/') + 1
);
const scaleAnnotation = scale !== 1 ? `@${scale}x` : '';
const pngPath = `${outputPath}/${svgName}${scaleAnnotation}.png`;
await execAsync(
`inkscape ${svgPath} -w ${newWidth} -h ${newHeight} --export-file ${pngPath}`
);
process.stdout.write('.');
}
const convertSvgToPngBundle = async (svgPath: string) =>
Promise.all([1, 2, 3].map((scale) => convertSvgToPng(svgPath, scale)));
async function run() {
await fse.ensureDir(imagesPath);
const svgPaths = await glob.promise(path.join(imagesPath, '**/*.svg'));
console.log(
`Converting ${svgPaths.length} SVG files in parallel, please wait`
);
const limit = pLimit(concurrency);
await Promise.all(
svgPaths.map((svgPath) => limit(() => convertSvgToPngBundle(svgPath)))
);
console.log('\nSuccessfully converted SVGs to PNGs');
}
run().catch((error) =>
console.error(`Failed to transform: ${error.toString()}`)
);
@isair
Copy link
Author

isair commented Feb 28, 2020

Assuming you are already in a TypeScript project with ts-node, install the following development dependencies:

yarn add -D fs-extra glob-promise p-limit
brew install inkscape

Add the following script to your package.json after placing this script under scripts:

"convert:svg": "ts-node ./scripts/svg2png.ts",

I'm using Inkscape as no NPM package I have tried has given as good results as the Inkscape CLI does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment