crop center and resize image using cwebp respecting aspect ratio (php)
If you want to use cwebp and resize image to exact width and height you will have no luck, as it doesn’t have powerful 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.
Before writing your own class to handle the calculation—which can be good practice and reusable for new formats—first try installing ImageMagick and the Imagick PHP extension.
Install imagick on ubuntu
sudo apt update
sudo apt install imagemagick php-imagick
<?php
class ImageResizeService
{
/**
* Resizes, crops, and converts images to WebP with high compression.
* * @param string $src Source file path
* @param string $dest Destination .webp path
* @param int|null $width Target width
* @param int|null $height Target height
* @param int $quality Compression quality (0-100)
* @return array|null Image stats or null on failure
*/
public static function resize(string $src, string $dest, $width = null, $height = null, int $quality = 50): ?array
{
try {
$img = new \Imagick($src);
// 1. Remove EXIF, Profiles, and Metadata (Crucial for < 10kb files)
$img->stripImage();
$origW = $img->getImageWidth();
$origH = $img->getImageHeight();
// 2. Determine Strategy
if ($width && $height) {
/**
* CROP & RESIZE (Fit)
* Only perform if the source is larger than the target in at least one dimension
*/
if ($origW > $width || $origH > $height) {
// cropThumbnailImage handles the aspect ratio math and cropping automatically
$img->cropThumbnailImage($width, $height);
}
} else {
/**
* PROPORTIONAL RESIZE
* We use scaleImage with '0' for the unknown dimension to maintain aspect ratio
*/
$targetW = $width ?? 0;
$targetH = $height ?? 0;
// Prevent Upscaling: Only scale if the image is actually larger than the target
if (($targetW && $origW > $targetW) || ($targetH && $origH > $targetH)) {
$img->scaleImage($targetW, $targetH);
}
}
// 3. WebP Encoding Optimizations
$img->setImageFormat('webp');
$img->setImageCompressionQuality($quality);
// Replicates cwebp -m 6: Highest compression effort for smallest file size
$img->setOption('webp:method', '6');
// Optional: Hint that this is a photo for better compression tuning
$img->setOption('webp:image-hint', 'photo');
// 4. Save and Cleanup
$img->writeImage($dest);
$finalWidth = $img->getImageWidth();
$finalHeight = $img->getImageHeight();
$fileSize = filesize($dest);
$img->clear();
$img->destroy();
return [
'width' => $finalWidth,
'height' => $finalHeight,
'size' => $fileSize
];
} catch (\Exception $e) {
// Log error: \Log::error("WebP Conversion Failed: " . $e->getMessage());
return null;
}
}
}
If you really need use cwebp or some other cli that is dumb and can only convert and resize without cropping you can make similar service yourself
<?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";
}
}