summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2020-04-10 15:57:06 +0200
committerGitHub <noreply@github.com>2020-04-10 15:57:06 +0200
commiteba3726e1e1b7ce0a98df4552cfdecfe05e0d63a (patch)
tree7d50a939305460fbb34078c1da49f992552747e7 /lib
parentedf8ce32cffdb920e8171207b342abbd7f1fbe73 (diff)
parent8f9bac26f874e105c017de2b1791b23c2a135b28 (diff)
downloadnextcloud-server-eba3726e1e1b7ce0a98df4552cfdecfe05e0d63a.tar.gz
nextcloud-server-eba3726e1e1b7ce0a98df4552cfdecfe05e0d63a.zip
Merge pull request #19495 from nextcloud/preview-generate-batch
optimize batch generation of previews
Diffstat (limited to 'lib')
-rw-r--r--lib/private/Preview/Generator.php112
-rw-r--r--lib/private/PreviewManager.php44
-rw-r--r--lib/private/legacy/OC_Image.php102
-rw-r--r--lib/public/IImage.php39
-rw-r--r--lib/public/IPreview.php13
5 files changed, 248 insertions, 62 deletions
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index f945849c276..650c3a6ba9f 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -91,22 +91,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();
}
@@ -123,41 +138,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;
- }
+ // If both width and heigth are -1 we just want the max preview
+ if ($width === -1 && $height === -1) {
+ $width = $maxWidth;
+ $height = $maxHeight;
+ }
- // Try to get a cached preview. Else generate (and store) one
- try {
+ // 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 {
- $preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
- } catch (NotFoundException $e) {
- $preview = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
+ 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("", 0, $e);
}
- } catch (\InvalidArgumentException $e) {
- throw new NotFoundException();
- }
- if ($preview->getSize() === 0) {
- $preview->delete();
- throw new NotFoundException('Cached preview size 0, invalid!');
+ if ($preview->getSize() === 0) {
+ $preview->delete();
+ throw new NotFoundException('Cached preview size 0, invalid!');
+ }
}
return $preview;
@@ -360,9 +391,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');
}
@@ -380,13 +410,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));
}
@@ -448,7 +478,7 @@ class Generator {
case 'image/gif':
return 'gif';
default:
- throw new \InvalidArgumentException('Not a valid mimetype');
+ throw new \InvalidArgumentException('Not a valid mimetype: "' . $mimeType . '"');
}
}
}
diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php
index adfc04199e3..0734e275926 100644
--- a/lib/private/PreviewManager.php
+++ b/lib/private/PreviewManager.php
@@ -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);
}
/**
diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php
index 829a9b81652..c8a2f6f458c 100644
--- a/lib/private/legacy/OC_Image.php
+++ b/lib/private/legacy/OC_Image.php
@@ -39,6 +39,8 @@
*
*/
+use OCP\IImage;
+
/**
* Class for basic image manipulation
*/
@@ -845,6 +847,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', ['app' => 'core']);
return false;
@@ -861,8 +874,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));
}
/**
@@ -871,6 +883,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', ['app' => 'core']);
return false;
@@ -896,9 +921,7 @@ class OC_Image implements \OCP\IImage {
imagedestroy($process);
return false;
}
- imagedestroy($this->resource);
- $this->resource = $process;
- return true;
+ return $process;
}
/**
@@ -969,6 +992,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', ['app' => 'core']);
return false;
@@ -993,9 +1032,7 @@ class OC_Image implements \OCP\IImage {
imagedestroy($process);
return false;
}
- imagedestroy($this->resource);
- $this->resource = $process;
- return true;
+ return $process;
}
/**
@@ -1045,6 +1082,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
*/
diff --git a/lib/public/IImage.php b/lib/public/IImage.php
index 67db6b097ef..6e6c28609d8 100644
--- a/lib/public/IImage.php
+++ b/lib/public/IImage.php
@@ -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;
}
diff --git a/lib/public/IPreview.php b/lib/public/IPreview.php
index b377d285acb..164cbbac2bf 100644
--- a/lib/public/IPreview.php
+++ b/lib/public/IPreview.php
@@ -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);
}