How to create a selfie generator

In 2023, several major marketing campaigns used the Remove Background API to create selfie generators that allowed users to share personalized branded selfies on their social media.

Among them were the Barbie Movie, Netflix's Black Mirror and Taylor Swift's 1989 album.

In this tutorial, I want to show you how easy it is to build a web app that offers the same set of features, simply by leveraging the Remove Background API and a bit of JavaScript.

If you're curious to see a demo of the web app we're going to build, one is available here: https://photoroom-api-web-demo.vercel.app.

You will need an API key, you can get it here: https://app.photoroom.com/api-dashboard

Important: This tutorial focuses on the key parts of the implementation. If you want to experiment with the code, I recommend you check out the full codebase, which is open-source and available on GitHub.

Step 1: Uploading an image to the Remove Background API

To allow users to pick the image they want to use, we can use a standard <input> tag:

<input type="file" id="image-input" accept="image/*">

Then, we need to upload the file to the Remove Background API.

The implementation is pretty straightforward since the API call is a standard HTTP POST request:

async function removeBackground() {
    const formData = new FormData()
    formData.append("image_file", imageInput.files[0])
    formData.append("format", "png")

    const response = await fetch("https://sdk.photoroom.com/v1/segment", {
        method: "POST",
        headers: {
            "X-Api-Key": apiKey,
        },
        body: formData,
    })

    if (!response.ok) throw new Error("Failed to remove background")

    const blob = await response.blob()
    return await loadImage(blob)
}

You will need an API key, you can get it here: https://app.photoroom.com/api-dashboard

Step 2: Compositing the image

After we have received the image with the background removed, we need to use it to compose the final image.

The trick to getting to the final image is to place the background-less image between a background and an overlay layer:

The code that implements this process is inside the function drawImageWithOverlay():

async function drawImageWithOverlay(image, text, scaleFactor = 1, rotation = 0) {
    // load the backround and overlay images 
    const background = await loadLocalImage("assets/background.png")
    const overlay = await loadLocalImage("assets/overlay.png")

    const canvasHeight = 1920
    const canvasWidth = 1080

    const scale = Math.min(canvasWidth / image.width, canvasHeight / image.height)
    const scaledWidth = image.width * scale
    const scaledHeight = image.height * scale

    // create the canvas to compose the images
    const canvas = document.createElement("canvas")
    canvas.width = canvasWidth
    canvas.height = canvasHeight
    const ctx = canvas.getContext("2d")

    // Draw the background
    ctx.drawImage(background, 0, 0, canvasWidth, canvasHeight)

    // Adjust offsetX and offsetY to keep the image centered
    const offsetX = (canvasWidth - scaledWidth) / 2
    const offsetY = (canvasHeight - scaledHeight) / 2

    // Draw the image with the removed background
    ctx.drawImage(image, offsetX, offsetY, scaledWidth, scaledHeight)
  
    // Draw overlay on top
    ctx.drawImage(overlay, 0, 0, canvasWidth, canvasHeight)

    return canvas
}

This function contains a bit of code, but the gist of it is pretty simple:

  1. we create a canvas and draw the background image

  2. then, we scale the user’s image and draw it on top

  3. finally, we draw the overlay image on top of everything else

Step 3: Adding the text

We’re almost there, we only need one last thing: allowing the user to input some text and overlaying it on the image.

Allowing users to set the text is simple: once again we use a standard <input> tag:

<label for="overlay-text" style="display: block; margin-bottom: 0.5rem;">This person is</label>
<input type="text" id="overlay-text" placeholder="Enter text overlay here..." maxlength="46">

The more challenging part is to draw the text on the canvas, with the curvature taken into account.

For this, we need to add some code to the function drawImageWithOverlay():

// Add text overlay
const fontSize = 58 // Increased font size
const curveRadius = 400 // Adjusted curve radius for less curvature
const centerX = canvasWidth / 2
const centerY = canvasHeight / 2 - 300
const whiteSpaceWidth = 15
const lineHeight = 58

ctx.font = `bold ${fontSize}px 'DM Sans', sans-serif`
ctx.fillStyle = "#000000"

const lines = ['This person is ', text]
let cursorPositionY = 0

// Iterate over each line of text
for (const line of lines) {
    const lineWidth = ctx.measureText(line).width
    const words = line.split(" ")
    const totalAngle = lineWidth / curveRadius
    const startAngle = (-Math.PI - totalAngle) / 2
    const endAngle = startAngle + totalAngle

    let cursorPositionX = 0

    // Iterate over each word in the line
    for (let i = 0; i < words.length; i++) {
        const word = words[i]

        // Iterate over each character in the word
        for (let j = 0; j < word.length; j++) {
            const char = word.charAt(j)
            const charWidth = ctx.measureText(char).width

            const x = cursorPositionX / lineWidth
            const angle = startAngle + x * (endAngle  - startAngle)

            ctx.save()
            ctx.translate(
                centerX + curveRadius * Math.cos(angle),
                centerY + curveRadius * Math.sin(angle) + cursorPositionY
            )
            ctx.rotate(Math.PI / 2 + angle)
            ctx.fillText(char, 0, 0)
            ctx.restore()

            cursorPositionX += charWidth
        }

        cursorPositionX += whiteSpaceWidth
    }

    cursorPositionY += lineHeight
}

As you can see, this code is quite complex. But here’s the logic behind it:

  1. We iterate over the lines of text

  2. For each line, we compute the coordinates of the semi-circle that we will use as a baseline

  3. Then, we iterate over each word and each character in order to draw each character on that baseline with the correct rotation

Step 4: Downloading the image

We’re almost finished, all that’s left to do is add a button that will allow users to download the final image so that they can share it on their social media.

To do this we’re first going to add a <button> to the HTML:

<a id="download-button" href="#" download="result.png" style="display: none;">
    <button>Download</button>
</a>

And then we will add the code to handle the download:

const finalImage = await drawImageWithOverlay(segmentedPhoto, text)

const downloadButton = document.getElementById("download-button")

finalImage.toBlob((blob) => {
    const url = URL.createObjectURL(blob)
    downloadButton.href = url
})

Here the strategy is quite simple:

  1. we take the content of the canvas

  2. we turn it into a binary file representation

  3. we set the href of the download tag to the URL of that binary representation

Conclusion

That’s it, thanks to the Remove Background API and a bit of 2D drawing code in JavaScript, we’ve been able to implement a simple web app that enables users to:

  1. upload a selfie

  2. customize it using pre-defined visual assets

  3. overlay some text on top

  4. download the result

From that point, it would be pretty easy to reuse this web app for an actual marketing campaign.

All you would need is to replace the background, overlay, and text font with the actual branded assets you want to use for the campaign.

The full codebase is open-source and available on GitHub.

Last updated