libvips/php-vips

Implementing BlurHash with php-vips

binaryfire opened this issue · 8 comments

Hi John

Is there any way to implement BlurHash with php-vips?

https://blurha.sh/
https://github.com/woltapp/blurhash

There are a couple of PHP implementations. One is pure PHP, the other uses Intervention:
https://github.com/kornrunner/php-blurhash
https://github.com/bepsvpt/blurhash

I'm using vips to generate responsive images for my users' uploads and would love to generate a BlurHash at the same time. They're an amazing alternative to placeholder images.

You could use libvips for the initial shrink, but you'd need to code the DCT yourself (not difficult).

Maybe libvips for the preprocessing and php-blurhash as the encoder?

Thanks, will give that a shot!

Ended up going with a 16px base64 webp + CSS blur. Seems to do the trick and there's no special decoding step needed.

I'm using thumbnail() with Q=20 and reduction_effort=4. The base64-encoded images are under 200b which is great. But the contrast is a little washed out compared to original.

Does thumbnail() have any image processing options that might help? Or should I be using a different method?

Performance is my main concern. It's a multi-tenant media library and I'm generating 5-6 webp size variants + the base64 placeholder for each upload.

thumbnail is resize only, but you can combine it with other operations, of course. Perhaps a strong sharpening filter after the resize would improve contrast?

php-vips is usually fast enough that you can generate size variants on the fly and don't need to produce them at upload time. Your clients will get images exactly sized for their screen, and you save a lot on storage costs. CPU is often cheaper and faster than storage now. You could perhaps only compute the blurhash at upload time and leave image generation until later.

$ vipsheader nina.jpg
nina.jpg: 6048x4032 uchar, 3 bands, srgb, jpegload
$ /usr/bin/time -f %M:%e vipsthumbnail nina.jpg
40448:0.06

60ms and 40mb of ram to resize a 6k x 4k pixel jpeg, and that's with process startup time.

... I tried a couple more ways of doing this. As a loop in php:

#!/usr/bin/env php
<?php

require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;

for ($i = 1; $i < count($argv); $i++) {
    $image = Vips\Image::thumbnail($argv[$i], 16);
    $buf = $image->writeToBuffer('.webp', [
        "Q" => 20
    ]);
}   

Now you save process start time (libvips takes maybe 30 or 40ms to start, since it must initialise many dependent libs). I timed with:

$ mkdir sample 
$ for i in {1..1000}; do cp ~/pics/nina.jpg sample/$i.jpg; done
$ /usr/bin/time -f %M:%e ./thumbnail.php sample/*.jpg
61408:27.56

So 27ms per jpg.

That's largely single-threaded. This PC has quite a few copres, so I can also run:

$ cd sample
$ time parallel vipsthumbnail {} -s 16 -o tn_%s.webp[Q=20] ::: *.jpg

real	0m3.135s
user	1m3.273s
sys	0m23.543s

3ms per jpg.

Nice. I was considering doing it on-demand but I’m using webp, which is a lot slower to generate than jpg. I’ll do some tests though. Maybe it’ll be fine.

Ah, you're using webp for all the derivatives? I agree, that's probably too slow.

Thanks for your help and suggestions! I'll close this now.