summaryrefslogtreecommitdiffstats
path: root/lib/private/Preview/Generator.php
diff options
context:
space:
mode:
authorRoeland Jago Douma <roeland@famdouma.nl>2016-10-14 21:21:37 +0200
committerRoeland Jago Douma <roeland@famdouma.nl>2016-11-03 14:00:33 +0100
commitd6f1287ae6407a8f1ef0d018fe197e0f920ed192 (patch)
treedb743f2c5022a00366fc45cb950aa8c1c6460317 /lib/private/Preview/Generator.php
parent02525fd98bb3994199ebd877968574610f6898ee (diff)
downloadnextcloud-server-d6f1287ae6407a8f1ef0d018fe197e0f920ed192.tar.gz
nextcloud-server-d6f1287ae6407a8f1ef0d018fe197e0f920ed192.zip
Move file
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Diffstat (limited to 'lib/private/Preview/Generator.php')
-rw-r--r--lib/private/Preview/Generator.php366
1 files changed, 366 insertions, 0 deletions
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
new file mode 100644
index 00000000000..6a2e2a4717e
--- /dev/null
+++ b/lib/private/Preview/Generator.php
@@ -0,0 +1,366 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Preview;
+
+use OC\Files\View;
+use OCP\Files\File;
+use OCP\Files\IAppData;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotFoundException;
+use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\IConfig;
+use OCP\IImage;
+use OCP\Image;
+use OCP\IPreview;
+use OCP\Preview\IProvider;
+
+class Generator {
+ //the thumbnail folder
+ const THUMBNAILS_FOLDER = 'thumbnails';
+
+ const MODE_FILL = 'fill';
+ const MODE_COVER = 'cover';
+
+ /** @var IRootFolder*/
+ private $rootFolder;
+ /** @var File */
+ private $file;
+ /** @var IPreview */
+ private $previewManager;
+ /** @var IConfig */
+ private $config;
+ /** @var IAppData */
+ private $appData;
+
+ /**
+ * @param IRootFolder $rootFolder
+ * @param IConfig $config
+ * @param IPreview $previewManager
+ * @param File $file
+ * @param IAppData $appData
+ */
+ public function __construct(
+ IRootFolder $rootFolder,
+ IConfig $config,
+ IPreview $previewManager,
+ File $file,
+ IAppData $appData
+ ) {
+ $this->rootFolder = $rootFolder;
+ $this->config = $config;
+ $this->file = $file;
+ $this->previewManager = $previewManager;
+ $this->appData = $appData;
+ }
+
+ /**
+ * Returns a preview of a file
+ *
+ * The cache is searched first and if nothing usable was found then a preview is
+ * generated by one of the providers
+ *
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param string $mode
+ * @return ISimpleFile
+ * @throws NotFoundException
+ */
+ public function getPreview($width = -1, $height = -1, $crop = false, $mode = Generator::MODE_FILL) {
+ if (!$this->previewManager->isMimeSupported($this->file->getMimeType())) {
+ throw new NotFoundException();
+ }
+
+ /*
+ * Get the preview folder
+ * TODO: Separate preview creation from storing previews
+ */
+ $previewFolder = $this->getPreviewFolder();
+
+ // Get the max preview and infer the max preview sizes from that
+ $maxPreview = $this->getMaxPreview($previewFolder);
+ list($maxWidth, $maxHeight) = $this->getPreviewSize($maxPreview);
+
+ // Calculate the preview size
+ list($width, $height) = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
+
+ // Try to get a cached preview. Else generate (and store) one
+ try {
+ $file = $this->getCachedPreview($previewFolder, $width, $height, $crop);
+ } catch (NotFoundException $e) {
+ $file = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight);
+ }
+
+ return $file;
+ }
+
+ /**
+ * @param ISimpleFolder $previewFolder
+ * @return ISimpleFile
+ * @throws NotFoundException
+ */
+ private function getMaxPreview(ISimpleFolder $previewFolder) {
+ $nodes = $previewFolder->getDirectoryListing();
+
+ foreach ($nodes as $node) {
+ if (strpos($node->getName(), 'max')) {
+ return $node;
+ }
+ }
+
+ $previewProviders = $this->previewManager->getProviders();
+ foreach ($previewProviders as $supportedMimeType => $providers) {
+ if (!preg_match($supportedMimeType, $this->file->getMimeType())) {
+ continue;
+ }
+
+ foreach ($providers as $provider) {
+ $provider = $provider();
+ if (!($provider instanceof IProvider)) {
+ continue;
+ }
+
+ list($view, $path) = $this->getViewAndPath($this->file);
+
+ $maxWidth = (int)$this->config->getSystemValue('preview_max_x', 2048);
+ $maxHeight = (int)$this->config->getSystemValue('preview_max_y', 2048);
+
+ $preview = $provider->getThumbnail($path, $maxWidth, $maxHeight, false, $view);
+
+ if (!($preview instanceof IImage)) {
+ continue;
+ }
+
+ $path = strval($preview->width()) . '-' . strval($preview->height()) . '-max.png';
+ $file = $previewFolder->newFile($path);
+ $file->putContent($preview->data());
+
+ return $file;
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ /**
+ * @param ISimpleFile $file
+ * @return int[]
+ */
+ private function getPreviewSize(ISimpleFile $file) {
+ $size = explode('-', $file->getName());
+ return [(int)$size[0], (int)$size[1]];
+ }
+
+ /**
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @return string
+ */
+ private function generatePath($width, $height, $crop) {
+ $path = strval($width) . '-' . strval($height);
+ if ($crop) {
+ $path .= '-crop';
+ }
+ $path .= '.png';
+ return $path;
+ }
+
+ /**
+ * @param File $file
+ * @return array
+ * This is required to create the old view and path
+ */
+ private function getViewAndPath(File $file) {
+ $owner = $file->getOwner()->getUID();
+
+ $userFolder = $this->rootFolder->getUserFolder($owner)->getParent();
+ $nodes = $userFolder->getById($file->getId());
+
+ $file = $nodes[0];
+
+ $view = new View($userFolder->getPath());
+ $path = $userFolder->getRelativePath($file->getPath());
+
+ return [$view, $path];
+ }
+
+ /**
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param string $mode
+ * @param int $maxWidth
+ * @param int $maxHeight
+ * @return int[]
+ */
+ private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight) {
+
+ /*
+ * If we are not cropping we have to make sure the requested image
+ * respects the aspect ratio of the original.
+ */
+ if (!$crop) {
+ $ratio = $maxHeight / $maxWidth;
+
+ if ($width === -1) {
+ $width = $height / $ratio;
+ }
+ if ($height === -1) {
+ $height = $width * $ratio;
+ }
+
+ $ratioH = $height / $maxHeight;
+ $ratioW = $width / $maxWidth;
+
+ /*
+ * Fill means that the $height and $width are the max
+ * Cover means min.
+ */
+ if ($mode === self::MODE_FILL) {
+ if ($ratioH > $ratioW) {
+ $height = $width * $ratio;
+ } else {
+ $width = $height / $ratio;
+ }
+ } else if ($mode === self::MODE_COVER) {
+ if ($ratioH > $ratioW) {
+ $width = $height / $ratio;
+ } else {
+ $height = $width * $ratio;
+ }
+ }
+ }
+
+ if ($height !== $maxHeight && $width !== $maxWidth) {
+ /*
+ * Scale to the nearest power of two
+ */
+ $pow2height = pow(2, ceil(log($height) / log(2)));
+ $pow2width = pow(2, ceil(log($width) / log(2)));
+
+ $ratioH = $height / $pow2height;
+ $ratioW = $width / $pow2width;
+
+ if ($ratioH < $ratioW) {
+ $width = $pow2width;
+ $height = $height / $ratioW;
+ } else {
+ $height = $pow2height;
+ $width = $width / $ratioH;
+ }
+ }
+
+ /*
+ * Make sure the requested height and width fall within the max
+ * of the preview.
+ */
+ if ($height > $maxHeight) {
+ $ratio = $height / $maxHeight;
+ $height = $maxHeight;
+ $width = $width / $ratio;
+ }
+ if ($width > $maxWidth) {
+ $ratio = $width / $maxWidth;
+ $width = $maxWidth;
+ $height = $height / $ratio;
+ }
+
+ return [(int)round($width), (int)round($height)];
+ }
+
+ /**
+ * @param ISimpleFolder $previewFolder
+ * @param ISimpleFile $maxPreview
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @param int $maxWidth,
+ * @param int $maxHeight
+ * @return ISimpleFile
+ * @throws NotFoundException
+ */
+ private function generatePreview(ISimpleFolder $previewFolder, ISimpleFile $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight) {
+ $preview = new Image($maxPreview->getContent());
+
+ if ($crop) {
+ if ($height !== $preview->height() && $width !== $preview->width()) {
+ //Resize
+ $widthR = $preview->width() / $width;
+ $heightR = $preview->height() / $height;
+
+ if ($widthR > $heightR) {
+ $scaleH = $height;
+ $scaleW = $maxWidth / $heightR;
+ } else {
+ $scaleH = $maxHeight / $widthR;
+ $scaleW = $width;
+ }
+ $preview->preciseResize(round($scaleW), round($scaleH));
+ }
+ $cropX = floor(abs($width - $preview->width()) * 0.5);
+ $cropY = 0;
+ $preview->crop($cropX, $cropY, $width, $height);
+ } else {
+ $preview->resize(max($width, $height));
+ }
+
+ $path = $this->generatePath($width, $height, $crop);
+ $file = $previewFolder->newFile($path);
+ $file->putContent($preview->data());
+
+ return $file;
+ }
+
+ /**
+ * @param ISimpleFolder $previewFolder
+ * @param int $width
+ * @param int $height
+ * @param bool $crop
+ * @return ISimpleFile
+ *
+ * @throws NotFoundException
+ */
+ private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop) {
+ $path = $this->generatePath($width, $height, $crop);
+
+ return $previewFolder->getFile($path);
+ }
+
+ /**
+ * Get the specific preview folder for this file
+ *
+ * @return ISimpleFolder
+ */
+ private function getPreviewFolder() {
+ try {
+ $folder = $this->appData->getFolder($this->file->getId());
+ } catch (NotFoundException $e) {
+ $folder = $this->appData->newFolder($this->file->getId());
+ }
+
+ return $folder;
+ }
+}