crop center and resize image using cwebp respecting aspect ratio (php)

Updated: 30th March 2023
Tags: php cwebp

If you want to use cwebp and resize image to exact width and height you will have no luck, as it doesn't have powerfull functions like imagemagick. Luckily, we can create our class that will calculate how much do we need to crop center so we can resize image after with respect of aspect ratio.

<?php
class ConvertToWebp
{
    //simply resize without proportion
    public static function resize(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality = 50): ?array
    {
        $imageSize = getimagesize($srcFilePath);
        if ($width > $imageSize[0] || $height > $imageSize[1]) {
            return self::convert($srcFilePath, $newFilePath, $quality);
        }


        $command = "cwebp -q $quality -resize $width $height $srcFilePath -o $newFilePath  > /dev/null 2>&1";

        exec($command);
        $imageSize = @getimagesize($newFilePath);
        //check if cwebp did it's job. If not use imagemagick
        if (!isset($imageSize[0]) || !$imageSize[0]) {
            $s = ($width?:'').'x'.($height?:'');
            $command = "convert $srcFilePath -strip -resize $s -format webp -quality $quality {$newFilePath}";
            exec($command);
            $imageSize = @getimagesize($newFilePath);
            if (!isset($imageSize[0]) || !$imageSize[0]) {
                return null;
            }
        }
        return [
            'width' => $imageSize[0],
            'height' => $imageSize[1],
            'size' => filesize($newFilePath)
        ];
    }

    //simply convert
    public static function convert(string $srcFilePath, string $newFilePath, int $quality = 50): ?array
    {
        $command = "cwebp -q $quality  $srcFilePath -o $newFilePath  > /dev/null 2>&1";

        exec($command);
        $imageSize = @getimagesize($newFilePath);
        //check if cwebp did it's job. If not use imagemagick
        if (!isset($imageSize[0]) || !$imageSize[0]) {
            $command = "convert $srcFilePath -strip -format webp -quality $quality {$newFilePath}";
            exec($command);
            $imageSize = @getimagesize($newFilePath);
            if (!isset($imageSize[0]) || !$imageSize[0]) {
                return null;
            }
        }
        return [
            'width' => $imageSize[0],
            'height' => $imageSize[1],
            'size' => filesize($newFilePath)
        ];
    }

    //crop center and resize respecting proportion
    public static function cropResize(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality = 50): ?array
    {
        $imageSize = getimagesize($srcFilePath);
        if (self::isTooBadProportion($imageSize[0], $imageSize[1], $width, $height)) {

            if ($imageSize[0] <= $width) {
                $command = self::cropSmallWidth($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
            } elseif ($imageSize[1] < $height) {
                $command = self::cropSmallHeight($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
            } elseif ($width >= $height) {
                $command = self::cropResizeWidth($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
            } elseif ($height > $width) {
                $command = self::cropResizeHeight($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
            }

        } else {
            $command = "cwebp -q $quality -resize $width $height $srcFilePath -o $newFilePath  > /dev/null 2>&1";
        }

        exec($command);

        $imageSize = @getimagesize($newFilePath);
        //imagemagick fallback if cwebp failed
        if (!isset($imageSize[0]) || !$imageSize[0]) {
            $imagePixels = $imageSize[0] * $imageSize[1];
            $wantPixels = $width * $height;

            $s = ($width?:'').'x'.($height?:'');

            if ($imagePixels < $wantPixels) {
                //we want bigger image, so we don't use strict fit and just optimize image
                $command = "convert $srcFilePath -strip -resize {$width}x{$height}\> -format webp -quality $quality {$newFilePath}";
            } else {
                $command = "convert $srcFilePath -strip -resize $s^ -gravity center -extent {$width}x{$height} -format webp -quality $quality {$newFilePath}";
            }

            exec($command);
            $imageSize = @getimagesize($newFilePath);
            if (!isset($imageSize[0]) || !$imageSize[0]) {
                return null;
            }
        }
        return [
            'width' => $imageSize[0],
            'height' => $imageSize[1],
            'size' => filesize($newFilePath)
        ];
    }

    public static function isTooBadProportion($currentWidth, $currentHeight, $wantWidth, $wantHeight): bool
    {
        $proportionCurrent = intval($currentHeight/$currentWidth*100);
        $proportionWant = intval($wantHeight/$wantWidth*100);
        if (abs($proportionCurrent-$proportionWant) >= 20) {
            return true;
        }
        return false;
    }


    private static function cropSmallWidth(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality, array $imageSize): string
    {
        $percentage = $height / $width;
        $width = $imageSize[0];
        $height = intval($width * $percentage);

        if ($imageSize[1] < $height) {
            return self::cropSmallHeight($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
        }

        $cropOffsetX = abs(intval(($imageSize[0] - $width)/2));
        $cropOffsetY = abs(intval(($imageSize[1] - $height)/2));
        $crop = "-crop $cropOffsetX $cropOffsetY $width $height";

        return "cwebp $crop -q $quality $srcFilePath -o $newFilePath  > /dev/null 2>&1";
    }

    private static function cropSmallHeight(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality, array $imageSize): string
    {
        $percentage = $width / $height;
        $height = $imageSize[1];
        $width = intval($height * $percentage);

        if ($imageSize[0] < $width) {
            return self::cropSmallWidth($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
        }

        $cropOffsetX = abs(intval(($imageSize[0] - $width)/2));
        $cropOffsetY = abs(intval(($imageSize[1] - $height)/2));
        $crop = "-crop $cropOffsetX $cropOffsetY $width $height";

        return "cwebp $crop -q $quality $srcFilePath -o $newFilePath  > /dev/null 2>&1";
    }

    private static function cropResizeWidth(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality, array $imageSize): string
    {
        $cropW = $imageSize[0];

        $cropH = intval($cropW * ($height / $width));

        if ($imageSize[1] < $cropH) {
            return self::cropResizeHeight($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
        }

        $cropOffsetX = abs(intval(($imageSize[0] - $cropW)/2));
        $cropOffsetY = abs(intval(($imageSize[1] - $cropH)/2));
        $crop = "-crop $cropOffsetX $cropOffsetY $cropW $cropH";

        return "cwebp $crop -resize $width $height -q $quality $srcFilePath -o $newFilePath  > /dev/null 2>&1";
    }

    private static function cropResizeHeight(string $srcFilePath, string $newFilePath, int $width, int $height, int $quality, array $imageSize): string
    {
        $cropH = $imageSize[1];

        $cropW = intval($cropH * ($width / $height));

        if ($imageSize[0] < $cropW) {
            return self::cropResizeWidth($srcFilePath, $newFilePath, $width, $height, $quality, $imageSize);
        }

        $cropOffsetX = abs(intval(($imageSize[0] - $cropW)/2));
        $cropOffsetY = abs(intval(($imageSize[1] - $cropH)/2));
        $crop = "-crop $cropOffsetX $cropOffsetY $cropW $cropH";

        return "cwebp $crop -resize $width $height -q $quality $srcFilePath -o $newFilePath  > /dev/null 2>&1";
    }
}