Skip to content

Draw overlays and watermarks

Use Workers to draw watermarks, logos, signatures over other images. Overlays support transparency, positioning, and compositing modes.

You can draw overlays in a Worker using two approaches:

Draw with cf.image

To draw overlays on a fetch() subrequest in Workers, you can add a draw array to your cf.image options.

Each entry in the array specifies an overlay and its options, including optimization parameters like width, height, fit, blur, and rotate. Overlays are drawn in the order they appear — the last entry is the topmost layer.

JavaScript
export default {
async fetch(request) {
const imageURL = "https://example.com/image.png";
return fetch(imageURL, {
cf: {
image: {
width: 800,
height: 600,
draw: [
{
url: "https://example.com/branding/logo.png",
bottom: 5,
right: 5,
fit: "contain",
width: 100,
height: 50,
opacity: 0.8,
},
],
},
},
});
},
};

Draw with the Images binding

The Images binding uses a chainable .draw() method to draw an overlay on top of an image. You can chain multiple .draw() calls for multiple overlays.

Pass the overlay image as the first argument, then the draw options as the second. To apply optimization parameters to the overlay image, pass an .input() chain with .transform() as the first argument.

JavaScript
export default {
async fetch(request, env) {
const img = await fetch("https://zzzdna.com/blue.png");
const watermark = await fetch("https://zzzdna.com/purple.png");
const response = (
await env.IMAGES
.input(img.body)
.draw(
env.IMAGES.input(watermark.body).transform({ width: 100 }),
{ bottom: 10, right: 10, opacity: 0.5 }
)
.output({ format: "image/avif" })
).response();
return response;
},
};

Options

The dimensions of the output image are always determined by the base image. The overlay is then drawn onto the base image's canvas.

The following draw-specific options can be used for positioning and blending.

url

Absolute URL of the overlay image when drawing with cf.image. Supports any supported image format. For watermarks or non-rectangular overlays, use PNG or WebP images.

When drawing with the Images binding, the overlay is passed as image bytes or an .input() chain instead of a URL. Refer to Draw with the Images binding.

width and height

Sets the maximum dimensions of the overlay image when drawing with cf.image. Accepts an integer (pixels) or a decimal between 0 and 1 representing a fraction of the base image's dimension. For example, height:0.25 sets the overlay height to 25% of the height of the base image.

Use fit and gravity to control how the overlay image is resized and cropped.

When drawing with the Images binding, the dimensions of the overlay image can be set using the .transform() method.

repeat

Determines whether to tile the overlay across the image.

Accepts the following values:

  • true — Tiles the overlay to cover the entire area. This is useful for watermarks.
  • x — Tiles the overlay horizontally only.
  • y — Tiles the overlay vertically only.

top, left, bottom, right

Sets the position of the overlay image as an offset, in pixels, to the specified edge. 0 aligns the overlay flush to the edge. If no position is specified, then the overlay is centered.

For example, { bottom: 0, right: 10 } places the overlay at the bottom-right corner, 10 pixels inward from the right edge.

Setting both left and right, or both top and bottom returns an error.

opacity

Sets the opacity of the overlay image. Accepts a decimal value between 0.0 (fully transparent) and 1.0 (fully opaque). For example, opacity: 0.5 makes the overlay semitransparent.

composite

Controls how the overlay is blended with the base image using Porter-Duff compositing operations. The default is over.

The composite mode only affects the area within the overlay's bounding box. The base image is always preserved outside of this area.

Accepts the following values:

over

Draws the overlay image on top of the base image. This is the default composite behavior.

The overlay covers the base where they overlap. Both images are visible where they do not overlap.

composite=over output

in

Shows the overlay only where the base image is opaque. If the overlay has transparent pixels that overlap with the base image, then those areas of the base image will also become transparent.

composite=in output

atop

Draws the overlay image on top of the base image, but only where the base image is opaque. This will clip the overlay image to the shape of the base image.

If the overlay has transparent pixels that overlap with the base image, then the base image remains visible (unlike in).

composite=atop output

out

Shows the overlay only where the base image is transparent. Within the overlay's bounding box, opaque areas of the base image will become transparent.

composite=out output

xor

Shows the areas of each image where the other is transparent. Overlapping opaque areas will become transparent. This can be used to create rounded corners or custom shapes.

composite=xor output

lighter

Adds the color values of both images, which makes the overlapping areas brighter.

composite=lighter output

Examples

Watermark

Tile a semitransparent watermark across the entire image using cf.image.

JavaScript
fetch(imageURL, {
cf: {
image: {
draw: [
{
url: "https://example.com/watermark.png",
repeat: true,
opacity: 0.2,
},
],
},
},
});

Logo in the corner

Position a logo at the bottom-right corner using cf.image.

JavaScript
fetch(imageURL, {
cf: {
image: {
draw: [
{
url: "https://example.com/logo.png",
bottom: 5,
right: 5,
},
],
},
},
});

Multiple overlays

Combine multiple overlays in one request using cf.image. They are drawn in order — the last entry is the topmost layer.

JavaScript
fetch(imageURL, {
cf: {
image: {
draw: [
{ url: "https://example.com/watermark.png", repeat: true, opacity: 0.2 },
{ url: "https://example.com/play-button.png" },
{ url: "https://example.com/logo.png", bottom: 5, right: 5 },
],
},
},
});

Rounded corners

Cut rounded corners from an image. A corner mask is drawn at each corner and rotated to match the position. xor removes the overlapping pixels.

The example below shows how this can be done using the Images binding.

TypeScript
const image = await fetch("https://example.com/photo.png");
const mask = await fetch("https://example.com/corner-mask.png");
let [topLeft, topRight] = mask.body.tee();
let bottomLeft, bottomRight;
[topLeft, bottomLeft] = topLeft.tee();
[topLeft, bottomRight] = topLeft.tee();
const output = await env.IMAGES
.input(image.body)
.draw(env.IMAGES.input(topLeft).transform({ rotate: 0 }), {
left: 0,
top: 0,
composite: "xor",
})
.draw(env.IMAGES.input(topRight).transform({ rotate: 90 }), {
right: 0,
top: 0,
composite: "xor",
})
.draw(env.IMAGES.input(bottomRight).transform({ rotate: 180 }), {
bottom: 0,
right: 0,
composite: "xor",
})
.draw(env.IMAGES.input(bottomLeft).transform({ rotate: 270 }), {
bottom: 0,
left: 0,
composite: "xor",
})
.output({ format: "image/png" });
return output.response();