1
votes

SonataMediaBundle increases PNG image size with this settings:

video_image:
    providers:
        - sonata.media.provider.image
    formats:
        medium: { width: 1306, quality: 100 }

Original image size is 246Kb (with same width and height), and the size of the "resized" image is 3Mb. It happens because of quality: 100 that sets png_compression_level => 0.

If I set quality: 0, PNG size is almost ok (and image looks very ok), but JPG compression makes image look like impressionism.

So I solved it with a custom resizer for PNG images.

But with setting png_compression_level => 9 the size of compressed image is still not ideal, it's 664Kb.

Converting PNG image to PNG8 solved this, and the size became very ok - 233Kb (that is even smaller than the original image), but I got some troubles with the alpha channel.

Most of images with transparency compressed fine, but some of them were corrupted:

original image (it is white on transparent bg)

a white border

compressed image

the same but smaller

This is my custom resizer (the code is a little bit ugly, because it's just a draft):

<?php

namespace AppBundle\Resizer;

use Gaufrette\File;
use Imagine\Gd\Image;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Resizer\SimpleResizer;

class PngCustomResizer extends SimpleResizer
{
    public function resize(MediaInterface $media, File $in, File $out, $format, array $settings)
    {
        /** @var Image $image */
        $image = $this->adapter->load($in->getContent());

        $thumbnail = $image->thumbnail($this->getBox($media, $settings), $this->mode);

        $resource = $thumbnail->getGdResource();

        $width = $thumbnail->getSize()->getWidth();
        $height = $thumbnail->getSize()->getHeight();

        // convert to png8 with alpha
        $img = imagecreatetruecolor($width, $height);
        $bga = imagecolorallocatealpha($img, 0, 0, 0, 127);
        imagecolortransparent($img, $bga);
        imagefill($img, 0, 0, $bga);
        imagecopy($img, $resource, 0, 0, 0, 0, $width, $height);
        imagetruecolortopalette($img, false, 255);
        imagealphablending($img, false);
        imagesavealpha($img, true);

        $optimizedImage = new Image($img, $image->palette(), $image->metadata());

        // set quality 0 to set png compression = 9
        $content = $optimizedImage->get($format, ['quality' => 0]);

        $out->setContent($content, $this->metadata->get($media, $out->getName()));
    }
}

Is there something wrong with my code, or may be I should use another way?

All I want is resize both JPG and PNG files without increasing size and without noticeable loss of quality.

UPDATE

  1. imagetruecolortopalette resets alpha values to 0 or 127, without 2-126 values. So image's edges loose their smoothness. I'm trying to fix it setting old alpha value for each pixel, but don't have success yet

  2. artefacts on the above-mentioned image appear just in small size resizing ('thumb' in this config)

    formats:
        wide: { width: 1306, quality: 95}
        mobile: { width: 640, quality: 95}
        thumb: { height: 50 , quality: 95}
    

so I presume that the trouble is in compounding some not-fully-transparent pixels

1
The transparency hides it, but it seems there is some data in those pixels after all. If you blank out all channels with a fully transparent alpha, those downsample artefacts should disappear. You (probably) could adjust your code to do that, or use an external tool.Jongware
could you please clarify what do you mean here "If you blank out all channels with a fully transparent alpha"? not sure I understand it wellPavel Alazankin
See this discussion on the ImageMagick forum: imagemagick.org/discourse-server/viewtopic.php?t=13746 and this (also ImageMagick) overview: imagemagick.org/Usage/formats/#png_compress. From there: "PNG image will preserve the color of fully-transparent pixels. That is even though you can not see it transparency has color, and PNG preserves that data."Jongware

1 Answers

1
votes

I tried different ways with GD functions, but did not get satisfactory result.

So the best way I found is to use Imagick instead of GD:

services.yml:

    sonata.media.resizer.custom:
        class: AppBundle\Resizer\CustomResizer
        arguments: [@sonata.media.adapter.image.imagick, 'outbound', @sonata.media.metadata.proxy]

AppBundle\Resizer\CustomResizer:

<?php

namespace AppBundle\Resizer;

use Sonata\MediaBundle\Resizer\SimpleResizer;

class PngCustomResizer extends SimpleResizer
{
}

config.yml:

sonata_media:
    providers:
        image:
            resizer: sonata.media.resizer.custom