How to downsize uploaded image assets to max width/height?

Is it possible to automaticaly downsize image assets while uploading to desired max. dimensions (width, height)?

Limit uploads by group vars “assets.max_upload_size” is not solution in my case.

It would be useful to have an config options, somethink like: “assets.max_image_width,assets.max_image_height”.

It’s not really possible by default.

What’s your actual goal? Do you want to resize uploaded images or do you want to reject uploads with wrong dimensions?

There are two events, that might be interesting:

// before moving file from temp folder to assets folder
$app->on('cockpit.asset.upload', function(&$asset, &$_meta, &$opts) {
    if ($asset['width'] > 3000) {
        // resize or reject image...
        $asset = null; // should reject (I didn't test it)
    }
});

// after moving file from temp folder to assets folder
$app->on('cockpit.asset.save', function(&$asset) {
    // change something after
});

Here is some inspiration, how to modify assets (paths):

And the source file, where the magic happens:

The actual goal is automaticaly resize uploaded images (not reject)
Finaly, assets folder will contain only proportionaly resized images to max. dimensions.

I’m not sure which event is more efficient for this purpose,
so for example:

// before moving file from temp folder to assets folder
$app->on('cockpit.asset.upload', function(&$asset, &$_meta, &$opts) {
    if ($asset['width'] > 1920) {
        // proportionaly resize image ...

        // set only new values $asset['width'], $asset['height'] is not solution
        // How can I do this? Any idea?
    }
});

You can use this snippet to resize assets automatically. It’s no perfect solution, because it takes a while to process the files and there is no trigger to catch uploaded files before any rendering happens. It also won’t fix the too much RAM issue, if your resolution is too high.

The assets manager needs some refactoring in the future, but for now it works.

$app->on('cockpit.assets.save', function(&$assets) {

    $maxWidth  = $this->retrieve('image_max_width', 1920);

    $method    = 'bestFit';
    $quality   = 100;

    foreach ($assets as &$asset) {

        if (isset($asset['width']) && isset($asset['height']) && $asset['width'] > $maxWidth) {

            $path = $this->path('#uploads:' . ltrim($asset['path'], '/'));

            // resize image with `/lib/Lime/Helper/Image.php`, that calls claviska\SimpleImage
            $img = $this('image')->take($path)->{$method}($maxWidth, $asset['height']);

            $result = file_put_contents($path, $img->toString(null, $quality));

            unset($img);

            // don't overwrite meta, if write process failed
            if ($result === false) continue;

            $info = getimagesize($path);
            $asset['width']  = $info[0];
            $asset['height'] = $info[1];
            $asset['size']   = filesize($path);

        }

    }

});

Source with annotations:

And I didn’t test this addon, yet, but maybe this is interesting, too:

1 Like

Thanks a lot Raffael!
I made some changes in your code snippet to allow this process with respect to the group configuration only + max. height option. It works good enough.

$app->on('cockpit.assets.save', function(&$assets) {
  
  $maxWidth  = $this->module('cockpit')->getGroupVar('assets.image_max_width', $this->retrieve('image_max_width', NULL));
  $maxHeight = $this->module('cockpit')->getGroupVar('assets.image_max_height', $this->retrieve('image_max_height', NULL));
  
  if (isset($maxWidth) || isset($maxHeight)) {
    $method    = 'bestFit';
    $quality   = 80;
    foreach ($assets as &$asset) {
        if (isset($asset['width']) && isset($asset['height']) && ($asset['width'] > $maxWidth || $asset['height'] > $maxHeight)) {
            $path = $this->path('#uploads:' . ltrim($asset['path'], '/'));

            $img = $this('image')->take($path)->{$method}($maxWidth, $maxHeight);
            $result = file_put_contents($path, $img->toString(null, $quality));
            unset($img);

            if ($result === false) continue;
            $info = getimagesize($path);
            $asset['width']  = $info[0];
            $asset['height'] = $info[1];
            $asset['size']   = filesize($path);
        }
    }
  }
});

Nice. I didn’t test it yet, but your code looks promising. I added a link to your modified version to my snippet.