Getting Started
Developer Guides
Convert SVG to PNG using Headless Chrome

Convert SVG to PNG using Headless Chrome

While imgix supports rasterization of SVG files, there are cases where you might need to convert SVG files to PNG internally for various reasons.

In this article, we will demonstrate an implementation example of converting SVG to PNG using headless chrome.

This can be useful when you need to rasterize SVG files that contain elements such as foreignObject or text elements with custom fonts, which are not natively supported by many image processors and would result in blank outputs, or when you have animated SVGs where you need to capture only the starting frame.

Prerequisites

While it's possible to create similar solutions in other cloud providers, in this example we will be using AWS services.

  • S3 bucket to store the SVG and PNG files
  • AWS Lambda (Runtime: Node.js 20.x)
  • A tailored Chromium fork (opens in a new tab)
  • Puppeteer-core to control the headless Chrome browser

Implementation

Step 1: Create a Lambda Layer

Create a Lambda Layer with the Chromium binary.

This step is necessary because the default Lambda environment has size limitations which prevent us from uploading the Chromium binary directly.

git clone --depth=1 https://github.com/Sparticuz/chromium.git && \
cd chromium && \
make chromium.zip && \
mv chromium.zip ..

The created chromium.zip file should be around ~64MB in size.

Upload the chromium.zip file to a S3 bucket and create a Lambda Layer with it.

For runtime, we select the latest Node.js 20.x version.

Step 2: Create a Lambda Function

Create a new Lambda function with the following settings:

  • Runtime: Node.js 20.x
  • Memory: 1024MB (adjust as needed)
  • Timeout: 5 minutes (adjust as needed)
  • Add the previously created Lambda Layer
  • Give the Lambda function the necessary permissions to read from and write to the S3 bucket

Step 3: Create the Lambda Function Code

Create the following project on your local machine:

    • package.json
    • index.mjs
  • package.json
    {
      "type": "module",
      "dependencies": {
        "aws-sdk": "^2.1036.0",
        "puppeteer-core": "^10.1.0"
      }
    }

    Warning

    For simple demonstration purposes, we use the suffix option in a trigger to only process files with the .svg extension. In a production environment, you might want to consider using a more robust trigger solution that can make a destinction between file types outside of the file extension.

    index.mjs
    import chromium from "@sparticuz/chromium"
    import puppeteer from "puppeteer-core"
    import AWS from "aws-sdk"
     
    const s3 = new AWS.S3()
     
    export const handler = async (event) => {
      const bucket = event.Records[0].s3.bucket.name
      const key = event.Records[0].s3.object.key
     
      // Download SVG file from S3
      const svgData = await s3.getObject({ Bucket: bucket, Key: key }).promise()
     
      let browser = null
     
      try {
        browser = await puppeteer.launch({
          args: chromium.args,
          defaultViewport: chromium.defaultViewport,
          executablePath: await chromium.executablePath(),
          headless: chromium.headless,
        })
     
        const page = await browser.newPage()
        await page.setContent(svgData.Body.toString("utf-8"))
        await page.waitForSelector("svg")
        const screenshot = await page.screenshot({ type: "png" })
     
        const outputKey = key.replace(".svg", ".png")
        await s3
          .putObject({
            Bucket: bucket,
            Key: outputKey,
            Body: screenshot,
            ContentType: "image/png",
          })
          .promise()
     
        return {
          statusCode: 200,
          body: `Successfully converted ${key} to PNG and uploaded to ${bucket}/${outputKey}`,
        }
      } catch (error) {
        console.error("Error:", error)
        return {
          statusCode: 500,
          body: `Failed to convert ${key} to PNG: ${error.message}`,
        }
      } finally {
        if (browser !== null) {
          await browser.close()
        }
      }
    }

    Next, inside the svg-to-png directory, run the following command to install the dependencies and zip necessary files:

    npm install && \
    zip -r svg-to-png.zip index.mjs node_modules package.json

    Upload the svg-to-png.zip file to the Lambda function under "Code source" > "Upload from" > ".zip file".

    Step 4: Set Up S3 Event Trigger

    Set up an S3 event trigger to the Lambda function to process SVG files.

    1. Navigate to "Add trigger"
    2. Select S3 and choose the desired bucket
    3. For Event types, select "All object create events"
    4. For the Prefix or Suffix option, enter the desired value (e.g.,svgs/ for prefix or .svg for suffix)

    Step 5: Test the Lambda Function

    Upload an SVG file to the S3 bucket and observe the Lambda function logs for the conversion status.

    Note that since we are running Chromium, the conversion process might take a few seconds or even minutes.

    Confirm that the PNG file is created in the same S3 bucket and contains the expected content including foreignObject elements.

    Conclusion

    In this tutorial, we have demonstrated how to convert SVG files to PNG using headless Chrome and AWS Lambda. This can be useful when you need to rasterize SVG files that contain elements such as foreignObject or text elements with custom fonts, which are not natively supported by many image processing libraries and would result in blank outputs. Using a headless browser allows us to accurately render these complex SVGs, leveraging the browser's rendering engine.

    Once the Lambda function is set up, you can easily scale the solution to process multiple SVG files concurrently.

    From there, you can use imgix to serve the PNG files and apply further transformations or optimizations as needed.

    References