diff options
author | Roeland Jago Douma <roeland@famdouma.nl> | 2016-08-04 19:41:04 +0200 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2016-11-03 14:00:32 +0100 |
commit | 958c1289b15f5bd877dd7ff3b7cf3540a0c5198a (patch) | |
tree | 94dea79e9d3fb96aa8528743960de15271d4c8b4 | |
parent | b129adfb58eb98a37278dbd5a2f30b52c90cb4fc (diff) | |
download | nextcloud-server-958c1289b15f5bd877dd7ff3b7cf3540a0c5198a.tar.gz nextcloud-server-958c1289b15f5bd877dd7ff3b7cf3540a0c5198a.zip |
New preview generator
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
-rw-r--r-- | core/ajax/preview.php | 28 | ||||
-rw-r--r-- | core/ajax/preview2.php | 36 | ||||
-rw-r--r-- | core/routes.php | 2 | ||||
-rw-r--r-- | lib/private/Preview.php | 2 | ||||
-rw-r--r-- | lib/private/Preview2.php | 366 |
5 files changed, 423 insertions, 11 deletions
diff --git a/core/ajax/preview.php b/core/ajax/preview.php index 6cfba6aef30..aac623b5ce6 100644 --- a/core/ajax/preview.php +++ b/core/ajax/preview.php @@ -49,19 +49,27 @@ if ($maxX === 0 || $maxY === 0) { exit; } -$info = \OC\Files\Filesystem::getFileInfo($file); +$folder = \OC::$server->getUserFolder(); -if (!$info instanceof OCP\Files\FileInfo || !$always && !\OC::$server->getPreviewManager()->isAvailable($info)) { +try { + $file = $folder->get($file); +} catch (\OCP\Files\NotFoundException $e) { + return \OC_Response::setStatus(404); +} + +if (!$file instanceof OCP\Files\File || !$always && !\OC::$server->getPreviewManager()->isAvailable($file)) { \OC_Response::setStatus(404); } else if (!$info->isReadable()) { \OC_Response::setStatus(403); } else { - $preview = new \OC\Preview(\OC_User::getUser(), 'files'); - $preview->setFile($file, $info); - $preview->setMaxX($maxX); - $preview->setMaxY($maxY); - $preview->setScalingUp($scalingUp); - $preview->setMode($mode); - $preview->setKeepAspect($keepAspect); - $preview->showPreview(); + $preview = new \OC\Preview2( + \OC::$server->getRootFolder(), + \OC::$server->getConfig(), + \OC::$server->getPreviewManager(), + $file + ); + $image = $preview->getPreview($maxX, $maxY, !$keepAspect, $mode); + + header('Content-Type: ' . $image->getMimeType()); + echo $image->getContent(); } diff --git a/core/ajax/preview2.php b/core/ajax/preview2.php new file mode 100644 index 00000000000..b2bbf64c6d2 --- /dev/null +++ b/core/ajax/preview2.php @@ -0,0 +1,36 @@ +<?php + +\OC_Util::checkLoggedIn(); +\OC::$server->getSession()->close(); + +$file = array_key_exists('file', $_GET) ? (string)$_GET['file'] : ''; +$maxX = array_key_exists('x', $_GET) ? (int)$_GET['x'] : '32'; +$maxY = array_key_exists('y', $_GET) ? (int)$_GET['y'] : '32'; +$keepAspect = array_key_exists('a', $_GET) ? true : false; +$always = array_key_exists('forceIcon', $_GET) ? (bool)$_GET['forceIcon'] : true; +$mode = array_key_exists('mode', $_GET) ? $_GET['mode'] : 'fill'; + +if ($file === '') { + //400 Bad Request + \OC_Response::setStatus(400); + \OCP\Util::writeLog('core-preview', 'No file parameter was passed', \OCP\Util::DEBUG); + exit; +} + +if ($maxX === 0 || $maxY === 0) { + //400 Bad Request + \OC_Response::setStatus(400); + \OCP\Util::writeLog('core-preview', 'x and/or y set to 0', \OCP\Util::DEBUG); + exit; +} + +$userFolder = \OC::$server->getUserFolder(); +$file = $userFolder->get($file); + +$p = new \OC\Preview2(\OC::$server->getRootFolder(), + \OC::$server->getConfig(), + \OC::$server->getPreviewManager(), + $file); + +$p->getPreview($maxX, $maxY, !$keepAspect, $mode); + diff --git a/core/routes.php b/core/routes.php index 7978001af7d..4b73a86042a 100644 --- a/core/routes.php +++ b/core/routes.php @@ -70,6 +70,8 @@ $this->create('search_ajax_search', '/core/search') // Routing $this->create('core_ajax_preview', '/core/preview') ->actionInclude('core/ajax/preview.php'); +$this->create('core_ajax_preview2', '/core/preview2') + ->actionInclude('core/ajax/preview2.php'); $this->create('core_ajax_preview', '/core/preview.png') ->actionInclude('core/ajax/preview.php'); $this->create('core_ajax_update', '/core/ajax/update.php') diff --git a/lib/private/Preview.php b/lib/private/Preview.php index ccaec738caf..caa1e89bacc 100644 --- a/lib/private/Preview.php +++ b/lib/private/Preview.php @@ -125,7 +125,7 @@ class Preview { $sysConfig = \OC::$server->getConfig(); $this->configMaxWidth = $sysConfig->getSystemValue('preview_max_x', 2048); $this->configMaxHeight = $sysConfig->getSystemValue('preview_max_y', 2048); - $this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 2); + $this->maxScaleFactor = $sysConfig->getSystemValue('preview_max_scale_factor', 1); //save parameters $this->setFile($file); diff --git a/lib/private/Preview2.php b/lib/private/Preview2.php new file mode 100644 index 00000000000..11e699a2e72 --- /dev/null +++ b/lib/private/Preview2.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; + +use OC\Files\View; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IImage; +use OCP\Image; +use OCP\IPreview; +use OCP\Preview\IProvider; + +class Preview2 { + //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; + + public function __construct( + IRootFolder $rootFolder, + IConfig $config, + IPreview $previewManager, + File $file + ) { + $this->rootFolder = $rootFolder; + $this->config = $config; + $this->file = $file; + $this->previewManager = $previewManager; + } + + /** + * 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 File + * @throws NotFoundException + */ + public function getPreview($width = -1, $height = -1, $crop = false, $mode = Preview2::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 Folder $previewFolder + * @return File + * @throws NotFoundException + */ + private function getMaxPreview(Folder $previewFolder) { + $nodes = $previewFolder->getDirectoryListing(); + + /** @var File $node */ + 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 File $file + * @return int[] + */ + private function getPreviewSize(File $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 Folder $previewFolder + * @param File $maxPreview + * @param int $width + * @param int $height + * @param bool $crop + * @param int $maxWidth, + * @param int $maxHeight + * @return File + * @throws NotFoundException + */ + private function generatePreview(Folder $previewFolder, File $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 Folder $previewFolder + * @param int $width + * @param int $height + * @param bool $crop + * @return File + * + * @throws NotFoundException + */ + private function getCachedPreview(Folder $previewFolder, $width, $height, $crop) { + $path = $this->generatePath($width, $height, $crop); + + return $previewFolder->get($path); + } + + /** + * Get the specific preview folder for this file + * + * @return Folder + */ + private function getPreviewFolder() { + $user = $this->file->getOwner(); + $user = $user->getUID(); + + $previewRoot = $this->rootFolder->getUserFolder($user); + $previewRoot = $previewRoot->getParent(); + + try { + /** @var Folder $previewRoot */ + $previewRoot = $previewRoot->get(self::THUMBNAILS_FOLDER); + } catch (NotFoundException $e) { + $previewRoot = $previewRoot->newFolder(self::THUMBNAILS_FOLDER); + } + + try { + $previewFolder = $previewRoot->get($this->file->getId()); + } catch (NotFoundException $e) { + $previewFolder = $previewRoot->newFolder($this->file->getId()); + } + + return $previewFolder; + } +} |