I want to convert images held as strings in variables as fast as possible to WebP format while shrinking larger images but do not enlarge smaller images.
The base system is a Debian 9.9 with PHP 7.3. I've tried to measure speed for the following techniques: imagejpeg
, imagewebp
, using cwep
and php-vips
. I used the following code:
$jpeg = function() use ($image) {
$old_image = @imagecreatefromstring($image);
$old_width = (int)@imagesx($old_image);
$old_height = (int)@imagesy($old_image);
$new_width = 1920;
$new_width = min($old_width, $new_width);
$ratio = $new_width / $old_width;
$new_height = $old_height * $ratio;
$new_image = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $new_width, $new_height, $old_width, $old_height);
ob_start();
imagejpeg($new_image, NULL, 75);
$image = ob_get_clean();
};
$webp = function() use ($image) {
$old_image = @imagecreatefromstring($image);
$old_width = (int)@imagesx($old_image);
$old_height = (int)@imagesy($old_image);
$new_width = 1920;
$new_width = min($old_width, $new_width);
$ratio = $new_width / $old_width;
$new_height = $old_height * $ratio;
$new_image = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $new_width, $new_height, $old_width, $old_height);
ob_start();
imagewebp($new_image, NULL, 75);
$image = ob_get_clean();
};
$convert = function(string $image, int $width, int $height) {
$cmd = sprintf('cwebp -m 0 -q 75 -resize %d %d -o - -- -', $width, $height);
$fd = [
0 => [ 'pipe', 'r' ], // stdin is a pipe that the child will read from
1 => [ 'pipe', 'w' ], // stdout is a pipe that the child will write to
2 => [ 'pipe', 'w' ], // stderr is a pipe that the child will write to
];
$process = proc_open($cmd, $fd, $pipes, NULL, NULL);
if (is_resource($process)) {
fwrite($pipes[0], $image);
fclose($pipes[0]);
$webp = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$result = proc_close($process);
if ($result === 0 && strlen($webp)) {
return $webp;
}
}
return FALSE;
};
$cwebp = function() use ($image, $convert) {
$old_image = @imagecreatefromstring($image);
$old_width = (int)@imagesx($old_image);
$old_height = (int)@imagesy($old_image);
$new_width = 1920;
$new_width = min($old_width, $new_width);
$ratio = $new_width / $old_width;
$new_height = $old_height * $ratio;
$image = $convert($image, $new_width, $new_height);
};
$vips = function() use ($image) {
$image = Vips\Image::newFromBuffer($image);
$old_width = (int)$image->get('width');
$old_height = (int)$image->get('height');
$new_width = 1920;
$new_width = min($old_width, $new_width);
$ratio = $new_width / $old_width;
// $new_height = $old_height * $ratio;
$image = $image->resize($ratio);
$image = $image->writeToBuffer('.webp[Q=75]');
};
I've called $jpeg()
, $webp()
, $cwebp()
and $vips()
ten times in a loop and runtime in seconds is:
JPEG: 0.65100622177124
WEBP: 1.4864070415497
CWEBP: 0.52562999725342
VIPS: 1.1211001873016
So calling cwebp
CLI tool seems to be the fastest way, that is surprising. I've read many times that vips
is a extremly fast tool (mostly faster than imagemagick
), so I'd like to focus on vips
.
Can anyone help me to optimize $vips()
for better performance? Maybe there are some options for writeToBuffer()
or resize()
which are not known to me. It's really important that all operations do work in memory only without reading files from disk or storing files on disk.
For speed, don't use resize
, use thumbnail_buffer
. It combines open and resize in a single operation, so it can take advantage of things like shrink-on-load. You can get a huge speedup, depending on the image formats and sizes.
You can match your cwebp settings with something like:
$vips = function() use ($source_bytes) {
$image = Vips\Image::thumbnail_buffer($source_bytes, 1920);
$dest_bytes = $image->writeToBuffer('.webp', [
'Q' => 75,
'reduction-effort' => 0,
'strip' => TRUE
]);
};
libvips seems slower at straight webp compression. I tried:
$ time cwebp -m 0 -q 75 ~/pics/k2.jpg -o x.webp
real 0m0.102s
user 0m0.087s
sys 0m0.012s
The matching vips command would be:
$ time vips copy ~/pics/k2.jpg x.webp[Q=75,reduction-effort=0,strip]
real 0m0.144s
user 0m0.129s
sys 0m0.024s
You can see it's perhaps 30% slower. There's no image processing here, just calls into libjpeg and libwebp, so cwebp must be using some libwebp save optimisation that libvips is not. They ran at the same speed a year ago -- I should read cwebp.c
again and see what's changed.
If you do some processing as well, then libvips becomes faster. I tried with a 10,000 x 10,000 pixel image and resize:
$ /usr/bin/time -F %M:%e cwebp -m 0 -q 75 ~/pics/wtc.jpg -resize 1920 1920 -o x.webp
618716:1.37
620mb of memory and 1.4s, versus:
$ /usr/bin/time -f %M:%e vipsthumbnail ~/pics/wtc.jpg -s 1920 -o x.webp[Q=75,reduction-effort=0,strip]
64024:1.08
64mb of memory and 1.1s.
So libvips comes out faster and needs less memory, despite being slower at webp compression, because it can resize quickly. libvips is doing a higher-quality resize as well (adaptive lanczos3), versus simple cubic (I think) for cwebp.