Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/gallery.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Paroz <github@oparoz.com>2015-01-11 21:00:38 +0300
committerOlivier Paroz <github@oparoz.com>2015-01-11 21:00:38 +0300
commitef0e637ad109845f9ba9af8f1a9a5ee816da1617 (patch)
tree86ad5f58f51032153da620674e63cb32f0017110 /preview
parent229cb7831b9ff9021828465693feae72099bf4c0 (diff)
PreviewService refactoring
Diffstat (limited to 'preview')
-rw-r--r--preview/preview.php351
1 files changed, 351 insertions, 0 deletions
diff --git a/preview/preview.php b/preview/preview.php
new file mode 100644
index 00000000..85ca4741
--- /dev/null
+++ b/preview/preview.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * ownCloud - galleryplus
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Olivier Paroz <owncloud@interfasys.ch>
+ *
+ * @copyright Olivier Paroz 2014-2015
+ */
+
+namespace OCA\GalleryPlus\Preview;
+
+use OCP\Files\File;
+
+use OCP\AppFramework\Http;
+
+use OCA\GalleryPlus\Utility\SmarterLogger;
+
+/**
+ * Generates previews
+ *
+ * @todo On OC8.1, replace \OC\Preview with OC::$server->getPreviewManager()
+ *
+ * @package OCA\GalleryPlus\Preview
+ */
+class Preview {
+
+ /**
+ * @type SmarterLogger
+ */
+ private $logger;
+ /**
+ * @type string
+ */
+ private $owner;
+ /**
+ * @type \OC\Preview
+ */
+ private $preview;
+ /**
+ * @type File
+ */
+ private $file;
+
+ /**
+ * Constructor
+ *
+ * @param SmarterLogger $logger
+ */
+ public function __construct(
+ SmarterLogger $logger
+ ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Initialises the object
+ *
+ * @fixme Private API, but can't use the PreviewManager yet as it's incomplete
+ *
+ * @param string $owner
+ * @param File $file
+ * @param string $imagePathFromFolder
+ */
+ public function setupView($owner, $file, $imagePathFromFolder) {
+ $this->owner = $owner;
+ $this->file = $file;
+ $this->preview = new \OC\Preview($owner, 'files', $imagePathFromFolder);
+ }
+
+ /**
+ * Decides if we should download the file instead of generating a preview
+ *
+ * @param bool $animatedPreview
+ * @param bool $download
+ *
+ * @return bool
+ */
+ public function previewRequired($animatedPreview, $download) {
+ $svgPreviewRequired = $this->isSvgPreviewRequired($this->preview);
+ $gifPreviewRequired = $this->isGifPreviewRequired($animatedPreview);
+
+ return $svgPreviewRequired && $gifPreviewRequired && !$download;
+ }
+
+ /**
+ * Decides if we should download the SVG or generate a preview
+ *
+ * @param \OC\Preview $preview
+ *
+ * @return bool
+ */
+ private function isSvgPreviewRequired($preview) {
+ $mime = $this->file->getMimeType();
+
+ /**
+ * SVGs are downloaded if the SVG converter is disabled
+ * Files of any media type are downloaded if requested by the client
+ */
+ if ($mime === 'image/svg+xml' && !$preview->isMimeSupported($mime)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Decides if we should download the GIF or generate a preview
+ *
+ * @param bool $animatedPreview
+ *
+ * @return bool
+ */
+ private function isGifPreviewRequired($animatedPreview) {
+ $mime = $this->file->getMimeType();
+ $animatedGif = $this->isGifAnimated($this->file);
+
+ /**
+ * GIFs are downloaded if they're animated and we want to show
+ * animations
+ */
+ if ($mime === 'image/gif' && $animatedPreview && $animatedGif) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests if a GIF is animated
+ *
+ * An animated gif contains multiple "frames", with each frame having a
+ * header made up of:
+ * * a static 4-byte sequence (\x00\x21\xF9\x04)
+ * * 4 variable bytes
+ * * a static 2-byte sequence (\x00\x2C) (Photoshop uses \x00\x21)
+ *
+ * We read through the file until we reach the end of the file, or we've
+ * found at least 2 frame headers
+ *
+ * @link http://php.net/manual/en/function.imagecreatefromgif.php#104473
+ *
+ * @param File $file
+ *
+ * @return bool
+ */
+ private function isGifAnimated($file) {
+ $fileHandle = $file->fopen('rb');
+ $count = 0;
+ while (!feof($fileHandle) && $count < 2) {
+ $chunk = fread($fileHandle, 1024 * 100); //read 100kb at a time
+ $count += preg_match_all(
+ '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches
+ );
+ }
+
+ fclose($fileHandle);
+
+ return $count > 1;
+ }
+
+ /**
+ * Returns a preview based on OC's preview class and our custom methods
+ *
+ * We don't throw an exception when the preview generator fails,
+ * instead, until the Preview class is fixed, we send the mime
+ * icon along with a 415 error code.
+ *
+ * @fixme setKeepAspect is missing from public interface.
+ * https://github.com/owncloud/core/issues/12772
+ *
+ * @param int $maxX
+ * @param int $maxY
+ * @param bool $keepAspect
+ *
+ * @return array
+ */
+ public function preparePreview($maxX, $maxY, $keepAspect) {
+ $this->preview->setMaxX($maxX);
+ $this->preview->setMaxY($maxY);
+ $this->preview->setScalingUp(false); // TODO: Need to read from settings
+ $this->preview->setKeepAspect($keepAspect);
+ $this->logger->debug("[PreviewService] Generating a new preview");
+ /** @type \OC_Image $previewData */
+ $previewData = $this->preview->getPreview();
+
+ if ($previewData->valid()) {
+ $perfectPreview = $this->previewValidator($maxX, $maxY);
+ } else {
+ $this->logger->debug("[PreviewService] ERROR! Did not get a preview");
+ $perfectPreview = array(
+ 'preview' => $this->getMimeIcon(),
+ 'status' => Http::STATUS_UNSUPPORTED_MEDIA_TYPE
+ );
+ }
+ $perfectPreview['mimetype'] = 'image/png'; // Previews are always sent as PNG
+
+ return $perfectPreview;
+ }
+
+ /**
+ * Makes sure we return previews of the asked dimensions and fix the cache
+ * if necessary
+ *
+ * The Preview class of OC7 sometimes return previews which are either
+ * wider or smaller than the asked dimensions. This happens when one of the
+ * original dimension is smaller than what is asked for
+ *
+ * @param int $maxX
+ * @param int $maxY
+ *
+ * @return array<resource,int>
+ */
+ private function previewValidator($maxX, $maxY) {
+ $previewData = $this->preview->getPreview();
+ $previewX = $previewData->width();
+ $previewY = $previewData->height();
+ $minWidth = 200; // Only fixing the square thumbnails
+
+ if (($previewX > $maxX
+ || ($previewX < $maxX || $previewY < $maxY)
+ && $maxX === $minWidth)
+ ) {
+ $fixedPreview = $this->fixPreview($previewData, $maxX, $maxY);
+ $previewData = $this->fixPreviewCache($fixedPreview);
+ }
+
+ return array(
+ 'preview' => $previewData,
+ 'status' => Http::STATUS_OK
+ );
+ }
+
+ /**
+ * Makes a preview fit in the asked dimension and fills the empty space
+ *
+ * @param \OC_Image $previewData
+ * @param int $maxX
+ * @param int $maxY
+ *
+ * @return resource
+ */
+ private function fixPreview($previewData, $maxX, $maxY) {
+ $previewWidth = $previewData->width();
+ $previewHeight = $previewData->height();
+ $fixedPreview = imagecreatetruecolor($maxX, $maxY); // Creates the canvas
+
+ // We make the background transparent
+ imagealphablending($fixedPreview, false);
+ $transparency = imagecolorallocatealpha($fixedPreview, 0, 0, 0, 127);
+ imagefill($fixedPreview, 0, 0, $transparency);
+ imagesavealpha($fixedPreview, true);
+
+ $newDimensions = $this->calculateNewDimensions($previewWidth, $previewHeight, $maxX, $maxY);
+
+ imagecopyresampled(
+ $fixedPreview, $previewData->resource(), $newDimensions['newX'], $newDimensions['newY'],
+ 0, 0, $newDimensions['newWidth'], $newDimensions['newHeight'],
+ $previewWidth, $previewHeight
+ );
+
+ return $fixedPreview;
+ }
+
+ /**
+ * Calculates the new dimensions so that it fits in the dimensions requested by the client
+ *
+ * @link https://stackoverflow.com/questions/3050952/resize-an-image-and-fill-gaps-of-proportions-with-a-color
+ *
+ * @param int $previewWidth
+ * @param int $previewHeight
+ * @param int $maxX
+ * @param int $maxY
+ *
+ * @return array
+ */
+ private function calculateNewDimensions($previewWidth, $previewHeight, $maxX, $maxY) {
+ if (($previewWidth / $previewHeight) >= ($maxX / $maxY)) {
+ $newWidth = $maxX;
+ $newHeight = $previewHeight * ($maxX / $previewWidth);
+ $newX = 0;
+ $newY = round(abs($maxY - $newHeight) / 2);
+ } else {
+ $newWidth = $previewWidth * ($maxY / $previewHeight);
+ $newHeight = $maxY;
+ $newX = round(abs($maxX - $newWidth) / 2);
+ $newY = 0;
+ }
+
+ return array(
+ 'newX' => $newX,
+ 'newY' => $newY,
+ 'newWidth' => $newWidth,
+ 'newHeight' => $newHeight,
+ );
+ }
+
+ /**
+ * Fixes the preview cache by replacing the broken thumbnail with ours
+ *
+ * @param resource $fixedPreview
+ *
+ * @return mixed
+ */
+ private function fixPreviewCache($fixedPreview) {
+ $owner = $this->owner;
+ $file = $this->file;
+ $preview = $this->preview;
+ $fixedPreviewObject = new \OC_Image($fixedPreview); // FIXME: Private API
+ $previewData = $preview->getPreview();
+
+ // Get the location where the broken thumbnail is stored
+ // FIXME: Private API
+ $thumbnailFolder = \OC::$SERVERROOT . '/data/' . $owner . '/';
+ $thumbnail = $thumbnailFolder . $preview->isCached($file->getId());
+
+ // Caching it for next time
+ if ($fixedPreviewObject->save($thumbnail)) {
+ $previewData = $fixedPreviewObject->data();
+ }
+
+ return $previewData;
+ }
+
+ /**
+ * Returns the media type icon when the server fails to generate a preview
+ *
+ * It's not more efficient for the browser to download the mime icon
+ * directly and won't be necessary once the Preview class sends the mime
+ * icon when it can't generate a proper preview
+ * https://github.com/owncloud/core/pull/12546
+ *
+ * @return \OC_Image
+ */
+ private function getMimeIcon() {
+ $mime = $this->file->getMimeType();
+ $iconData = new \OC_Image(); // FIXME: Private API
+
+ // FIXME: private API
+ $image = \OC::$SERVERROOT . mimetype_icon($mime);
+ // OC8 version
+ //$image = $this->serverRoot() . \OCP\Template::mimetype_icon($mime);
+
+ $iconData->loadFromFile($image);
+
+ return $iconData;
+ }
+
+} \ No newline at end of file