diff options
author | Roeland Jago Douma <roeland@famdouma.nl> | 2016-10-14 21:21:37 +0200 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2016-11-03 14:00:33 +0100 |
commit | d6f1287ae6407a8f1ef0d018fe197e0f920ed192 (patch) | |
tree | db743f2c5022a00366fc45cb950aa8c1c6460317 /lib/private/Preview/Generator.php | |
parent | 02525fd98bb3994199ebd877968574610f6898ee (diff) | |
download | nextcloud-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.php | 366 |
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; + } +} |