Improve sharpness of resized images
Closed this issue · 13 comments
It seems that images that are resized by the browser appear sharper compared to images resized by Imagine (tested with JPEG quality 100). We should try to find out how browsers achieve that and improve the resize process to get similar results.
The default filter is FILTER_UNDEFINED
- but I could not find any information on what Imagick and Gmagick will then actually use when using the \Imagick::FILTER_UNDEFINED
constant during resize 🤔. I think you'd want at least bicubic.
GD seems to use bilinear interpolation (https://stackoverflow.com/a/41925160/374996).
It is unclear what browsers might use - some sources suggest they also use bilinear, but I suspect this can differ from browser to browser and OS to OS.
Looks like someone looked into this: https://entropymine.com/resamplescope/notes/browsers/
But the article seems to be many years old...
Changing
Line 165 in 088e1f8
->resize($coordinates->getSize(), \Imagine\Image\ImageInterface::FILTER_LANCZOS)
Created a similar result to the resizing of the browser in my test. The sharpness was noticeably better.
Which filter to use when downscaling is always highly debated 😁
Lanczos might be better for certain images but subjectively worse for others.
Sinc is in theory the best, but also the slowest (though I suspect that ImageMagick's Sinc is actually Lanczos-3?)
Bicubic should be better for larger images or ones with less details, i.e. less sharp edges.
Lanczos should be better for small images or ones with more details, i.e. more sharp edges.
Since browsers seem to use Lanczos-2 or Lanczos-3 when downscaling, I guess FILTER_LANCZOS
might be a reasonable choice.
Which filter to use when downscaling is always highly debated 😁
But I guess our current FILTER_UNDEFINED
(which I guess is just linear interpolation?) is not one of the popular choices?
Yes - I actually assumed it would be bicubic by default (at least with Imagick/Gmagick), but apparently that's not the case.
I just tried many combinations of imagescale()
, imagesetinterpolation()
, imagecopyresampled()
, imagecopyresized()
and imageaffine()
with several IMG_*
constants in order to find a similar solution for GD
but I was not able to get any results better than imagecopyresampled()
(which is what we currently use).
I just tested with $dest = imagescale($this->resource, $width, $height, IMG_BICUBIC);
:
Test image: https://www.pexels.com/photo/woman-wearing-a-checkered-tank-top-12167132/
Original (i.e. scaled down by the browser)
Bilinear
Bicubic
When switching between the images you can see some differences. The bicubic image gets noticably darker, but retains some details a tiny bit more than the bilinear one.
IMG_BICUBIC_FIXED
would retain the image brightness and makes it look sharper, but introduces aliasing.
Do they also work for scaling down by more than 4x? (like resizing from 1000 down to 200) In my tests many approaches looked very bad for these cases.
Well in this case the scaling was already more than 4x (4.6875x). The original width is 3000, the target width is 640.
Bei Verwendung der obigen Anpassung werden Vorschaubilder von svg-Dateien im Backend nicht mehr angezeigt und auch bei der Verwendung von Bildgrößen erfolgt keine Anzeige der SVG-Datei.
Hier der Stacktrace:
Imagine\Exception\InvalidArgumentException:
Unsupported filter type, SVG only supports ImageInterface::FILTER_UNDEFINED filter
at vendor/contao/imagine-svg/src/Image.php:154
at Contao\ImagineSvg\Image->resize()
(vendor/contao/image/src/Resizer.php:165)
at Contao\Image\Resizer->executeResize()
(vendor/contao/image/src/DeferredResizer.php:224)
at Contao\Image\DeferredResizer->executeDeferredResize()
(vendor/contao/image/src/DeferredResizer.php:140)
at Contao\Image\DeferredResizer->resizeDeferredImage()
(vendor/contao/core-bundle/src/Image/Resizer.php:39)
at Contao\CoreBundle\Image\Resizer->resizeDeferredImage()
(vendor/contao/core-bundle/src/Controller/ImagesController.php:53)
at Contao\CoreBundle\Controller\ImagesController->__invoke()
(vendor/symfony/http-kernel/HttpKernel.php:181)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw()
(vendor/symfony/http-kernel/HttpKernel.php:76)
at Symfony\Component\HttpKernel\HttpKernel->handle()
(vendor/symfony/http-kernel/Kernel.php:197)
at Symfony\Component\HttpKernel\Kernel->handle()
(public/index.php:42)
Unsupported filter type, SVG only supports ImageInterface::FILTER_UNDEFINED filter
Will be fixed by contao/imagine-svg#42