diff --git a/apps/Launcher/qml/FileBrowser.qml b/apps/Launcher/qml/FileBrowser.qml index 1303eff6..f088a5de 100644 --- a/apps/Launcher/qml/FileBrowser.qml +++ b/apps/Launcher/qml/FileBrowser.qml @@ -60,12 +60,14 @@ Rectangle { GradientStop { position: 0.0; color: Style.placeholderTopColor } GradientStop { position: 1.0; color: Style.placeholderBottomColor } } - Image { - id: thumbnail - anchors.fill: parent - source: "image://thumbnail/" + filePath - fillMode: Image.PreserveAspectFit - } + visible: thumbnail.status !== Image.Ready + } + Image { + id: thumbnail + anchors.fill: placeholder + fillMode: Image.PreserveAspectFit + cache: false + source: "image://thumbnail/" + filePath } Text { diff --git a/doc/Changelog.md b/doc/Changelog.md index 61edc2dd..75dd31e2 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,6 +3,11 @@ Changelog {#Changelog} # git master (1.1.0) +* [46](https://github.com/BlueBrain/Tide/pull/46): + Improved Launcher: + - The creation and caching of file thumbnails is more efficient. + - File thumbnails are displayed with their correct aspect ratio. + Session preview images are of better quality (no longer pixelated). * [38](https://github.com/BlueBrain/Tide/pull/38): Fix a segfault that occured when opening grayscale images. * [36](https://github.com/BlueBrain/Tide/pull/36): diff --git a/tests/cpp/core/StateSerializationTests.cpp b/tests/cpp/core/StateSerializationTests.cpp index 8009a1fe..4adf2f54 100644 --- a/tests/cpp/core/StateSerializationTests.cpp +++ b/tests/cpp/core/StateSerializationTests.cpp @@ -414,16 +414,17 @@ BOOST_AUTO_TEST_CASE( testStateSerializationToFile ) BOOST_CHECK_LT( previewError, 0.02f ); // 4) Test restoring - DisplayGroupPtr loadedDisplayGroup = boost::make_shared( wallSize ); - StateSerializationHelper loader( loadedDisplayGroup ); + DisplayGroupPtr loadedGroup = boost::make_shared( wallSize ); + StateSerializationHelper loader( loadedGroup ); BOOST_CHECK( loader.load( TEST_DIR + "/test.dcx" )); - BOOST_REQUIRE_EQUAL( loadedDisplayGroup->getContentWindows().size(), + BOOST_REQUIRE_EQUAL( loadedGroup->getContentWindows().size(), displayGroup->getContentWindows().size( )); - BOOST_REQUIRE_EQUAL( loadedDisplayGroup->getShowWindowTitles(), + BOOST_REQUIRE_EQUAL( loadedGroup->getShowWindowTitles(), displayGroup->getShowWindowTitles()); - BOOST_REQUIRE_EQUAL( loadedDisplayGroup->getCoordinates(), QRectF( QPointF( 0, 0 ), wallSize )); - checkWindow( loadedDisplayGroup->getContentWindows()[0] ); + BOOST_REQUIRE_EQUAL( loadedGroup->getCoordinates(), + QRectF( QPointF( 0, 0 ), wallSize )); + checkWindow( loadedGroup->getContentWindows()[0] ); // 4) Cleanup cleanupTestDir(); diff --git a/tests/resources/state_v0.dcxpreview b/tests/resources/state_v0.dcxpreview index 5f7cc2d0..51089f10 100644 Binary files a/tests/resources/state_v0.dcxpreview and b/tests/resources/state_v0.dcxpreview differ diff --git a/tide/core/StatePreview.cpp b/tide/core/StatePreview.cpp index 45e243bb..9fd51f22 100644 --- a/tide/core/StatePreview.cpp +++ b/tide/core/StatePreview.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -55,12 +55,11 @@ namespace { -static const QSize PREVIEW_IMAGE_SIZE( 512, 512 ); -static const QSize THUMBNAIL_SIZE( 128, 128 ); +const QSize PREVIEW_IMAGE_SIZE( 512, 512 ); } StatePreview::StatePreview( const QString &dcxFileName ) - : dcxFileName_( dcxFileName ) + : _dcxFileName( dcxFileName ) { } @@ -71,12 +70,12 @@ QString StatePreview::getFileExtension() QImage StatePreview::getImage() const { - return previewImage_; + return _previewImage; } QString StatePreview::previewFilename() const { - QFileInfo fileinfo( dcxFileName_ ); + QFileInfo fileinfo( _dcxFileName ); const QString extension = fileinfo.suffix().toLower(); if( extension != "dcx" ) @@ -84,7 +83,7 @@ QString StatePreview::previewFilename() const put_flog( LOG_WARN, "wrong state file extension: '%s' for session: '%s'" "(expected: .dcx)", extension.toLocal8Bit().constData( ), - dcxFileName_.toLocal8Bit().constData( )); + _dcxFileName.toLocal8Bit().constData( )); return QString(); } return fileinfo.path() + "/" + fileinfo.completeBaseName() + getFileExtension(); @@ -117,7 +116,8 @@ void StatePreview::generateImage( const QSize& wallDimensions, const QString& filename = window->getContent()->getURI(); ThumbnailGeneratorPtr generator = - ThumbnailGeneratorFactory::getGenerator( filename, THUMBNAIL_SIZE ); + ThumbnailGeneratorFactory::getGenerator( filename, + area.size().toSize( )); const QImage image = generator->generate( filename ); painter.drawImage( area, image ); @@ -125,12 +125,12 @@ void StatePreview::generateImage( const QSize& wallDimensions, painter.end(); - previewImage_ = preview; + _previewImage = preview; } bool StatePreview::saveToFile() const { - const bool success = previewImage_.save( previewFilename(), "PNG" ); + const bool success = _previewImage.save( previewFilename(), "PNG" ); if( !success ) put_flog( LOG_ERROR, "Saving StatePreview image failed: '%s'", @@ -142,9 +142,5 @@ bool StatePreview::saveToFile() const bool StatePreview::loadFromFile() { QImageReader reader( previewFilename( )); - if ( reader.canRead( )) - { - return reader.read( &previewImage_ ); - } - return false; + return reader.canRead() && reader.read( &_previewImage ); } diff --git a/tide/core/StatePreview.h b/tide/core/StatePreview.h index e933ee07..d53b79e4 100644 --- a/tide/core/StatePreview.h +++ b/tide/core/StatePreview.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -96,8 +96,8 @@ class StatePreview protected: QString previewFilename() const; - QString dcxFileName_; - QImage previewImage_; + QString _dcxFileName; + QImage _previewImage; }; #endif // STATEPREVIEW_H diff --git a/tide/core/thumbnail/DefaultThumbnailGenerator.cpp b/tide/core/thumbnail/DefaultThumbnailGenerator.cpp index b672deae..ab9c78fa 100644 --- a/tide/core/thumbnail/DefaultThumbnailGenerator.cpp +++ b/tide/core/thumbnail/DefaultThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -39,15 +39,14 @@ #include "DefaultThumbnailGenerator.h" -DefaultThumbnailGenerator::DefaultThumbnailGenerator(const QSize &size) - : ThumbnailGenerator(size) +DefaultThumbnailGenerator::DefaultThumbnailGenerator( const QSize& size ) + : ThumbnailGenerator( size ) { } -QImage DefaultThumbnailGenerator::generate(const QString& filename) const +QImage DefaultThumbnailGenerator::generate( const QString& ) const { - QImage img = createGradientImage( QColor(Qt::black), QColor(Qt::white) ); - paintText(img, "FILE"); - addMetadataToImage(img, filename); + QImage img = createGradientImage( QColor( Qt::black ), QColor( Qt::white )); + paintText( img, "FILE" ); return img; } diff --git a/tide/core/thumbnail/DefaultThumbnailGenerator.h b/tide/core/thumbnail/DefaultThumbnailGenerator.h index eef53cf8..bd09ced2 100644 --- a/tide/core/thumbnail/DefaultThumbnailGenerator.h +++ b/tide/core/thumbnail/DefaultThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -45,9 +45,9 @@ class DefaultThumbnailGenerator : public ThumbnailGenerator { public: - DefaultThumbnailGenerator(const QSize& size); + DefaultThumbnailGenerator( const QSize& size ); - QImage generate(const QString& filename) const override; + QImage generate( const QString& filename ) const final; }; -#endif // DEFAULTTHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/FolderThumbnailGenerator.cpp b/tide/core/thumbnail/FolderThumbnailGenerator.cpp index 7e12e3a0..f8c2d5d7 100644 --- a/tide/core/thumbnail/FolderThumbnailGenerator.cpp +++ b/tide/core/thumbnail/FolderThumbnailGenerator.cpp @@ -39,14 +39,14 @@ #include "FolderThumbnailGenerator.h" -#include -#include - +#include "ContentFactory.h" #include "ThumbnailGeneratorFactory.h" #include "ThumbnailGenerator.h" -#include "ContentFactory.h" #include "log.h" +#include +#include + namespace { const int FOLDER_THUMBNAILS_X = 2; @@ -57,14 +57,13 @@ const QString FOLDER_TEXT( "folder" ); FolderThumbnailGenerator::FolderThumbnailGenerator( const QSize& size ) : ThumbnailGenerator( size ) -{ -} +{} QImage FolderThumbnailGenerator::generate( const QString& filename ) const { const QDir dir( filename ); if( dir.exists( )) - return createFolderImage( dir, true ); + return _createFolderImage( dir, true ); put_flog( LOG_ERROR, "invalid directory: %s", filename.toLocal8Bit().constData( )); @@ -72,60 +71,35 @@ QImage FolderThumbnailGenerator::generate( const QString& filename ) const } QImage -FolderThumbnailGenerator::generatePlaceholderImage( const QDir& dir ) const +FolderThumbnailGenerator::_createFolderImage( const QDir& dir, + const bool generateThumbnails ) const { QImage img = createGradientImage( Qt::black, Qt::white ); - addMetadataToImage( img, dir.path( )); - return img; -} -void FolderThumbnailGenerator::addMetadataToImage( QImage& img, - const QString& url ) const -{ - img.setText( "dir", "true" ); - ThumbnailGenerator::addMetadataToImage( img, url ); -} - -QVector -FolderThumbnailGenerator::calculatePlacement( int nX, int nY, float padding, - float totalWidth, - float totalHeight ) const -{ - const float totalPaddingWidth = padding*(nX+1); - const float imageWidth = (1.0f-totalPaddingWidth)/(float)nX; + const QFileInfoList& fileList = _getSupportedFilesInDir( dir ); - const float totalPaddingHeight = padding*(nY+1); - const float imageHeight = (1.0f-totalPaddingHeight)/(float)nY; + if( generateThumbnails && fileList.size() > 0 ) + _paintThumbnailsMosaic( img, fileList ); + else + paintText( img, FOLDER_TEXT ); - QVector rect; - for( int j = 0; j < nY; ++j ) - { - const float y = padding + j*(imageHeight+padding); - for( int i = 0; i < nX; ++i ) - { - const float x = padding + i*(imageWidth+padding); - rect.append( QRect( x*totalWidth, y*totalHeight, - imageWidth*totalWidth, - imageHeight*totalHeight )); - } - } - return rect; + return img; } -void FolderThumbnailGenerator::paintThumbnailsMosaic( QImage& img, - const QFileInfoList& - fileList) const +void FolderThumbnailGenerator::_paintThumbnailsMosaic( QImage& img, + const QFileInfoList& + fileList ) const { const int numPreviews = std::min( FOLDER_THUMBNAILS_X*FOLDER_THUMBNAILS_Y, fileList.size( )); if( numPreviews == 0 ) return; - QVector rect = calculatePlacement( FOLDER_THUMBNAILS_X, - FOLDER_THUMBNAILS_Y, - FOLDER_THUMBNAILS_PADDING, - img.size().width(), - img.size().height( )); + QVector rect = _calculatePlacement( FOLDER_THUMBNAILS_X, + FOLDER_THUMBNAILS_Y, + FOLDER_THUMBNAILS_PADDING, + img.size().width(), + img.size().height( )); QPainter painter( &img ); for( int i = 0; i < numPreviews; ++i ) { @@ -135,33 +109,53 @@ void FolderThumbnailGenerator::paintThumbnailsMosaic( QImage& img, QImage thumbnail; // Avoid recursion into subfolders if( QDir( filename ).exists( )) - thumbnail = createFolderImage( QDir( filename ), false ); + thumbnail = _createFolderImage( QDir( filename ), false ); else - thumbnail = ThumbnailGeneratorFactory::getGenerator( filename, size_ )->generate( filename ); + { + const auto size = rect[i].size().toSize(); + auto generator = + ThumbnailGeneratorFactory::getGenerator( filename, size ); + thumbnail = generator->generate( filename ); + } - painter.drawImage( rect[i], thumbnail ); + // Draw the thumbnail centered in its rectangle, preserving aspect ratio + QSizeF paintedSize( thumbnail.size( )); + paintedSize.scale( rect[i].size(), Qt::KeepAspectRatio ); + QRectF paintRect( QPointF(), paintedSize ); + paintRect.moveCenter( rect[i].center( )); + painter.drawImage( paintRect, thumbnail ); } painter.end(); } -QImage -FolderThumbnailGenerator::createFolderImage( const QDir& dir, - const bool generateThumbnails ) const +QVector +FolderThumbnailGenerator::_calculatePlacement( int nX, int nY, float padding, + float totalWidth, + float totalHeight ) const { - QImage img = generatePlaceholderImage( dir ); - - const QFileInfoList& fileList = getSupportedFilesInDir( dir ); + const float totalPaddingWidth = padding*(nX+1); + const float imageWidth = (1.0f-totalPaddingWidth)/(float)nX; - if( generateThumbnails && fileList.size() > 0 ) - paintThumbnailsMosaic(img, fileList); - else - paintText( img, FOLDER_TEXT ); + const float totalPaddingHeight = padding*(nY+1); + const float imageHeight = (1.0f-totalPaddingHeight)/(float)nY; - return img; + QVector rect; + for( int j = 0; j < nY; ++j ) + { + const float y = padding + j*(imageHeight+padding); + for( int i = 0; i < nX; ++i ) + { + const float x = padding + i*(imageWidth+padding); + rect.append( QRect( x*totalWidth, y*totalHeight, + imageWidth*totalWidth, + imageHeight*totalHeight )); + } + } + return rect; } QFileInfoList -FolderThumbnailGenerator::getSupportedFilesInDir( QDir dir ) const +FolderThumbnailGenerator::_getSupportedFilesInDir( QDir dir ) const { dir.setFilter( QDir::Files | QDir::NoDotAndDotDot ); QStringList filters = ContentFactory::getSupportedFilesFilter(); diff --git a/tide/core/thumbnail/FolderThumbnailGenerator.h b/tide/core/thumbnail/FolderThumbnailGenerator.h index 0230b473..04feb275 100644 --- a/tide/core/thumbnail/FolderThumbnailGenerator.h +++ b/tide/core/thumbnail/FolderThumbnailGenerator.h @@ -49,18 +49,16 @@ class FolderThumbnailGenerator : public ThumbnailGenerator public: FolderThumbnailGenerator( const QSize& size ); - QImage generate( const QString& filename ) const override; + QImage generate( const QString& filename ) const final; private: - QImage generatePlaceholderImage( const QDir& dir ) const; - void addMetadataToImage( QImage& img, const QString& url ) const; - QImage createFolderImage( const QDir& dir, bool generateThumbnails) const; - QVector calculatePlacement( int nX, int nY, float padding, - float totalWidth, - float totalHeight ) const; - void paintThumbnailsMosaic( QImage &img, - const QFileInfoList& fileList ) const; - QFileInfoList getSupportedFilesInDir( QDir dir ) const; + QImage _createFolderImage( const QDir& dir, bool generateThumbnails ) const; + QVector _calculatePlacement( int nX, int nY, float padding, + float totalWidth, + float totalHeight ) const; + void _paintThumbnailsMosaic( QImage& img, + const QFileInfoList& fileList ) const; + QFileInfoList _getSupportedFilesInDir( QDir dir ) const; }; #endif diff --git a/tide/core/thumbnail/ImageThumbnailGenerator.cpp b/tide/core/thumbnail/ImageThumbnailGenerator.cpp index e6d95416..05de416f 100644 --- a/tide/core/thumbnail/ImageThumbnailGenerator.cpp +++ b/tide/core/thumbnail/ImageThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -44,7 +44,7 @@ #include "log.h" -#define SIZEOF_MEGABYTE (1024*1024) +#define SIZEOF_MEGABYTE (1024*1024) #define MAX_IMAGE_FILE_SIZE (100*SIZEOF_MEGABYTE) ImageThumbnailGenerator::ImageThumbnailGenerator( const QSize& size ) @@ -54,22 +54,13 @@ ImageThumbnailGenerator::ImageThumbnailGenerator( const QSize& size ) QImage ImageThumbnailGenerator::generate( const QString& filename ) const { - QImage img; - QImageReader reader( filename ); if( reader.canRead( )) { - if (QFileInfo(filename).size() < MAX_IMAGE_FILE_SIZE) - { - img = reader.read(); - img = img.scaled(size_, aspectRatioMode_); - } - else - { - img = createLargeImagePlaceholder(); - } - addMetadataToImage( img, filename ); - return img; + if( QFileInfo( filename ).size() > MAX_IMAGE_FILE_SIZE ) + return _createLargeImagePlaceholder(); + + return reader.read().scaled( _size, _aspectRatioMode ); } put_flog( LOG_ERROR, "could not open image file: '%s'", @@ -77,7 +68,7 @@ QImage ImageThumbnailGenerator::generate( const QString& filename ) const return createErrorImage( "image" ); } -QImage ImageThumbnailGenerator::createLargeImagePlaceholder() const +QImage ImageThumbnailGenerator::_createLargeImagePlaceholder() const { QImage img = createGradientImage( Qt::darkBlue, Qt::white ); paintText( img, "LARGE\nIMAGE" ); diff --git a/tide/core/thumbnail/ImageThumbnailGenerator.h b/tide/core/thumbnail/ImageThumbnailGenerator.h index 922bbe64..745925cf 100644 --- a/tide/core/thumbnail/ImageThumbnailGenerator.h +++ b/tide/core/thumbnail/ImageThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -45,12 +45,12 @@ class ImageThumbnailGenerator : public ThumbnailGenerator { public: - ImageThumbnailGenerator(const QSize &size); + ImageThumbnailGenerator( const QSize& size ); - QImage generate(const QString& filename) const override; + QImage generate( const QString& filename ) const final; -protected: - QImage createLargeImagePlaceholder() const; +private: + QImage _createLargeImagePlaceholder() const; }; -#endif // IMAGETHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/MovieThumbnailGenerator.cpp b/tide/core/thumbnail/MovieThumbnailGenerator.cpp index 2d1c2a1b..d7fc2584 100644 --- a/tide/core/thumbnail/MovieThumbnailGenerator.cpp +++ b/tide/core/thumbnail/MovieThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -59,10 +59,7 @@ QImage MovieThumbnailGenerator::generate( const QString& filename ) const const double target = PREVIEW_RELATIVE_POSITION * movie.getDuration(); PicturePtr picture = movie.getFrame( target ); if( picture ) - { - QImage image = picture->toQImage().scaled( size_, aspectRatioMode_ ); - addMetadataToImage( image, filename ); - return image; - } + return picture->toQImage().scaled( _size, _aspectRatioMode ); + return createErrorImage( "movie" ); } diff --git a/tide/core/thumbnail/MovieThumbnailGenerator.h b/tide/core/thumbnail/MovieThumbnailGenerator.h index 85e91e8b..f23ca352 100644 --- a/tide/core/thumbnail/MovieThumbnailGenerator.h +++ b/tide/core/thumbnail/MovieThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -45,9 +45,9 @@ class MovieThumbnailGenerator : public ThumbnailGenerator { public: - MovieThumbnailGenerator(const QSize &size); + MovieThumbnailGenerator( const QSize& size ); - QImage generate(const QString& filename) const override; + QImage generate( const QString& filename ) const final; }; -#endif // MOVIETHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/PDFThumbnailGenerator.cpp b/tide/core/thumbnail/PDFThumbnailGenerator.cpp index f9a21e71..0ac8be55 100644 --- a/tide/core/thumbnail/PDFThumbnailGenerator.cpp +++ b/tide/core/thumbnail/PDFThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -42,6 +42,15 @@ #include "PDF.h" #include "log.h" +#include + +namespace +{ +const size_t sizeOfMegabyte = 1024 * 1024; +// empirical value used to minimize thumbnail generation time +const qint64 maxPdfPageSize = 2 * sizeOfMegabyte; +} + PDFThumbnailGenerator::PDFThumbnailGenerator( const QSize& size ) : ThumbnailGenerator( size ) { @@ -58,8 +67,11 @@ QImage PDFThumbnailGenerator::generate( const QString& filename ) const return createErrorImage( "pdf" ); } - QImage image = pdf.renderToImage( size_ ); + if( QFileInfo( filename ).size() > maxPdfPageSize * pdf.getPageCount( )) + return _createLargePdfPlaceholder(); + const QSize imageSize = pdf.getSize().scaled( _size, _aspectRatioMode ); + const QImage image = pdf.renderToImage( imageSize ); if( image.isNull( )) { put_flog( LOG_ERROR, "could not render pdf file: '%s'", @@ -67,6 +79,12 @@ QImage PDFThumbnailGenerator::generate( const QString& filename ) const return createErrorImage( "pdf" ); } - addMetadataToImage( image, filename ); return image; } + +QImage PDFThumbnailGenerator::_createLargePdfPlaceholder() const +{ + QImage img = createGradientImage( Qt::darkBlue, Qt::white ); + paintText( img, "LARGE\nPDF" ); + return img; +} diff --git a/tide/core/thumbnail/PDFThumbnailGenerator.h b/tide/core/thumbnail/PDFThumbnailGenerator.h index bbb33b13..e1394e5f 100644 --- a/tide/core/thumbnail/PDFThumbnailGenerator.h +++ b/tide/core/thumbnail/PDFThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -47,7 +47,10 @@ class PDFThumbnailGenerator : public ThumbnailGenerator public: PDFThumbnailGenerator( const QSize& size ); - QImage generate( const QString& filename ) const override; + QImage generate( const QString& filename ) const final; + +private: + QImage _createLargePdfPlaceholder() const; }; -#endif // PDFTHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/PyramidThumbnailGenerator.cpp b/tide/core/thumbnail/PyramidThumbnailGenerator.cpp index f2352354..d4c03ff2 100644 --- a/tide/core/thumbnail/PyramidThumbnailGenerator.cpp +++ b/tide/core/thumbnail/PyramidThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -47,19 +47,13 @@ PyramidThumbnailGenerator::PyramidThumbnailGenerator( const QSize& size ) { } -QImage PyramidThumbnailGenerator::generate( const QString &filename ) const +QImage PyramidThumbnailGenerator::generate( const QString& filename ) const { - QImage image = DynamicTexture( filename ).getRootImage(); + const QImage image = DynamicTexture( filename ).getRootImage(); if( !image.isNull( )) - { - image = image.scaled( size_, aspectRatioMode_ ); - addMetadataToImage( image, filename ); - return image; - } - else - { - put_flog( LOG_ERROR, "could not open pyramid file: '%s'", - filename.toLatin1().constData( )); - return createErrorImage( "pyramid" ); - } + return image.scaled( _size, _aspectRatioMode ); + + put_flog( LOG_ERROR, "could not open pyramid file: '%s'", + filename.toLatin1().constData( )); + return createErrorImage( "pyramid" ); } diff --git a/tide/core/thumbnail/PyramidThumbnailGenerator.h b/tide/core/thumbnail/PyramidThumbnailGenerator.h index 172b1a1b..f91b4f36 100644 --- a/tide/core/thumbnail/PyramidThumbnailGenerator.h +++ b/tide/core/thumbnail/PyramidThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -50,4 +50,4 @@ class PyramidThumbnailGenerator : public ThumbnailGenerator QImage generate( const QString& filename ) const override; }; -#endif // PYRAMIDTHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/StateThumbnailGenerator.cpp b/tide/core/thumbnail/StateThumbnailGenerator.cpp index 59c2a2bc..dd9bc76c 100644 --- a/tide/core/thumbnail/StateThumbnailGenerator.cpp +++ b/tide/core/thumbnail/StateThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -40,39 +40,39 @@ #include "StateThumbnailGenerator.h" #include "StatePreview.h" + #include -StateThumbnailGenerator::StateThumbnailGenerator(const QSize &size) - : ThumbnailGenerator(size) +StateThumbnailGenerator::StateThumbnailGenerator( const QSize& size ) + : ThumbnailGenerator( size ) { } -QImage StateThumbnailGenerator::generate(const QString &filename) const +QImage StateThumbnailGenerator::generate( const QString& filename ) const { QImage img = createGradientImage( Qt::darkCyan, Qt::cyan ); - paintText(img, "DCX"); + paintText( img, "DCX" ); - StatePreview filePreview(filename); - if (filePreview.loadFromFile()) + StatePreview filePreview( filename ); + if( filePreview.loadFromFile( )) { - QImage preview = filePreview.getImage(); - - QPainter painter(&img); - QRect rect = scaleRectAroundCenter(img.rect(), 0.8f); - painter.drawImage(rect, preview); + QPainter painter( &img ); + const QRect rect = _scaleRectAroundCenter( img.rect(), 0.8f ); + painter.drawImage( rect, filePreview.getImage( )); } - addMetadataToImage(img, filename); return img; } -QRect StateThumbnailGenerator::scaleRectAroundCenter(const QRect& rect, float scaleFactor) const +QRect +StateThumbnailGenerator::_scaleRectAroundCenter( const QRect& rect, + const float scaleFactor ) const { - const float topLeftFactor = 0.5f*(1.0f-scaleFactor); + const float topLeftFactor = 0.5f * ( 1.0f - scaleFactor ); - return QRect(topLeftFactor*rect.size().width(), - topLeftFactor*rect.size().height(), - scaleFactor*rect.size().width(), - scaleFactor*rect.size().height() + return QRect( topLeftFactor*rect.size().width(), + topLeftFactor*rect.size().height(), + scaleFactor*rect.size().width(), + scaleFactor*rect.size().height() ); } diff --git a/tide/core/thumbnail/StateThumbnailGenerator.h b/tide/core/thumbnail/StateThumbnailGenerator.h index d4866e87..4bca0bf0 100644 --- a/tide/core/thumbnail/StateThumbnailGenerator.h +++ b/tide/core/thumbnail/StateThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -45,12 +45,12 @@ class StateThumbnailGenerator : public ThumbnailGenerator { public: - StateThumbnailGenerator(const QSize &size); + StateThumbnailGenerator( const QSize& size ); - QImage generate(const QString& filename) const override; + QImage generate( const QString& filename ) const final; -protected: - QRect scaleRectAroundCenter(const QRect& rect, float scaleFactor) const; +private: + QRect _scaleRectAroundCenter( const QRect& rect, float scaleFactor ) const; }; -#endif // STATETHUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/ThumbnailGenerator.cpp b/tide/core/thumbnail/ThumbnailGenerator.cpp index a609eebc..33a711a6 100644 --- a/tide/core/thumbnail/ThumbnailGenerator.cpp +++ b/tide/core/thumbnail/ThumbnailGenerator.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -48,55 +48,50 @@ #define THUMBNAIL_FONT_SIZE 30 -// TODO change this when the slide show is adapted to keep aspect ratio -#define ASPECT_RATIO Qt::IgnoreAspectRatio - -ThumbnailGenerator::ThumbnailGenerator(const QSize &size) - : size_(size) - , aspectRatioMode_(ASPECT_RATIO) -{ -} - -void ThumbnailGenerator::addMetadataToImage(QImage &img, const QString &url) const +ThumbnailGenerator::ThumbnailGenerator( const QSize& size ) + : _size( size ) + , _aspectRatioMode( Qt::KeepAspectRatio ) { - img.setText("source", url); } -QImage ThumbnailGenerator::createErrorImage(const QString &message) const +QImage ThumbnailGenerator::createErrorImage( const QString& message) const { - QImage img = createGradientImage(Qt::red, Qt::darkRed); - paintText(img, message); + QImage img = createGradientImage( Qt::red, Qt::darkRed ); + paintText( img, message ); return img; } -QImage ThumbnailGenerator::createGradientImage( const QColor& bgcolor1, const QColor& bgcolor2 ) const +QImage ThumbnailGenerator::createGradientImage( const QColor& bgcolor1, + const QColor& bgcolor2 ) const { - QImage img( size_, QImage::Format_RGB32 ); + QImage img( _size, QImage::Format_RGB32 ); QPainter painter( &img ); - QPoint p1( GRADIENT_START_X * img.width(), GRADIENT_START_Y * img.height( )); - QPoint p2( GRADIENT_END_X * img.width(), GRADIENT_END_Y * img.height( )); + const QPoint p1( GRADIENT_START_X * img.width(), + GRADIENT_START_Y * img.height( )); + const QPoint p2( GRADIENT_END_X * img.width(), + GRADIENT_END_Y * img.height( )); QLinearGradient linearGrad( p1, p2 ); linearGrad.setColorAt( 0, bgcolor1 ); linearGrad.setColorAt( 1, bgcolor2 ); - painter.setBrush(linearGrad); - painter.fillRect( 0, 0, img.width(), img.height(), QBrush(linearGrad)); + painter.setBrush( linearGrad ); + painter.fillRect( 0, 0, img.width(), img.height(), QBrush( linearGrad )); painter.end(); return img; } -void ThumbnailGenerator::paintText(QImage& img, const QString& text) const +void ThumbnailGenerator::paintText( QImage& img, const QString& text ) const { QFont font; - font.setStyleHint(QFont::Times, QFont::PreferAntialias); - font.setPointSize(THUMBNAIL_FONT_SIZE); + font.setStyleHint( QFont::Times, QFont::PreferAntialias ); + font.setPointSize( THUMBNAIL_FONT_SIZE ); QPainter painter( &img ); - painter.setRenderHint(QPainter::Antialiasing); - painter.setBrush(Qt::black); - painter.setFont(font); + painter.setRenderHint( QPainter::Antialiasing ); + painter.setBrush( Qt::black ); + painter.setFont( font ); int flags = Qt::AlignVCenter | Qt::AlignHCenter | Qt::TextWrapAnywhere; - painter.drawText(img.rect(), flags, text); + painter.drawText( img.rect(), flags, text ); painter.end(); } diff --git a/tide/core/thumbnail/ThumbnailGenerator.h b/tide/core/thumbnail/ThumbnailGenerator.h index 24304362..d053fddf 100644 --- a/tide/core/thumbnail/ThumbnailGenerator.h +++ b/tide/core/thumbnail/ThumbnailGenerator.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -40,27 +40,27 @@ #ifndef THUMBNAILGENERATOR_H #define THUMBNAILGENERATOR_H -#include +#include #include #include -#include +#include class ThumbnailGenerator { public: - ThumbnailGenerator(const QSize& size); + ThumbnailGenerator( const QSize& size ); virtual ~ThumbnailGenerator() {} - virtual QImage generate(const QString& filename) const = 0; + virtual QImage generate( const QString& filename ) const = 0; protected: - QSize size_; - Qt::AspectRatioMode aspectRatioMode_; + const QSize _size; + const Qt::AspectRatioMode _aspectRatioMode; - void addMetadataToImage(QImage& img, const QString &url) const; - QImage createErrorImage(const QString& message) const; - QImage createGradientImage(const QColor &bgcolor1, const QColor &bgcolor2) const; - void paintText(QImage &img, const QString &text) const; + QImage createErrorImage( const QString& message ) const; + QImage createGradientImage( const QColor& bgcolor1, + const QColor& bgcolor2 ) const; + void paintText( QImage& img, const QString& text ) const; }; -#endif // THUMBNAILGENERATOR_H +#endif diff --git a/tide/core/thumbnail/ThumbnailGeneratorFactory.cpp b/tide/core/thumbnail/ThumbnailGeneratorFactory.cpp index 8e3341b9..23619e78 100644 --- a/tide/core/thumbnail/ThumbnailGeneratorFactory.cpp +++ b/tide/core/thumbnail/ThumbnailGeneratorFactory.cpp @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -41,8 +41,8 @@ #include "config.h" #if TIDE_ENABLE_PDF_SUPPORT +# include "PDFContent.h" # include "PDFThumbnailGenerator.h" -# include "../PDFContent.h" #endif #include "DefaultThumbnailGenerator.h" @@ -52,62 +52,49 @@ #include "PyramidThumbnailGenerator.h" #include "StateThumbnailGenerator.h" +#include "DynamicTextureContent.h" #include "MovieContent.h" #include "TextureContent.h" -#include "DynamicTextureContent.h" -#include #include -ThumbnailGeneratorFactory::ThumbnailGeneratorFactory() -{ -} - -ThumbnailGeneratorPtr ThumbnailGeneratorFactory::getGenerator(const QString &filename, const QSize &size) +ThumbnailGeneratorPtr +ThumbnailGeneratorFactory::getGenerator( const QString& filename, + const QSize& size ) { - const QString& extension = QFileInfo(filename).suffix().toLower(); + const QString& extension = QFileInfo( filename ).suffix().toLower(); - if (!filename.isEmpty() && QDir(filename).exists()) - { - return ThumbnailGeneratorPtr(new FolderThumbnailGenerator(size)); - } + if( !filename.isEmpty() && QDir( filename ).exists( )) + return ThumbnailGeneratorPtr( new FolderThumbnailGenerator( size )); if( extension == "dcx" ) - { - return ThumbnailGeneratorPtr(new StateThumbnailGenerator(size)); - } + return ThumbnailGeneratorPtr( new StateThumbnailGenerator( size )); if( MovieContent::getSupportedExtensions().contains( extension )) - { - return ThumbnailGeneratorPtr(new MovieThumbnailGenerator(size)); - } + return ThumbnailGeneratorPtr( new MovieThumbnailGenerator( size )); if( TextureContent::getSupportedExtensions().contains( extension )) - { - return ThumbnailGeneratorPtr(new ImageThumbnailGenerator(size)); - } + return ThumbnailGeneratorPtr( new ImageThumbnailGenerator( size )); if( DynamicTextureContent::getSupportedExtensions().contains( extension )) - { - return ThumbnailGeneratorPtr(new PyramidThumbnailGenerator(size)); - } + return ThumbnailGeneratorPtr( new PyramidThumbnailGenerator( size )); #if TIDE_ENABLE_PDF_SUPPORT if( PDFContent::getSupportedExtensions().contains( extension )) - { - return ThumbnailGeneratorPtr(new PDFThumbnailGenerator(size)); - } + return ThumbnailGeneratorPtr( new PDFThumbnailGenerator( size )); #endif - return ThumbnailGeneratorPtr(new DefaultThumbnailGenerator(size)); + return ThumbnailGeneratorPtr( new DefaultThumbnailGenerator( size )); } -ThumbnailGeneratorPtr ThumbnailGeneratorFactory::getDefaultGenerator(const QSize &size) +ThumbnailGeneratorPtr +ThumbnailGeneratorFactory::getDefaultGenerator( const QSize& size ) { - return ThumbnailGeneratorPtr(new DefaultThumbnailGenerator(size)); + return ThumbnailGeneratorPtr( new DefaultThumbnailGenerator( size )); } -FolderThumbnailGeneratorPtr ThumbnailGeneratorFactory::getFolderGenerator(const QSize &size) +FolderThumbnailGeneratorPtr +ThumbnailGeneratorFactory::getFolderGenerator( const QSize& size ) { - return FolderThumbnailGeneratorPtr(new FolderThumbnailGenerator(size)); + return FolderThumbnailGeneratorPtr( new FolderThumbnailGenerator( size )); } diff --git a/tide/core/thumbnail/ThumbnailGeneratorFactory.h b/tide/core/thumbnail/ThumbnailGeneratorFactory.h index 42d6ea57..d61d0c6d 100644 --- a/tide/core/thumbnail/ThumbnailGeneratorFactory.h +++ b/tide/core/thumbnail/ThumbnailGeneratorFactory.h @@ -1,5 +1,5 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Raphael Dumusc */ /* All rights reserved. */ /* */ @@ -40,26 +40,26 @@ #ifndef THUMBNAILGENERATORFACTORY_H #define THUMBNAILGENERATORFACTORY_H -#include +#include + #include -#include +#include class ThumbnailGenerator; class FolderThumbnailGenerator; -typedef boost::shared_ptr ThumbnailGeneratorPtr; -typedef boost::shared_ptr FolderThumbnailGeneratorPtr; +typedef std::unique_ptr ThumbnailGeneratorPtr; +typedef std::unique_ptr FolderThumbnailGeneratorPtr; class ThumbnailGeneratorFactory { public: - ThumbnailGeneratorFactory(); - - static ThumbnailGeneratorPtr getGenerator(const QString& filename, const QSize& size); + static ThumbnailGeneratorPtr getGenerator( const QString& filename, + const QSize& size ); - static ThumbnailGeneratorPtr getDefaultGenerator(const QSize& size); + static ThumbnailGeneratorPtr getDefaultGenerator( const QSize& size ); - static FolderThumbnailGeneratorPtr getFolderGenerator(const QSize& size); + static FolderThumbnailGeneratorPtr getFolderGenerator( const QSize& size ); }; -#endif // THUMBNAILGENERATORFACTORY_H +#endif diff --git a/tide/core/thumbnail/ThumbnailProvider.cpp b/tide/core/thumbnail/ThumbnailProvider.cpp index 8051a2f4..62918815 100644 --- a/tide/core/thumbnail/ThumbnailProvider.cpp +++ b/tide/core/thumbnail/ThumbnailProvider.cpp @@ -43,11 +43,16 @@ #include "ThumbnailGenerator.h" #include "ThumbnailGeneratorFactory.h" +#include #include #include +#include + namespace { +const int cacheMaxSize = 200; +const QString cacheModificationDateKey( "lastModificationDate" ); const char* folderImg = "qrc:/img/folder.png"; const char* unknownFileImg = "qrc:/img/unknownfile.png"; } @@ -56,7 +61,9 @@ ThumbnailProvider::ThumbnailProvider( const QSize defaultSize ) : QQuickImageProvider( QQuickImageProvider::Image, QQuickImageProvider::ForceAsynchronousImageLoading ) , _defaultSize( defaultSize ) -{} +{ + _cache.setMaxCost( cacheMaxSize ); +} QImage ThumbnailProvider::requestImage( const QString& filename, QSize* size, const QSize& requestedSize ) @@ -68,12 +75,23 @@ QImage ThumbnailProvider::requestImage( const QString& filename, QSize* size, if( size ) *size = newSize; + if( _isImageInCache( filename )) + return *_cache[filename]; + auto generator = ThumbnailGeneratorFactory::getGenerator( filename, newSize ); const QImage image = generator->generate( filename ); if( !image.isNull( )) + { + // QCache requires a * and takes ownership, a new QImage is required + QImage* cacheImage = new QImage( image ); + cacheImage->setText( cacheModificationDateKey, + QFileInfo( filename ).lastModified().toString( )); + _cache.insert( filename, cacheImage ); return image; + } + // Thumbnail generation failed, return a placeholder const QFileInfo fileInfo( filename ); if( fileInfo.isFile( )) { @@ -81,7 +99,6 @@ QImage ThumbnailProvider::requestImage( const QString& filename, QSize* size, assert( !im.isNull( )); return im; } - if( fileInfo.isDir( )) { static QImage im( folderImg ); @@ -91,3 +108,13 @@ QImage ThumbnailProvider::requestImage( const QString& filename, QSize* size, return image; // Silence compiler warning } + +bool ThumbnailProvider::_isImageInCache( const QString& filename ) const +{ + if( !_cache.contains( filename )) + return false; + + const QFileInfo info( filename ); + return info.lastModified().toString() == + _cache.object( filename )->text( cacheModificationDateKey ); +} diff --git a/tide/core/thumbnail/ThumbnailProvider.h b/tide/core/thumbnail/ThumbnailProvider.h index c87c9348..dd114b2c 100644 --- a/tide/core/thumbnail/ThumbnailProvider.h +++ b/tide/core/thumbnail/ThumbnailProvider.h @@ -42,9 +42,21 @@ #define THUMBNAILPROVIDER_H #include +#include /** * Provide thumbnails for files and folders to the Qml FileBrowser. + * + * The provider maintains an internal cache to speed up the request of + * previously generated images. It also takes care of regenerating the thumbnail + * if the file has been modified since the last request (checking the file + * modification date). + * + * Example of correct usage in Qml: + * Image { + * source: "image://thumbnail/" + filePath + * cache: false + * } */ class ThumbnailProvider : public QQuickImageProvider { @@ -56,6 +68,9 @@ class ThumbnailProvider : public QQuickImageProvider private: const QSize _defaultSize; + QCache _cache; + + bool _isImageInCache( const QString& filename ) const; }; #endif