Image processing

Apply effects to a user's image with an editing extension.

The basic lifecycle of an editing extension has three steps:

  1. Receive an image from Canva.

  2. Perform some kind of processing on the image.

  3. Return the manipulated image to Canva.

All editing extensions perform these steps.

This guide explains how to create an editing extension that implements each step in this lifecycle. This lays the groundwork for developing extensions with more advanced features, such as rich controls and presets.

Prerequisites

  • You're comfortable with JavaScript.

  • You know what an editing extension is.

  • You've read the Quick start guide.

Step 1: Initialize an editing extension

When a user opens an editing extension, Canva:

  1. Places an iframe on top of the user's image.

  2. Injects a JavaScript client into the iframe.

  3. Attaches the client to the window object.

An extension can use the client to interact with Canva.

To initialize the client:

  1. Call the init method:

    const canva = window.canva.init();

    This method returns a CanvaApiClient object, which provides methods for interacting with Canva.

  2. Register a callback with the onReady method:

    const canva = window.canva.init();
    canva.onReady(async (opts) => {
    console.log(opts);
    });

    This callback runs when the extension loads. It receives an opts object that contains information the extension needs to render and manipulate the user's image.

Step 2: Render the user's image

When a user opens an extension, the extension must download and render the user's image in the iframe. If it doesn't, the iframe remains empty and the user's image appears to vanish.

To let an extension download the user's image, Canva passes an opts.element parameter into the onReady callback. This parameter is a CanvaElement object. It contains metadata about how the image appears in the design (e.g. position and rotation) and URLs for downloading the user's image.

An extension can download the user's image in three different resolutions:

  • thumbnail

  • preview

  • full

In general, extensions should download the preview version of the user's image. Compared to the full version, the average user won't notice a difference in terms of visual clarity, but the extension will be more performant.

To download and render the user's image:

  1. Destructure imageHelpers from the window.canva object:

    const { imageHelpers } = window.canva;

    The imageHelpers object provides helper methods for common image operations.

  2. Pass the opts.element parameter into the fromElement method:

    canva.onReady(async (opts) => {
    // Convert the CanvaElement into a CanvaImageBlob
    const image = await imageHelpers.fromElement(opts.element, 'preview');
    });

    This downloads the user's image and converts it into a CanvaImageBlob. The second argument of "preview" tells Canva to download the preview version of the image. (The default value is "full".)

  3. Use the toCanvas method to convert the CanvaImageBlob into an HTMLCanvasElement:

    canva.onReady(async (opts) => {
    // Convert the CanvaElement into a CanvaImageBlob
    const image = await imageHelpers.fromElement(opts.element, 'preview');
    // Convert the CanvaImageBlob into an HTMLCanvasElement
    const canvas = await imageHelpers.toCanvas(image);
    });
  4. Append the HTMLCanvasElement to the document.body:

    canva.onReady(async (opts) => {
    // Convert the CanvaElement into a CanvaImageBlob
    const image = await imageHelpers.fromElement(opts.element, 'preview');
    // Convert the CanvaImageBlob into an HTMLCanvasElement
    const canvas = await imageHelpers.toCanvas(image);
    // Render the HTMLCanvasElement
    document.body.appendChild(canvas);
    });

    This renders the HTMLCanvasElement in the iframe.

  5. Call the updateControlPanel method:

    canva.onReady(async (opts) => {
    // Convert the CanvaElement into a CanvaImageBlob
    const image = await imageHelpers.fromElement(opts.element, 'preview');
    // Convert the CanvaImageBlob into an HTMLCanvasElement
    const canvas = await imageHelpers.toCanvas(image);
    // Render the HTMLCanvasElement
    document.body.appendChild(canvas);
    // Render the control panel
    canva.updateControlPanel([]);
    });

    This method is required even if the extension doesn't render rich controls. Without it, the control panel remains in a loading state.

After making these changes, preview the extension and see the user's image appear in the iframe.

Step 3: Apply an effect to the user's image

An editing extension can apply effects to the user's image at various points in its lifecycle, such as when:

  • The extension loads.

  • The user selects a preset.

  • The user interacts with a rich control.

Canva doesn't care how an extension applies effects to the user's image, but some common tools include:

The following snippet uses the Canvas API to invert the colors of the user's image when the extension loads:

canva.onReady(async (opts) => {
// Convert the CanvaElement into a CanvaImageBlob
const image = await imageHelpers.fromElement(opts.element, 'preview');
// Convert the CanvaImageBlob into a HTMLCanvasElement
const canvas = await imageHelpers.toCanvas(image);
// Render the user's image in the iframe
document.body.appendChild(canvas);
// Get a 2D drawing context
const context = canvas.getContext('2d');
// Invert the colors of the user's image
context.filter = 'invert(100%)';
// Draw the inverted image into the HTMLCanvasElement
context.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// Render the control panel. (This is always required, even if
// the extension doesn't have rich controls.)
canva.updateControlPanel([]);
});

This is what the extension looks like:

Step 4: Update the user's image

Even though extensions should download the preview version of the user's image when the extension loads, extensions must always save the highest possible resolution of the user's image.

To accomplish this, Canva calls the onImageUpdate callback immediately before attempting to save the user's image. This callback receives the full-resolution version of the user's image via the opts.image parameter. Upon receiving this request, an extension must:

  • Replace the original image with the updated image.

  • Re-apply all effects to the updated image.

To handle image update requests:

  1. Register a callback with the onImageUpdate method:

    canva.onImageUpdate(async (opts) => {
    console.log(opts);
    });
  2. Use the toImageElement method to convert the CanvaImageBlob into an HTMLImageElement:

    canva.onImageUpdate(async (opts) => {
    // Convert the CanvaImageBlob into an HTMLImageElement
    const img = await imageHelpers.toImageElement(opts.image);
    });
  3. Draw the updated image into the existing HTMLCanvasElement:

    canva.onImageUpdate(async (opts) => {
    // Convert the CanvaImageBlob into an HTMLImageElement
    const img = await imageHelpers.toImageElement(opts.image);
    // Get the current HTMLCanvasElement
    const canvas = document.querySelector('canvas');
    // Draw the HTMLImageElement into the HTMLCanvasElement
    canvas.drawImage(img, 0, 0, canvas.width, canvas.height);
    });

    In this case, the extension doesn't need to re-apply the invert filter, because the context.filter property is attached to the HTMLCanvasElement. In a more advanced extension though, the logic for applying an effect is often refactored into a separate function and reused throughout the extension's lifecycle.

Step 5: Save the user's image

If the user user closes an extension without clicking the Cancel button, Canva calls the onSaveRequest callback. In this callback, the extension must return the manipulated image to Canva as a CanvaImageBlob. Canva can then persist the changes the user has made to their image.

To handle save requests:

  1. Register a callback with the onSaveRequest method:

    canva.onSaveRequest(async () => {
    // code goes here
    });
  2. Use the fromCanvas method to return the user's image as a CanvaImageBlob:

    canva.onSaveRequest(async () => {
    // Get the HTMLCanvasElement
    const canvas = document.querySelector('canvas');
    // Convert the HTMLCanvasElement into a CanvaImageBlob
    return await imageHelpers.fromCanvas('image/jpeg', canvas);
    });

    You can also return the user's image as a CanvaElement.

Example

// Get helper methods for working with images
const { imageHelpers } = window.canva;
// Initialize the client
const canva = window.canva.init();
// The extension has loaded
canva.onReady(async (opts) => {
// Convert the CanvaElement into a CanvaImageBlob
const image = await imageHelpers.fromElement(opts.element, 'preview');
// Convert the CanvaImageBlob into a HTMLCanvasElement
const canvas = await imageHelpers.toCanvas(image);
// Render the user's image in the iframe
document.body.appendChild(canvas);
// Get a 2D drawing context
const context = canvas.getContext('2d');
// Invert the colors of the user's image
context.filter = 'invert(100%)';
// Draw the inverted image into the HTMLCanvasElement
context.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// Render the control panel. (This is always required, even if
// the extension doesn't have rich controls.)
canva.updateControlPanel([]);
});
// Canva has requested the extension to update the user's image
canva.onImageUpdate(async (opts) => {
// Get the updated image
const img = await imageHelpers.toImageElement(opts.image);
// Get the HTMLCanvasElement that contains the user's image
const canvas = document.querySelector('canvas');
// Get a 2D drawing context
const context = canvas.getContext('2d');
// Draw the updated image into the HTMLCanvasElement
context.drawImage(img, 0, 0, canvas.width, canvas.height);
});
// Canva has requested the extension to save the user's image
canva.onSaveRequest(async () => {
// Get the HTMLCanvasElement that contains the user's image
const canvas = document.querySelector('canvas');
// Return the image to Canva as a CanvaImageBlob
return await imageHelpers.fromCanvas('image/jpeg', canvas);
});