← Images / images / tutorials
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:
- Přidejte Images Paid předplatné k vašemu účtu. To vám umožní navázat Images API na váš Worker.
- Vytvořte R2 bucket, kam se nahrají transformované obrázky.
- Vytvořte nový projekt Worker.
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:
- Obrázky k transformaci, změně velikosti a kódování obrázků přímo z vašeho Workeru.
- R2 pro připojení bucketu k ukládání transformovaných obrázků.
- Assets pro přístup ke statickému obrázku, který bude použit jako vizuální vodoznak.
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:
- Překryjte obrázek vizuálním vodoznakem, který jsme přidali do adresáře assets.
- Transkódujte obrázek (s vodoznakem) na
AVIF. Tím se obrázek zkomprimuje a zmenší se velikost souboru. - Nahrajte transformovaný obrázek do R2.
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.