INTEGRITY Dokumentace

Transformace obrázků nahraných uživateli před jejich nahráním do R2

V této příručce vytvoříte aplikaci, která přijímá nahrávané obrázky, překryje obrázek vizuálním vodoznakem a poté uloží transformovaný obrázek do vašeho úložiště R2.


Images vám umožňuje zvolit si, kde budou vaše původní obrázky uložené. Transformovat můžete i obrázky uložené mimo produkt Images, například v R2.

Pokud ukládáte média nahraná uživateli do R2, může se hodit obrázky před nahráním do R2 bucketu optimalizovat nebo jinak upravit.

Naučíte se, jak pomocí bindings propojit služby Developer Platform se svým Workerem a jak využívat různé optimalizační funkce v Images API.

Předpoklady

Než začnete, budete muset provést následující kroky:

Pokud jste tu noví, přečtěte si, jak vytvořte si svého prvního Workera.

1: Nastavte svůj projekt Worker

Nejprve budete muset svůj projekt nastavit tak, aby využíval následující zdroje na Developer Platform:

Přidejte bindings do konfigurace Wrangler

Nakonfigurujte konfigurační soubor Wrangler a přidejte bindings pro Images, R2 a Assets:

{
	"images": {
		"binding": "IMAGES"
	},
	"r2_buckets": [
		{
			"binding": "R2",
			"bucket_name": "<BUCKET>"
		}
	],
	"assets": {
		"directory": "./<DIRECTORY>",
		"binding": "ASSETS"
	}
}
[images]
binding = "IMAGES"

[[r2_buckets]]
binding = "R2"
bucket_name = "<BUCKET>"

[assets]
directory = "./<DIRECTORY>"
binding = "ASSETS"

Nahraďte <BUCKET> s názvem bucketu R2, do kterého budete nahrávat obrázky po jejich transformaci. Ve svém kódu Workeru budete moci na tento bucket odkazovat pomocí env.R2.

Nahraďte ./<DIRECTORY> s názvem adresáře projektu, kam se uloží překryvný obrázek. Ve svém kódu Workeru budete moci na tato aktiva odkazovat pomocí env.ASSETS.

Nastavte si adresář assets

Protože chceme každému nahranému obrázku přidat vizuální vodoznak, potřebujete místo pro uložení překryvného obrázku.

Adresář assets ve vašem projektu umožňuje nahrát statické soubory jako součást Workeru. Při nasazení projektu se tyto nahrané soubory spolu s kódem Workeru nasadí do infrastruktury Cloudflare v rámci jediné operace.

Po nakonfigurování souboru Wrangler nahrajte překryvný obrázek do zadaného adresáře. V naší ukázkové aplikaci je tímto adresářem ./assets obsahuje překryvný obrázek.

2: Vytvořte frontend

Budete muset vytvořit rozhraní aplikace, které uživatelům umožní nahrávat obrázky.

V tomto příkladu je frontend vykreslován přímo ze skriptu Worker.

K tomu vytvořte nový html proměnná, která obsahuje form prvek pro přijímání nahrávání. V fetch, vytvořte nový Response s Content-Type: text/html hlavičku pro doručení vašeho statického HTML webu klientovi:

const html = `
<!DOCTYPE html>
        <html>
          <head>
            <meta charset="UTF-8">
            <title>Upload Image</title>
          </head>
          <body>
            <h1>Upload an image</h1>
            <form method="POST" enctype="multipart/form-data">
              <input type="file" name="image" accept="image/*" required />
              <button type="submit">Upload</button>
            </form>
          </body>
        </html>
`;

export default {
	async fetch(request, env) {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			// This is called when the user submits the form
		}
	},
};
const html = `
<!DOCTYPE html>
        <html>
          <head>
            <meta charset="UTF-8">
            <title>Upload Image</title>
          </head>
          <body>
            <h1>Upload an image</h1>
            <form method="POST" enctype="multipart/form-data">
              <input type="file" name="image" accept="image/*" required />
              <button type="submit">Upload</button>
            </form>
          </body>
        </html>
`;

interface Env {
	IMAGES: ImagesBinding;
	R2: R2Bucket;
	ASSETS: Fetcher;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			// This is called when the user submits the form
		}
	},
} satisfies ExportedHandler<Env>;

3: Načtěte nahraný obrázek

Jakmile máte form, musíte zajistit, aby bylo možné nahrané obrázky transformovat.

Protože form umožňuje uživatelům nahrávat soubory přímo z disku, nemůžete použít fetch() k získání obrázku z URL. Místo toho budete pracovat s tělem obrázku jako s proudem bajtů.

K tomu parsujte nahraný soubor z form a získejte jeho stream:

export default {
	async fetch(request, env) {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();
			} catch (err) {
				console.log(err.message);
			}
		}
	},
};
export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();
			} catch (err) {
				console.log((err as Error).message);
			}
		}
	},
} satisfies ExportedHandler<Env>;

4: Transformujte obrázek

U každého nahraného obrázku chcete provést následující akce:

Nastavení překryvného obrázku

Chcete-li načíst obrázek překryvné vrstvy z adresáře assets, vytvořte funkci assetUrl pak použijte env.ASSETS k získání watermark.png obrázek:

function assetUrl(request, path) {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request, env) {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
			} catch (err) {
				console.log(err.message);
			}
		}
	},
};
function assetUrl(request: Request, path: string): URL {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
			} catch (err) {
				console.log((err as Error).message);
			}
		}
	},
} satisfies ExportedHandler<Env>;

Přidat vodoznak a překódovat obrázek

S vazbou Images můžete pracovat prostřednictvím env.IMAGES.

Sem umístíte všechny operace optimalizace, které chcete na obrázku provést. Zde použijete .draw() funkci k použití vizuálního vodoznaku na nahraný obrázek a poté použijte .output() ke kódování obrázku jako AVIF:

function assetUrl(request, path) {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request, env) {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
				if (!watermarkStream) {
					return new Response("Failed to fetch watermark", { status: 500 });
				}

				// Apply watermark and convert to AVIF
				const imageResponse = (
					await env.IMAGES.input(fileStream)
						// Draw the watermark on top of the image
						.draw(
							env.IMAGES.input(watermarkStream).transform({
								width: 100,
								height: 100,
							}),
							{ bottom: 10, right: 10, opacity: 0.75 },
						)
						// Output the final image as AVIF
						.output({ format: "image/avif" })
				).response();
			} catch (err) {
				console.log(err.message);
			}
		}
	},
};
function assetUrl(request: Request, path: string): URL {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
				if (!watermarkStream) {
					return new Response("Failed to fetch watermark", { status: 500 });
				}

				// Apply watermark and convert to AVIF
				const imageResponse = (
					await env.IMAGES.input(fileStream)
						// Draw the watermark on top of the image
						.draw(
							env.IMAGES.input(watermarkStream).transform({
								width: 100,
								height: 100,
							}),
							{ bottom: 10, right: 10, opacity: 0.75 },
						)
						// Output the final image as AVIF
						.output({ format: "image/avif" })
				).response();
			} catch (err) {
				console.log((err as Error).message);
			}
		}
	},
} satisfies ExportedHandler<Env>;

5: Nahrajte do R2

Nahrajte transformovaný obrázek do R2.

Vytvořením fileName proměnné můžete zadat název transformovaného obrázku. V tomto příkladu před nahráním do R2 připojíte k názvu původního obrázku datum.

Zde je úplný kód příkladu:

const html = `
<!DOCTYPE html>
        <html>
          <head>
            <meta charset="UTF-8">
            <title>Upload Image</title>
          </head>
          <body>
            <h1>Upload an image</h1>
            <form method="POST" enctype="multipart/form-data">
              <input type="file" name="image" accept="image/*" required />
              <button type="submit">Upload</button>
            </form>
          </body>
        </html>
`;

function assetUrl(request, path) {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request, env) {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
				if (!watermarkStream) {
					return new Response("Failed to fetch watermark", { status: 500 });
				}

				// Apply watermark and convert to AVIF
				const imageResponse = (
					await env.IMAGES.input(fileStream)
						// Draw the watermark on top of the image
						.draw(
							env.IMAGES.input(watermarkStream).transform({
								width: 100,
								height: 100,
							}),
							{ bottom: 10, right: 10, opacity: 0.75 },
						)
						// Output the final image as AVIF
						.output({ format: "image/avif" })
				).response();

				// Add timestamp to file name
				const fileName = `image-${Date.now()}.avif`;

				// Upload to R2
				await env.R2.put(fileName, imageResponse.body);

				return new Response(`Image uploaded successfully as ${fileName}`, {
					status: 200,
				});
			} catch (err) {
				console.log(err.message);
				return new Response("Internal error", { status: 500 });
			}
		}
		return new Response("Method not allowed", { status: 405 });
	},
};
interface Env {
	IMAGES: ImagesBinding;
	R2: R2Bucket;
	ASSETS: Fetcher;
}

const html = `
<!DOCTYPE html>
        <html>
          <head>
            <meta charset="UTF-8">
            <title>Upload Image</title>
          </head>
          <body>
            <h1>Upload an image</h1>
            <form method="POST" enctype="multipart/form-data">
              <input type="file" name="image" accept="image/*" required />
              <button type="submit">Upload</button>
            </form>
          </body>
        </html>
`;

function assetUrl(request: Request, path: string): URL {
	const url = new URL(request.url);
	url.pathname = path;
	return url;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (request.method === "GET") {
			return new Response(html, { headers: { "Content-Type": "text/html" } });
		}
		if (request.method === "POST") {
			try {
				// Parse form data
				const formData = await request.formData();
				const file = formData.get("image");
				if (!file || typeof file.stream !== "function") {
					return new Response("No image file provided", { status: 400 });
				}

				// Get uploaded image as a readable stream
				const fileStream = file.stream();

				// Fetch image as watermark
				const watermarkResponse = await env.ASSETS.fetch(
					assetUrl(request, "watermark.png"),
				);
				const watermarkStream = watermarkResponse.body;
				if (!watermarkStream) {
					return new Response("Failed to fetch watermark", { status: 500 });
				}

				// Apply watermark and convert to AVIF
				const imageResponse = (
					await env.IMAGES.input(fileStream)
						// Draw the watermark on top of the image
						.draw(
							env.IMAGES.input(watermarkStream).transform({
								width: 100,
								height: 100,
							}),
							{ bottom: 10, right: 10, opacity: 0.75 },
						)
						// Output the final image as AVIF
						.output({ format: "image/avif" })
				).response();

				// Add timestamp to file name
				const fileName = `image-${Date.now()}.avif`;

				// Upload to R2
				await env.R2.put(fileName, imageResponse.body);

				return new Response(`Image uploaded successfully as ${fileName}`, {
					status: 200,
				});
			} catch (err) {
				console.log((err as Error).message);
				return new Response("Internal error", { status: 500 });
			}
		}
		return new Response("Method not allowed", { status: 405 });
	},
} satisfies ExportedHandler<Env>;

Další kroky

V tomto tutoriálu jste se naučili, jak propojit Worker s různými prostředky na Developer Platform a vytvořit aplikaci, která přijímá nahrávané obrázky, transformuje je a výsledek nahraje do R2.

Dále můžete nastavit transformační URL pro dynamickou optimalizaci obrázků uložených v R2.