Implementing the Webp image format on websites running MODX Revolution

16.05.2023

Good afternoon. Today I want to tell about the implementation of the format webp for sites written in MODX.


Webp - format developed by Google specifically for use on websites. At the moment, the tools to assess the speed of your site will require the use of this format. Initially only supported by Chrome browser, but at the moment it is supported by most browsers updated to the current version. Nevertheless, there are still enough devices that do not understand it at all, which means that we will have to invent a universal solution.


Initially, I used the plugin phpThumbOn. This plugin can not only prepare a preview of a given size, but also convert it into the format you want, including in webp. It looks like this:


Using the standard parser:

[[*image_tv_name:phpthumbon=`q=80&w=200&h=200&&zc=1&f=webp`]]

C using fenom:

{$_modx->resource. image_tv_name | phpthumbon: ' q=80w=200&h=200&zc=1&f=webp'}

Or

{'phpThumbOn' | snippet : ['input' => 'assets/images/image_name.jpg, ', 'options' => ' q=80&w=200&h=200&zc=1&f=webp ']}

q - image compression quality
w and h - width and height of the generated preview.
f - format of the generated preview.


The disadvantage of this method is that webp images will be given to all browsers, including those that do not understand it. I rolled up my sleeves and searched Google, looking for a workaround. All of the solutions I found had their weaknesses, so it was time to write my own and try to combine the strengths.

The task was shaped like this:

  • Create a webp copy when a new jpg or png image is uploaded.
  • Delete the created copy when you delete the original.
  • If the user's browser supports the format webp substitute images in pictures and backgrounds (except those that are written through css files).
  • When displaying a page to check if there is a webp copy of the image, if not, then create it.
  • Make it compatible with phpThumbOn, because this plugin allows you to automatically crop images without allowing your content manager to make a mistake with their resolution.

So, here's the solution itself:


Step 1 Install phpthumbon.


Step 2 Create a WebPConvertor snippet:

<?php

$outFilePath=substr(MODX_BASE_PATH,0,-1).'/'.ltrim($url, '/');
$outFile=pathinfo($outFilePath);
$webpFile=$outFile['dirname'] . '/' . $outFile['basename'] . '.webp';
if (!file_exists($webpFile)){
    $ext=$outFile['extension'];
    if ($ext=="jpg" || $ext=="jpeg")
        $img = imageCreateFromJpeg($outFilePath);
    if ($ext=="png")
        $img = imageCreateFromPng($outFilePath);
    imagepalettetotruecolor($img);
    imagealphablending($img, true);
    imagesavealpha($img, true);
    imagewebp($img, $webpFile, 100);
    imagedestroy($img);
    return $webpFile;
}

On input this snippet gets the path to the image and converts it by creating a file like image_name.jpg -> image_name.jpg.webp. The first version of the snippet cut the native resolution, but there was some confusion because of the jpg and png images with the same name.

Step 3. Create the WebpPlugin plugin:
In the plugin we set 4 events.

OnFileManagerUpload - responsible for converting the uploaded image.
OnFileManagerFileRemove - responsible for removing the webp copy when removing the original.
OnFileManagerFileRename - responsible for renaming the copy when removing the original.
OnWebPagePrerender - for replacing the original images on the webp.

<?php
if ($modx->event->name =='OnFileManagerUpload') {
    $modx->runSnippet('WebPConverter',array(
        'url' => $directory.$files['file']['name']
     ));
}
if ($modx->event->name =='OnFileManagerFileRemove') {
    if(file_exists($path.".webp"))
        unlink($path.".webp");
}
if ($modx->event->name =='OnFileManagerFileRename') {
    if(file_exists($modx->getOption('base_path').$_POST['path'].".webp"))
        rename($modx->getOption('base_path').$_POST['path'].".webp", $path.".webp");
}
if($modx->event->name == 'OnWebPagePrerender' && stripos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false){
    $content = $modx->Event->params['documentOutput'];     
    $content = &$modx->resource->_output;
    $imgs = array();
    
    preg_match_all('/<source[^>]+>/i',$content, $result1);
        preg_match_all('/<img[^>]+>/i',$content, $result2);
        $result = array(array_merge($result1[0], $result2[0]));
    
    if (count($result))
    {
        foreach($result[0] as $img_tag)
        {        
            preg_match('/(src)=("[^"]*")/i',$img_tag, $img[$img_tag]);        
            if (!$img[$img_tag])
                preg_match('/(srcset)=("[^"]*")/i',$img_tag, $img[$img_tag]);
            $img_real = str_replace('"','',$img[$img_tag][2]);
            $img_real = str_replace('./','',$img_real);            
               if ((strpos($img_real, '.jpg')!==false) or (strpos($img_real, '.jpeg')!==false) or (strpos($img_real, '.png')!==false)) $imgs[] = $img_real;                     
        }
        $imgs = array_unique($imgs);
        foreach($imgs as $img_real)
        {
        if(($img_real) && (file_exists($modx->config['base_path'].$img_real)))
            {   
                $img_real = ltrim($img_real, '/');
                $img_file = pathinfo($img_real);
                $img_webp = $img_file['dirname'] . '/' . $img_file['basename'] . '.webp';
                if (file_exists($img_webp))
                    $content = str_replace($img_real, $img_webp, $content);
                else{
                    $img_webp = $modx->runSnippet('WebPConverter',array(
                    'url' => $img_real
                    ));
                }
            }
        }
    }
    $result='';
    
    
    preg_match_all('/url\(([^)]*)"?\)/iu', $content, $result);
    $imgs = array();
    if (count($result))
    {
        foreach($result[1] as $img_tag)
        {        
            $img_real = str_replace('./','',$img_tag);            
               if ((strpos($img_real, '.jpg')!==false) or (strpos($img_real, '.jpeg')!==false) or (strpos($img_real, '.png')!==false)) $imgs[] = $img_real;                     
        }
        $imgs = array_unique($imgs);
        foreach($imgs as $img_real)
        {
        if(($img_real) && (file_exists($modx->config['base_path'].$img_real)))
            {   
                $img_real = ltrim($img_real, '/');
                $img_file = pathinfo($img_real);
                $img_webp = $img_file['dirname'] . '/' . $img_file['basename'] . '.webp';
                
                if (file_exists($img_webp))
                    $content = str_replace($img_real, $img_webp, $content);
                else{
                    $img_webp = $modx->runSnippet('WebPConverter',array(
                    'url' => $img_real
                    ));
                }
            }
        }
    }
    $modx->Event->output($content);
}

Please note that the conversion process takes some time and by the time the page is already given to the browser it will not yet be completed. To avoid references to images that are not yet ready, the first time image swapping will not happen, and to test the plugin performance it is necessary to reload the page twice. Also, the conversion loads the server, and if your site is constantly loading new images (eg users), you need to keep track of this nuance.


Step 4. final. Replace the contents of the phpThumbOn snippet by adding the line

$modx->runSnippet('WebPConvertor',array('url' => $modx->phpThumbOn->run($scriptProperties)));

So that it would work out:

<?php
/**
 * phpThumbOn
 * Создание превьюх картинок
 *
 * Copyright 2013 by Agel_Nash <Agel_Nash@xaker.ru>
 *
 * @category images
 * @license GNU General Public License (GPL), http://www.gnu.org/copyleft/gpl.html
 * @author Agel_Nash <Agel_Nash@xaker.ru>
 */
if(empty($modx) || !($modx instanceof modX)) return '';
$componentPath = (string)$modx->getOption('phpthumbon.core_path', null, $modx->getOption('core_path').'components/phpthumbon/');
if(!isset($modx->phpThumbOn)){
    $modx->phpThumbOn = $modx->getService("phpthumbon","phpThumbOn",$componentPath.'model/phpthumbon/', $scriptProperties);
}
if(!($flag = ($modx->phpThumbOn instanceof phpThumbOn))){
    $modx->phpThumbOn = null;
}
$modx->runSnippet('WebPConvertor',array('url' => $modx->phpThumbOn->run($scriptProperties)));
return $flag ? $modx->phpThumbOn->run($scriptProperties) : $modx->getOption('phpthumbon.noimage', $scriptProperties);

When the preview is already generated, the path to the image will be passed to our WebPConvertor snippet for conversion. I assume that you can connect in a similar way to other similar snippets, such as phpThumb and phpThumbOf.


The up-to-date version is on githab.

 

 

Let's talk
about your project?

We will receive your application. And our manager will call you back to discuss the details.
91% of clients stay with us on a permanent cooperation!
91% of clients stay with us on a permanent cooperation!