diff --git a/apps/files/css/detailsView.css b/apps/files/css/detailsView.css index ffead92312..8eded7acda 100644 --- a/apps/files/css/detailsView.css +++ b/apps/files/css/detailsView.css @@ -19,6 +19,27 @@ float: left; } +#app-sidebar .thumbnailContainer.image { + margin-left: -15px; + margin-right: -35px; /* 15 + 20 for the close button */ + margin-top: -15px; +} + +#app-sidebar .image .thumbnail { + width:100%; + display:block; + height: 250px; + background-repeat: no-repeat; + background-position: 50% top; + background-size: 100%; + float: none; + margin: 0; +} + +#app-sidebar .image.portrait .thumbnail { + background-size: contain; +} + #app-sidebar .thumbnail { width: 75px; height: 75px; diff --git a/apps/files/js/fileinfomodel.js b/apps/files/js/fileinfomodel.js index 05060854fb..1c850239cd 100644 --- a/apps/files/js/fileinfomodel.js +++ b/apps/files/js/fileinfomodel.js @@ -52,6 +52,15 @@ return this.get('mimetype') === 'httpd/unix-directory'; }, + /** + * Returns whether this file is an image + * + * @return {boolean} true if this is an image, false otherwise + */ + isImage: function() { + return this.get('mimetype').substr(0, 6) === 'image/'; + }, + /** * Returns the full path to this file * diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index ac96d58701..9593ee79e6 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1359,6 +1359,12 @@ if (options.y) { urlSpec.y = options.y; } + if (options.a) { + urlSpec.a = options.a; + } + if (options.mode) { + urlSpec.mode = options.mode; + } if (etag){ // use etag as cache buster @@ -1377,9 +1383,14 @@ img.onload = function(){ // if loading the preview image failed (no preview for the mimetype) then img.width will < 5 if (img.width > 5) { - ready(previewURL); + ready(previewURL, img); + } else if (options.error) { + options.error(); } }; + if (options.error) { + img.onerror = options.error; + } img.src = previewURL; }, diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index f647a0de2a..785eed8d71 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -10,7 +10,7 @@ (function() { var TEMPLATE = - '' + + '
' + '
' + '

{{name}}

' + '
' + @@ -106,6 +106,7 @@ if (this.model) { var isFavorite = (this.model.get('tags') || []).indexOf(OC.TAG_FAVORITE) >= 0; this.$el.html(this.template({ + type: this.model.isImage()? 'image': '', nameLabel: t('files', 'Name'), name: this.model.get('displayName') || this.model.get('name'), pathLabel: t('files', 'Path'), @@ -123,16 +124,51 @@ // TODO: we really need OC.Previews var $iconDiv = this.$el.find('.thumbnail'); + $iconDiv.addClass('icon-loading'); + $container = this.$el.find('.thumbnailContainer'); if (!this.model.isDirectory()) { this._fileList.lazyLoadPreview({ path: this.model.getFullPath(), mime: this.model.get('mimetype'), etag: this.model.get('etag'), - x: 75, - y: 75, - callback: function(previewUrl) { - $iconDiv.css('background-image', 'url("' + previewUrl + '")'); - } + y: this.model.isImage() ? 250: 75, + x: this.model.isImage() ? 99999 /* only limit on y */ : 75, + a: this.model.isImage() ? 1 : null, + callback: function(previewUrl, img) { + $iconDiv.previewImg = previewUrl; + if (img) { + $iconDiv.removeClass('icon-loading'); + if(img.height > img.width) { + $container.addClass('portrait'); + } + } + if (this.model.isImage() && img) { + $iconDiv.parent().addClass('image'); + var targetHeight = img.height / window.devicePixelRatio; + if (targetHeight <= 75) { + $container.removeClass('image'); // small enough to fit in normaly + targetHeight = 75; + } + } else { + targetHeight = 75; + } + + // only set background when we have an actual preview + // when we dont have a preview we show the mime icon in the error handler + if (img) { + $iconDiv.css({ + 'background-image': 'url("' + previewUrl + '")', + 'height': targetHeight + }); + } + }.bind(this), + error: function() { + $iconDiv.removeClass('icon-loading'); + this.$el.find('.thumbnailContainer').removeClass('image'); //fall back to regular view + $iconDiv.css({ + 'background-image': 'url("' + $iconDiv.previewImg + '")' + }); + }.bind(this) }); } else { // TODO: special icons / shared / external diff --git a/core/ajax/preview.php b/core/ajax/preview.php index fc98d80eb0..baa0ed4ec6 100644 --- a/core/ajax/preview.php +++ b/core/ajax/preview.php @@ -31,6 +31,7 @@ $maxY = array_key_exists('y', $_GET) ? (int)$_GET['y'] : '36'; $scalingUp = array_key_exists('scalingup', $_GET) ? (bool)$_GET['scalingup'] : true; $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 @@ -56,6 +57,7 @@ if (!$info instanceof OCP\Files\FileInfo || !$always && !\OC::$server->getPrevie $preview->setMaxX($maxX); $preview->setMaxY($maxY); $preview->setScalingUp($scalingUp); + $preview->setMode($mode); $preview->setKeepAspect($keepAspect); $preview->showPreview(); } diff --git a/lib/private/preview.php b/lib/private/preview.php index 5dcab476a4..978da1161c 100644 --- a/lib/private/preview.php +++ b/lib/private/preview.php @@ -38,6 +38,9 @@ class Preview { //the thumbnail folder const THUMBNAILS_FOLDER = 'thumbnails'; + const MODE_FILL = 'fill'; + const MODE_COVER = 'cover'; + //config private $maxScaleFactor; /** @var int maximum width allowed for a preview */ @@ -56,6 +59,7 @@ class Preview { private $scalingUp; private $mimeType; private $keepAspect = false; + private $mode = self::MODE_FILL; //used to calculate the size of the preview to generate /** @var int $maxPreviewWidth max width a preview can have */ @@ -331,6 +335,19 @@ class Preview { return $this; } + /** + * Set whether to cover or fill the specified dimensions + * + * @param string $mode + * + * @return \OC\Preview + */ + public function setMode($mode) { + $this->mode = $mode; + + return $this; + } + /** * Sets whether we need to generate a preview which keeps the aspect ratio of the original file * @@ -531,14 +548,22 @@ class Preview { * @param int $askedWidth * @param int $askedHeight * + * @param int $originalWidth + * @param int $originalHeight * @return \int[] */ - private function applyAspectRatio($askedWidth, $askedHeight) { - $originalRatio = $this->maxPreviewWidth / $this->maxPreviewHeight; + private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) { + if(!$originalWidth){ + $originalWidth= $this->maxPreviewWidth; + } + if (!$originalHeight) { + $originalHeight = $this->maxPreviewHeight; + } + $originalRatio = $originalWidth / $originalHeight; // Defines the box in which the preview has to fit $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1; - $askedWidth = min($askedWidth, $this->maxPreviewWidth * $scaleFactor); - $askedHeight = min($askedHeight, $this->maxPreviewHeight * $scaleFactor); + $askedWidth = min($askedWidth, $originalWidth * $scaleFactor); + $askedHeight = min($askedHeight, $originalHeight * $scaleFactor); if ($askedWidth / $originalRatio < $askedHeight) { // width restricted @@ -550,6 +575,32 @@ class Preview { return [(int)$askedWidth, (int)$askedHeight]; } + /** + * Resizes the boundaries to cover the area + * + * @param int $askedWidth + * @param int $askedHeight + * @param int $previewWidth + * @param int $previewHeight + * @return \int[] + */ + private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) { + $originalRatio = $previewWidth / $previewHeight; + // Defines the box in which the preview has to fit + $scaleFactor = $this->scalingUp ? $this->maxScaleFactor : 1; + $askedWidth = min($askedWidth, $previewWidth * $scaleFactor); + $askedHeight = min($askedHeight, $previewHeight * $scaleFactor); + + if ($askedWidth / $originalRatio > $askedHeight) { + // height restricted + $askedHeight = round($askedWidth / $originalRatio); + } else { + $askedWidth = round($askedHeight * $originalRatio); + } + + return [(int)$askedWidth, (int)$askedHeight]; + } + /** * Makes sure an upscaled preview doesn't end up larger than the max dimensions defined in the * config @@ -791,7 +842,15 @@ class Preview { */ if ($this->keepAspect) { list($askedWidth, $askedHeight) = - $this->applyAspectRatio($askedWidth, $askedHeight); + $this->applyAspectRatio($askedWidth, $askedHeight, $previewWidth, $previewHeight); + } + + if ($this->mode === self::MODE_COVER) { + list($scaleWidth, $scaleHeight) = + $this->applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight); + } else { + $scaleWidth = $askedWidth; + $scaleHeight = $askedHeight; } /** @@ -799,7 +858,7 @@ class Preview { * Takes the scaling ratio into consideration */ list($newPreviewWidth, $newPreviewHeight) = $this->scale( - $image, $askedWidth, $askedHeight, $previewWidth, $previewHeight + $image, $scaleWidth, $scaleHeight, $previewWidth, $previewHeight ); // The preview has been resized and should now have the asked dimensions @@ -1000,6 +1059,9 @@ class Preview { if ($this->keepAspect && !$isMaxPreview) { $previewPath .= '-with-aspect'; } + if ($this->mode === self::MODE_COVER) { + $previewPath .= '-cover'; + } $previewPath .= '.png'; return $previewPath;