Sharp — rasmlarni qayta ishlash, resize, compress

~15 daq · Express · Fayl va Media

Muammodan boshlaylik

Foydalanuvchi profil rasmi yukladi — 4000×3000 piksel, 8 MB. Siz uni shunday saqlaysiz. Sahifada 50 ta profil rasmi ko'rsatilganda brauzer 400 MB yuklab oladi. Sahifa 30 soniya yuklanadi.

Yoki boshqa holat: e-commerce sayt. Har bir mahsulot rasmi original holda — 5-10 MB. Sahifani 20 mahsulot bilan ochsangiz — internet chiqimi 100-200 MB. Ko'pchilik foydalanuvchi sahifani yopib ketadi.

Yechim: serverda rasmni qabul qilingan zahoti qayta ishlash. Kichraytirish, siqish, formatlash.


Sharp nima?

Sharp — Node.js da rasmlarni qayta ishlash uchun eng tez kutubxona. C++ da yozilgan libvips ustida qurilgan — ImageMagick dan 4-5 marta tez.

npm install sharp

Asosiy amallar

Resize — o'lchamni o'zgartirish

const sharp = require('sharp');

// Fayldan faylga
await sharp('kirish.jpg')
  .resize(300, 300)       // ← kenglik × balandlik
  .toFile('chiqish.jpg');
// Nisbatni saqlagan holda resize
await sharp('kirish.jpg')
  .resize(800, null)      // ← kenglik 800, balandlik avtomatik
  .toFile('chiqish.jpg');

await sharp('kirish.jpg')
  .resize(null, 600)      // ← balandlik 600, kenglik avtomatik
  .toFile('chiqish.jpg');
// Kvadrat crop — profil rasmlari uchun
await sharp('kirish.jpg')
  .resize(300, 300, {
    fit: 'cover',         // ← to'ldiradi va kerakli qismini kesadi
    position: 'center',   // ← markazdan kesadi
  })
  .toFile('profil.jpg');

fit parametri nima uchun muhim? Turli nisbatdagi rasmlarni bir xil o'lchamga keltirish kerak. cover — rasm to'liq to'ldiriladi (qismlari kesilishi mumkin). contain — rasm ichiga sig'diriladi (bo'sh joy qolishi mumkin). fill — cho'ziladi (nisbat buziladi).

Format o'zgartirish va siqish

// JPEG ga o'tkazish va siqish
await sharp('kirish.png')
  .jpeg({ quality: 80 })  // ← 80% sifat, hajm ~60% kamayadi
  .toFile('chiqish.jpg');

// PNG siqish
await sharp('kirish.png')
  .png({ compressionLevel: 9 }) // ← 0-9, 9 = maksimal siqish
  .toFile('chiqish.png');

// WebP — zamonaviy format, JPEG dan ~30% kichik
await sharp('kirish.jpg')
  .webp({ quality: 80 })
  .toFile('chiqish.webp');

Nima uchun WebP? Xuddi shunday sifatda JPEG dan 25-35% kichik. Zamonaviy brauzerlarning barchasi qo'llab-quvvatlaydi. Sayt tezligini sezilarli oshiradi.


Multer bilan birga ishlatish

Oldingi darsda Multer ning memoryStorage ni eslab qoldingizmi? Aynan shu yerda kerak bo'ladi:

const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');

const app = express();

// Multer — diskka emas, xotiraga saqlaydi
const upload = multer({
  storage: multer.memoryStorage(),        // ← buffer sifatida xotirada
  limits: { fileSize: 10 * 1024 * 1024 }, // ← 10 MB (original)
  fileFilter: (req, file, cb) => {
    const allowed = ['image/jpeg', 'image/png', 'image/webp'];
    allowed.includes(file.mimetype) ? cb(null, true) : cb(new Error('Faqat rasm'));
  }
});

// Profil rasmi yuklash
app.post('/profil/rasm', upload.single('rasm'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'Rasm yuborilmadi' });
  }

  const filename = `profil-${Date.now()}.webp`;
  const outputPath = path.join('uploads', filename);

  await sharp(req.file.buffer)    // ← xotiradagi buffer
    .resize(300, 300, {
      fit: 'cover',
      position: 'center',
    })
    .webp({ quality: 85 })        // ← WebP formatga, 85% sifat
    .toFile(outputPath);

  res.json({
    message: 'Rasm saqlandi',
    url: `/uploads/${filename}`,
  });
});

app.use('/uploads', express.static('uploads'));
app.listen(3000);

Bir nechta o'lcham yaratish (thumbnails)

E-commerce saytlarda bir rasmning bir nechta varianti kerak: kichik (ro'yxat uchun), o'rta (karta uchun), katta (batafsil sahifa uchun):

app.post('/mahsulot/rasm', upload.single('rasm'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'Rasm yuborilmadi' });
  }

  const baseName = `mahsulot-${Date.now()}`;

  // Barcha o'lchamlarni parallel yaratish
  await Promise.all([
    sharp(req.file.buffer)
      .resize(100, 100, { fit: 'cover' })
      .webp({ quality: 80 })
      .toFile(`uploads/${baseName}-kichik.webp`),

    sharp(req.file.buffer)
      .resize(400, 400, { fit: 'cover' })
      .webp({ quality: 85 })
      .toFile(`uploads/${baseName}-orta.webp`),

    sharp(req.file.buffer)
      .resize(1200, null)           // ← kenglik 1200, balandlik avtomatik
      .webp({ quality: 90 })
      .toFile(`uploads/${baseName}-katta.webp`),
  ]);

  res.json({
    kichik: `/uploads/${baseName}-kichik.webp`,
    orta: `/uploads/${baseName}-orta.webp`,
    katta: `/uploads/${baseName}-katta.webp`,
  });
});

Promise.all nima uchun? Uchta rasm ketma-ket yaratilsa — birinchisi tugamaguncha ikkinchisi boshlanmaydi. Promise.all bilan barchasi parallel ishlaydi — 3 marta tez.


Metadata o'qish

app.post('/rasm-info', upload.single('rasm'), async (req, res) => {
  const metadata = await sharp(req.file.buffer).metadata();

  res.json({
    format: metadata.format,   // ← 'jpeg', 'png', 'webp'
    kenglik: metadata.width,
    balandlik: metadata.height,
    hajm: req.file.size,
    kanallar: metadata.channels, // ← 3 (RGB) yoki 4 (RGBA)
  });
});

Watermark qo'shish

app.post('/rasm-watermark', upload.single('rasm'), async (req, res) => {
  const watermark = await fs.promises.readFile('watermark.png'); // ← shaffof PNG

  const filename = `wm-${Date.now()}.webp`;

  await sharp(req.file.buffer)
    .resize(1200, null)
    .composite([{
      input: watermark,
      gravity: 'southeast',  // ← o'ng pastga
      blend: 'over',
    }])
    .webp({ quality: 85 })
    .toFile(`uploads/${filename}`);

  res.json({ url: `/uploads/${filename}` });
});

Tez-tez uchraydigan xatolar

1. Katta rasmda xotira tugashi

// ❌ — 50 MB li rasm xotiraga yuklanadi
const upload = multer({
  storage: multer.memoryStorage(),
  // limits yo'q!
});

Sababi: memoryStorage faylni RAMga yuklaydi. 100 ta parallel so'rovda har biri 50 MB bo'lsa — 5 GB RAM kerak. Server crash bo'ladi.

// ✅ — hajmni cheklang
const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 10 * 1024 * 1024 } // ← 10 MB maksimal
});

2. Sharp zanjirida await unutilishi

// ❌ — await yo'q, fayl yaratilishini kutmaymiz
sharp(buffer).resize(300, 300).toFile('chiqish.jpg');
res.json({ message: 'Saqlandi' }); // ← aslida saqlanmagan!
// ✅ — await bilan kutish
await sharp(buffer).resize(300, 300).toFile('chiqish.jpg');
res.json({ message: 'Saqlandi' });

3. Animatsiyali GIF ni Sharp bilan qayta ishlash

// ❌ — Sharp GIF animatsiyasini qo'llab-quvvatlamaydi (default)
await sharp('animatsiya.gif').resize(300, 300).toFile('chiqish.gif');
// Natija: faqat birinchi kadr saqlanadi

Sababi: Sharp libvips bilan ishlaydi — GIF animatsiyasi uchun alohida { animated: true } parametri kerak yoki gifsicle kabi maxsus kutubxona ishlatiladi.

4. Original faylni o'chirish

// ❌ — resize qilganingiz original faylni o'chiradi
await sharp('original.jpg')
  .resize(300)
  .toFile('original.jpg'); // ← bir xil nom!

Sababi: Sharp fayl yozayotganda asl fayl o'qiladi va ustiga yoziladi — natija buzilishi mumkin. Har doim boshqa fayl nomiga yozing.


Xulosa

  • Sharp — Node.js da eng tez rasm qayta ishlash kutubxonasi
  • resize() — o'lchamni o'zgartiradi, fit: 'cover' nisbatni saqlaydi
  • webp({ quality: 80-90 }) — JPEG dan kichik, sifat yaxshi
  • Multer memoryStorage + Sharp — fayl diskka saqlanmasdan qayta ishlanadi
  • Promise.all — bir nechta o'lcham parallel yaratiladi, tezroq

Keyingi dars: 34-dars: Amazon S3 — fayl saqlash, upload, delete, signed URL