Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,8 @@
'OC\\PhoneNumberUtil' => $baseDir . '/lib/private/PhoneNumberUtil.php',
'OC\\PreviewManager' => $baseDir . '/lib/private/PreviewManager.php',
'OC\\PreviewNotAvailableException' => $baseDir . '/lib/private/PreviewNotAvailableException.php',
'OC\\Preview\\AVIF' => $baseDir . '/lib/private/Preview/AVIF.php',
'OC\\Preview\\AVIFImagick' => $baseDir . '/lib/private/Preview/AVIFImagick.php',
'OC\\Preview\\BMP' => $baseDir . '/lib/private/Preview/BMP.php',
'OC\\Preview\\BackgroundCleanupJob' => $baseDir . '/lib/private/Preview/BackgroundCleanupJob.php',
'OC\\Preview\\Bitmap' => $baseDir . '/lib/private/Preview/Bitmap.php',
Expand All @@ -2145,6 +2147,7 @@
'OC\\Preview\\Generator' => $baseDir . '/lib/private/Preview/Generator.php',
'OC\\Preview\\GeneratorHelper' => $baseDir . '/lib/private/Preview/GeneratorHelper.php',
'OC\\Preview\\HEIC' => $baseDir . '/lib/private/Preview/HEIC.php',
'OC\\Preview\\Heif' => $baseDir . '/lib/private/Preview/Heif.php',
'OC\\Preview\\IMagickSupport' => $baseDir . '/lib/private/Preview/IMagickSupport.php',
'OC\\Preview\\Illustrator' => $baseDir . '/lib/private/Preview/Illustrator.php',
'OC\\Preview\\Image' => $baseDir . '/lib/private/Preview/Image.php',
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\PhoneNumberUtil' => __DIR__ . '/../../..' . '/lib/private/PhoneNumberUtil.php',
'OC\\PreviewManager' => __DIR__ . '/../../..' . '/lib/private/PreviewManager.php',
'OC\\PreviewNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/PreviewNotAvailableException.php',
'OC\\Preview\\AVIF' => __DIR__ . '/../../..' . '/lib/private/Preview/AVIF.php',
'OC\\Preview\\AVIFImagick' => __DIR__ . '/../../..' . '/lib/private/Preview/AVIFImagick.php',
'OC\\Preview\\BMP' => __DIR__ . '/../../..' . '/lib/private/Preview/BMP.php',
'OC\\Preview\\BackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Preview/BackgroundCleanupJob.php',
'OC\\Preview\\Bitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/Bitmap.php',
Expand All @@ -2186,6 +2188,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Preview\\Generator' => __DIR__ . '/../../..' . '/lib/private/Preview/Generator.php',
'OC\\Preview\\GeneratorHelper' => __DIR__ . '/../../..' . '/lib/private/Preview/GeneratorHelper.php',
'OC\\Preview\\HEIC' => __DIR__ . '/../../..' . '/lib/private/Preview/HEIC.php',
'OC\\Preview\\Heif' => __DIR__ . '/../../..' . '/lib/private/Preview/Heif.php',
'OC\\Preview\\IMagickSupport' => __DIR__ . '/../../..' . '/lib/private/Preview/IMagickSupport.php',
'OC\\Preview\\Illustrator' => __DIR__ . '/../../..' . '/lib/private/Preview/Illustrator.php',
'OC\\Preview\\Image' => __DIR__ . '/../../..' . '/lib/private/Preview/Image.php',
Expand Down
19 changes: 19 additions & 0 deletions lib/private/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ private function _output(?string $filePath = null, ?string $mimeType = null): bo
case 'image/webp':
$imageType = IMAGETYPE_WEBP;
break;
case 'image/avif':
$imageType = IMAGETYPE_AVIF;
break;
default:
throw new \Exception('Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
}
Expand Down Expand Up @@ -281,6 +284,9 @@ private function _output(?string $filePath = null, ?string $mimeType = null): bo
case IMAGETYPE_WEBP:
$retVal = imagewebp($this->resource, null, $this->getWebpQuality());
break;
case IMAGETYPE_AVIF:
$retVal = imageavif($this->resource, $filePath);
break;
default:
$retVal = imagepng($this->resource, $filePath);
}
Expand Down Expand Up @@ -747,6 +753,19 @@ public function loadFromFile($imagePath = false) {
$this->logger->debug('Image->loadFromFile, WEBP images not supported: ' . $imagePath, ['app' => 'core']);
}
break;
case IMAGETYPE_AVIF:
if (imagetypes() & IMG_AVIF) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
// An animated AVIF decodes to its first frame, which is the
// right thing for a preview, and a sequence libgd cannot read
// returns false and is handled below like any other failure
$this->resource = @imagecreatefromavif($imagePath);
} else {
$this->logger->debug('Image->loadFromFile, AVIF images not supported: ' . $imagePath, ['app' => 'core']);
}
break;
/*
case IMAGETYPE_TIFF_II: // (intel byte order)
break;
Expand Down
20 changes: 20 additions & 0 deletions lib/private/Preview/AVIF.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OC\Preview;

class AVIF extends Image {
/**
* {@inheritDoc}
*/
#[\Override]
public function getMimeType(): string {
return '/image\/avif/';
}
}
41 changes: 41 additions & 0 deletions lib/private/Preview/AVIFImagick.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OC\Preview;

/**
* Creates a JPG preview of a HEIF file holding AV1, using ImageMagick via
* the PECL extension.
*
* The AVIF provider beside this one reads the format through libgd, which
* is where most installations will get it and which needs nothing enabled.
* This one is for a libgd built without libavif, on a server whose
* ImageMagick does carry the codec.
*
* @package OC\Preview
*/
class AVIFImagick extends Heif {
/**
* {@inheritDoc}
*/
#[\Override]
public function getMimeType(): string {
return '/image\/avif/';
}

#[\Override]
protected function formatHint(): string {
return 'avif';
}

#[\Override]
protected function queryFormat(): string {
return 'AVIF';
}
}
134 changes: 7 additions & 127 deletions lib/private/Preview/HEIC.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,13 @@

namespace OC\Preview;

use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\IImage;
use OCP\Image;
use OCP\Server;
use Psr\Log\LoggerInterface;

/**
* Creates a JPG preview using ImageMagick via the PECL extension
* Creates a JPG preview of a HEIF file holding h265, using ImageMagick via
* the PECL extension.
*
* @package OC\Preview
*/
class HEIC extends ProviderV2 {
class HEIC extends Heif {
/**
* {@inheritDoc}
*/
Expand All @@ -31,127 +25,13 @@ public function getMimeType(): string {
return '/image\/(x-)?hei(f|c)/';
}

/**
* {@inheritDoc}
*/
#[\Override]
public function isAvailable(FileInfo $file): bool {
return in_array('HEIC', \Imagick::queryFormats('HEI*'), true);
protected function formatHint(): string {
return 'heic';
}

/**
* {@inheritDoc}
*/
#[\Override]
public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
if (!$this->isAvailable($file)) {
return null;
}

$tmpPath = $this->getLocalFile($file);
if ($tmpPath === false) {
Server::get(LoggerInterface::class)->error(
'Failed to get local file to generate thumbnail for: ' . $file->getPath(),
['app' => 'core']
);
return null;
}

// Creates \Imagick object from the heic file
try {
$bp = $this->getResizedPreview($tmpPath, $maxX, $maxY);
$bp->setFormat('jpg');
} catch (\Exception $e) {
Server::get(LoggerInterface::class)->error(
'File: ' . $file->getPath() . ' Imagick says:',
[
'exception' => $e,
'app' => 'core',
]
);
return null;
}

$this->cleanTmpFiles();

//new bitmap image object
$image = new Image();
$image->loadFromData((string)$bp);
//check if image object is valid
return $image->valid() ? $image : null;
}

/**
* Returns a preview of maxX times maxY dimensions in JPG format
*
* * The default resolution is already 72dpi, no need to change it for a bitmap output
* * It's possible to have proper colour conversion using profileimage().
* ICC profiles are here: http://www.color.org/srgbprofiles.xalter
* * It's possible to Gamma-correct an image via gammaImage()
*
* @param string $tmpPath the location of the file to convert
* @param int $maxX
* @param int $maxY
*
* @return \Imagick
*
* @throws \Exception
*/
private function getResizedPreview($tmpPath, $maxX, $maxY) {
$bp = new \Imagick();

// Some HEIC files just contain (or at least are identified as) other formats
// like JPEG. We just need to check if the image is safe to process.
$bp->pingImage('heic:' . $tmpPath . '[0]');
$mimeType = $bp->getImageMimeType();
if (!preg_match('/^image\/(x-)?(png|jpeg|gif|bmp|tiff|webp|hei(f|c)|avif)$/', $mimeType)) {
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
}

// Layer 0 contains either the bitmap or a flat representation of all vector layers
$bp->readImage('heic:' . $tmpPath . '[0]');

// Fix orientation from EXIF
$bp->autoOrient();

$bp->setImageFormat('jpg');

$bp = $this->resize($bp, $maxX, $maxY);

return $bp;
}

/**
* Returns a resized \Imagick object
*
* If you want to know more on the various methods available to resize an
* image, check out this link : @link https://stackoverflow.com/questions/8517304/what-the-difference-of-sample-resample-scale-resize-adaptive-resize-thumbnail-im
*
* @param \Imagick $bp
* @param int $maxX
* @param int $maxY
*
* @return \Imagick
*/
private function resize($bp, $maxX, $maxY) {
[$previewWidth, $previewHeight] = array_values($bp->getImageGeometry());

// We only need to resize a preview which doesn't fit in the maximum dimensions
if ($previewWidth > $maxX || $previewHeight > $maxY) {
// If we want a small image (thumbnail) let's be most space- and time-efficient
if ($maxX <= 500 && $maxY <= 500) {
$bp->thumbnailImage($maxY, $maxX, true);
$bp->stripImage();
} else {
// A bigger image calls for some better resizing algorithm
// According to http://www.imagemagick.org/Usage/filter/#lanczos
// the catrom filter is almost identical to Lanczos2, but according
// to https://www.php.net/manual/en/imagick.resizeimage.php it is
// significantly faster
$bp->resizeImage($maxX, $maxY, \Imagick::FILTER_CATROM, 1, true);
}
}

return $bp;
protected function queryFormat(): string {
return 'HEIC';
}
}
Loading
Loading