mirror of
https://github.com/nextcloud/server.git
synced 2026-10-24 12:17:23 +02:00
Small previews like the 64px and 256px ones of the files list and grid view were resized from the fully decoded max preview, a 12 MP image for a camera photo. libjpeg can scale by 1/2, 1/4 or 1/8 while decoding: with Imagick, decoding a 12 MP JPEG this way takes about 30ms instead of about 90ms with GD, and the resize then starts from a 500x380 px image instead of a 4032x3024 px one. Use it when Imagick is available and the requested preview is at most 1/8 of the max preview, otherwise decode the full max preview as before. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
78 lines
2.3 KiB
PHP
78 lines
2.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OC\Preview;
|
|
|
|
use OCP\Files\File;
|
|
use OCP\Files\SimpleFS\ISimpleFile;
|
|
use OCP\IImage;
|
|
use OCP\Image as OCPImage;
|
|
use OCP\IPreview;
|
|
use OCP\Preview\IProviderV2;
|
|
|
|
/**
|
|
* Very small wrapper class to make the generator fully unit testable
|
|
* @psalm-import-type ProviderClosure from IPreview
|
|
*/
|
|
class GeneratorHelper {
|
|
public function __construct(
|
|
private readonly IMagickSupport $imagickSupport,
|
|
) {
|
|
}
|
|
|
|
public function getThumbnail(IProviderV2 $provider, File $file, int $maxWidth, int $maxHeight, bool $crop = false): IImage|false {
|
|
if ($provider instanceof Imaginary) {
|
|
return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
|
|
}
|
|
return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
|
|
}
|
|
|
|
public function getImage(ISimpleFile $maxPreview): IImage {
|
|
$image = new OCPImage();
|
|
$image->loadFromData($maxPreview->getContent());
|
|
return $image;
|
|
}
|
|
|
|
/**
|
|
* Decode a JPEG preview at a reduced size of at least $minWidth x $minHeight,
|
|
* much faster than a full decode. Requires Imagick.
|
|
*/
|
|
public function getScaledImage(ISimpleFile $preview, int $minWidth, int $minHeight): ?IImage {
|
|
if ($preview->getMimeType() !== 'image/jpeg' || !$this->imagickSupport->hasExtension()) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$imagick = new \Imagick();
|
|
// libjpeg scales by multiples of 1/8, twice the size keeps enough detail
|
|
$imagick->setOption('jpeg:size', ($minWidth * 2) . 'x' . ($minHeight * 2));
|
|
$imagick->readImageBlob($preview->getContent());
|
|
if ($imagick->getImageWidth() < $minWidth || $imagick->getImageHeight() < $minHeight) {
|
|
return null;
|
|
}
|
|
// Re-encoded as JPEG, so derived previews keep the mimetype
|
|
$imagick->setImageFormat('jpeg');
|
|
$imagick->setImageCompressionQuality(95);
|
|
$data = $imagick->getImageBlob();
|
|
$imagick->clear();
|
|
} catch (\ImagickException) {
|
|
return null;
|
|
}
|
|
|
|
$image = new OCPImage();
|
|
$image->loadFromData($data);
|
|
return $image->valid() ? $image : null;
|
|
}
|
|
|
|
/**
|
|
* @param \Closure|string $providerClosure (string is only authorized in unit tests)
|
|
*/
|
|
public function getProvider(\Closure|string $providerClosure): IProviderV2|false {
|
|
return $providerClosure();
|
|
}
|
|
}
|