contao/image

Improve sharpness of resized images

Closed this issue · 13 comments

ausi commented

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.

ausi commented

Looks like someone looked into this: https://entropymine.com/resamplescope/notes/browsers/
But the article seems to be many years old...

ausi commented

Changing

->resize($coordinates->getSize())
to:

->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.

ausi commented

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.

ausi commented

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

pexels-isi-parente-198266207-12167132-1mwdfap1768q8xy_bilinear

Bicubic

pexels-isi-parente-198266207-12167132-1mwdfap1768q8xy_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.

ausi commented

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)     
ausi commented
Unsupported filter type, SVG only supports ImageInterface::FILTER_UNDEFINED filter

Will be fixed by contao/imagine-svg#42