Skip to content

Commit

Permalink
AVIF support added
Browse files Browse the repository at this point in the history
If the AVIF conversion function is present in PHP/OS version the extension will additionally generate AVIF file.
  • Loading branch information
PELock committed Feb 9, 2023
1 parent f1afc50 commit 80ccc02
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 50 deletions.
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Image widget with an auto WebP file generation for Yii2 Framework

**ImgOpt** is an image optimization widget for [Yii2 Framework](https://www.yiiframework.com) with auto [WebP](https://developers.google.com/speed/webp) image format generation from `PNG` and `JPG` files.
**ImgOpt** is an image optimization widget for [Yii2 Framework](https://www.yiiframework.com) with auto [WebP](https://developers.google.com/speed/webp) & [AVIF](https://caniuse.com/avif) image formats generation from `PNG` and `JPG` files.

https://www.yiiframework.com/extension/pelock/yii2-imgopt

## How to make my website faster?

Expand All @@ -12,7 +14,7 @@ _But_ the entire process would require me to go manually and use some sort of im

To hell with that! We can do better!

## Automate PNG & JPG to WebP conversion
## Automate PNG & JPG to WebP & AVIF conversion

I have decided to create a Yii2 widget that would automate this task.

Expand All @@ -32,16 +34,17 @@ Replace your `IMG` tag within your `HTML` templates with a call to:

(Image path is relative to [Yii2 Framework @webroot alias](https://www.yiiframework.com/wiki/667/yii-2-list-of-path-aliases-available-with-default-basic-and-advanced-app))

And once run, the widget code will generate a new WebP image file on the fly (original image is left **untouched**) and he following HTML code gets generated:
And once run, the widget code will generate a new WebP & AVIF image files on the fly (original image is left **untouched**) and he following HTML code gets generated:

```html
<picture>
<source type="image/avif" srcset="/images/product/extra.avif">
<source type="image/webp" srcset="/images/product/extra.webp">
<img src="/images/product/extra.png" alt="Extra product">
</picture>
```

The browser will pick up the best source for the provided image, and thanks to revolutionary WebP compression, it will make your website loading faster.
The browser will pick up the best source for the provided image, and thanks to revolutionary WebP and AVIF compression, it will make your website loading faster.

## Image lazy-loading

Expand All @@ -55,13 +58,18 @@ The generated output looks like this:

```html
<picture>
<source type="image/avif" srcset="/images/product/extra.avif">
<source type="image/webp" srcset="/images/product/extra.webp">
<img src="/images/product/extra.png" loading="lazy">
</picture>
```

Use it to make your website loading times even faster.

## AVIF image generation (new in v1.3.0)

ImgOpt will automatically generate AVIF file if it's supported by the existing PHP installation. If the conversion function is not available, it will just skip this step.

## Automatic WebP generation for updated images (new in v1.2.0)

ImgOpt will set the modification date of the generated WebP image to match the modification date of the original image file.
Expand Down Expand Up @@ -100,31 +108,31 @@ I knew you would ask about it! By default the conversion tries all the steps fro
| --------------------- | -------------- |
| [![Social Media Bot](https://www.pelock.com/img/media_social_bot.png)](https://www.pelock.com/products/social-media-bot) | [![Social Media Bot](https://www.pelock.com/img/media_social_bot.webp)](https://www.pelock.com/products/social-media-bot/install) |

If the generated WebP image is larger than the original image, the default `<img>` tag will be generated.
If the generated WebP or AVIF image is larger than the original image, the default `<img>` tag will be generated.

## Disable WebP images serving
## Disable WebP/AVIF images serving

If for some reason you want to disable WebP file serving via the HTML `<picture>` tag, you can do it per widget settings:

```php
<?= \PELock\ImgOpt\ImgOpt::widget(["src" => "/images/product/extra.png", "alt" => "Extra product", "disable" => true ]) ?>
```

## Recreate WebP file
## Recreate WebP/AVIF files

The widget code automatically detects if there's a WebP image in the directory with the original image. If it's not there - it will recreate it. It's only done once.
The widget code automatically detects if there's a WebP/AVIF images in the directory with the original image. If it's not there - it will recreate them. It's only done once.

If you wish to force the widget code to recreate it anyway, pass the special param to the widget code:

```php
<?= \PELock\ImgOpt\ImgOpt::widget(["src" => "/images/product/extra.png", "alt" => "Extra product", "recreate" => true ]) ?>
```

You might want to recreate all of the WebP files and to do that without modifying, change the widget source code from:
You might want to recreate all of the WebP and AVIF files and to do that without modifying, change the widget source code from:

```php
/**
* @var bool set to TRUE to recreate *ALL* of the WebP files again (optional)
* @var bool set to TRUE to recreate *ALL* of the WebP and AVIF files again (optional)
*/
const RECREATE_ALL = false;
```
Expand Down Expand Up @@ -162,6 +170,7 @@ And it will generate this HTML code:
```html
<a href="/images/sunset.jpg" data-lightbox="image-1" data-title="Sunset">
<picture>
<source type="image/avif" srcset="/images/sunset-thumbnail.avif">
<source type="image/webp" srcset="/images/sunset-thumbnail.webp">
<img src="/images/sunset-thumbnail.png" alt="Sunset">
</picture>
Expand All @@ -170,6 +179,10 @@ And it will generate this HTML code:

## Bugs, questions, feature requests

Hope you like it. For questions, bug & feature requests visit my site:
If you are interested in my software or have any questions regarding it, technical or legal issues, or if something is not clear, [please contact me](https://www.pelock.com/contact). I'll be happy to answer all of your questions.

Bartosz Wójcik

Bartosz Wójcik | https://www.pelock.com
* Visit my site at — https://www.pelock.com
* Twitter — https://twitter.com/PELock
* GitHub — https://github.com/PELock
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "pelock/yii2-imgopt",
"version": "1.2.0",
"description": "Image optimization widget for Yii2 Framework with auto WebP image format generation from PNG/JPG files.",
"version": "1.3.0",
"description": "Image optimization widget for Yii2 Framework with auto WebP/AVIF image format generation from PNG/JPG files.",
"type": "yii2-extension",
"keywords": ["image", "optimization", "yii2", "webp", "jpg", "png", "generator"],
"keywords": ["image", "optimization", "yii2", "webp", "avif", "jpg", "png", "generator"],
"license": "Apache-2.0",
"authors": [
{
Expand Down
90 changes: 55 additions & 35 deletions src/ImgOpt.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/**
* @link https://www.pelock.com/
* @copyright Copyright (c) 2021 PELock LLC
* @copyright Copyright (c) 2021-2023 PELock LLC
* @license Apache-2.0
*/
namespace pelock\imgopt;
Expand All @@ -11,29 +11,30 @@
use yii\helpers\Html;

/**
* Image optimization widget for Yii2 Framework with auto WebP image format generation from PNG/JPG files.
* Image optimization widget for Yii2 Framework with auto WebP & AVIF image format generation from PNG/JPG files.
*
* What it does? Instead of static images like this:
*
* ```html
* <img src="/images/product/extra.png" alt="Extra product">
* ```
*
* It will generate an extra WebP image file (in the same directory the provided
* It will generate an extra WebP & AVIF image files (in the same directory the provided
* image is located) and serve it to your browser in HTML code, with a default
* fallback to the original image for browsers that doesn't support WebP images.
* fallback to the original image for browsers that doesn't support WebP/AVIF images.
*
* Replace your IMG tag within your templates with a call to:
*
* ```php
* <?= \pelock\imgopt\ImgOpt::widget(["src" => "/images/product/extra.png", "alt" => "Extra product" ]) ?>
* ```
*
* And it will generate a WebP image file (original image is left untouched) and
* And it will generate a WebP & AVIF image files (original image is left untouched) and
* the following HTML code gets generated:
*
* ```html
* <picture>
* <source type="image/avif" srcset="/images/product/extra.avif">
* <source type="image/webp" srcset="/images/product/extra.webp">
* <img src="/images/product/extra.png" alt="Extra product">
* </picture>
Expand All @@ -60,6 +61,7 @@
* ```html
* <a href="/images/sunset.jpg" data-lightbox="image-1" data-title="Sunset">
* <picture>
* <source type="image/avif" srcset="/images/sunset-thumbnail.avif">
* <source type="image/webp" srcset="/images/sunset-thumbnail.webp">
* <img src="/images/sunset-thumbnail.png" alt="Sunset">
* </picture>
Expand Down Expand Up @@ -92,7 +94,12 @@ class ImgOpt extends Widget
/**
* @var string path to the generated WebP file format (short path) or null
*/
private $_webp;
private $_webp = null;

/**
* @var string path to the generated AVIF file format (short path) or null
*/
private $_avif = null;

/**
* @var string image alternative description used as alt="description" property (optional)
Expand Down Expand Up @@ -145,12 +152,12 @@ class ImgOpt extends Widget
public $lightbox_title;

/**
* @var bool set to TRUE to recreate the WebP file again (optional)
* @var bool set to TRUE to recreate the WebP and AVIF files again (optional)
*/
public $recreate = false;

/**
* @var bool set to TRUE to recreate *ALL* of the WebP files again (optional)
* @var bool set to TRUE to recreate *ALL* of the WebP and AVIF files again (optional)
*/
const RECREATE_ALL = false;

Expand All @@ -164,18 +171,22 @@ class ImgOpt extends Widget
*/
const DISABLE_WEBP = false;

/**
* @var string disable AVIF files usages at all (use it for debugging purposes) (optional)
*/
const DISABLE_AVIF = false;

/**
* Generates optimized WebP file from the provided image, relative to the
* Generates optimized WebP/AVIF file from the provided image, relative to the
* Yii2 @webroot file alias.
*
* @param string $img Relative path to the image in @webroot Yii2 directory
* @param bool $recreate Recreate the WebP file again
* @return string|null Path to the WebP file (relative to @webroot) or null (marks usage of the original image only)
* @param bool $recreate Recreate the output file again
* @return string|null Path to the output image file (relative to @webroot) or null (marks usage of the original image only)
*/
private function get_or_convert_to_webp($img, $recreate = false)
private function get_or_convert_to_dest_format($img, $global_flag, $file_extension, $convertion_function, $recreate = false)
{
if (self::DISABLE_WEBP || $this->disable)
if ( ($global_flag === true) || ($this->disable == true) || (function_exists($convertion_function) == false) )
{
return null;
}
Expand Down Expand Up @@ -206,32 +217,31 @@ private function get_or_convert_to_webp($img, $recreate = false)

$ext = strtolower($file_info["extension"]);

$webp_filename_with_extension = $short_file_info["filename"] . ".webp";
$output_filename_with_extension = $short_file_info["filename"] . $file_extension;

$webp_short_path = $short_file_info["dirname"] . "/" . $webp_filename_with_extension;
$webp_full_path = $file_info["dirname"] . "/" . $webp_filename_with_extension;
$output_short_path = $short_file_info["dirname"] . "/" . $output_filename_with_extension;
$output_full_path = $file_info["dirname"] . "/" . $output_filename_with_extension;

// if the WEBP file already exists check if we want to re-create it
if ($recreate === false && file_exists($webp_full_path))
if ($recreate === false && file_exists($output_full_path))
{

// if the WEBP file is bigger than the original image
// if the output file is bigger than the original image
// use the original image
if (filesize($webp_full_path) >= $original_file_size)
if (filesize($output_full_path) >= $original_file_size)
{
return null;
}

$webp_modification_time = filemtime($webp_full_path);
$output_modification_time = filemtime($output_full_path);

// if the modification dates on the original image
// and WEBP image are the same = use the WEBP image
// in any other case - recreate the file
if ($img_modification_time !== false && $webp_modification_time !== false)
if ($img_modification_time !== false && $output_modification_time !== false)
{
if ($img_modification_time === $webp_modification_time)
if ($img_modification_time === $output_modification_time)
{
return $webp_short_path;
return $output_short_path;
}
}
}
Expand All @@ -257,44 +267,52 @@ private function get_or_convert_to_webp($img, $recreate = false)
do
{
// generate output WEBP file
imagewebp($img, $webp_full_path, $quality);
try
{
call_user_func($convertion_function, $img, $output_full_path, $quality);
}
catch(yii\base\ErrorException $exception)
{
imagedestroy($img);
return null;
}


// decrease quality
$quality -= 5;

// no point in going below 70% quality
if ($quality < 70) break;
}
while (filesize($webp_full_path) >= $original_file_size);
while (filesize($output_full_path) >= $original_file_size);

// release input image
imagedestroy($img);


// set modification time on the WEBP file to match the
// modification time of the original image
if ($img_modification_time !== false)
{
touch($webp_full_path, $img_modification_time);
touch($output_full_path, $img_modification_time);
}


// if the final WEBP image is bigger than the original file
// don't use it (use the original only)
if (filesize($webp_full_path) >= $original_file_size)
if (filesize($output_full_path) >= $original_file_size)
{
return null;
}

return $webp_short_path;
return $output_short_path;
}


public function init()
{
parent::init();

$this->_webp = $this->get_or_convert_to_webp($this->src, (self::RECREATE_ALL == true || $this->recreate == true));
$this->_webp = $this->get_or_convert_to_dest_format($this->src, self::DISABLE_WEBP, ".webp", "imagewebp", (self::RECREATE_ALL == true || $this->recreate == true));
$this->_avif = $this->get_or_convert_to_dest_format($this->src, self::DISABLE_AVIF, ".avif", "imageavif", (self::RECREATE_ALL == true || $this->recreate == true));


// handle Lightbox parameters
if ($this->lightbox_data)
Expand Down Expand Up @@ -331,11 +349,13 @@ public function run()
]);

// was WebP image generated from our unoptimized image?
if ($this->_webp)
if ($this->_webp != null || $this->_avif != null)
{
// include it within <picture> tag
$html = "<picture>";
$html .= Html::tag("source", [], ["srcset" => $this->_webp, "type" => "image/webp"]);

if ($this->_avif) $html .= Html::tag("source", [], ["srcset" => $this->_avif, "type" => "image/avif"]);
if ($this->_webp) $html .= Html::tag("source", [], ["srcset" => $this->_webp, "type" => "image/webp"]);

// fallback image (unoptimized)
$html .= $img;
Expand Down

0 comments on commit 80ccc02

Please sign in to comment.