8.24-bob: Rasm/media qayta ishlash — sharp
8-QISM — NestJS (chuqur) · 24-mavzu · Amaliy real mavzu
1. Kirish va motivatsiya
5.11 va 8.8'da fayl yuklashni ko'rdik — endi yuklangan rasmni qayta ishlash ni o'rganamiz. Bu — har rasm bilan ishlaydigan loyihada (e-commerce mahsulot rasmi, avatar, e'lon, galereya) kerak bo'ladigan real mavzu. Muammo: foydalanuvchi 8 megabaytli, 4000×3000 piksel rasm yuklaydi — uni shundayligicha saqlasangiz: server joyi to'ladi, sayt sekin yuklanadi (mobil internetda dahshat), turli joyda turli o'lcham kerak (ro'yxatda kichik, sahifada katta). Yechim — rasmni avtomatik qayta ishlash: o'lchamni kichraytirish, siqish, format o'zgartirish, thumbnail yaratish.
Node.js'da rasm qayta ishlashning standarti — sharp (eng tez, eng ko'p ishlatiladigan kutubxona — libvips ustida, C darajasida tez). U: resize (o'lcham), format konversiya (JPEGWebP — 30% kichikroq), siqish (sifat), thumbnail (kichik nusxa), watermark (suv belgisi), crop (kesish), metadata (EXIF o'qish/tozalash). Bularning hammasi — millisekundlar ichida, kam xotira (streaming). Bu — sayt tezligi (Core Web Vitals — SEO — 13) va saqlash xarajatining muhim qismi.
Bu bob: nega rasm qayta ishlash, sharp asoslari, resize (o'lcham strategiyalari), format va siqish (WebP/AVIF — zamonaviy), bir necha o'lcham (responsive — thumbnail/medium/large), watermark, EXIF/metadata (xavfsizlik — joylashuv tozalash), avatar/crop, upload bilan integratsiya (5.11 davomi), va fonda qayta ishlash (navbat — 8.22). Bu bob 5.11/8.8 (yuklash), 8.22 (navbat), 13 (Next.js Image) bilan bog'liq. Rasm qayta ishlash — sayt tezligi va sifatining muhim qismi.
O'xshatish: rasm qayta ishlash — fotosurat studiyasi. Mijoz xom, katta foto (RAW) keltiradi (8MB original). Studiya: kerakli o'lchamga kesadi/kichraytiradi (resize — pasport uchun kichik, devor uchun katta), sifatni saqlab siqadi (optimize — fayl yengil), turli nusxa tayyorlaydi (thumbnail — albom uchun, katta — chop etish uchun), logotip qo'shadi (watermark), va shaxsiy ma'lumotni (foto qayerda olingani — GPS — EXIF) tozalaydi. Backend — raqamli foto studiya: bitta yuklangan rasmdan barcha kerakli, optimallashtirilgan nusxalarni avtomatik yaratadi.
Nega muhim?
- Sayt tezligi — kichik/optimallashgan rasm (mobil, SEO — 13).
- Saqlash xarajati — 8MB200KB (40x kam joy).
- Turli o'lcham — responsive (thumbnail, medium, large).
- Har rasm loyihada — mahsulot, avatar, e'lon, galereya.
2. Nazariya — chuqur tushuntirish
2.1. Nega rasm qayta ishlash
MUAMMO (xom rasm shundayligicha):
8MB, 4000×3000 server joyi to'ladi, sayt sekin (mobil — dahshat)
Turli joyda turli o'lcham (ro'yxat — kichik, sahifa — katta)
Eski format (JPEG — WebP'dan 30% katta)
EXIF — foydalanuvchi joylashuvi (maxfiylik!)
YECHIM (sharp bilan):
Resize (kerakli o'lcham), optimize (siqish — 8MB 200KB)
Format (WebP/AVIF — zamonaviy, kichik)
Bir necha o'lcham (thumbnail/medium/large)
EXIF tozalash (maxfiylik)Nega kerak: xom rasm — katta (server/tezlik muammosi), bir o'lcham (turli joyga mos emas), eski format (katta), EXIF (maxfiylik). sharp hal qiladi: resize, optimize (8MB200KB), format (WebP), bir necha o'lcham, EXIF tozalash. Sayt tezligi (mobil, SEO — 13) va xarajat (saqlash) — to'g'ridan rasmga bog'liq. Optimallashtirilmagan rasm — eng keng tezlik muammosi.
2.2. sharp asoslari
import * as sharp from "sharp";
// sharp — zanjir (fluent — 9.2 Builder): o'qi o'zgartir chiqar
const natija = await sharp(inputBuffer) // kirish (buffer/fayl)
.resize(800, 600) // o'lcham (2.3)
.webp({ quality: 80 }) // format + sifat (2.4)
.toBuffer(); // chiqish (buffer/fayl)
// Yoki faylga
await sharp("input.jpg").resize(800).toFile("output.webp");sharp asoslari — zanjir (fluent — Builder 9.2):
sharp(input)(buffer/fayl/stream).resize()/.webp()/... (o'zgartirishlar).toBuffer()/.toFile()(chiqish). libvips ustida (C — eng tez, kam xotira). Async (Promise). Kirish — memoryStorage buffer 5.11-bob yoki fayl/stream. Chiqish — buffer (S3'ga — 5.11) yoki fayl. Eng tez Node.js rasm kutubxonasi (production standart).
2.3. Resize (o'lcham strategiyalari)
// Resize rejimlari (fit — belgilangan o'lchamga qanday sig'dirish)
sharp(input).resize(300, 300, { fit: "cover" }); // to'ldirish (proporsiya saqlab, ortiqchani kesadi — avatar)
sharp(input).resize(300, 300, { fit: "contain" }); // ichiga (proporsiya saqlab, atrofga bo'sh joy — fon bilan)
sharp(input).resize(300, 300, { fit: "inside" }); // proporsiya saqlab, ichiga — hech biri oshmasin (default)
sharp(input).resize(300, 300, { fit: "outside" }); // proporsiya saqlab, hech biri kichik bo'lmasin (kamida qoplaydi)
sharp(input).resize(300, 300, { fit: "fill" }); // aniq 300×300 — proporsiya buziladi (cho'ziladi)
sharp(input).resize(800, null); // faqat kenglik (balandlik proporsional — avtomatik)
sharp(input).resize(null, 600); // faqat balandlik (kenglik avtomatik)
sharp(input).resize(800, null, { withoutEnlargement: true }); // kichikni kattalashtirmaslik
// position — cover/contain'da qaysi qism qoladi / qayerga tekislanadi
sharp(input).resize(300, 300, { fit: "cover", position: "top" }); // yuqorini saqlab kesadi (portret — yuz)
sharp(input).resize(300, 300, { fit: "cover", position: "attention" }); // diqqat markazi (yuz/kontrast) avtomatik
sharp(input).resize(300, 300, { fit: "cover", position: "entropy" }); // eng ko'p detalli qismni saqlaydi
// background — contain'dagi bo'sh joy yoki shaffof PNG'ni JPEG fonini to'ldirish
sharp(input).resize(300, 300, { fit: "contain", background: "#ffffff" }); // oq fon
sharp(input).resize(300, 300, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }); // shaffof (PNG/WebP)Resize strategiyalari:
fit— belgilangan quti ichiga qanday sig'dirish: cover (to'ldirish — proporsiya saqlab ortiqchani kesadi — avatar, thumbnail), contain (ichiga — proporsiya saqlab, qolgan joybackgroundbilan to'ladi), inside (proporsiya saqlab, tomonlarning hech biri berilgandan oshmaydi — default), outside (proporsiya saqlab, tomonlarning hech biri kichik bo'lmaydi — kamida qutini qoplaydi), fill ( aniq o'lchamga majburiy cho'zish — proporsiya buziladi, kamdan-kam kerak). Faqat bitta o'lcham berilsa (resize(800, null)yokiresize(null, 600)) — ikkinchisi proporsional hisoblanadi.position(cover/contain uchun) — qaysi qism kesilib qolishini boshqaradi:centre(default),top/left/... (chekka), yoki aqlli variantlarattention(kontrast/yuz eng ko'p bo'lgan joyni saqlaydi) vaentropy(eng ko'p ma'lumotli qism).background— contain'dagi bo'sh joyni yoki shaffof rasmni matnsiz formatga (JPEG) o'tkazishda fonni to'ldiradi (alpha: 0— shaffof saqlash).withoutEnlargement(kichik rasmni kattalashtirmaslik — sifat buzilmasin). To'g'ri fit — maqsadga bog'liq (avatarcover+attention; mahsulotinside). Eng ko'p ishlatiladigan amal.
2.4. Format va siqish (WebP/AVIF)
FORMATLAR (zamonaviy kichik):
- JPEG: keng, lekin katta (eski)
- WebP: 25-35% kichik (JPEG'dan) — zamonaviy, keng qo'llab-quvvatlanadi
- AVIF: 50% kichik — eng yangi (ba'zi eski brauzer qo'llamaydi)
- PNG: shaffoflik (lekin katta — faqat kerak bo'lganda)
SIQISH (quality 0-100):
.webp({ quality: 80 }) sifat/hajm balansi (80 — odatda yaxshi)Format va siqish: WebP (JPEG'dan 25-35% kichik — zamonaviy, keng) — default tanlov; AVIF (50% kichik — eng yangi, lekin eski brauzer cheklov); JPEG (eski, keng); PNG (shaffoflik — katta). Siqish (
quality0-100 — 80 odatda yaxshi balans — ko'z farqlamaydi, lekin hajm yarmi). WebP'ga o'tish — eng oson tezlik yutug'i (30% kichik, sifat bir xil). Format + sifat — hajmni keskin kamaytiradi.
AVIF vs WebP taqqoslash (bir xil fotosuratni turli formatga o'tkazganda taxminiy hajm/xususiyat):
| Xususiyat | JPEG | WebP | AVIF |
|---|---|---|---|
| Nisbiy hajm (bir xil sifatda) | 100% (asos) | ~65-75% | ~45-55% |
| Shaffoflik (alpha) | |||
| Kodlash tezligi | tez | tez | sekin (CPU og'ir — effort) |
| Brauzer qo'llovi (2026) | universal | deyarli universal | zamonaviy (eski Safari/IE yo'q) |
| Animatsiya | (cheklangan) | ||
| Eng mos joy | fallback, keng moslik | default zamonaviy | maksimal siqish, banner/hero |
Amaliy tanlov: default sifatida WebP (siqish + moslik balansi), maksimal tejash kerak bo'lganda (hero rasm, ko'p trafik) AVIF + WebP fallback (
<picture>yoki Accept header — Misol 8). AVIF kodlash CPU'ni ko'p yeydi (effortpast qo'ying yoki fonda — 2.9). Eski brauzer uchun JPEG fallback saqlab qoling.
2.5. Bir necha o'lcham (responsive)
// Bir rasmdan — bir necha o'lcham (turli joyga)
const olchamlar = [
{ nom: "thumbnail", en: 150 }, // ro'yxat, avatar
{ nom: "medium", en: 600 }, // sahifa
{ nom: "large", en: 1200 }, // to'liq ko'rish
];
async hammaOlchamYarat(input: Buffer, id: string) {
const natijalar: Record<string, string> = {};
for (const o of olchamlar) {
const buffer = await sharp(input).resize(o.en, null, { withoutEnlargement: true })
.webp({ quality: 80 }).toBuffer();
natijalar[o.nom] = await this.s3.yukla(buffer, `${id}-${o.nom}.webp`); // S3 (5.11)
}
return natijalar; // { thumbnail: url, medium: url, large: url }
}Bir necha o'lcham (responsive — muhim amaliyot): bir yuklangan rasmdan bir necha o'lcham (thumbnail — ro'yxat, medium — sahifa, large — to'liq). Frontend kerakli o'lchamni ishlatadi (mobil — kichik, desktop — katta —
srcset— 13). DB'ga barcha URL saqlanadi (5.11: 2.12). Tezlik: ro'yxatda 1200px rasm yuklash — behuda (150px yetadi). Responsive rasm — sayt tezligining kaliti.
2.6. Watermark (suv belgisi)
// Watermark — logotip/matn qo'shish (mualliflik, brending)
async watermark(input: Buffer): Promise<Buffer> {
const logo = await sharp("logo.png").resize(150).png().toBuffer();
return sharp(input)
.composite([{ // ustiga qo'yish
input: logo,
gravity: "southeast", // o'ng past burchak
blend: "over",
}])
.toBuffer();
}
// Matn watermark — SVG orqali (matnni rasmga aylantirib composite)Watermark (suv belgisi) — rasmga logotip/matn qo'shish (mualliflik himoyasi, brending).
composite(ustiga qo'yish) +gravity(joylashuv — burchak/markaz) +blend(aralashtirish). Logo (PNG — shaffof) yoki matn (SVGrasm). E'lon, mahsulot rasmiga brending. Foydali, lekin majburiy emas (UX — ortiqcha watermark bezovta qiladi).
2.7. EXIF va metadata (xavfsizlik)
// EXIF — rasm metadata (kamera, SANA, GPS JOYLASHUV — maxfiylik!)
const metadata = await sharp(input).metadata(); // { width, height, format, exif }
// EXIF tozalash (foydalanuvchi joylashuvi oshkor bo'lmasin — 14)
const tozalangan = await sharp(input)
.rotate() // EXIF orientation'ga qarab to'g'rilash
.withMetadata({ exif: {} }) // EXIF tozalash (yoki .toBuffer — default tozalaydi)
.toBuffer();EXIF/metadata (xavfsizlik — ko'p unutiladi): rasm EXIF'da GPS joylashuv, kamera, sana bo'ladi (telefon rasmida — foydalanuvchi uyi koordinatasi!). Yuklangan rasmni saqlashda EXIF tozalanishi kerak (maxfiylik — 14).
.rotate()(EXIF orientation'ga qarab to'g'rilash — aks holda rasm yon tomonda chiqadi),.toBuffer()default EXIF tozalaydi.metadata()(o'lcham/format o'qish — validatsiya). EXIF tozalash — maxfiylik majburiyati.
2.8. Validatsiya va xavfsizlik (sharp bilan)
// sharp — rasm haqiqiyligini ham tekshiradi (magic bytes — 8.8: 2.4)
async tekshirVaIshla(buffer: Buffer) {
const metadata = await sharp(buffer).metadata(); // rasm emas xato (haqiqiy tekshiruv)
if (!["jpeg", "png", "webp"].includes(metadata.format)) {
throw new BadRequestException("Faqat rasm");
}
if (metadata.width > 8000 || metadata.height > 8000) { // juda katta (DoS)
throw new BadRequestException("Rasm o'lchami juda katta");
}
// Qayta ishlash optimallashgan, xavfsiz
return sharp(buffer).resize(2000, null, { withoutEnlargement: true }).webp({ quality: 82 }).toBuffer();
}Validatsiya + xavfsizlik:
sharp(buffer).metadata()— rasm haqiqiyligini tekshiradi (rasm bo'lmasa xato — magic bytes — 8.8: 2.4 — MIME spoof himoyasi). O'lcham cheklash (juda katta DoS — "decompression bomb"). Qayta ishlangan rasm — xavfsizroq (sharp uni qaytadan kodlaydi — yashirin zararli kod yo'qoladi). sharp — validatsiya + himoya + optimizatsiya birga. ParseFilePipe 8.8-bob bilan birga.
2.9. Fonda qayta ishlash (navbat — 8.22)
// Og'ir qayta ishlash (ko'p o'lcham, video) fonda (8.22)
@Post("upload")
@UseInterceptors(FileInterceptor("rasm"))
async yukla(@UploadedFile() file: Express.Multer.File) {
// 1. Original tez saqlash (yoki vaqtinchalik)
const url = await this.s3.yukla(file.buffer, `temp/${randomUUID()}`);
// 2. Qayta ishlash navbatga (so'rovni bloklamaslik — 8.22)
await this.imageQueue.add("process", { url, userId: file.userId });
return { url, holat: "qayta ishlanmoqda" }; // darrov javob
}
// Worker: bir necha o'lcham, watermark, optimize (fonda — 8.22)Fonda qayta ishlash: ko'p o'lcham + watermark + optimize — biroz vaqt oladi so'rovni bloklamaslik uchun navbatga 8.22-bob. Original tez saqlash qayta ishlash worker'da (fonda) tayyor bo'lganda DB yangilash/bildirishnoma 8.23-bob. Oddiy bitta resize — sinxron (tez); ko'p o'lcham/video — fonda. Katta yuklash (galereya) — albatta fonda. UX: foydalanuvchi kutmaydi.
2.10. sharp o'rnatish va libvips
# sharp o'rnatish — libvips (C kutubxona) bilan birga keladi (prebuilt binary)
npm install sharp
# platformaga mos binary avtomatik yuklanadi. Docker/CI'da boshqa platforma bo'lsa:
npm install --os=linux --cpu=x64 sharp # (masalan Alpine Linux uchun --libc=musl)// Versiya va libvips holatini tekshirish (diagnostika)
import * as sharp from "sharp";
console.log(sharp.versions); // { vips: "8.15.1", sharp: "0.33.x" }
console.log(sharp.format); // qo'llab-quvvatlanadigan format ro'yxati (jpeg, webp, avif...)O'rnatish (libvips):
npm install sharp— sharp libvips (yuqori tezlikdagi C rasm kutubxonasi) ustida ishlaydi va platformaga mos prebuilt binary avtomatik yuklanadi (kompilyatsiya shart emas). Docker/CI'da boshqa OS/CPU bo'lsa (masalan lokal Windows, deploy Linux Alpine) —--os/--cpu/--libcbayroqlari bilan to'g'ri binary'ni o'rnating (aks holda "Could not load the sharp module" xatosi — 6-bo'lim, Xato 2).sharp.versionsvasharp.format— diagnostika uchun. libvips streaming ishlaydi — katta rasmni ham kam xotira bilan qayta ishlaydi (ImageMagick'dan tez va yengil).
2.11. Geometrik amallar (flip, flop, rotate, extract, trim)
sharp(input).rotate(90); // 90° soat yo'nalishida burish
sharp(input).rotate(); // EXIF orientation'ga qarab avto-to'g'rilash (2.7)
sharp(input).rotate(45, { background: "#ffffff" }); // ixtiyoriy burchak (bo'sh joy fon bilan)
sharp(input).flip(); // vertikal aylantirish (yuqoripast)
sharp(input).flop(); // gorizontal aylantirish (chapo'ng — ko'zgu)
// extract — aniq to'rtburchakni kesib olish (crop — piksel koordinatasi bilan)
sharp(input).extract({ left: 100, top: 50, width: 400, height: 300 });
// trim — bir xil rangdagi chekka (bo'sh ramka) ni avtomatik olib tashlash
sharp(input).trim(); // logotip atrofidagi ortiqcha bo'shliqGeometrik amallar:
rotate(gradus)(burish — burchak bersangiz o'sha darajaga; bo'sh — EXIF avto-to'g'rilash),flip(vertikal — ustma-ust),flop(gorizontal — ko'zgu aksi).extract— aniq koordinata (left/top/width/height) bilan to'rtburchak kesish (resizecover'dan farqli — bu qat'iy piksel; foydalanuvchi tanlagan crop maydonini qo'llashda ishlatiladi).trim— bir xil rangdagi ortiqcha chekkalarni (masalan logotip atrofidagi shaffof/oq bo'shliq) avtomatik qirqadi.extract+resizebirga: avval kerakli qismni kesib, keyin o'lchamlash mumkin.
2.12. Format sozlamalari (progressive, lossless, chroma)
// toFormat — formatni dinamik tanlash (o'zgaruvchi bilan)
const fmt = "webp";
sharp(input).toFormat(fmt as keyof sharp.FormatEnum, { quality: 80 });
// JPEG — progressive (bosqichma-bosqich yuklanadi — sekin internetda UX yaxshi)
sharp(input).jpeg({ quality: 82, progressive: true, mozjpeg: true }); // mozjpeg — yaxshiroq siqish
// PNG — lossless (yo'qotishsiz), siqish darajasi
sharp(input).png({ compressionLevel: 9, palette: true }); // palette — kam rangli PNG kichrayadi
// WebP — lossless yoki lossy
sharp(input).webp({ quality: 80, lossless: false, effort: 4 }); // effort 0-6 (sekinroq = kichikroq)
// AVIF — eng zamonaviy, eng kichik
sharp(input).avif({ quality: 50, effort: 4 });
// GIF — asosan animatsiya uchun (statik rasmga WebP/PNG afzal)
sharp(input, { animated: true }).gif({ colours: 128, effort: 7 }); // rang palitrasi (kam rang = kichik)Format sozlamalari:
toFormat(nom, opsiya)— formatni dinamik (o'zgaruvchi orqali) tanlash (.jpeg()/.webp()— statik muqobil). JPEG uchunprogressive: true(rasm bosqichma-bosqich, xiraroqdan aniqroqqa yuklanadi — sekin tarmoqda idrok tezligi yaxshi) vamozjpeg: true(yaxshiroq siqish algoritmi). PNGpalette: true(kam rangli rasmni sezilarli kichraytiradi),compressionLevel0-9. WebP/AVIFlossless(yo'qotishsiz — grafik/logotip uchun) vaeffort(yuqori = sekinroq lekin kichikroq fayl). GIF (.gif({ colours })) — asosan animatsiya uchun; statik rasmda WebP/PNG samaraliroq. Fotosurat lossy WebP/AVIF; logotip/grafik lossless yoki palette PNG; animatsiya animatsiyali WebP 2.17-bob.
2.13. Rang va filtr amallari (blur, sharpen, grayscale, tint, gamma, negate)
sharp(input).blur(5); // xiralashtirish (radius — placeholder, maxfiylik)
sharp(input).sharpen(); // o'tkirlash (resize'dan keyin aniqlikni tiklaydi)
sharp(input).grayscale(); // oq-qora (kul rang)
sharp(input).tint({ r: 255, g: 240, b: 220 }); // rang tusi berish (issiq/sovuq)
sharp(input).gamma(2.2); // gamma korreksiya (yorug'lik egri chizig'i)
sharp(input).negate(); // rang inversiyasi (negativ)
sharp(input).modulate({ brightness: 1.1, saturation: 1.2, hue: 15 }); // yorqinlik/to'yinganlik/tus
sharp(input).median(3); // shovqin kamaytirish (median filtr)Rang va filtr:
blur(radius)(xiralashtirish — LQIP placeholder Misol 9, yoki maxfiy joyni yopish),sharpen()(o'tkirlash — kichraytirishdan keyin aniqlikni qaytaradi, thumbnail uchun foydali),grayscale()(oq-qora),tint()(rang tusi — brend rangi),gamma()(yorug'lik korreksiyasi),negate()(negativ),modulate()(yorqinlik/to'yinganlik/tus — bir amalda). Amaliy:resizesharpenketma-ketligi thumbnail sifatini oshiradi;blur+ kichik o'lcham placeholder.
2.14. metadata va stats (o'lcham, EXIF, tahlil)
const meta = await sharp(input).metadata();
// { width, height, format, space, channels, hasAlpha, orientation, exif, density, isProgressive }
const stats = await sharp(input).stats();
// { channels: [{ min, max, mean, ... }], isOpaque, entropy, dominant: { r, g, b } }
// dominant — asosiy rang (galereya foni, placeholder rangi uchun)metadata va stats:
metadata()— rasmni to'liq dekodlamasdan (tez) uning xususiyatlarini o'qiydi:width/height(validatsiya — 2.8),format,hasAlpha(shaffoflik bormi — JPEG'ga o'tkazishdan oldin fon kerakmi bilish),orientation(EXIF burilish — 2.7),exif/density.stats()— piksellarni tahlil qiladi: har kanalmean/min/max,entropy(rasm murakkabligi),dominant(asosiy rang — bo'sh joy foni yoki placeholder rangi sifatida ishlatiladi).metadata()— arzon (header o'qiydi);stats()— qimmatroq (barcha piksel).
2.15. Chiqish: toBuffer vs toFile va stream pipeline
// toBuffer — natijani xotirada Buffer (S3'ga yuborish, DB, HTTP javob — 5.11)
const buffer = await sharp(input).resize(800).webp().toBuffer();
// toFile — to'g'ridan diskka (format kengaytmadan aniqlanadi)
const info = await sharp(input).resize(800).toFile("output.webp"); // { width, height, size, format }
// Stream pipeline: Multer stream sharp transform S3 (buffersiz, kam xotira)
import { PassThrough } from "stream";
const transformer = sharp().resize(1200).webp({ quality: 80 }); // Transform stream
const passthrough = new PassThrough();
transformer.pipe(passthrough); // sharp S3 stream
fileStream.pipe(transformer); // Multer stream sharp
await this.s3.upload({ Body: passthrough, Key: key }).promise(); // oqim orqali yuklashChiqish rejimlari:
toBuffer()— natija xotiradaBuffer(S3'ga yuklash, HTTP javobi, base64 — eng ko'p ishlatiladi backend'da).toFile(yo'l)— to'g'ridan diskka yozadi va{ width, height, size, format }qaytaradi (lokal saqlash, CLI). Stream pipeline — sharp obyekti (sharp()argumentsiz) Transform stream bo'lib ishlaydi:multer stream sharp S3zanjiri butun faylni xotiraga yuklamasdan oqim orqali qayta ishlaydi (juda katta fayl — kam xotira). Kichik-o'rta rasmtoBuffer(soddaroq); juda katta yoki oqim manba pipeline.
2.16. Xavfsizlik va performance (limitInputPixels, concurrency, cache)
// Image bomb himoyasi — kichik faylda ulkan piksel (dekompressiya bombasi — DoS)
sharp(input, { limitInputPixels: 268402689 }); // ~16384×16384 default; kamaytirish mumkin
sharp(input, { limitInputPixels: 100_000_000 }); // maks 100 megapiksel
sharp(input, { limitInputPixels: false }); // o'chirmang (xavfli!)
// failOn — buzuq rasmda qattiq to'xtash (truncated fayl qabul qilmaslik)
sharp(input, { failOn: "error" });
// Global performance sozlamalar (server ishga tushishida bir marta)
sharp.concurrency(4); // parallel thread soni (CPU yadrolari)
sharp.cache({ memory: 200, files: 20, items: 100 }); // libvips operatsiya keshi
sharp.cache(false); // serverless (Lambda)'da keshni o'chirishXavfsizlik va performance:
limitInputPixels— image bomb (dekompressiya bombasi) himoyasi: kichik siqilgan fayl ochilganda ulkan piksel maydoniga aylanib xotirani to'ldirishi mumkin (DoS). sharp default ~16384² chegara qo'yadi — kamaytirish mumkin, lekinfalseqilmang.failOn: "error"— buzuq/kesilgan faylni rad etadi.concurrency— parallel qayta ishlash thread soni (default CPU yadrosi soni);cache— libvips ichki keshi (takroriy amal tez). Serverless (AWS Lambda — 8.24 dan tashqari) muhitidasharp.cache(false)vaconcurrency(1)ko'pincha tavsiya etiladi (izolyatsiya, xotira chegarasi).
2.17. Animatsiyali rasm va cloud storage
// Animatsiyali GIF/WebP — barcha kadrlarni saqlab qayta ishlash
const anim = await sharp(input, { animated: true }) // animated: true — barcha kadr
.resize(300) // barcha kadrga qo'llaniladi
.webp() // animatsiyali WebP (GIF'dan kichik)
.toBuffer();
// Cloudinary — tashqi rasm xizmati (URL orqali transform, sharp o'rniga/bilan)
// Yuklash: cloudinary.uploader.upload(buffer) optimallash URL'da (w_300,f_auto,q_auto)
// sharp — o'z serveringizda nazorat; Cloudinary/imgproxy — CDN + on-the-fly transformAnimatsiyali va cloud:
{ animated: true }— GIF/animatsiyali WebP ni qayta ishlashda barcha kadrlarni saqlaydi (aks holda faqat birinchi kadr qoladi); resize barcha kadrga qo'llaniladi. Animatsiyali GIF WebP konvertatsiya hajmni keskin kamaytiradi. Cloud storage/xizmat — uchta yondashuv: (1) sharp + S3 + CDN — rasmni o'z serveringizda sharp bilan qayta ishlab, natijani S3'ga (yoki har qanday obyekt-ombor) yuklaysiz, oldiga CloudFront/Cloudflare CDN qo'yasiz. To'liq nazorat, arzon, lekin kod va infratuzilma o'zingizda. (2) Cloudinary — rasmni yuklaysiz, barcha transform URL parametri orqali on-the-fly bo'ladi (https://res.cloudinary.com/.../w_300,f_auto,q_auto/rasm.jpg—f_autobrauzerga mos format tanlaydi,q_autoavto sifat), built-in CDN va kesh bilan. Tez ishga tushadi, lekin trafik oshgani sari qimmat. (3) imgproxy (yoki Thumbor) — o'zingiz hostlaydigan on-the-fly transform serveri: original S3'da turadi, imgproxy imzolangan URL orqali kerakli o'lchamni real-time yasab beradi (Cloudinary'ning ochiq muqobili). Kichik-o'rta loyiha sharp + S3 + CDN (arzon, nazorat, oldindan variant); tez prototip yoki media-og'ir loyiha Cloudinary/imgproxy (on-the-fly, CDN tayyor). Ko'pincha aralash: yuklashda sharp bilan bir necha asosiy variant + CDN kesh.
2.18. Best practices (rasm)
Yuklangan rasmni QAYTA ISHLA (xom saqlama — 2.1)
WebP/AVIF format + siqish (quality ~80 — 2.4)
Bir necha o'lcham (responsive — thumbnail/medium/large — 2.5)
EXIF tozalash (maxfiylik — GPS — 14, 2.7) + .rotate()
sharp validatsiya (rasm haqiqiyligi, o'lcham — DoS — 2.8)
Og'ir fonda (navbat — 2.9, 8.22)
Original saqlash (qayta ishlash kerak bo'lsa) yoki o'chirish
S3/CDN 5.11-bob + lazy loading (frontend — 13)
withoutEnlargement (kichikni kattalashtirmaslik — 2.3)
limitInputPixels (image bomb himoyasi — DoS — 2.16)
resize sharpen (thumbnail aniqligi — 2.13)
animated: true (GIF/animatsiyali WebP — 2.17)
Serverless'da sharp.cache(false) (2.16)3. Sintaksis — tez ma'lumotnoma
// sharp zanjir (2.2)
sharp(input).resize(en, balandlik, { fit }).webp({ quality }).toBuffer()
// Resize 2.3-bob: fit: "cover"|"contain"|"inside"; withoutEnlargement
// Format 2.4-bob: .webp/.jpeg/.avif/.png({ quality })
// Composite 2.6-bob: .composite([{ input: logo, gravity, blend }])
// Metadata 2.7-bob: .metadata() { width, height, format }
// EXIF 2.7-bob: .rotate().withMetadata({ exif: {} })
// Geometrik 2.11-bob: .rotate(gradus) .flip() .flop() .extract({left,top,width,height}) .trim()
// Format 2.12-bob: .toFormat(nom) .jpeg({progressive,mozjpeg}) .png({palette}) .webp({lossless,effort})
// Filtr 2.13-bob: .blur(r) .sharpen() .grayscale() .tint() .gamma() .negate() .modulate()
// Stats 2.14-bob: .stats() { dominant, entropy, channels }
// Chiqish 2.15-bob: .toBuffer() / .toFile(yo'l) / sharp() Transform stream (pipe)
// Xavfsizlik 2.16-bob: sharp(in, { limitInputPixels, failOn }); sharp.concurrency()/cache()
// Animatsiya 2.17-bob: sharp(in, { animated: true })4. Batafsil kod namunalari
Misol 1 — Image service (asosiy — 2.2, 2.4)
@Injectable()
export class ImageService {
constructor(private s3: S3Service) {}
async optimallash(buffer: Buffer): Promise<Buffer> {
return sharp(buffer)
.rotate() // EXIF orientation (2.7)
.resize(2000, null, { withoutEnlargement: true, fit: "inside" }) // maks 2000px (2.3)
.webp({ quality: 82 }) // WebP siqish (2.4)
.toBuffer(); // EXIF avtomatik tozalanadi (2.7)
}
}Misol 2 — Bir necha o'lcham (responsive — 2.5)
@Injectable()
export class ImageVariantService {
private readonly OLCHAMLAR = [
{ nom: "thumb", en: 150 },
{ nom: "small", en: 400 },
{ nom: "medium", en: 800 },
{ nom: "large", en: 1600 },
];
async variantlarYarat(buffer: Buffer, prefix: string): Promise<Record<string, string>> {
const natijalar: Record<string, string> = {};
// Parallel (Promise.all — tez)
await Promise.all(this.OLCHAMLAR.map(async (o) => {
const out = await sharp(buffer)
.rotate()
.resize(o.en, null, { withoutEnlargement: true, fit: "inside" })
.webp({ quality: o.en <= 400 ? 75 : 82 }) // kichikga ko'proq siqish
.toBuffer();
natijalar[o.nom] = await this.s3.yukla(out, `${prefix}-${o.nom}.webp`);
}));
return natijalar; // { thumb, small, medium, large } URL
}
}Misol 3 — Avatar (crop — 2.3)
async avatar(buffer: Buffer, userId: string): Promise<string> {
const out = await sharp(buffer)
.rotate()
.resize(256, 256, { fit: "cover", position: "centre" }) // kvadrat (cover — kesadi — 2.3)
.webp({ quality: 85 })
.toBuffer();
return this.s3.yukla(out, `avatars/${userId}.webp`);
}
// Dumaloq avatar — frontend CSS (border-radius); yoki SVG mask (sharp)Misol 4 — Watermark (2.6)
async watermark(buffer: Buffer): Promise<Buffer> {
const meta = await sharp(buffer).metadata();
// Logo o'lchamini rasmga moslab (10%)
const logoEn = Math.round((meta.width || 800) * 0.15);
const logo = await sharp("assets/logo.png").resize(logoEn).png().toBuffer();
return sharp(buffer)
.composite([{ input: logo, gravity: "southeast", blend: "over" }]) // o'ng past (2.6)
.webp({ quality: 82 })
.toBuffer();
}Misol 5 — Validatsiya (sharp + DoS himoyasi — 2.8)
@Injectable()
export class ImageValidationPipe implements PipeTransform {
async transform(file: Express.Multer.File) {
if (!file) throw new BadRequestException("Rasm yo'q");
try {
const meta = await sharp(file.buffer).metadata(); // rasm emas xato (8.8: 2.4)
if (!["jpeg", "png", "webp", "gif"].includes(meta.format!)) {
throw new BadRequestException("Faqat rasm fayllari");
}
if ((meta.width || 0) > 10000 || (meta.height || 0) > 10000) {
throw new BadRequestException("Rasm o'lchami juda katta (DoS)"); // (2.8)
}
return file;
} catch {
throw new BadRequestException("Yaroqsiz rasm");
}
}
}
// Controller
@Post("upload")
@UseInterceptors(FileInterceptor("rasm"))
yukla(@UploadedFile(ImageValidationPipe) file: Express.Multer.File) {
return this.imageService.qaytaIshla(file.buffer);
}Misol 6 — To'liq upload + variant (5.11 davomi)
@Post("products/:id/images")
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Rol.ADMIN) // (8.7)
@UseInterceptors(FilesInterceptor("rasmlar", 10)) // ko'p rasm (8.8)
async mahsulotRasmlari(
@Param("id") productId: string,
@UploadedFiles() files: Express.Multer.File[],
) {
const natijalar = [];
for (const file of files) {
await sharp(file.buffer).metadata(); // validatsiya (2.8)
const variantlar = await this.variantService.variantlarYarat( // 4 o'lcham (Misol 2)
file.buffer, `products/${productId}/${randomUUID()}`,
);
natijalar.push(variantlar);
}
await this.productsService.rasmlarQosh(productId, natijalar); // DB'ga URL (5.11: 2.12)
return natijalar;
}Misol 7 — Fonda qayta ishlash (navbat — 2.9)
@Post("upload")
@UseInterceptors(FileInterceptor("rasm"))
async yukla(@UploadedFile(ImageValidationPipe) file: Express.Multer.File, @CurrentUser() user) {
// 1. Original vaqtinchalik saqlash (tez)
const tempKey = `temp/${randomUUID()}`;
await this.s3.yukla(file.buffer, tempKey);
// 2. Qayta ishlash navbatga (8.22)
const job = await this.imageQueue.add("process", { tempKey, userId: user.id });
return { jobId: job.id, holat: "qayta ishlanmoqda" };
}
@Processor("image")
export class ImageProcessor extends WorkerHost {
async process(job: Job) {
const buffer = await this.s3.yuklab(job.data.tempKey); // original
const variantlar = await this.variantService.variantlarYarat(buffer, `final/${job.id}`);
await this.s3.ochir(job.data.tempKey); // temp tozalash
await this.notify.yubor(job.data.userId, "Rasm tayyor", variantlar); // (8.23)
}
}Misol 8 — Format aniqlash (brauzer qo'llab-quvvatlasa AVIF — 2.4)
// Accept header'ga qarab format (AVIF > WebP > JPEG)
@Get("images/:id")
async rasm(@Param("id") id: string, @Headers("accept") accept: string, @Res() res: Response) {
const buffer = await this.s3.yuklab(`images/${id}`);
let out: Buffer, contentType: string;
if (accept?.includes("image/avif")) {
out = await sharp(buffer).avif({ quality: 70 }).toBuffer(); // eng kichik (2.4)
contentType = "image/avif";
} else if (accept?.includes("image/webp")) {
out = await sharp(buffer).webp({ quality: 80 }).toBuffer();
contentType = "image/webp";
} else {
out = await sharp(buffer).jpeg({ quality: 82 }).toBuffer(); // eski brauzer
contentType = "image/jpeg";
}
res.setHeader("Content-Type", contentType);
res.setHeader("Cache-Control", "public, max-age=31536000"); // CDN kesh (1 yil)
res.send(out);
}
// Production'da CDN (Cloudflare/imgproxy) afzal — real-time emasMisol 9 — Placeholder/blur (LQIP — UX)
// Kichik blur placeholder (rasm yuklanguncha ko'rsatish — UX, 13)
async blurPlaceholder(buffer: Buffer): Promise<string> {
const tiny = await sharp(buffer)
.resize(20) // juda kichik (20px)
.blur()
.webp({ quality: 30 })
.toBuffer();
return `data:image/webp;base64,${tiny.toString("base64")}`; // inline base64 (DB'ga)
}
// Frontend: blur placeholder asosiy rasm yuklangach almashtirish (silliq UX)Misol 10 — Foydalanuvchi tanlagan crop (extract — 2.11)
// Frontend crop editor koordinatalarini qo'llash (avatar tahrirlash)
interface CropDto {
left: number; top: number; width: number; height: number;
}
async cropVaSaqla(buffer: Buffer, crop: CropDto, userId: string): Promise<string> {
const meta = await sharp(buffer).metadata();
// Chegara tekshiruvi (crop rasm ichida bo'lishi shart — aks holda sharp xato beradi)
if (crop.left + crop.width > (meta.width || 0) || crop.top + crop.height > (meta.height || 0)) {
throw new BadRequestException("Crop maydoni rasm chegarasidan tashqarida");
}
const out = await sharp(buffer)
.rotate() // EXIF 2.7-bob — koordinatadan oldin to'g'rilash
.extract(crop) // aniq to'rtburchak kesish (2.11)
.resize(512, 512, { fit: "cover" }) // standart o'lchamga (2.3)
.sharpen() // kesish/o'lchamdan keyin o'tkirlash (2.13)
.webp({ quality: 85 })
.toBuffer();
return this.s3.yukla(out, `avatars/${userId}.webp`);
}Misol 11 — Stream pipeline (Multer sharp S3, kam xotira — 2.15)
// Katta rasmni butun xotiraga yuklamasdan oqim orqali qayta ishlash
import { PassThrough } from "stream";
import { Upload } from "@aws-sdk/lib-storage";
@Injectable()
export class ImageStreamService {
constructor(private s3: S3ClientProvider) {}
async streamYukla(fileStream: NodeJS.ReadableStream, key: string): Promise<string> {
// sharp() argumentsiz — Transform stream sifatida ishlaydi (2.15)
const transformer = sharp({ limitInputPixels: 100_000_000 }) // image bomb himoyasi (2.16)
.rotate()
.resize(1600, null, { withoutEnlargement: true, fit: "inside" })
.webp({ quality: 82 });
const passthrough = new PassThrough();
fileStream.pipe(transformer).pipe(passthrough); // Multer sharp S3 oqimi
const upload = new Upload({
client: this.s3.client,
params: { Bucket: "media", Key: `${key}.webp`, Body: passthrough, ContentType: "image/webp" },
});
await upload.done();
return `https://cdn.example.com/${key}.webp`;
}
}
// Diskless streaming — RAM'da butun fayl yo'q; juda katta yuklash uchunMisol 12 — Animatsiyali GIF WebP (2.17)
// Animatsiyali GIF ni kichikroq animatsiyali WebP ga aylantirish
async gifOptimallash(buffer: Buffer): Promise<Buffer> {
const meta = await sharp(buffer, { animated: true }).metadata();
// pages — kadr soni; pageHeight — bitta kadr balandligi
if ((meta.pages || 1) > 1) {
return sharp(buffer, { animated: true }) // barcha kadr (2.17)
.resize(400, null, { withoutEnlargement: true })
.webp({ quality: 75, effort: 4 }) // animatsiyali WebP (GIF'dan ancha kichik)
.toBuffer();
}
// Statik GIF oddiy WebP
return sharp(buffer).resize(400, null, { withoutEnlargement: true }).webp({ quality: 80 }).toBuffer();
}Misol 13 — Image modul (to'liq)
src/images/
├── image.service.ts (optimallash — Misol 1)
├── image-variant.service.ts (responsive — Misol 2)
├── image-stream.service.ts (stream pipeline — Misol 11)
├── image-validation.pipe.ts (validatsiya + limitInputPixels — Misol 5, 2.16)
├── image.processor.ts (fonda — Misol 7)
├── images.controller.ts (upload + crop + serve — Misol 6, 8, 10)
└── images.module.ts
Ilova ishga tushishida (main.ts):
sharp.concurrency(4); sharp.cache({ memory: 200 }); // performance 2.16-bob
Oqim:
Upload (5.11/8.8) validatsiya (sharp — 2.8) variant yaratish (Misol 2)
S3 5.11-bob DB'ga URL'lar (5.11: 2.12) frontend srcset (13)
Og'ir navbat (Misol 7); serve CDN kesh (Misol 8)5. To'g'ri va noto'g'ri holatlar
1) Xom rasmni saqlash
8MB original (sekin, joy — 2.1)
resize + WebP (200KB)2) Bir o'lcham hamma joyga
ro'yxatda 2000px rasm (behuda — 2.5)
responsive (thumbnail/medium/large)3) EXIF tozalanmagan
GPS joylashuv oshkor (maxfiylik — 14, 2.7)
EXIF tozalash + .rotate()4) Validatsiyasiz (faqat MIME)
MIME spoof (8.8: 2.4)
sharp metadata (haqiqiy tekshiruv — 2.8)5) Og'ir qayta ishlash sinxron
10 rasm × 4 o'lcham so'rovda (timeout — 2.9)
navbat (fonda)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Rasm yon tomonda (aylangan)
Sababi: EXIF orientation 2.7-bob. Yechimi: .rotate().
Xato 2 — sharp o'rnatilmaydi ("Could not load the sharp module" — platforma)
Sababi: native binary (libvips) platformaga mos emas (lokal Windows, deploy Linux/Alpine — 2.10). Yechimi: to'g'ri platforma binary'sini o'rnatish (npm install --os=linux --cpu=x64 --libc=musl sharp); Docker (10).
Xato 2b — Animatsiyali GIF birinchi kadr bo'lib qoladi
Sababi: animated: true berilmagan 2.17-bob. Yechimi: sharp(input, { animated: true }).
Xato 3 — Xotira ko'p (katta rasm)
Sababi: juda katta rasm 2.8-bob. Yechimi: o'lcham cheklash; sharp({ limitInputPixels }).
Xato 4 — Sekin (har serve'da qayta ishlash)
Sababi: real-time qayta ishlash 2.4-bob. Yechimi: oldindan variant; CDN kesh (Misol 8).
Xato 5 — Format eski brauzerda ochilmaydi
Sababi: AVIF eski brauzer 2.4-bob. Yechimi: Accept header (Misol 8) / WebP fallback.
Xato 6 — Og'ir yuklash timeout
Sababi: sinxron 2.9-bob. Yechimi: navbat.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Fayl yuklash (5.11, 8.8): rasm qabul.
- S3 5.11-bob: saqlash.
- Navbat 8.22-bob: fonda qayta ishlash.
- Validatsiya 8.8-bob: magic bytes + sharp.
- Push 8.23-bob: "rasm tayyor".
- Xavfsizlik (14): EXIF, DoS.
- Next.js Image (13): responsive, optimize.
- SEO (13): sayt tezligi.
8. Eng yaxshi amaliyotlar (best practices)
- Yuklangan rasmni qayta ishla (xom saqlama — 2.1).
- WebP/AVIF + siqish (quality ~80 — 2.4).
- Bir necha o'lcham (responsive — 2.5).
- EXIF tozalash + .rotate() (maxfiylik — 14, 2.7).
- sharp validatsiya (rasm haqiqiyligi, DoS — 2.8).
- Og'ir fonda (navbat — 2.9).
- S3/CDN + kesh (5.11, Misol 8).
- withoutEnlargement (kichikni kattalashtirmaslik — 2.3).
- Blur placeholder (UX — Misol 9).
- Original saqlash/o'chirish (qaror — 2.10).
9. Amaliy loyiha: "Rasm Qayta Ishlash Tizimi"
Rasm qayta ishlashni amalda mustahkamlash.
Maqsad
To'liq rasm tizimi: upload, validatsiya, responsive variant, watermark, EXIF, fonda.
Talablar (requirements)
- Optimallash: resize + WebP + EXIF (Misol 1, 2.4, 2.7).
- Responsive: bir necha o'lcham (Misol 2, 2.5).
- Avatar: crop kvadrat (Misol 3, 2.3).
- Watermark: logo (Misol 4, 2.6).
- Validatsiya: sharp + DoS (Misol 5, 2.8).
- Upload: ko'p rasm + variant (Misol 6, 8.8).
- Fonda: navbat (Misol 7, 2.9).
- Format: Accept header (AVIF/WebP — Misol 8, 2.4).
- Placeholder: blur LQIP (Misol 9).
- CDN kesh: serve optimizatsiya (Misol 8).
Maslahatlar (hint)
- .rotate() (EXIF — 2.7, 1-xato).
- Responsive (2.5, 2-holat).
- EXIF tozalash (14, 3-holat).
- sharp validatsiya (2.8, 4-holat).
- Og'ir navbat (2.9, 6-xato).
- CDN kesh (Misol 8, 4-xato).
"Tayyor" mezonlari (acceptance criteria)
- Optimallash (WebP).
- Responsive variant.
- Avatar (crop).
- Watermark.
- Validatsiya (sharp).
- Upload (ko'p).
- Fonda (navbat).
- Format (Accept).
- Placeholder.
- CDN kesh.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda rasm qayta ishlashni o'rgandik:
- Nega kerak (tezlik, joy, maxfiylik — 2.1); sharp (zanjir — 2.2); resize (fit strategiyalari — 2.3).
- Format/siqish (WebP/AVIF — 2.4); responsive (bir necha o'lcham — 2.5); watermark 2.6-bob.
- EXIF tozalash (maxfiylik — 2.7); validatsiya (sharp — DoS — 2.8); fonda (navbat — 2.9).
Keyingi bob — 8.25: Multi-tenancy (SaaS). Rasmni bildik; endi SaaS arxitekturasining asosini — multi-tenancy (bir ilova, ko'p mijoz-kompaniya, ma'lumot izolyatsiyasi) — o'rganamiz. Har B2B SaaS (CRM, hisobot tizimi) uchun zarur.
Foydalanilgan rasmiy/ishonchli manbalar
- sharp rasmiy hujjati — API to'liq:
resize(fit/position/background),composite,metadata/stats,rotate/extract/trim, format opsiyalari (jpeg/png/webp/avif/gif),limitInputPixels, globalconcurrency/cache. - libvips hujjati — sharp asosidagi C kutubxona: streaming arxitektura, xotira/tezlik xarakteristikasi.
- web.dev — zamonaviy rasm formatlari — WebP/AVIF taqqoslash, responsive rasm (
srcset/sizes),<picture>va format fallback. - MDN — responsive images va
<picture>elementi — brauzer format tanlash, lazy loading. - NestJS rasmiy hujjati — fayl yuklash (
FileInterceptor/FilesInterceptor),ParseFilePipe, customPipeTransform. - AWS SDK v3 (
@aws-sdk/lib-storageUpload) — stream orqali S3'ga yuklash (pipeline — Misol 11). - OWASP — fayl yuklash xavfsizligi — MIME/kontent validatsiya, decompression bomb (image bomb) himoyasi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!