From c0ca8f2c79c9b0e9f6db47dd86a1434d4e3de5bb Mon Sep 17 00:00:00 2001 From: Ismael Asensio Date: Sat, 21 Sep 2019 16:15:04 +0200 Subject: [PATCH] [dolphin] Animate gifs on preview Summary: Adds the capability to view animated images on the preview in the information panel. This was a request from a user back in 2009 (https://bugs.kde.org/show_bug.cgi?id=182257), but I think nowadays with stickers/memes and what not, it's even more useful. It keeps the size default transition of the preview viewer before starting the animation, so that the visual integration is smoother. {F7289110} FEATURE: 182257 FIXED-IN: 19.11.80 Test Plan: Open the information panel and hover over some animated images (gif/webp/mng) Reviewers: #dolphin, #vdg, ngraham, elvisangelaccio Reviewed By: #vdg, ngraham Subscribers: pino, fuksitter, meven, broulik, kfm-devel Tags: #dolphin Differential Revision: https://phabricator.kde.org/D23538 --- .../information/informationpanelcontent.cpp | 10 ++- src/panels/information/pixmapviewer.cpp | 78 +++++++++++++++++-- src/panels/information/pixmapviewer.h | 14 ++++ 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/panels/information/informationpanelcontent.cpp b/src/panels/information/informationpanelcontent.cpp index b051603fd8..2a8682a127 100644 --- a/src/panels/information/informationpanelcontent.cpp +++ b/src/panels/information/informationpanelcontent.cpp @@ -169,6 +169,7 @@ void InformationPanelContent::showItem(const KFileItem& item) if (item != m_item) { m_item = item; + m_preview->stopAnimatedImage(); refreshMetaData(); } refreshPreview(); @@ -237,7 +238,8 @@ void InformationPanelContent::refreshPreview() refreshPixmapView(); const QString mimeType = m_item.mimetype(); - m_isVideo = mimeType.startsWith(QLatin1String("video/")); + const bool isAnimatedImage = m_preview->isAnimatedImage(itemUrl.toLocalFile()); + m_isVideo = !isAnimatedImage && mimeType.startsWith(QLatin1String("video/")); usePhonon = m_isVideo || mimeType.startsWith(QLatin1String("audio/")); if (usePhonon) { @@ -268,6 +270,9 @@ void InformationPanelContent::refreshPreview() adjustWidgetSizes(parentWidget()->width()); } } else { + if (isAnimatedImage) { + m_preview->setAnimatedImageFileName(itemUrl.toLocalFile()); + } // When we don't need it, hide the phonon widget first to avoid flickering m_phononWidget->hide(); m_preview->show(); @@ -276,6 +281,7 @@ void InformationPanelContent::refreshPreview() } } } else { + m_preview->stopAnimatedImage(); m_preview->hide(); m_phononWidget->hide(); } @@ -303,6 +309,8 @@ void InformationPanelContent::showItems(const KFileItemList& items) m_previewJob->kill(); } + m_preview->stopAnimatedImage(); + m_preview->setPixmap( QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous) ); diff --git a/src/panels/information/pixmapviewer.cpp b/src/panels/information/pixmapviewer.cpp index 311995ec2f..2601e82ae5 100644 --- a/src/panels/information/pixmapviewer.cpp +++ b/src/panels/information/pixmapviewer.cpp @@ -21,14 +21,18 @@ #include +#include +#include #include #include PixmapViewer::PixmapViewer(QWidget* parent, Transition transition) : QWidget(parent), + m_animatedImage(nullptr), m_transition(transition), m_animationStep(0), - m_sizeHint() + m_sizeHint(), + m_hasAnimatedImage(false) { setMinimumWidth(KIconLoader::SizeEnormous); setMinimumHeight(KIconLoader::SizeEnormous); @@ -52,6 +56,11 @@ void PixmapViewer::setPixmap(const QPixmap& pixmap) return; } + // Avoid flicker with static pixmap if an animated image is running + if (m_animatedImage && m_animatedImage->state() == QMovie::Running) { + return; + } + if ((m_transition != NoTransition) && (m_animation.state() == QTimeLine::Running)) { m_pendingPixmaps.enqueue(pixmap); if (m_pendingPixmaps.count() > 5) { @@ -65,15 +74,26 @@ void PixmapViewer::setPixmap(const QPixmap& pixmap) m_pixmap = pixmap; update(); - const bool animate = (m_transition != NoTransition) && - (m_pixmap.size() != m_oldPixmap.size()); - if (animate) { + const bool animateTransition = (m_transition != NoTransition) && + (m_pixmap.size() != m_oldPixmap.size()); + if (animateTransition) { m_animation.start(); + } else if (m_hasAnimatedImage) { + // If there is no transition animation but an animatedImage + // and it is not already running, start animating now + if (m_animatedImage->state() != QMovie::Running) { + m_animatedImage->setScaledSize(m_pixmap.size()); + m_animatedImage->start(); + } } } void PixmapViewer::setSizeHint(const QSize& size) { + if (m_animatedImage && size != m_sizeHint) { + m_animatedImage->stop(); + } + m_sizeHint = size; updateGeometry(); } @@ -83,13 +103,37 @@ QSize PixmapViewer::sizeHint() const return m_sizeHint; } +void PixmapViewer::setAnimatedImageFileName(const QString &fileName) +{ + if (!m_animatedImage) { + m_animatedImage = new QMovie(this); + connect(m_animatedImage, &QMovie::frameChanged, this, &PixmapViewer::updateAnimatedImageFrame); + } + + if (m_animatedImage->fileName() != fileName) { + m_animatedImage->stop(); + m_animatedImage->setFileName(fileName); + } + + m_hasAnimatedImage = m_animatedImage->isValid() && (m_animatedImage->frameCount() > 1); +} + + +QString PixmapViewer::animatedImageFileName() const +{ + if (!m_hasAnimatedImage) { + return QString(); + } + return m_animatedImage->fileName(); +} + void PixmapViewer::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); QPainter painter(this); - if (m_transition != NoTransition) { + if (m_transition != NoTransition || (m_hasAnimatedImage && m_animatedImage->state() != QMovie::Running)) { const float value = m_animation.currentValue(); const int scaledWidth = static_cast((m_oldPixmap.width() * (1.0 - value)) + (m_pixmap.width() * value)); const int scaledHeight = static_cast((m_oldPixmap.height() * (1.0 - value)) + (m_pixmap.height() * value)); @@ -118,8 +162,32 @@ void PixmapViewer::checkPendingPixmaps() m_pixmap = pixmap; update(); m_animation.start(); + } else if (m_hasAnimatedImage) { + m_animatedImage->setScaledSize(m_pixmap.size()); + m_animatedImage->start(); } else { m_oldPixmap = m_pixmap; } } +void PixmapViewer::updateAnimatedImageFrame() +{ + Q_ASSERT (m_animatedImage); + + m_pixmap = m_animatedImage->currentPixmap(); + update(); +} + +void PixmapViewer::stopAnimatedImage() +{ + if (m_hasAnimatedImage) { + m_animatedImage->stop(); + m_hasAnimatedImage = false; + } +} + +bool PixmapViewer::isAnimatedImage(const QString &fileName) +{ + const QByteArray imageFormat = QImageReader::imageFormat(fileName); + return !imageFormat.isEmpty() && QMovie::supportedFormats().contains(imageFormat); +} diff --git a/src/panels/information/pixmapviewer.h b/src/panels/information/pixmapviewer.h index 46e5cf5fc7..37071045fb 100644 --- a/src/panels/information/pixmapviewer.h +++ b/src/panels/information/pixmapviewer.h @@ -26,6 +26,7 @@ #include class QPaintEvent; +class QMovie; /** * @brief Widget which shows a pixmap centered inside the boundaries. @@ -73,20 +74,33 @@ public: void setSizeHint(const QSize& size); QSize sizeHint() const override; + void setAnimatedImageFileName(const QString& fileName); + QString animatedImageFileName() const; + + void stopAnimatedImage(); + + /** + * Checks if \a fileName contains an animated image supported by QMovie. + */ + static bool isAnimatedImage(const QString &fileName); + protected: void paintEvent(QPaintEvent* event) override; private Q_SLOTS: void checkPendingPixmaps(); + void updateAnimatedImageFrame(); private: QPixmap m_pixmap; QPixmap m_oldPixmap; + QMovie* m_animatedImage; QQueue m_pendingPixmaps; QTimeLine m_animation; Transition m_transition; int m_animationStep; QSize m_sizeHint; + bool m_hasAnimatedImage; }; inline QPixmap PixmapViewer::pixmap() const