Miniaturki zdjęć to małe wielkie rzeczy – decydują o szybkości strony, wygodzie użytkownika i wyniku w wyszukiwarce. Pokażę Ci, jak w praktyce generować miniaturki w PHP, tak by były lekkie, ostre i zawsze idealnie dopasowane. Dorzucę też garść naszych patentów z RemnetCMS, które pomagają robić to szybko i bezpiecznie.

Dlaczego miniaturki mają znaczenie
Miniaturki (thumbnails) to nie tylko pomniejszone obrazy. To przemyślana wersja grafiki, która zdejmuje ciężar z serwera i przeglądarki, poprawia Core Web Vitals i wspiera SEO. Kiedy zdjęcia ładują się błyskawicznie, użytkownicy zostają dłużej, klikają częściej, a strona działa płynnie nawet na wolnym łączu. W Remnet tworzymy miniaturki tak, aby miały sens biznesowy: szybkie listy produktów, blogi bez „skaczących” elementów i galerie, które nie zabijają transferu danych.
Dwie drogi w PHP: GD czy Imagick?
W świecie PHP miniaturki najczęściej generuje się za pomocą biblioteki GD albo Imagick. Obie dadzą radę, ale różnią się możliwościami i wydajnością. W praktyce wybór zależy od serwera, dostępnych formatów i skali projektu.
| Cecha | GD | Imagick |
|---|---|---|
| Dostępność | Domyślnie dostępna w wielu hostingach | Wymaga zainstalowanego ImageMagick |
| Jakość i ostrość | Dobra przy właściwym skalowaniu | Bardzo dobra, świetne algorytmy |
| Wydajność | Lekka, ale mniej zaawansowana | Szybka i zoptymalizowana dla większych plików |
| Wsparcie formatów | JPEG, PNG, GIF, WebP (zależnie od wersji) | JPEG, PNG, GIF, WebP, AVIF (jeśli ImageMagick je wspiera) |
| Dodatkowe efekty | Podstawowe | Bogate: rozmycia, ostrość, profil kolorów |
Proporcje, kadrowanie i tryby skalowania
Kluczem do dobrych miniaturek jest przewidywalny kadr. Najczęściej używamy dwóch podejść:
- cover (wypełnienie) – miniatura ma dokładne wymiary, a obraz jest przycięty, by wypełnić całą ramkę; idealne dla listingów i kart produktu.
- contain (dopasowanie) – obraz mieści się w ramce bez przycinania, mogą zostać marginesy; dobre dla galerii i obiektów o różnych proporcjach.
Jeśli chcesz pilnować „ważnego” miejsca na zdjęciu (np. twarz, produkt), rozważ mechanizm punktu skupienia – przechowywany w metadanych i używany podczas kadrowania. W RemnetCMS często stosujemy domyślny crop centralny plus opcję ręcznej korekty, by panować nad estetyką siatek zdjęć.
Bezpieczne i szybkie przetwarzanie – plan działania
- Weryfikacja pliku: przed obróbką sprawdź MIME (getimagesize) i rozszerzenie. Nie ufaj danym od klienta.
- Limit pamięci: duże zdjęcia potrafią wywołać błąd pamięci. Oszacuj RAM (szerokość × wysokość × 4 bajty) i w razie potrzeby odrzuć lub przeskaluj wstępnie.
- Orientacja EXIF: telefony zapisują rotację w EXIF – trzeba ją uwzględnić, w przeciwnym razie miniatury będą „na boku”.
- Wybór formatu wyjściowego: preferuj WebP (a jeśli środowisko wspiera – AVIF) ze względu na świetny stosunek jakości do wagi.
- Nazewnictwo: zapisuj miniatury z parametrami w nazwie, np. produkt-800x600-cover.webp, aby łatwo je cache’ować.
- Cache i nagłówki: długie max-age i immutable dla niezmiennych plików. Przyspiesza to kolejne odwiedziny i score w Lighthouse.
- Zapisy atomowe: generuj do pliku tymczasowego i wykonuj rename, by uniknąć wyścigu zapisu przy równoczesnych żądaniach.
Przykład z biblioteką GD
Prosty, praktyczny przykład generowania miniatury w trybie cover i contain. Obsługuje orientację EXIF i zapis do WebP/JPEG/PNG.
<?php
function createThumbnailGD($srcPath, $dstPath, $targetW, $targetH, $mode = 'cover', $format = 'webp') {
if (!file_exists($srcPath)) {
throw new RuntimeException('Brak pliku źródłowego');
}
[$w, $h, $type] = getimagesize($srcPath);
$mime = image_type_to_mime_type($type);
switch ($mime) {
case 'image/jpeg': $src = imagecreatefromjpeg($srcPath); break;
case 'image/png': $src = imagecreatefrompng($srcPath); break;
case 'image/gif': $src = imagecreatefromgif($srcPath); break;
case 'image/webp': $src = function_exists('imagecreatefromwebp') ? imagecreatefromwebp($srcPath) : null; break;
default: $src = null; break;
}
if (!$src) {
throw new RuntimeException('Nieobsługiwany format obrazu');
}
// Korekta orientacji EXIF dla JPEG
if ($mime === 'image/jpeg' && function_exists('exif_read_data')) {
$exif = @exif_read_data($srcPath);
if (!empty($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 3: $src = imagerotate($src, 180, 0); break;
case 6: $src = imagerotate($src, -90, 0); [$w,$h] = [$h,$w]; break;
case 8: $src = imagerotate($src, 90, 0); [$w,$h] = [$h,$w]; break;
}
}
}
// Wyliczenie kadrowania
$srcRatio = $w / $h; $dstRatio = $targetW / $targetH;
if ($mode === 'cover') {
if ($srcRatio > $dstRatio) {
$newH = $h; $newW = (int) round($h * $dstRatio);
$srcX = (int) round(($w - $newW) / 2); $srcY = 0;
} else {
$newW = $w; $newH = (int) round($w / $dstRatio);
$srcX = 0; $srcY = (int) round(($h - $newH) / 2);
}
$copyW = $newW; $copyH = $newH;
} else { // contain
$srcX = 0; $srcY = 0; $copyW = $w; $copyH = $h;
if ($srcRatio > $dstRatio) {
$targetH = (int) round($targetW / $srcRatio);
} else {
$targetW = (int) round($targetH * $srcRatio);
}
}
$dst = imagecreatetruecolor($targetW, $targetH);
// Obsługa przezroczystości
imagealphablending($dst, false);
imagesavealpha($dst, true);
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
imagefilledrectangle($dst, 0, 0, $targetW, $targetH, $transparent);
imagecopyresampled($dst, $src, 0, 0, $srcX, $srcY, $targetW, $targetH, $copyW, $copyH);
// Zapis
$ok = false; $quality = 82;
switch ($format) {
case 'webp':
if (function_exists('imagewebp')) { $ok = imagewebp($dst, $dstPath, $quality); }
break;
case 'jpg': case 'jpeg':
imageinterlace($dst, 1);
$ok = imagejpeg($dst, $dstPath, $quality);
break;
case 'png':
$ok = imagepng($dst, $dstPath, 6);
break;
}
imagedestroy($src); imagedestroy($dst);
if (!$ok) { throw new RuntimeException('Nie udało się zapisać miniatury'); }
}
Ta funkcja wystarczy dla większości zastosowań: blog, portfolio, listing produktów. Jeśli przetwarzasz naprawdę duże pliki lub chcesz wyciskać maksymalną jakość, spójrz na Imagick.
Przykład z Imagick
Imagick to bardziej zaawansowane narzędzie o świetnych algorytmach skalowania i ostrzenia. Poniżej prosty przykład, który generuje miniaturę w trybie cover.
<?php
function createThumbnailImagick($srcPath, $dstPath, $targetW, $targetH, $format = 'webp') {
$img = new Imagick($srcPath);
// Orientacja i profil kolorów
$orientation = $img->getImageOrientation();
switch ($orientation) {
case Imagick::ORIENTATION_RIGHTTOP: $img->rotateimage('#000', 90); break;
case Imagick::ORIENTATION_BOTTOMRIGHT:$img->rotateimage('#000', 180); break;
case Imagick::ORIENTATION_LEFTBOTTOM: $img->rotateimage('#000', -90); break;
}
$img->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
// cover: przytnij do proporcji docelowych, potem skaluj
$img->cropThumbnailImage($targetW, $targetH);
// Opcjonalny lekki sharpening po skalowaniu
$img->unsharpMaskImage(0.5, 0.5, 0.6, 0.05);
$format = strtolower($format);
if ($format === 'jpg') $format = 'jpeg';
$img->setImageFormat($format);
if (in_array($format, ['jpeg','webp','avif'])) {
$img->setImageCompressionQuality(82);
}
// Zapis do pliku tymczasowego i atomowy rename
$tmp = $dstPath . '.tmp';
$img->writeImage($tmp);
$img->destroy();
rename($tmp, $dstPath);
}
W Imagick przy większych plikach zyskasz na jakości i czasie przetwarzania. Dodatkowo łatwo skorzystać z AVIF, jeśli Twój ImageMagick i biblioteki systemowe go obsługują.
Nazewnictwo i cache, czyli porządek musi być
Przemyślane nazewnictwo miniaturek pomaga zarówno programiście, jak i serwerowi cache. Popularny schemat: nazwa-800x600-cover.webp. Taki plik można zwracać prosto z Nginx z długim Cache-Control, bez angażowania PHP. Dla generacji „na żądanie” sprawdza się strategia try_files: jeśli miniatura istnieje – serwuj ją; jeśli nie – wywołaj skrypt generujący i zapisz wynik.
location /thumbs/ {
try_files $uri /thumb.php?$args;
add_header Cache-Control 'public, max-age=31536000, immutable';
}
W RemnetCMS stosujemy też cache-warmer: po dodaniu obrazu system zawczasu generuje kilka najpopularniejszych rozmiarów, żeby pierwsze odsłony były natychmiastowe.
Miniaturki responsywne w praktyce
Jedna miniatura to za mało dla współczesnych ekranów. Warto przygotować zestaw rozmiarów i wysyłać je przez srcset oraz sizes. Format WebP/AVIF można serwować w <picture> z automatycznym fallbackiem do JPEG/PNG.
<picture>
<source type='image/avif' srcset='foto-400-cover.avif 400w, foto-800-cover.avif 800w, foto-1200-cover.avif 1200w' sizes='(max-width: 800px) 100vw, 800px'>
<source type='image/webp' srcset='foto-400-cover.webp 400w, foto-800-cover.webp 800w, foto-1200-cover.webp 1200w' sizes='(max-width: 800px) 100vw, 800px'>
<img src='foto-800-cover.jpg' srcset='foto-400-cover.jpg 400w, foto-800-cover.jpg 800w, foto-1200-cover.jpg 1200w' sizes='(max-width: 800px) 100vw, 800px' width='800' height='600' loading='lazy' decoding='async' alt='Opis zdjęcia'>
</picture>
Kluczowe detale: atrybuty width/height zapobiegają skakaniu układu (CLS), a loading="lazy" ogranicza transfer. W połączeniu z dobrym cache wyniki w Lighthouse robią się zielone jak wiosenna trawa.
Generować przy wgraniu czy „on the fly”?
Oba podejścia są poprawne – różnią się kosztami i wygodą.
- Przy wgraniu: generujesz od razu kilka rozmiarów. Plusy – stałe czasy odpowiedzi i brak skoków obciążenia. Minus – dodatkowe miejsce na dysku.
- On the fly: generujesz przy pierwszym żądaniu. Plus – oszczędzasz miejsce, tworzysz tylko potrzebne rozmiary. Minus – pierwszy użytkownik może poczekać ułamek sekundy.
W RemnetCMS zwykle łączymy oba światy: kluczowe rozmiary powstają od razu, a egzotyczne wymiary są tworzone „na żądanie” i zapisywane do cache. Dodatkowo generator działa w tle, by nie obciążać żądań użytkowników.
Jakość, kompresja i formaty: WebP i AVIF na prowadzeniu
Ustawienie jakości to delikatny balans. W praktyce 80–85 dla JPEG/WEBP jest bezpieczne, a dla PNG warto włączyć paletę (gd przy ograniczonej liczbie kolorów) lub przejść na WebP/AVIF. AVIF ma świetną kompresję przy zachowaniu jakości, ale jego kodowanie bywa wolniejsze i nie wszędzie jest dostępne – dobrym kompromisem bywa WebP jako domyślka i AVIF dla nowocześniejszych przeglądarek.
Bezpieczeństwo: nie tylko piksele
Obróbka obrazów to też wektor ataku, dlatego sprawdzaj rozszerzenia i MIME, narzucaj limity rozmiaru i pamięci, odrzucaj pliki z nietypowymi profilami ICC, a pliki tymczasowe zapisuj w katalogu bez prawa do wykonywania. Pamiętaj o nadpisaniu metadanych lub ich usunięciu, jeśli nie są potrzebne – to dodatkowe kilobajty mniej i mniej danych wrażliwych w plikach.
Autorski CMS vs WordPress: jak uniknąć „wtyczkowego” balastu
WordPress potrafi generować miniatury, ale w praktyce kończy się to dziesiątkami rozmiarów zdefiniowanych przez motyw i wtyczki, co spowalnia uploady i zjada miejsce. Do tego dochodzi brak spójnego pipeline’u obrazów oraz różna jakość kodowania między wtyczkami. W podejściu autorskim, które stosujemy w RemnetCMS, pipeline jest jeden, przewidywalny i szybki: minimalny kod po stronie klienta, zoptymalizowane algorytmy i zero nadmiarowych rozmiarów. Efekt? Stabilna wydajność, krótkie czasy TTFB i lepsze wyniki Core Web Vitals – bez doganiania problemów po każdej aktualizacji wtyczki.
Przykładowy workflow w RemnetCMS
- Walidacja obrazu i korekta EXIF.
- Natychmiastowa generacja 2–4 rozmiarów „złotych” (np. 400, 800, 1200 px szerokości).
- Zapisy atomowe i wersjonowanie nazw oparte na hashach, by uniknąć kolizji i wspierać cache immutable.
- Serwowanie z Nginx i długim Cache-Control; PHP omijamy w 99% żądań.
- Opcjonalna generacja „egzotycznych” miniaturek on the fly + zaplecione kolejkowanie w tle.
Taki układ zapewnia przewidywalne czasy odpowiedzi i pełną kontrolę nad jakością, bez niespodzianek w wydajności.
Typowe problemy i jak ich uniknąć
- Pikseloza po skalowaniu: skaluj w dół etapami lub używaj algorytmów wyższej jakości (Imagick: Lanczos). Po skalowaniu lekki unsharp mask pomaga odzyskać wrażenie ostrości.
- Miniatury „na boku”: zawsze uwzględnij EXIF albo wymuś neutralną orientację po rotacji.
- Ciężkie strony kategorii: ogranicz do rzeczywistych rozmiarów widoku (np. 400–600 px dla kart) i włącz lazy-loading. Nie ładuj pełnych 3000 px w siatce miniaturek.
- Zbyt wiele rozmiarów: trzymaj się kilku logicznych breakpointów. Każdy dodatkowy rozmiar to koszti miejsce na dysku.
Checklist: miniaturki PHP, które robią robotę
- Wybierz bibliotekę: GD dla prostoty, Imagick dla jakości i dużych plików.
- Ustal tryb: cover dla siatek, contain dla galerii mieszanych.
- Obsłuż EXIF i przezroczystość.
- Preferuj WebP/AVIF, ustaw rozsądną jakość (ok. 82).
- Używaj srcset/sizes i atrybutów width/height.
- Wdróż cache po stronie serwera (Nginx) i wersjonowanie nazw.
- Generuj na uploadzie najważniejsze rozmiary, resztę „na żądanie”.
- Testuj na realnych zdjęciach z aparatów i zrzutach ekranu – to inne profile i kompresja.
Gdy miniaturki stają się przewidywalne i lekkie, cała strona przyspiesza, a użytkownicy widzą to od pierwszego scrolla. A to zwykle przekłada się na więcej konwersji, lepszy SEO i spokojniejszy sen administratora.





