Compare commits

...

2 commits

Author SHA1 Message Date
Robin Appelman
636b4b752b
optimize batch generation of previews
by allowing the generation of multiple previews at once we save on having to find, open and decode the max-preview for every preview of the same file

the main use case for this is the preview generator app (pr for that comming next)

in my local testing this saves about 25% of time when using the preview generator app

Signed-off-by: Robin Appelman <robin@icewind.nl>
2020-02-16 02:34:09 +01:00
Robin Appelman
f00a59b4e2
allow generating multiple preview sizes for a single file at once
this saves having to do some of the overhead multiple times

Signed-off-by: Robin Appelman <robin@icewind.nl>
2020-02-16 01:54:48 +01:00
5 changed files with 249 additions and 63 deletions

View file

@ -93,22 +93,37 @@ class Generator {
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
$specification = [
'width' => $width,
'height' => $height,
'crop' => $crop,
'mode' => $mode,
];
$this->eventDispatcher->dispatch(
IPreview::EVENT,
new GenericEvent($file, $specification)
);
// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
return $this->generatePreviews($file, [$specification], $mimeType);
}
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
//Make sure that we can read the file
if (!$file->isReadable()) {
throw new NotFoundException('Cannot read file');
}
$this->eventDispatcher->dispatch(
IPreview::EVENT,
new GenericEvent($file, [
'width' => $width,
'height' => $height,
'crop' => $crop,
'mode' => $mode
])
);
if ($mimeType === null) {
$mimeType = $file->getMimeType();
}
@ -125,41 +140,57 @@ class Generator {
// Get the max preview and infer the max preview sizes from that
$maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion);
$maxPreviewImage = null; // only load the image when we need it
if ($maxPreview->getSize() === 0) {
$maxPreview->delete();
throw new NotFoundException('Max preview size 0, invalid!');
}
list($maxWidth, $maxHeight) = $this->getPreviewSize($maxPreview, $previewVersion);
[$maxWidth, $maxHeight] = $this->getPreviewSize($maxPreview, $previewVersion);
// If both width and heigth are -1 we just want the max preview
if ($width === -1 && $height === -1) {
$width = $maxWidth;
$height = $maxHeight;
}
$preview = null;
// Calculate the preview size
list($width, $height) = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
foreach ($specifications as $specification) {
$width = $specification['width'] ?? -1;
$height = $specification['height'] ?? -1;
$crop = $specification['crop'] ?? false;
$mode = $specification['mode'] ?? IPreview::MODE_FILL;
// No need to generate a preview that is just the max preview
if ($width === $maxWidth && $height === $maxHeight) {
return $maxPreview;
}
// Try to get a cached preview. Else generate (and store) one
try {
try {
$preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
} catch (NotFoundException $e) {
$preview = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
// If both width and heigth are -1 we just want the max preview
if ($width === -1 && $height === -1) {
$width = $maxWidth;
$height = $maxHeight;
}
} catch (\InvalidArgumentException $e) {
throw new NotFoundException();
}
if ($preview->getSize() === 0) {
$preview->delete();
throw new NotFoundException('Cached preview size 0, invalid!');
// Calculate the preview size
[$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
// No need to generate a preview that is just the max preview
if ($width === $maxWidth && $height === $maxHeight) {
// ensure correct return value if this was the last one
$preview = $maxPreview;
continue;
}
// Try to get a cached preview. Else generate (and store) one
try {
try {
$preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
} catch (NotFoundException $e) {
if ($maxPreviewImage === null) {
$maxPreviewImage = $this->helper->getImage($maxPreview);
}
$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
}
} catch (\InvalidArgumentException $e) {
throw new NotFoundException();
}
if ($preview->getSize() === 0) {
$preview->delete();
throw new NotFoundException('Cached preview size 0, invalid!');
}
}
return $preview;
@ -362,9 +393,8 @@ class Generator {
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
private function generatePreview(ISimpleFolder $previewFolder, ISimpleFile $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
$preview = $this->helper->getImage($maxPreview);
private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
$preview = $maxPreview;
if (!$preview->valid()) {
throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
}
@ -382,13 +412,13 @@ class Generator {
$scaleH = $maxHeight / $widthR;
$scaleW = $width;
}
$preview->preciseResize((int)round($scaleW), (int)round($scaleH));
$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
}
$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
$preview->crop($cropX, $cropY, $width, $height);
$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
} else {
$preview->resize(max($width, $height));
$preview = $maxPreview->resizeCopy(max($width, $height));
}

View file

@ -151,6 +151,22 @@ class PreviewManager implements IPreview {
return !empty($this->providers);
}
private function getGenerator(): Generator {
if ($this->generator === null) {
$this->generator = new Generator(
$this->config,
$this,
$this->appData,
new GeneratorHelper(
$this->rootFolder,
$this->config
),
$this->eventDispatcher
);
}
return $this->generator;
}
/**
* Returns a preview of a file
*
@ -169,20 +185,22 @@ class PreviewManager implements IPreview {
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
if ($this->generator === null) {
$this->generator = new Generator(
$this->config,
$this,
$this->appData,
new GeneratorHelper(
$this->rootFolder,
$this->config
),
$this->eventDispatcher
);
}
return $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
}
return $this->generator->getPreview($file, $width, $height, $crop, $mode, $mimeType);
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 19.0.0
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
}
/**

View file

@ -38,6 +38,8 @@
*
*/
use OCP\IImage;
/**
* Class for basic image manipulation
*/
@ -844,6 +846,17 @@ class OC_Image implements \OCP\IImage {
* @return bool
*/
public function resize($maxSize) {
$result = $this->resizeNew($maxSize);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}
/**
* @param $maxSize
* @return resource | bool
*/
private function resizeNew($maxSize) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
@ -860,8 +873,7 @@ class OC_Image implements \OCP\IImage {
$newHeight = $maxSize;
}
$this->preciseResize((int)round($newWidth), (int)round($newHeight));
return true;
return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
}
/**
@ -870,6 +882,19 @@ class OC_Image implements \OCP\IImage {
* @return bool
*/
public function preciseResize(int $width, int $height): bool {
$result = $this->preciseResizeNew($width, $height);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}
/**
* @param int $width
* @param int $height
* @return resource | bool
*/
public function preciseResizeNew(int $width, int $height) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
@ -895,9 +920,7 @@ class OC_Image implements \OCP\IImage {
imagedestroy($process);
return false;
}
imagedestroy($this->resource);
$this->resource = $process;
return true;
return $process;
}
/**
@ -968,6 +991,22 @@ class OC_Image implements \OCP\IImage {
* @return bool for success or failure
*/
public function crop(int $x, int $y, int $w, int $h): bool {
$result = $this->cropNew($x, $y, $w, $h);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}
/**
* Crops the image from point $x$y with dimension $wx$h.
*
* @param int $x Horizontal position
* @param int $y Vertical position
* @param int $w Width
* @param int $h Height
* @return resource | bool
*/
public function cropNew(int $x, int $y, int $w, int $h) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
return false;
@ -992,9 +1031,7 @@ class OC_Image implements \OCP\IImage {
imagedestroy($process);
return false;
}
imagedestroy($this->resource);
$this->resource = $process;
return true;
return $process;
}
/**
@ -1044,6 +1081,55 @@ class OC_Image implements \OCP\IImage {
return false;
}
public function copy(): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = imagecreatetruecolor($this->width(), $this->height());
imagecopy(
$image->resource(),
$this->resource(),
0,
0,
0,
0,
$this->width(),
$this->height()
);
return $image;
}
public function cropCopy(int $x, int $y, int $w, int $h): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->cropNew($x, $y, $w, $h);
return $image;
}
public function preciseResizeCopy(int $width, int $height): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->preciseResizeNew($width, $height);
return $image;
}
public function resizeCopy(int $maxSize): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->resizeNew($maxSize);
return $image;
}
/**
* Resizes the image preserving ratio, returning a new copy
*
* @param integer $maxSize The maximum size of either the width or height.
* @return bool
*/
public function copyResize($maxSize): IImage {
}
/**
* Destroys the current image and resets the object
*/

View file

@ -190,4 +190,43 @@ interface IImage {
* @since 8.1.0
*/
public function scaleDownToFit($maxWidth, $maxHeight);
/**
* create a copy of this image
*
* @return IImage
* @since 19.0.0
*/
public function copy(): IImage;
/**
* create a new cropped copy of this image
*
* @param int $x Horizontal position
* @param int $y Vertical position
* @param int $w Width
* @param int $h Height
* @return IImage
* @since 19.0.0
*/
public function cropCopy(int $x, int $y, int $w, int $h): IImage;
/**
* create a new resized copy of this image
*
* @param int $width
* @param int $height
* @return IImage
* @since 19.0.0
*/
public function preciseResizeCopy(int $width, int $height): IImage;
/**
* create a new resized copy of this image
*
* @param integer $maxSize The maximum size of either the width or height.
* @return IImage
* @since 19.0.0
*/
public function resizeCopy(int $maxSize): IImage;
}

View file

@ -115,4 +115,17 @@ interface IPreview {
* @since 8.0.0
*/
public function isAvailable(\OCP\Files\FileInfo $file);
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 19.0.0
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null);
}