Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 30 additions & 25 deletions src/content/docs/images/examples/watermark-from-kv.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---

summary: Draw a watermark from KV on an image from R2
pcx_content_type: example
title: Watermarks
Expand All @@ -9,41 +8,47 @@ description: Draw a watermark from KV on an image from R2
reviewed: 2025-04-03
---

import { TypeScriptExample } from "~/components";

<TypeScriptExample>

```ts
interface Env {
BUCKET: R2Bucket,
NAMESPACE: KVNamespace,
IMAGES: ImagesBinding,
BUCKET: R2Bucket;
NAMESPACE: KVNamespace;
IMAGES: ImagesBinding;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const watermarkKey = "my-watermark";
const sourceKey = "my-source-image";
async fetch(request, env, ctx): Promise<Response> {
const watermarkKey = "my-watermark";
const sourceKey = "my-source-image";

const cache = await caches.open("transformed-images");
const cacheKey = new URL(sourceKey + "/" + watermarkKey, request.url);
const cacheResponse = await cache.match(cacheKey);
const cache = await caches.open("transformed-images");
const cacheKey = new URL(sourceKey + "/" + watermarkKey, request.url);
const cacheResponse = await cache.match(cacheKey);

if (cacheResponse) {
return cacheResponse;
}
if (cacheResponse) {
return cacheResponse;
}

let watermark = await env.NAMESPACE.get(watermarkKey, "stream");
let source = await env.BUCKET.get(sourceKey);
let watermark = await env.NAMESPACE.get(watermarkKey, "stream");
let source = await env.BUCKET.get(sourceKey);

if (!watermark || !source) {
return new Response("Not found", { status: 404 });
}
if (!watermark || !source) {
return new Response("Not found", { status: 404 });
}

const result = await env.IMAGES.input(source.body)
.draw(watermark)
.output({ format: "image/jpeg" });
const result = await env.IMAGES.input(source.body)
.draw(watermark)
.output({ format: "image/jpeg" });

const response = result.response();
const response = result.response();

ctx.waitUntil(cache.put(cacheKey, response.clone()));
ctx.waitUntil(cache.put(cacheKey, response.clone()));

return result.response();
},
return response;
},
} satisfies ExportedHandler<Env>;
```

</TypeScriptExample>
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function generateSignedUrl(url) {

export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(event.request.url);
const url = new URL(request.url);
const imageDeliveryURL = new URL(
url.pathname
.slice(1)
Expand Down
29 changes: 18 additions & 11 deletions src/content/docs/images/transform-images/bindings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar:
order: 4
---

import { WranglerConfig } from "~/components";
import { WranglerConfig, TypeScriptExample } from "~/components";

A [binding](/workers/runtime-apis/bindings/) connects your [Worker](/workers/) to external resources on the Developer Platform, like [Images](/images/transform-images/transform-via-workers/), [R2 buckets](/r2/buckets/), or [KV Namespaces](/kv/concepts/kv-namespaces/).

Expand Down Expand Up @@ -61,26 +61,29 @@ Within your Worker code, you can interact with this binding by using `env.IMAGES

For example, to draw a resized watermark on an image:

<TypeScriptExample>

```ts
// Fetch the watermark from Workers Assets, R2, KV etc
const watermark: ReadableStream = ...
const watermark: ReadableStream = getWatermarkStream();

// Fetch the main image
const image: ReadableStream = ...
const image: ReadableStream = getImageStream();

const response = (
await env.IMAGES.input(image)
.draw(
env.IMAGES.input(watermark)
.transform({ width: 32, height: 32}),
{ bottom: 32, right: 32 }
)
.output({ format: "image/avif" })
).response()
await env.IMAGES.input(image)
.draw(env.IMAGES.input(watermark).transform({ width: 32, height: 32 }), {
bottom: 32,
right: 32,
})
.output({ format: "image/avif" })
).response();

return response;
```

</TypeScriptExample>

### `.output()`

- You must define [a supported format](/images/transform-images/#supported-output-formats) such as AVIF, WebP, or JPEG for the [transformed image](/images/transform-images/).
Expand All @@ -89,6 +92,8 @@ return response;

For example, to rotate, resize, and blur an image, then output the image as AVIF:

<TypeScriptExample>

```ts
const info = await env.IMAGES.info(stream);
// Stream contains a valid image, and width/height is available on the info object
Expand All @@ -107,6 +112,8 @@ const response = (
return response;
```

</TypeScriptExample>

### `.info()`

- Outputs information about the image, such as `format`, `fileSize`, `width`, and `height`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default {
const requestURL = new URL(request.url);
// Append the request path such as "/assets/image1.jpg" to the hiddenImageOrigin.
// You could also process the path to add or remove directories, modify filenames, etc.
const imageURL = hiddenImageOrigin + requestURL.path;
const imageURL = hiddenImageOrigin + requestURL.pathname;
// This will fetch image from the given URL, but to the website's visitors this
// will appear as a response to the original request. Visitor’s browser will
// not see this URL.
Expand All @@ -61,13 +61,12 @@ export default {
const imageURL = … // detail omitted in this example, see the previous example

const requestURL = new URL(request.url)
const resizingOptions = {
width: requestURL.searchParams.get("width"),
}
const width = parseInt(requestURL.searchParams.get("width"), 10);
const resizingOptions = { width }
// If someone tries to manipulate your image URLs to reveal higher-resolution images,
// you can catch that and refuse to serve the request (or enforce a smaller size, etc.)
if (resizingOptions.width > 1000) {
throw Error("We dont allow viewing images larger than 1000 pixels wide")
return new Response("We don't allow viewing images larger than 1000 pixels wide", { status: 400 })
}
return fetch(imageURL, {cf:{image:resizingOptions}})
},};
Expand All @@ -85,7 +84,7 @@ export default {

// The regex selects the first path component after the "images"
// prefix, and the rest of the path (e.g. "/images/first/rest")
const match = requestURL.path.match(/images\/([^/]+)\/(.+)/);
const match = requestURL.pathname.match(/images\/([^/]+)\/(.+)/);

// You can require the first path component to be one of the
// predefined sizes only, and set actual dimensions accordingly.
Expand Down Expand Up @@ -121,14 +120,14 @@ Cloudflare image transformations cache resized images to aid performance. Images
const signedHeaders = generatedSignedHeaders();

fetch(private_url, {
headers: signedHeaders
cf: {
image: {
format: "auto",
"origin-auth": "share-publicly"
}
}
})
headers: signedHeaders,
cf: {
image: {
format: "auto",
"origin-auth": "share-publicly",
},
},
});
```

When using this code, the following headers are passed through to the origin, and allow your request to be successful:
Expand Down
104 changes: 51 additions & 53 deletions src/content/docs/images/transform-images/draw-overlays.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pcx_content_type: reference
title: Draw overlays and watermarks
sidebar:
order: 5

---

You can draw additional images on top of a resized image, with transparency and blending effects. This enables adding of watermarks, logos, signatures, vignettes, and other effects to resized images.
Expand All @@ -12,61 +11,59 @@ This feature is available only in [Workers](/images/transform-images/transform-v

```js
fetch(imageURL, {
cf: {
image: {
width: 800,
height: 600,
draw: [
{
url: 'https://example.com/branding/logo.png', // draw this image
bottom: 5, // 5 pixels from the bottom edge
right: 5, // 5 pixels from the right edge
fit: 'contain', // make it fit within 100x50 area
width: 100,
height: 50,
opacity: 0.8, // 20% transparent
},
],
},
},
cf: {
image: {
width: 800,
height: 600,
draw: [
{
url: "https://example.com/branding/logo.png", // draw this image
bottom: 5, // 5 pixels from the bottom edge
right: 5, // 5 pixels from the right edge
fit: "contain", // make it fit within 100x50 area
width: 100,
height: 50,
opacity: 0.8, // 20% transparent
},
],
},
},
});
```

## Draw options

The `draw` property is an array. Overlays are drawn in the order they appear in the array (the last array entry is the topmost layer). Each item in the `draw` array is an object, which can have the following properties:

- `url`
- Absolute URL of the image file to use for the drawing. It can be any of the supported file formats. For drawing watermarks or non-rectangular overlays, Cloudflare recommends that you use PNG or WebP images.

- `width` and `height`
- Maximum size of the overlay image, in pixels. It must be an integer.

* `url`
* Absolute URL of the image file to use for the drawing. It can be any of the supported file formats. For drawing watermarks or non-rectangular overlays, Cloudflare recommends that you use PNG or WebP images.

* `width` and `height`
* Maximum size of the overlay image, in pixels. It must be an integer.
- `fit` and `gravity`
- Affects interpretation of `width` and `height`. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).

* `fit` and `gravity`
* Affects interpretation of `width` and `height`. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).
- `opacity`
- Floating-point number between `0` (transparent) and `1` (opaque). For example, `opacity: 0.5` makes overlay semitransparent.

* `opacity`
* Floating-point number between `0` (transparent) and `1` (opaque). For example, `opacity: 0.5` makes overlay semitransparent.
- `repeat`
- If set to `true`, the overlay image will be tiled to cover the entire area. This is useful for stock-photo-like watermarks.
- If set to `"x"`, the overlay image will be tiled horizontally only (form a line).
- If set to `"y"`, the overlay image will be tiled vertically only (form a line).

* `repeat`
* If set to `true`, the overlay image will be tiled to cover the entire area. This is useful for stock-photo-like watermarks.
* If set to `"x"`, the overlay image will be tiled horizontally only (form a line).
* If set to `"y"`, the overlay image will be tiled vertically only (form a line).

* `top`, `left`, `bottom`, `right`
* Position of the overlay image relative to a given edge. Each property is an offset in pixels. `0` aligns exactly to the edge. For example, `left: 10` positions left side of the overlay 10 pixels from the left edge of the image it is drawn over. `bottom: 0` aligns bottom of the overlay with bottom of the background image.
- `top`, `left`, `bottom`, `right`
- Position of the overlay image relative to a given edge. Each property is an offset in pixels. `0` aligns exactly to the edge. For example, `left: 10` positions left side of the overlay 10 pixels from the left edge of the image it is drawn over. `bottom: 0` aligns bottom of the overlay with bottom of the background image.

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

If no position is specified, the image will be centered.

* `background`
* Background color to add underneath the overlay image. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).
- `background`
- Background color to add underneath the overlay image. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).

* `rotate`
* Number of degrees to rotate the overlay image by. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).
- `rotate`
- Number of degrees to rotate the overlay image by. Same as [for the main image](/images/transform-images/transform-via-workers/#fetch-options).

## Draw using the Images binding

Expand All @@ -76,14 +73,15 @@ The accepted options for the overlaid image are `opacity`, `repeat`, `top`, `lef

```js
// Fetch image and watermark
const img = await fetch('https://example.com/image.png');
const watermark = await fetch('https://example.com/watermark.png');
const img = await fetch("https://example.com/image.png");
const watermark = await fetch("https://example.com/watermark.png");

const response = await env.IMAGES.input(img.body)
.transform({ width: 1024 })
.draw(watermark.body, { "opacity": 0.25, "repeat": true })
.output({ format: "image/avif" })
.response();
const response = (
await env.IMAGES.input(img.body)
.transform({ width: 1024 })
.draw(watermark.body, { opacity: 0.25, repeat: true })
.output({ format: "image/avif" })
).response();

return response;
```
Expand All @@ -95,10 +93,10 @@ In the example below, the watermark is manipulated with `rotate` and `width` bef
```js
// Fetch image and watermark
const response = (
await env.IMAGES.input(img.body)
.transform({ width: 1024 })
.draw(watermark.body, { "opacity": 0.25, "repeat": true })
.output({ format: "image/avif" })
await env.IMAGES.input(img.body)
.transform({ width: 1024 })
.draw(watermark.body, { opacity: 0.25, repeat: true })
.output({ format: "image/avif" })
).response();
```

Expand All @@ -114,7 +112,7 @@ image: {
repeat: true, // Tiled over entire image
opacity: 0.2, // and subtly blended
},
];
],
}
```

Expand All @@ -128,7 +126,7 @@ image: {
bottom: 5, // Positioned near bottom right corner
right: 5,
},
];
],
}
```

Expand All @@ -141,7 +139,7 @@ image: {
url: 'https://example.com/play-button.png',
// Center position is the default
},
];
],
}
```

Expand All @@ -155,6 +153,6 @@ image: {
{ url: 'https://example.com/watermark.png', repeat: true, opacity: 0.2 },
{ url: 'https://example.com/play-button.png' },
{ url: 'https://example.com/by-me.png', bottom: 5, right: 5 },
];
],
}
```
Loading