MidТеория13 min

GD и Imagick

GD Library, Imagick, создание и обработка изображений, ресайз, фильтры, водяные знаки

PHP предоставляет два основных расширения для работы с изображениями: GD (встроенная библиотека, есть практически всегда) и Imagick (обёртка над ImageMagick -- мощным инструментом для обработки изображений). GD хорошо подходит для базовых операций, Imagick -- для сложных преобразований и профессиональной работы.

GD Library

Проверка и конфигурация

<?php
declare(strict_types=1);

// Check if GD is available
var_dump(extension_loaded('gd'));  // true

// Get detailed GD info
$gdInfo = gd_info();

echo $gdInfo['GD Version'];             // "bundled (2.1.0 compatible)"
echo $gdInfo['FreeType Support'];       // true (for TTF fonts)
echo $gdInfo['JPEG Support'];           // true
echo $gdInfo['PNG Support'];            // true
echo $gdInfo['GIF Read Support'];       // true
echo $gdInfo['GIF Create Support'];     // true
echo $gdInfo['WebP Support'];           // true
echo $gdInfo['AVIF Support'];           // true (PHP 8.1+)

// Supported image types as bitmask
$types = imagetypes();
var_dump($types & IMG_PNG);   // non-zero if PNG supported
var_dump($types & IMG_JPEG);  // non-zero if JPEG supported
var_dump($types & IMG_WEBP);  // non-zero if WebP supported
var_dump($types & IMG_AVIF);  // non-zero if AVIF supported (PHP 8.1+)

Создание изображений

<?php
declare(strict_types=1);

// imagecreatetruecolor() — creates a true-color image (24-bit)
// Always use this over imagecreate()
$width = 800;
$height = 600;
$image = imagecreatetruecolor($width, $height);

// imagecreate() — palette-based image (limited to 256 colors, legacy)
// $image = imagecreate(800, 600); // avoid unless needed

// Enable alpha blending and save alpha channel
imagealphablending($image, true);
imagesavealpha($image, true);

// Allocate colors: imagecolorallocate(image, red, green, blue)
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
$red   = imagecolorallocate($image, 255, 0, 0);
$green = imagecolorallocate($image, 0, 128, 0);
$blue  = imagecolorallocate($image, 0, 0, 255);

// Colors with alpha (0 = opaque, 127 = fully transparent)
$semiTransparentRed = imagecolorallocatealpha($image, 255, 0, 0, 64);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);

// Fill entire image with white
imagefill($image, 0, 0, $white);

// Output and cleanup
imagepng($image, '/tmp/blank.png');
imagedestroy($image);

Загрузка существующих изображений

<?php
declare(strict_types=1);

// Load from file based on format
$jpeg = imagecreatefromjpeg('/path/to/photo.jpg');
$png  = imagecreatefrompng('/path/to/image.png');
$gif  = imagecreatefromgif('/path/to/animation.gif');
$webp = imagecreatefromwebp('/path/to/image.webp');
// $avif = imagecreatefromavif('/path/to/image.avif'); // PHP 8.1+

// Load from string (e.g., from database or HTTP response)
$data = file_get_contents('/path/to/photo.jpg');
$image = imagecreatefromstring($data);

// Auto-detect format and load
function loadImage(string $path): \GdImage
{
    $info = getimagesize($path);
    if ($info === false) {
        throw new \RuntimeException('Cannot read image: ' . $path);
    }

    return match ($info[2]) {
        IMAGETYPE_JPEG => imagecreatefromjpeg($path),
        IMAGETYPE_PNG  => imagecreatefrompng($path),
        IMAGETYPE_GIF  => imagecreatefromgif($path),
        IMAGETYPE_WEBP => imagecreatefromwebp($path),
        IMAGETYPE_BMP  => imagecreatefrombmp($path),
        default => throw new \RuntimeException(
            'Unsupported image type: ' . image_type_to_mime_type($info[2])
        ),
    };
}

// Get image dimensions
$image = loadImage('/path/to/photo.jpg');
echo imagesx($image); // width in pixels
echo imagesy($image); // height in pixels

Рисование примитивов

<?php
declare(strict_types=1);

$image = imagecreatetruecolor(400, 400);
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
$red   = imagecolorallocate($image, 220, 50, 50);
$blue  = imagecolorallocate($image, 50, 50, 220);
$green = imagecolorallocate($image, 50, 180, 50);
imagefill($image, 0, 0, $white);

// imageline(image, x1, y1, x2, y2, color)
imageline($image, 0, 0, 399, 399, $black); // diagonal line

// imagerectangle(image, x1, y1, x2, y2, color) — outline
imagerectangle($image, 50, 50, 150, 150, $red);

// imagefilledrectangle — filled rectangle
imagefilledrectangle($image, 200, 50, 350, 150, $blue);

// imageellipse(image, cx, cy, width, height, color) — outline
imageellipse($image, 200, 300, 200, 100, $green);

// imagefilledellipse — filled ellipse
imagefilledellipse($image, 200, 300, 180, 80, $green);

// imagearc(image, cx, cy, width, height, start_deg, end_deg, color)
imagearc($image, 100, 300, 100, 100, 0, 270, $red); // 3/4 circle

// imagefilledarc — filled arc (for pie charts)
imagefilledarc(
    $image, 100, 300, 100, 100, 0, 90,
    $blue, IMG_ARC_PIE
);

// imagepolygon — polygon outline
$points = [100, 10, 50, 80, 150, 80]; // triangle (x1,y1, x2,y2, x3,y3)
imagepolygon($image, $points, 3, $black);

// imagesetthickness — line thickness
imagesetthickness($image, 3);
imageline($image, 0, 200, 399, 200, $red); // thick line

// imagesetpixel — individual pixel
imagesetpixel($image, 200, 200, $red);

imagepng($image, '/tmp/shapes.png');
imagedestroy($image);

Текст на изображениях

<?php
declare(strict_types=1);

$image = imagecreatetruecolor(600, 200);
$white = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
imagefill($image, 0, 0, $white);

// imagestring() — built-in bitmap fonts (1-5)
imagestring($image, 5, 10, 10, 'Built-in font (size 5)', $black);
imagestring($image, 3, 10, 30, 'Built-in font (size 3)', $black);
imagestring($image, 1, 10, 50, 'Built-in font (size 1)', $black);

// imagestringup() — vertical text (bottom to top)
imagestringup($image, 5, 550, 190, 'Vertical', $black);

// imagettftext() — TrueType fonts (much better quality)
// imagettftext(image, size, angle, x, y, color, font_file, text)
$fontPath = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf';

if (file_exists($fontPath)) {
    imagettftext($image, 20, 0, 10, 100, $black, $fontPath, 'Hello, World!');
    imagettftext($image, 14, 0, 10, 130, $black, $fontPath, 'TrueType шрифты');
    imagettftext($image, 24, 15, 10, 180, $black, $fontPath, 'Rotated text');

    // imagettfbbox() — get bounding box for text (before drawing)
    $bbox = imagettfbbox(20, 0, $fontPath, 'Hello, World!');
    // Returns array of 8 values: x/y for 4 corners
    // [0] lower-left X, [1] lower-left Y
    // [2] lower-right X, [3] lower-right Y
    // [4] upper-right X, [5] upper-right Y
    // [6] upper-left X, [7] upper-left Y

    $textWidth  = abs($bbox[2] - $bbox[0]);
    $textHeight = abs($bbox[7] - $bbox[1]);

    // Center text horizontally
    $x = (imagesx($image) - $textWidth) / 2;
    imagettftext($image, 20, 0, (int) $x, 170, $black, $fontPath, 'Centered!');
}

imagepng($image, '/tmp/text.png');
imagedestroy($image);

Ресайз и кроп

<?php
declare(strict_types=1);

// Load original image
$source = imagecreatefromjpeg('/path/to/photo.jpg');
$srcW = imagesx($source);
$srcH = imagesy($source);

// --- Method 1: imagescale() (simplest, PHP 5.5+) ---
// Scale to width 400, preserve aspect ratio
$resized = imagescale($source, 400); // height auto-calculated
// or with specific height:
$resized = imagescale($source, 400, 300, IMG_BILINEAR_FIXED);

// --- Method 2: imagecopyresampled() (best quality) ---
$newW = 400;
$newH = 300;
$destination = imagecreatetruecolor($newW, $newH);

imagecopyresampled(
    $destination,    // destination image
    $source,         // source image
    0, 0,            // destination x, y
    0, 0,            // source x, y
    $newW, $newH,    // destination width, height
    $srcW, $srcH     // source width, height
);

// --- Method 3: imagecopyresized() (faster, lower quality) ---
// Same signature as imagecopyresampled, but uses nearest-neighbor
// Use only when speed is critical and quality doesn't matter

// --- Crop ---
// imagecrop(image, ['x' => ..., 'y' => ..., 'width' => ..., 'height' => ...])
$cropped = imagecrop($source, [
    'x' => 100,
    'y' => 50,
    'width' => 300,
    'height' => 300,
]);

// imagecropauto() — auto-crop based on content
$autoCropped = imagecropauto($source, IMG_CROP_DEFAULT);
// Modes: IMG_CROP_DEFAULT, IMG_CROP_TRANSPARENT, IMG_CROP_BLACK, IMG_CROP_WHITE

// --- Practical: resize maintaining aspect ratio ---
function resizeImage(
    \GdImage $source,
    int $maxWidth,
    int $maxHeight,
): \GdImage {
    $srcW = imagesx($source);
    $srcH = imagesy($source);

    $ratio = min($maxWidth / $srcW, $maxHeight / $srcH);

    // Don't upscale
    if ($ratio >= 1.0) {
        $newW = $srcW;
        $newH = $srcH;
    } else {
        $newW = (int) round($srcW * $ratio);
        $newH = (int) round($srcH * $ratio);
    }

    $dest = imagecreatetruecolor($newW, $newH);

    // Preserve transparency for PNG
    imagealphablending($dest, false);
    imagesavealpha($dest, true);
    $transparent = imagecolorallocatealpha($dest, 0, 0, 0, 127);
    imagefilledrectangle($dest, 0, 0, $newW - 1, $newH - 1, $transparent);

    imagecopyresampled($dest, $source, 0, 0, 0, 0, $newW, $newH, $srcW, $srcH);

    return $dest;
}

Rotate и Flip

<?php
declare(strict_types=1);

$image = imagecreatefromjpeg('/path/to/photo.jpg');

// imagerotate(image, angle, bg_color)
// Angle in degrees, counter-clockwise
$rotated90  = imagerotate($image, 90, 0);   // 90° counter-clockwise
$rotated180 = imagerotate($image, 180, 0);
$rotated270 = imagerotate($image, 270, 0);  // or -90°

// Arbitrary rotation (non-90 degree) fills corners with bg_color
$bg = imagecolorallocate($image, 255, 255, 255);
$rotated45 = imagerotate($image, 45, $bg);

// imageflip() — mirror image
imageflip($image, IMG_FLIP_HORIZONTAL); // mirror left-right
imageflip($image, IMG_FLIP_VERTICAL);   // mirror top-bottom
imageflip($image, IMG_FLIP_BOTH);       // mirror both (= 180° rotation)

Фильтры

<?php
declare(strict_types=1);

$image = imagecreatefromjpeg('/path/to/photo.jpg');

// imagefilter(image, filter_type, ...args)

// Grayscale — remove all color
imagefilter($image, IMG_FILTER_GRAYSCALE);

// Brightness — adjust brightness (-255 to 255)
imagefilter($image, IMG_FILTER_BRIGHTNESS, 50);   // brighter
imagefilter($image, IMG_FILTER_BRIGHTNESS, -50);  // darker

// Contrast — adjust contrast (-100 to 100)
imagefilter($image, IMG_FILTER_CONTRAST, -20);    // more contrast
imagefilter($image, IMG_FILTER_CONTRAST, 20);     // less contrast

// Colorize — add color tint (r, g, b, alpha)
imagefilter($image, IMG_FILTER_COLORIZE, 100, 0, 0, 0); // red tint
imagefilter($image, IMG_FILTER_COLORIZE, 0, 0, 100, 0); // blue tint

// Edge detection
imagefilter($image, IMG_FILTER_EDGEDETECT);

// Emboss
imagefilter($image, IMG_FILTER_EMBOSS);

// Gaussian blur (apply multiple times for stronger blur)
for ($i = 0; $i < 5; $i++) {
    imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
}

// Selective blur — preserves edges better
imagefilter($image, IMG_FILTER_SELECTIVE_BLUR);

// Sharpen — opposite of blur
imagefilter($image, IMG_FILTER_SHARPEN);

// Negate — invert colors
imagefilter($image, IMG_FILTER_NEGATE);

// Pixelate — block_size, advanced_pixelation
imagefilter($image, IMG_FILTER_PIXELATE, 10, true);

// Smooth — smoothing level (-8 to 8)
imagefilter($image, IMG_FILTER_SMOOTH, 5);

imagejpeg($image, '/tmp/filtered.jpg', 90);
imagedestroy($image);

Вывод и сохранение

<?php
declare(strict_types=1);

$image = imagecreatetruecolor(100, 100);
$red = imagecolorallocate($image, 255, 0, 0);
imagefill($image, 0, 0, $red);

// Save to file
imagejpeg($image, '/tmp/output.jpg', 90);     // quality 0-100 (default 75)
imagepng($image, '/tmp/output.png', 6);         // compression 0-9 (default 6)
imagegif($image, '/tmp/output.gif');
imagewebp($image, '/tmp/output.webp', 85);     // quality 0-100
// imageavif($image, '/tmp/output.avif', 50, 4); // quality, speed (PHP 8.1+)

// Output directly to browser (null as filename)
header('Content-Type: image/png');
imagepng($image); // outputs to stdout

// Capture to string using output buffering
ob_start();
imagepng($image);
$pngData = ob_get_clean();

// Base64 encode for embedding in HTML
$base64 = base64_encode($pngData);
echo '<img src="data:image/png;base64,' . $base64 . '">';

imagedestroy($image);

EXIF и ориентация

<?php
declare(strict_types=1);

// exif_read_data() — read EXIF metadata from JPEG/TIFF
$exif = exif_read_data('/path/to/photo.jpg', 'ANY_TAG', true);

if ($exif !== false) {
    echo $exif['FILE']['FileName'] ?? 'unknown';
    echo $exif['COMPUTED']['Width'] ?? 'unknown';
    echo $exif['COMPUTED']['Height'] ?? 'unknown';
    echo $exif['IFD0']['Make'] ?? 'unknown';     // Camera manufacturer
    echo $exif['IFD0']['Model'] ?? 'unknown';    // Camera model
    echo $exif['EXIF']['DateTimeOriginal'] ?? 'unknown';
    echo $exif['EXIF']['ExposureTime'] ?? 'unknown';
    echo $exif['EXIF']['FNumber'] ?? 'unknown';
    echo $exif['EXIF']['ISOSpeedRatings'] ?? 'unknown';
}

// Fix orientation (phones rotate via EXIF, not actual pixels)
function fixOrientation(string $path): \GdImage
{
    $image = imagecreatefromjpeg($path);
    $exif = @exif_read_data($path);

    if ($exif === false || !isset($exif['Orientation'])) {
        return $image;
    }

    return match ((int) $exif['Orientation']) {
        2 => tap($image, fn($img) => imageflip($img, IMG_FLIP_HORIZONTAL)),
        3 => imagerotate($image, 180, 0),
        4 => tap($image, fn($img) => imageflip($img, IMG_FLIP_VERTICAL)),
        5 => tap(imagerotate($image, 270, 0), fn($img) => imageflip($img, IMG_FLIP_HORIZONTAL)),
        6 => imagerotate($image, 270, 0),
        7 => tap(imagerotate($image, 90, 0), fn($img) => imageflip($img, IMG_FLIP_HORIZONTAL)),
        8 => imagerotate($image, 90, 0),
        default => $image,
    };
}

// Helper to apply operation and return same image
function tap(\GdImage $image, callable $fn): \GdImage
{
    $fn($image);
    return $image;
}

Imagick (ImageMagick)

Imagick -- PHP-обёртка над ImageMagick. Поддерживает 200+ форматов изображений, предоставляет более мощные инструменты обработки и лучшее качество ресайза.

Базовые операции

<?php
declare(strict_types=1);

// Check availability
var_dump(extension_loaded('imagick')); // true
// Install: apt install php-imagick / pecl install imagick

// Create from file
$imagick = new \Imagick('/path/to/photo.jpg');

// Create from blob (string data)
$data = file_get_contents('/path/to/photo.jpg');
$imagick = new \Imagick();
$imagick->readImageBlob($data);

// Create blank image
$imagick = new \Imagick();
$imagick->newImage(800, 600, new \ImagickPixel('white'));
$imagick->setImageFormat('png');

// Get dimensions
echo $imagick->getImageWidth();   // 800
echo $imagick->getImageHeight();  // 600

// Get format
echo $imagick->getImageFormat();  // "JPEG", "PNG", etc.

// Get file size
echo $imagick->getImageLength();  // bytes

Ресайз и кроп

<?php
declare(strict_types=1);

$imagick = new \Imagick('/path/to/photo.jpg');

// resizeImage(width, height, filter, blur)
// Best quality filters: FILTER_LANCZOS, FILTER_MITCHELL
$imagick->resizeImage(400, 300, \Imagick::FILTER_LANCZOS, 1);

// thumbnailImage(width, height, bestfit, fill)
// Simpler API — bestfit=true preserves aspect ratio
$imagick->thumbnailImage(200, 200, true);

// scaleImage(width, height)
// Proportional if one dimension is 0
$imagick->scaleImage(400, 0); // scale to width 400, auto height

// cropImage(width, height, x, y)
$imagick->cropImage(300, 300, 50, 50);

// cropThumbnailImage(width, height) — crop to exact size
// Resizes and center-crops to fill exact dimensions
$imagick->cropThumbnailImage(200, 200);

Преобразования

<?php
declare(strict_types=1);

$imagick = new \Imagick('/path/to/photo.jpg');

// Rotate
$imagick->rotateImage(new \ImagickPixel('white'), 90);

// Flip / Flop
$imagick->flipImage();  // vertical mirror
$imagick->flopImage();  // horizontal mirror

// Blur
$imagick->blurImage(5, 3);           // radius, sigma
$imagick->gaussianBlurImage(5, 3);   // Gaussian blur

// Sharpen
$imagick->sharpenImage(2, 1);        // radius, sigma
$imagick->unsharpMaskImage(2, 1, 1, 0.05); // radius, sigma, amount, threshold

// Auto-orient (fix EXIF rotation)
$imagick->autoOrient(); // PHP 8.3+ or imagick 3.4.4+

// Compression and quality
$imagick->setImageFormat('jpeg');
$imagick->setImageCompressionQuality(85); // 0-100

// Strip metadata (EXIF, profiles) — reduces file size
$imagick->stripImage();

// Output
$imagick->writeImage('/tmp/output.jpg');
echo $imagick->getImageBlob(); // get as string

Compositing (водяные знаки)

<?php
declare(strict_types=1);

// Add watermark to image
$photo = new \Imagick('/path/to/photo.jpg');
$watermark = new \Imagick('/path/to/watermark.png');

// Resize watermark to 20% of photo width
$wmWidth = (int) ($photo->getImageWidth() * 0.2);
$watermark->scaleImage($wmWidth, 0); // proportional

// Set opacity
$watermark->evaluateImage(\Imagick::EVALUATE_MULTIPLY, 0.3, \Imagick::CHANNEL_ALPHA);

// Position: bottom-right with 10px margin
$x = $photo->getImageWidth() - $watermark->getImageWidth() - 10;
$y = $photo->getImageHeight() - $watermark->getImageHeight() - 10;

// Composite
$photo->compositeImage(
    $watermark,
    \Imagick::COMPOSITE_OVER,
    $x,
    $y
);

$photo->writeImage('/tmp/watermarked.jpg');
$photo->destroy();
$watermark->destroy();

Сравнение GD и Imagick

Характеристика GD Imagick
Установка Обычно встроена Нужна отдельная установка
Форматы ~10 (JPEG, PNG, GIF, WebP, AVIF) 200+
Качество ресайза Хорошее Отличное (Lanczos)
Текст Базовый (TTF) Продвинутый
Память Меньше Больше
Скорость Быстрее для простых задач Быстрее для сложных
API Процедурный ООП
Слои/композиция Ограничено Полноценная

Практические примеры

Генератор thumbnail

<?php
declare(strict_types=1);

final class ThumbnailGenerator
{
    public function __construct(
        private readonly int $maxWidth = 300,
        private readonly int $maxHeight = 300,
        private readonly int $quality = 85,
    ) {}

    public function generate(string $sourcePath, string $outputPath): void
    {
        $info = getimagesize($sourcePath);
        if ($info === false) {
            throw new \RuntimeException('Invalid image: ' . $sourcePath);
        }

        [$srcW, $srcH] = $info;
        $source = $this->loadImage($sourcePath, $info[2]);

        // Calculate new dimensions
        $ratio = min(
            $this->maxWidth / $srcW,
            $this->maxHeight / $srcH,
            1.0 // never upscale
        );

        $newW = (int) round($srcW * $ratio);
        $newH = (int) round($srcH * $ratio);

        // Create thumbnail
        $thumb = imagecreatetruecolor($newW, $newH);

        // Preserve transparency
        imagealphablending($thumb, false);
        imagesavealpha($thumb, true);

        imagecopyresampled($thumb, $source, 0, 0, 0, 0, $newW, $newH, $srcW, $srcH);

        // Save based on extension
        $extension = strtolower(pathinfo($outputPath, PATHINFO_EXTENSION));
        match ($extension) {
            'jpg', 'jpeg' => imagejpeg($thumb, $outputPath, $this->quality),
            'png'         => imagepng($thumb, $outputPath, 6),
            'webp'        => imagewebp($thumb, $outputPath, $this->quality),
            'gif'         => imagegif($thumb, $outputPath),
            default       => throw new \RuntimeException('Unsupported format: ' . $extension),
        };

        imagedestroy($source);
        imagedestroy($thumb);
    }

    private function loadImage(string $path, int $type): \GdImage
    {
        return match ($type) {
            IMAGETYPE_JPEG => imagecreatefromjpeg($path),
            IMAGETYPE_PNG  => imagecreatefrompng($path),
            IMAGETYPE_GIF  => imagecreatefromgif($path),
            IMAGETYPE_WEBP => imagecreatefromwebp($path),
            default => throw new \RuntimeException('Unsupported type'),
        };
    }
}

$generator = new ThumbnailGenerator(maxWidth: 200, maxHeight: 200);
$generator->generate('/uploads/photo.jpg', '/uploads/thumbs/photo_200.jpg');

Водяной знак (GD)

<?php
declare(strict_types=1);

function addTextWatermark(
    string $imagePath,
    string $outputPath,
    string $text = 'SAMPLE',
    string $fontPath = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
): void {
    $image = imagecreatefromjpeg($imagePath);
    $w = imagesx($image);
    $h = imagesy($image);

    // Semi-transparent white text
    $color = imagecolorallocatealpha($image, 255, 255, 255, 80);

    // Calculate font size relative to image
    $fontSize = (int) ($w * 0.05);

    // Get text bounding box
    $bbox = imagettfbbox($fontSize, -30, $fontPath, $text);
    $textW = abs($bbox[2] - $bbox[0]);
    $textH = abs($bbox[7] - $bbox[1]);

    // Center text
    $x = (int) (($w - $textW) / 2);
    $y = (int) (($h + $textH) / 2);

    imagettftext($image, $fontSize, -30, $x, $y, $color, $fontPath, $text);

    imagejpeg($image, $outputPath, 90);
    imagedestroy($image);
}

addTextWatermark('/uploads/photo.jpg', '/uploads/watermarked.jpg');

Простой CAPTCHA генератор

<?php
declare(strict_types=1);

function generateCaptcha(
    int $length = 5,
    string $fontPath = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
): array {
    // Generate random code
    $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $code = '';
    for ($i = 0; $i < $length; $i++) {
        $code .= $chars[random_int(0, strlen($chars) - 1)];
    }

    // Create image
    $width = 180;
    $height = 60;
    $image = imagecreatetruecolor($width, $height);

    // Random background
    $bg = imagecolorallocate(
        $image,
        random_int(220, 255),
        random_int(220, 255),
        random_int(220, 255)
    );
    imagefill($image, 0, 0, $bg);

    // Add noise lines
    for ($i = 0; $i < 6; $i++) {
        $lineColor = imagecolorallocate(
            $image,
            random_int(150, 200),
            random_int(150, 200),
            random_int(150, 200)
        );
        imageline(
            $image,
            random_int(0, $width),
            random_int(0, $height),
            random_int(0, $width),
            random_int(0, $height),
            $lineColor
        );
    }

    // Draw each character with random position/angle/color
    for ($i = 0; $i < $length; $i++) {
        $color = imagecolorallocate(
            $image,
            random_int(0, 100),
            random_int(0, 100),
            random_int(0, 100)
        );
        $angle = random_int(-20, 20);
        $x = 15 + $i * 30 + random_int(-5, 5);
        $y = 40 + random_int(-5, 5);
        imagettftext($image, 22, $angle, $x, $y, $color, $fontPath, $code[$i]);
    }

    // Add noise dots
    for ($i = 0; $i < 100; $i++) {
        $dotColor = imagecolorallocate(
            $image,
            random_int(100, 200),
            random_int(100, 200),
            random_int(100, 200)
        );
        imagesetpixel(
            $image,
            random_int(0, $width - 1),
            random_int(0, $height - 1),
            $dotColor
        );
    }

    // Return code and image data
    ob_start();
    imagepng($image);
    $imageData = ob_get_clean();
    imagedestroy($image);

    return [
        'code' => $code,
        'image' => 'data:image/png;base64,' . base64_encode($imageData),
    ];
}

$captcha = generateCaptcha();
// Store $captcha['code'] in session, show $captcha['image'] to user

Извлечение информации об изображении

<?php
declare(strict_types=1);

function getImageInfo(string $path): array
{
    if (!file_exists($path)) {
        throw new \RuntimeException('File not found: ' . $path);
    }

    $info = getimagesize($path);
    if ($info === false) {
        throw new \RuntimeException('Not a valid image: ' . $path);
    }

    $result = [
        'width'     => $info[0],
        'height'    => $info[1],
        'mime'      => $info['mime'],
        'type'      => image_type_to_extension($info[2], false),
        'bits'      => $info['bits'] ?? null,
        'channels'  => $info['channels'] ?? null,
        'file_size' => filesize($path),
        'megapixels' => round(($info[0] * $info[1]) / 1_000_000, 2),
        'aspect_ratio' => round($info[0] / $info[1], 4),
    ];

    // EXIF data for JPEG
    if ($info[2] === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
        $exif = @exif_read_data($path, 'EXIF', true);
        if ($exif !== false) {
            $result['exif'] = [
                'camera'   => trim(($exif['IFD0']['Make'] ?? '') . ' ' . ($exif['IFD0']['Model'] ?? '')),
                'date'     => $exif['EXIF']['DateTimeOriginal'] ?? null,
                'iso'      => $exif['EXIF']['ISOSpeedRatings'] ?? null,
                'exposure' => $exif['EXIF']['ExposureTime'] ?? null,
                'aperture' => $exif['EXIF']['FNumber'] ?? null,
            ];
        }
    }

    return $result;
}

$info = getImageInfo('/path/to/photo.jpg');
// ['width' => 4032, 'height' => 3024, 'mime' => 'image/jpeg', ...]

Проверь себя

5 из 11

Какая функция применяет фильтр размытия в GD?

Как исправить ориентацию JPEG-фотографии, снятой на телефон?

Что вернёт getimagesize() для валидного JPEG файла?

Что делает imagesavealpha($image, true)?

Для чего используется imagettfbbox()?