contao / image

Contao Image Library
12 stars 5 forks source link

Improve sharpness of resized images #107

Open ausi opened 3 weeks ago

ausi commented 3 weeks ago

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.

fritzmg commented 3 weeks ago

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 3 weeks ago

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

ausi commented 3 weeks ago

Changing https://github.com/contao/image/blob/088e1f877fb4b6f97afe522934f148a6e5371890/src/Resizer.php#L165 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.

fritzmg commented 3 weeks ago

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 3 weeks ago

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? ☺️

fritzmg commented 3 weeks ago

Yes - I actually assumed it would be bicubic by default (at least with Imagick/Gmagick), but apparently that's not the case.

ausi commented 3 weeks ago

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

fritzmg commented 3 weeks ago

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.

fritzmg commented 3 weeks ago

IMG_BICUBIC_FIXED would retain the image brightness and makes it look sharper, but introduces aliasing.

ausi commented 2 weeks ago

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.

fritzmg commented 2 weeks ago

Well in this case the scaling was already more than 4x (4.6875x). The original width is 3000, the target width is 640.

mlwebworker commented 9 minutes ago

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)