Automatic WebP Image Conversion on Upload in 1C-Bitrix

6 min read
The WebP format can reduce image file size and decrease the amount of data transferred when users load website pages.

In 1C-Bitrix, images can be automatically converted to WebP before they are saved to the file system.

In this example, we will process the standard image fields of an infoblock element:

PREVIEW_PICTURE — preview image;
DETAIL_PICTURE — detail image.

We will use the OnBeforeIBlockElementAdd and OnBeforeIBlockElementUpdate events. They are triggered before an element is saved and allow us to modify the uploaded file data.

How the conversion works

When an infoblock element is created or updated, the handler checks the PREVIEW_PICTURE and DETAIL_PICTURE fields.

If the uploaded file is a JPEG or PNG image, it is opened with the GD library and saved to a temporary WebP file.

The following values in the file array are then replaced:

name — file name;
type — MIME type;
tmp_name — path to the temporary file;
size — size of the converted file.

1C-Bitrix then saves the WebP file using its standard file handling mechanism.

If the image cannot be converted, the original file is returned without modification.

Code for init.php

The code can be placed, for example, in:


/local/php_interface/init.php


<?php

use Bitrix\Main\EventManager;

$eventManager = EventManager::getInstance();

$eventManager->addEventHandlerCompatible(
    'iblock',
    'OnBeforeIBlockElementAdd',
    ['ProjectWebpConverter', 'handle']
);

$eventManager->addEventHandlerCompatible(
    'iblock',
    'OnBeforeIBlockElementUpdate',
    ['ProjectWebpConverter', 'handle']
);

final class ProjectWebpConverter
{
    private const QUALITY = 80;

    private const IMAGE_FIELDS = [
        'PREVIEW_PICTURE',
        'DETAIL_PICTURE',
    ];

    public static function handle(array &$fields): void
    {
        foreach (self::IMAGE_FIELDS as $fieldName) {
            if (
                empty($fields[$fieldName])
                || !is_array($fields[$fieldName])
            ) {
                continue;
            }

            $fields[$fieldName] = self::convertToWebp(
                $fields[$fieldName]
            );
        }
    }

    private static function convertToWebp(array $file): array
    {
        if (!empty($file['del'])) {
            return $file;
        }

        if (
            isset($file['error'])
            && (int)$file['error'] !== UPLOAD_ERR_OK
        ) {
            self::log(
                'File upload error, code: '
                . (int)$file['error']
            );

            return $file;
        }

        $tmpName = (string)($file['tmp_name'] ?? '');

        if (
            $tmpName === ''
            || !is_file($tmpName)
            || !is_readable($tmpName)
        ) {
            return $file;
        }

        if (!self::isWebpSupported()) {
            self::log(
                'Conversion skipped: GD does not support WebP'
            );

            return $file;
        }

        $imageInfo = @getimagesize($tmpName);

        if ($imageInfo === false) {
            self::log(
                'Unable to detect image parameters: '
                . $tmpName
            );

            return $file;
        }

        $mimeType = (string)($imageInfo['mime'] ?? '');

        if ($mimeType === 'image/webp') {
            return $file;
        }

        if (!in_array(
            $mimeType,
            ['image/jpeg', 'image/png'],
            true
        )) {
            return $file;
        }

        $image = null;
        $webpTempFile = null;

        try {
            switch ($mimeType) {
                case 'image/jpeg':
                    $image = @imagecreatefromjpeg($tmpName);
                    break;

                case 'image/png':
                    $image = @imagecreatefrompng($tmpName);
                    break;
            }

            if (!$image) {
                self::log(
                    'GD was unable to open the image: '
                    . $tmpName
                );

                return $file;
            }

            if ($mimeType === 'image/png') {
                if (function_exists('imagepalettetotruecolor')) {
                    imagepalettetotruecolor($image);
                }

                imagealphablending($image, false);
                imagesavealpha($image, true);
            }

            $webpTempFile = tempnam(
                sys_get_temp_dir(),
                'bx_webp_'
            );

            if ($webpTempFile === false) {
                self::log(
                    'Unable to create temporary WebP file'
                );

                return $file;
            }

            $result = @imagewebp(
                $image,
                $webpTempFile,
                self::QUALITY
            );

            imagedestroy($image);
            $image = null;

            if (
                !$result
                || !is_file($webpTempFile)
                || filesize($webpTempFile) === 0
            ) {
                @unlink($webpTempFile);

                self::log(
                    'Unable to save the image as WebP'
                );

                return $file;
            }

            $originalName = (string)($file['name'] ?? 'image');

            $baseName = pathinfo(
                $originalName,
                PATHINFO_FILENAME
            );

            if ($baseName === '') {
                $baseName = 'image';
            }

            $file['name'] = $baseName . '.webp';
            $file['type'] = 'image/webp';
            $file['tmp_name'] = $webpTempFile;
            $file['size'] = filesize($webpTempFile);
            $file['error'] = UPLOAD_ERR_OK;

            register_shutdown_function(
                static function () use ($webpTempFile): void {
                    if (is_file($webpTempFile)) {
                        @unlink($webpTempFile);
                    }
                }
            );

            self::log(
                sprintf(
                    'Image %s converted to %s',
                    $originalName,
                    $file['name']
                )
            );

            return $file;

        } catch (\Throwable $e) {

            if ($image) {
                @imagedestroy($image);
            }

            if (
                $webpTempFile
                && is_file($webpTempFile)
            ) {
                @unlink($webpTempFile);
            }

            self::log(
                'WebP conversion error: '
                . $e->getMessage()
            );

            return $file;
        }
    }

    private static function isWebpSupported(): bool
    {
        if (
            !extension_loaded('gd')
            || !function_exists('imagewebp')
            || !function_exists('gd_info')
        ) {
            return false;
        }

        $gdInfo = gd_info();

        return !empty($gdInfo['WebP Support']);
    }

    private static function log(string $message): void
    {
        if (function_exists('AddMessage2Log')) {
            AddMessage2Log(
                $message,
                'webp-converter'
            );
        }
    }
}

Supported image formats

This example automatically converts:

JPEG → WebP
PNG → WebP

If the uploaded file is already in WebP format, it is not converted again.

Other formats remain unchanged.

Why GIF files are not converted

GIF files are intentionally excluded from automatic conversion.

When GD opens an animated GIF, it processes only a single frame. Therefore, a standard GIF-to-WebP conversion may turn an animated image into a static one.

If the project uses only static GIF files, support for this format can be added separately.

Checking WebP support in GD

Having the GD extension installed does not automatically mean that WebP is supported by the server's GD build.

You can temporarily check the GD configuration with the following code:


<?php

echo '<pre>';
print_r(gd_info());
echo '</pre>';

The resulting array should contain:


[WebP Support] => 1

Remove the diagnostic code after checking the server configuration.

WebP quality

The output image quality is defined by the following constant:


private const QUALITY = 80;

A value of 80 is a reasonable starting point for most photographs.

The value can be increased or decreased depending on the image quality and file size requirements of the project.

Example of the file array

Before conversion, the uploaded file array may look like this:


[
    'name' => 'product-photo.jpg',
    'type' => 'image/jpeg',
    'tmp_name' => '/tmp/php12345',
    'size' => 245812,
    'error' => 0,
    'MODULE_ID' => 'iblock',
]

After successful conversion:


[
    'name' => 'product-photo.webp',
    'type' => 'image/webp',
    'tmp_name' => '/tmp/bx_webp_xxxxx',
    'size' => 128540,
    'error' => 0,
    'MODULE_ID' => 'iblock',
]

Other service values in the file array are preserved.

Server requirements

Before using this solution, make sure that GD is installed and that the GD build supports WebP.

You should also check the following PHP settings:


upload_max_filesize
post_max_size
memory_limit

The memory_limit setting is particularly important.

JPEG and PNG files are stored in compressed form, but GD decodes the entire image into memory during processing. As a result, a large image may require considerably more RAM than the original file size suggests.

PHP must also have write access to the temporary directory returned by:


sys_get_temp_dir()

JPEG and EXIF orientation

When a JPEG image is opened and saved again using GD, the original EXIF metadata is not preserved.

This is particularly important for photographs uploaded directly from smartphones. Some images store their correct orientation in EXIF metadata while the actual pixel data is saved in a different orientation.

If users frequently upload photographs directly from mobile devices, vertical images should be tested separately.

If necessary, EXIF Orientation handling can be added before the WebP conversion.

Which fields are processed

The handler in this example processes only the standard image fields of an infoblock element:


PREVIEW_PICTURE
DETAIL_PICTURE

It does not automatically convert:
  • section images;
  • File-type infoblock properties;
  • custom user fields;
  • additional product images;
  • other file fields.
Separate event handlers or file processing logic are required for those cases.

How to test the solution

After adding the handler, create a test infoblock element and upload a JPEG or PNG file as the preview or detail image.

After saving the element, check:
  1. The extension of the saved file.
  2. The MIME type of the image.
  3. Whether a WebP file was created in the upload directory.
  4. Whether the image is displayed correctly on the website.
  5. Whether PNG transparency is preserved.
  6. Uploading an existing WebP file.
  7. Removing an existing image.
  8. Uploading a large image.
It is also useful to test updating an existing infoblock element.

Logging

The example uses the standard 1C-Bitrix function:


AddMessage2Log()

It can be used to record conversion errors and successful operations.

After the solution has been tested, detailed logging of successful conversions can be removed, leaving only error logging if required.

Summary

The OnBeforeIBlockElementAdd and OnBeforeIBlockElementUpdate events make it possible to automatically convert JPEG and PNG files to WebP before an image is saved in 1C-Bitrix.

Users can continue to work with the standard image upload form while conversion is performed automatically on the server.

If conversion is not possible or WebP is not supported by the server, the original file is returned without modification.

This approach makes it possible to introduce WebP without changing the standard image upload workflow in the 1C-Bitrix administration interface.
Back to blog