Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AVIF Thumbnail Generation #40048

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

JanisPlayer
Copy link
Contributor

@JanisPlayer JanisPlayer commented Aug 25, 2023

Summary

This pull request aims to enable the creation of preview thumbnails for AVIF files. This is currently a work-in-progress idea that others can contribute to. I've started by implementing a basic version that generates preview thumbnails using GD.

Currently, the preview generator generates a JPEG thumbnail, but a more efficient format like WebP would be better suited, as these images don't always require an alpha channel.

Accomplished:

  • AVIF preview thumbnails are generated upon file upload.
  • AVIF preview thumbnails are displayed in the thumbnail view.

Issues:

  • The Nextcloud Imaginary version doesn't yet support AVIF; this could potentially be improved. (Difficult to impossible to implement. Add AVIF Thumbnail Generation #40048 (comment))
  • Alpha channel detection doesn't work with Imagick and getImageAlphaChannel for AVIF.
  • For AVIF.php, it's better to use HEIC.php as a template, as it might be better than WebP.php, and it uses ProviderV2, which can lead to better optimizations for AVIF and the removal of unnecessary comments.
  • Since there are many AI apps that utilize the preview generator or the previews, the API should be restructured to allow app developers to pass output format and quality as parameters. However, app developers should also query which format the preview is using. Still, I would advise against JPEG previews due to quality settings; quality settings should ideally be queried on a per-image basis rather than globally, as it impacts recognition. However, this would require more complex database management. Therefore, it might be better to stick with a global quality setting or avoid using old JPEG previews for AI apps and always generate new ones. I have created a patch for Recognize that addresses the issues for testing with the app.

Content

This pull request is intended to enable the generation of preview thumbnails for AVIF files. It's still in its early stages and open for collaborative input.

I have implemented a basic functionality that currently generates preview thumbnails using DG. However, there is room for improvement, such as using a more suitable format like WebP or JPEG instead of PNG.

Experimental features have been added that allow the generation of all images as AVIF. To enable this, 'preview_format' => 'avif' must be set; if not, JPEG is used for AVIF due to performance reasons. Therefore, AVIF is an option for experienced users.

WebP is converted to WebP by default. However, it's still under consideration whether it might be better to handle WebP similarly to AVIF due to the performance required for WebP encoding and the existing support in Imaginary.

These features have been added as tests, and I have tried not to make significant code changes.

Accomplishments:

  • Implemented the creation of AVIF preview thumbnails upon file upload.
  • Enabled the display of AVIF preview thumbnails in the thumbnail view.

TODO

Experimental:

  • AVIF can use as preview format:
  • 'preview_format' => 'avif', in the config all image use from now AVIF.
  • Without `'preview_format' the default format for AVIF is JPEG.
  • Don't use a res over 1080p is really AVIF. But you can make the quality on 50.
  • Change quality with occ config:app:set preview avif_quality --value="30"
  • The speed is set on the max (10).
  • WebP can use as preview format:
  • 'preview_format' => 'webp', in the config all image use from now WebP.
  • Change quality with occ config:app:set preview webp_quality --value="30"

Checklist

Screenshots

File Browser
Android File Browser
Android Media
Android Viewer

Viewer

  • Documentation (manuals or wiki) has been updated or is not required.
  • Backports requested where applicable (e.g., critical bugfixes).

For those who want to test this out, I have a ZIP file with the changes for version 28.0.1 here.
You should just need to replace the files; I'm already using it myself.
However, it's not yet complete but demonstrates what's possible.
To revert everything back, you just need to replace the modified files with the original ones.
I've added the most important changes to the files, so if you encounter any errors, you can simply report them and include the log if there's an error included.

latest.zip (27.1.2)
latest.zip (28.0.1)

# cd /tmp/ && wget https://download.nextcloud.com/server/releases/latest.zip && unzip latest.zip #to reset
cd /tmp/ && wget https://github.com/nextcloud/server/files/14053549/latest.zip
unzip -j -o latest.zip 'nextcloud/lib/private/Preview/Generator.php' -d /var/www/nextcloud/lib/private/Preview/
unzip -j -o latest.zip 'nextcloud/lib/composer/composer/autoload_classmap.php' -d /var/www/nextcloud/lib/composer/composer/
unzip -j -o latest.zip 'nextcloud/lib/composer/composer/autoload_static.php' -d /var/www/nextcloud/lib/composer/composer/
unzip -j -o latest.zip 'nextcloud/lib/private/Collaboration/Reference/LinkReferenceProvider.php' -d /var/www/nextcloud/lib/private/Collaboration/Reference/
unzip -j -o latest.zip 'nextcloud/lib/private/Preview/AVIF.php' -d /var/www/nextcloud/lib/private/Preview/
unzip -j -o latest.zip 'nextcloud/lib/private/PreviewManager.php' -d /var/www/nextcloud/lib/private/
unzip -j -o latest.zip 'nextcloud/lib/private/legacy/OC_Image.php' -d /var/www/nextcloud/lib/private/legacy/
unzip -j -o latest.zip 'nextcloud/resources/config/mimetypemapping.dist.json' -d /var/www/nextcloud/resources/config/
# Apps
unzip -u -j -o latest.zip 'nextcloud/apps/photos/lib/AppInfo/Application.php' -d /var/www/nextcloud/apps/photos/lib/AppInfo/
unzip -u -j -o latest.zip 'nextcloud/apps/viewer/cypress/e2e/images/image.avif.cy.ts' -d /var/www/nextcloud/apps/viewer/cypress/e2e/images/
unzip -u -j -o latest.zip 'nextcloud/apps/viewer/js/*' -d /var/www/nextcloud/apps/viewer/js/
unzip -u -j -o latest.zip 'nextcloud/apps/viewer/src/models/images.js' -d /var/www/nextcloud/apps/viewer/src/models/
unzip -u -j -o latest.zip 'nextcloud/apps/memories/lib/AppInfo/Application.php' -d /var/www/nextcloud/apps/memories/lib/AppInfo/
wget https://raw.githubusercontent.com/nextcloud/recognize/926f922532c88d6dea3147a73e49b3aeea9c4b00/lib/Classifiers/Classifier.php -O /var/www/nextcloud/apps/recognize/lib/Classifiers/Classifier.php

sudo chown -R www-data:www-data /var/www/nextcloud/
service apache2 restart

Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
}

public function isAvailable(FileInfo $file): bool {
return (bool)(imagetypes() & IMG_AVIF);

Check failure

Code scanning / Psalm

UndefinedConstant Error

Const IMG_AVIF is not defined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could add IMG_AVIF using define for older PHP versions below 8.1 or check for support. However, it's recommended to use a higher PHP version for Nextcloud, so I don't think it's a real issue.

@@ -714,6 +714,20 @@
$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
}
break;
case IMAGETYPE_AVIF:

Check failure

Code scanning / Psalm

UndefinedConstant Error

Const IMAGETYPE_AVIF is not defined
lib/private/legacy/OC_Image.php Fixed Show fixed Hide fixed
lib/private/legacy/OC_Image.php Fixed Show fixed Hide fixed
@JanisPlayer
Copy link
Contributor Author

I conducted an AVIF benchmark with GD, and the results are as follows:

  • AVIF's speed needs to be pushed to the maximum.
  • At high resolutions, AVIF becomes a performance issue.

I considered adding AVIF and WebP because many users desire them. I would say it's possible, but with a limited resolution. Whether the images would actually be smaller and look better is a question.

One could also test if ImageMagick performs better in this test, but I doubt it, at least without acceleration options.

The next problem is, of course, browser support. With PHP 8.1, one could integrate WebP and AVIF into the generator and simply override the preview format with a system variable. However, this solution would be tailored to administrators who are knowledgeable about it.

I would suggest a solution involving Imaginary, similar to WebP. Here, I could add the libraries and customize the settings to make it work without hanging up the entire Nextcloud when an image is too large. However, there's an issue with this approach. I'm not a Go programmer, and I would need the Nextcloud version of the Docker. Some forks partially support AVIF.

If another developer optimizes Imaginary to prevent AVIF timeouts, then I think it could be feasible and would be less disruptive to the cloud.

Regarding the apps, they still need optimization, but I'm working on it. I want to keep this fork without additional changes for custom formats. As I mentioned before, I'm not entirely sure about WebP and AVIF, which can take up to 6 times longer than JPEG through GD. It might be a good idea for a developer to decide. I could add the option for testing purposes. However, I'm not sure if this option would make everyone happy. For me, displaying AVIF images in the gallery is already a significant step.

Adding to Nextcloud is easy, and there are different ways to do it. I'm considering switching to ImageMagick to convert AVIF images to JPEG, which requires fewer changes. Similarly, converting WebP images to JPEG or WebP instead of PNG. But I would need permission from a developer for this idea. However, I think WebP image previews are unnecessarily large, and if I do it for AVIF, I might as well do it for WebP.

Next, if things continue this way, we might add JPEG XL, and suddenly Nextcloud will become the most popular viewer. :D That's why I need feedback on the best implementation.

<?php
// Report all PHP errors
error_reporting(-1);
ini_set('error_reporting', E_ALL);

for ($i = 1; $i <= 10; $i++) {
    $startTime = microtime(true);
    $image = imagecreatefromjpeg('input.jpg');  // Read a JPG file
    $maxWidth = 1920;
    $maxHeight = 1920;
    $width = imagesx($image);
    $height = imagesy($image);

    // Calculate new dimensions while maintaining aspect ratio
    if ($maxWidth > 0 && $maxHeight > 0) {
        if ($width > $maxWidth || $height > $maxHeight) {
            $aspectRatio = $width / $height;
            if ($width > $height) {
                $newWidth = $maxWidth;
                $newHeight = $maxWidth / $aspectRatio;
            } else {
                $newHeight = $maxHeight;
                $newWidth = $maxHeight * $aspectRatio;
            }
            $image = imagescale($image, $newWidth, $newHeight);
        }
    }

    ob_start();

    if ($i <= 8) {
        // Iterationen 1-8: AVIF
        $res = imageavif($image, null, 90, 10);  // Save an AVIF file
    } elseif ($i == 9) {
        // Iteration 9: WebP
        $res = imagewebp($image, null, 90);  // Save a WebP file
    } else {
        // Iteration 10: JPEG
        $res = imagejpeg($image, null, 90);  // Save a JPEG file
    }
    $fileSize = ob_get_length(); // Get the file size in bytes
    ob_get_clean();
    imagedestroy($image);

    $endTime = microtime(true);
    $executionTime = ($endTime - $startTime) * 1000; // Convert to milliseconds

    if ($i <= 8) {
	echo "AVIF: ";
    } elseif ($i == 9) {
	echo "WebP: ";
    } else {
	echo "JPEG: ";
    }

    echo "Iteration $i: Time: " . round($executionTime, 2) . " milliseconds, File Size: $fileSize bytes<br>";
}
?>

@szaimen szaimen added this to the Nextcloud 28 milestone Sep 10, 2023
lib/private/legacy/OC_Image.php Fixed Show fixed Hide fixed
@@ -714,6 +777,16 @@
$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
}
break;
case IMAGETYPE_AVIF:
if (imagetypes() & IMG_AVIF) {

Check failure

Code scanning / Psalm

UndefinedConstant Error

Const IMG_AVIF is not defined
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = @imagecreatefromavif($imagePath);

Check failure

Code scanning / Psalm

UndefinedFunction Error

Function imagecreatefromavif does not exist
case "image/avif":
/** @psalm-suppress InvalidScalarArgument */
imageinterlace($this->resource, (PHP_VERSION_ID >= 80100 ? true : 1));
$retVal = imageavif($this->resource, $filePath, $this->getAvifQuality(), 10);

Check failure

Code scanning / Psalm

UndefinedFunction Error

Function imageavif does not exist
switch ($this->mimeType) {
case 'image/png':
case 'image/jpeg':
case 'image/webp':

Check failure

Code scanning / Psalm

TypeDoesNotContainType Error

Type never for $this->mimeType is never =string(image/webp)
case "image/avif":
/** @psalm-suppress InvalidScalarArgument */
imageinterlace($this->resource, (PHP_VERSION_ID >= 80100 ? true : 1));
$res = imageavif($this->resource, null, $this->getAvifQuality(), 10);

Check failure

Code scanning / Psalm

UndefinedFunction Error

Function imageavif does not exist
@szaimen szaimen requested review from rullzer, ChristophWurst, J0WI, a team, ArtificialOwl, nfebe and sorbaugh and removed request for a team September 11, 2023 06:57
@ChristophWurst ChristophWurst removed their request for review September 11, 2023 06:57
_output() is maybe useless
WebP have longer time to encode as PNG and JPEG, maybe remove it too by default for WebP.

Signed-off-by: JanisPlayer <[email protected]>
$imageType = IMAGETYPE_WEBP;
break;
case 'avif':
$imageType = IMAGETYPE_AVIF;

Check failure

Code scanning / Psalm

UndefinedConstant Error

Const IMAGETYPE_AVIF is not defined
@JanisPlayer
Copy link
Contributor Author

JanisPlayer commented Sep 12, 2023

So, just to keep you updated on the progress:

I'm currently testing the whole setup. I also conducted a sort of survey where I explained to people that AVIF comes with certain limitations (performance, resolution, browser support) and is probably not even considered for experimental use but needs to be implemented differently.

That's why I wanted to ask you, where is Nextcloud's Imaginary? I'd like to add AVIF support to it, which could potentially solve many of these problems except for browser support. Ideally, I'd like to add AVIF only for decoding, allowing AVIF to be converted to JPEG or WebP using Imaginary. There are already AVIF support forks that enable decoding with Imaginary, as far as I've seen, so you'd only need to adapt and incorporate parts of those.

Regarding AVIF image support as a preview format, I see it this way: At most, it could be added with restrictions as an extra option, with a lot of warnings in the documentation explaining the limitations. Or, it could simply be implemented through Imaginary, which would be better suited for this purpose. The issue of browser support is also something to consider and mention.

But I think you can already see that I've added it in a way that doesn't require changing the entire codebase. I want to leave that to others who plan to do that with their PR. My focus for now is primarily on making AVIF images convert to JPEG, WebP, or PNG as previews and then be processed correctly in Nextcloud apps. I've already completed that part, but I'm not really satisfied with the current solution. However, as I mentioned before, I didn't want to make extensive changes to the entire generator for adding a new image format.

I've also made changes so that WebP images are no longer converted to PNG but to WebP preview images. However, this also consumes some resources, and PNG or JPEG might be better suited for this purpose. PNG is particularly suitable due to the alpha channel. Here, you could also add a test to check if the WebP image uses an alpha channel because PNG images are always large and consume a lot of storage.

Everything else, for me, is just experimenting and seeing what's possible.

Here are some specific questions for you:

  • What do you think of my solution of making as few code changes as possible?
  • Is it reasonable to encode AVIF as JPEG, or should we consider using WebP or PNG?
  • How do you feel about the change where WebP is converted to a WebP preview image from now on? Is it acceptable in terms of performance with DG and WebP browser support as a preview?
  • Did I miss any important Nextcloud apps?
  • What are your thoughts on the idea of experimental features?
  • AVIF or WebP through DG as a preview format when set in the config - does this make sense to you? I'm not entirely sure about AVIF, and even with WebP, Imaginary can already handle it, and WebP requires some performance.
  • Should I add AVIF in a similar way to how it's done in HEIC.php?
  • Are the license comments unnecessary or auto-generated and can they be removed?

Signed-off-by: JanisPlayer <[email protected]>
Signed-off-by: JanisPlayer <[email protected]>
@JanisPlayer
Copy link
Contributor Author

Small update:
I've been running this Nextcloud modification on my system for a while now.
Everything works wonderfully, there are no major errors that I've noticed, and the log also doesn't report any issues.
Currently, I'm using my Nextcloud with Imaginary and WebP previews, but I've also tested the experimental features with AVIF over GD, and there were no problems.
I chose GD because it was the simplest option, and ImageMagick was only used if GD lacked support. The only downside to GD is that it can't handle alpha channels in AVIF images.
The only issues I noticed were with thumbnails of HEIC, but this problem seems to be related to Imaginary and not my patch.
Users haven't reported any problems with the unofficial test patch under the pull request.
Now it's up to the real Nextcloud devs to decide what stays in the pull request and how it stays, whether it can be improved.
Once I know what stays or how it stays, I can create proper documentation and start initiating pull requests for the apps.
I improvised parts of this patch to accommodate user requests, so there is always room for improvement. For example, how often the format is called from the config and the entire call process.
I added AVIF completely and cleanly because this function is the most important part of the entire pull request.
For widely used apps, I had to take care of their support. It was relatively easy for photo apps and viewer apps, but for AI apps that needed the preview generator, I had to provide my own generator with GD.
So, there may be missing apps in this regard.
Other than that, AVIF doesn't cause any issues when displaying, and all patches are fulfilled.
Looking forward to your feedback on the new features so that I can finish the rest.

@cromefire
Copy link

cromefire commented Dec 23, 2023

Edge Browser don't work with AVIF as preview format, need a warning in the documentation which browser not supported.

Apparently that will be solved next month, so not sure if it's even worth it to add anymore: https://www.neowin.net/news/microsoft-edge-121-finally-brings-avif-support/

Edit: It happened and is here now. AVIF is also part of Baseline 2024: https://caniuse.com/avif

This was referenced Mar 12, 2024
This was referenced Mar 20, 2024
@skjnldsv skjnldsv mentioned this pull request Mar 28, 2024
81 tasks
@skjnldsv skjnldsv modified the milestones: Nextcloud 29, Nextcloud 30 Mar 28, 2024
This was referenced Jul 30, 2024
This was referenced Aug 5, 2024
@skjnldsv skjnldsv mentioned this pull request Aug 13, 2024
@skjnldsv skjnldsv removed this from the Nextcloud 30 milestone Aug 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants