[vlc-commits] [Git][videolan/vlc][master] 13 commits: qt: simplify image assignment in roundimage
Jean-Baptiste Kempf (@jbk)
gitlab at videolan.org
Sat Jun 11 16:54:55 UTC 2022
Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC
Commits:
19cc1b35 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: simplify image assignment in roundimage
- - - - -
4082222d by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: support image provider in RoundImage
- - - - -
6191c7d2 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: implement classes for custom ml cover generation via image provider
- - - - -
8cfcf077 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: provide custom cover generation via medialib
- - - - -
7bebe5a8 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: use in-memory caching for custom ml covers
- - - - -
375dd232 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: remove unused code from CoverGenerator
- - - - -
87e7ee3c by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: reduce custom cover default size
saves memory
- - - - -
c4c2adf8 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: don't use medialib cover for genre
covers are custom generated
- - - - -
95f4aa56 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: remove mlitemcover
- - - - -
2aa6e722 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: improve network image reading in roundimage
removes blocking call when reading network image
- - - - -
04841706 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: increase cache size of round image
this is done to improve caching of generated cover
- - - - -
43e42fec by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: remove smooth property from cover generator
- - - - -
716ff4b7 by Prince Gupta at 2022-06-11T15:44:58+00:00
qt: fix image scaling in cover generator
- - - - -
27 changed files:
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/medialibrary/medialib.cpp
- modules/gui/qt/medialibrary/medialib.hpp
- modules/gui/qt/medialibrary/mlbasemodel.hpp
- + modules/gui/qt/medialibrary/mlcustomcover.cpp
- modules/gui/qt/medialibrary/mlitemcover.hpp → modules/gui/qt/medialibrary/mlcustomcover.hpp
- modules/gui/qt/medialibrary/mlfolder.cpp
- modules/gui/qt/medialibrary/mlfolder.hpp
- modules/gui/qt/medialibrary/mlgenre.cpp
- modules/gui/qt/medialibrary/mlgenre.hpp
- modules/gui/qt/medialibrary/mlgenremodel.cpp
- modules/gui/qt/medialibrary/mlgroup.cpp
- modules/gui/qt/medialibrary/mlgroup.hpp
- modules/gui/qt/medialibrary/mlhelper.cpp
- modules/gui/qt/medialibrary/mlhelper.hpp
- − modules/gui/qt/medialibrary/mlitemcover.cpp
- modules/gui/qt/medialibrary/mlplaylist.cpp
- modules/gui/qt/medialibrary/mlplaylist.hpp
- modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
- modules/gui/qt/medialibrary/mlvideofoldersmodel.cpp
- modules/gui/qt/medialibrary/mlvideogroupsmodel.cpp
- modules/gui/qt/util/covergenerator.cpp
- modules/gui/qt/util/covergenerator.hpp
- modules/gui/qt/widgets/native/roundimage.cpp
- modules/gui/qt/widgets/native/roundimage.hpp
- po/POTFILES.in
Changes:
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -179,8 +179,6 @@ libqt_plugin_la_SOURCES = \
gui/qt/medialibrary/mlgroup.hpp \
gui/qt/medialibrary/mlhelper.cpp \
gui/qt/medialibrary/mlhelper.hpp \
- gui/qt/medialibrary/mlitemcover.cpp \
- gui/qt/medialibrary/mlitemcover.hpp \
gui/qt/medialibrary/mllistcache.cpp \
gui/qt/medialibrary/mllistcache.hpp \
gui/qt/medialibrary/mlthreadpool.cpp \
@@ -212,6 +210,8 @@ libqt_plugin_la_SOURCES = \
gui/qt/medialibrary/mlplaylistmodel.hpp \
gui/qt/medialibrary/thumbnailcollector.hpp \
gui/qt/medialibrary/thumbnailcollector.cpp \
+ gui/qt/medialibrary/mlcustomcover.hpp \
+ gui/qt/medialibrary/mlcustomcover.cpp \
gui/qt/menus/custom_menus.cpp \
gui/qt/menus/custom_menus.hpp \
gui/qt/menus/qml_menu_wrapper.cpp \
=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -4,6 +4,7 @@
#include "medialibrary/medialib.hpp"
#include "medialibrary/mlqmltypes.hpp"
+#include "medialibrary/mlcustomcover.hpp"
#include "medialibrary/mlalbummodel.hpp"
#include "medialibrary/mlartistmodel.hpp"
#include "medialibrary/mlalbumtrackmodel.hpp"
@@ -63,6 +64,8 @@ using namespace vlc::playlist;
namespace {
+const QString MLCUSTOMCOVER_PROVIDERID = "mlcustomcover";
+
template<class T>
class SingletonRegisterHelper
{
@@ -148,6 +151,14 @@ bool MainUI::setup(QQmlEngine* engine)
engine->setOutputWarningsToStandardError(false);
connect(engine, &QQmlEngine::warnings, this, &MainUI::onQmlWarning);
+ if (m_mainCtx->hasMediaLibrary())
+ {
+ auto customCover = new MLCustomCover(MLCUSTOMCOVER_PROVIDERID, m_mainCtx->getMediaLibrary());
+ m_mainCtx->getMediaLibrary()->setCustomCover(customCover);
+
+ engine->addImageProvider(MLCUSTOMCOVER_PROVIDERID, customCover);
+ }
+
m_component = new QQmlComponent(engine, QStringLiteral("qrc:/main/MainInterface.qml"), QQmlComponent::PreferSynchronous, engine);
if (m_component->isLoading())
{
=====================================
modules/gui/qt/medialibrary/medialib.cpp
=====================================
@@ -520,3 +520,13 @@ void MediaLib::runOnMLThreadTargetDestroyed(QObject * object)
//no need to disconnect QObject::destroyed, as object is currently being destroyed
}
}
+
+MLCustomCover *MediaLib::customCover() const
+{
+ return m_customCover;
+}
+
+void MediaLib::setCustomCover(MLCustomCover *newCustomCover)
+{
+ m_customCover = newCustomCover;
+}
=====================================
modules/gui/qt/medialibrary/medialib.hpp
=====================================
@@ -29,6 +29,8 @@
#include "util/qmlinputitem.hpp"
+class MLCustomCover;
+
namespace vlc {
namespace playlist {
class Media;
@@ -176,6 +178,9 @@ public:
*/
void cancelMLTask(const QObject* object, quint64 taskId);
+ MLCustomCover *customCover() const;
+ void setCustomCover(MLCustomCover *newCustomCover);
+
signals:
void discoveryStarted();
void discoveryCompleted();
@@ -195,6 +200,7 @@ private slots:
private:
qt_intf_t* m_intf;
+ MLCustomCover *m_customCover {};
bool m_idle = false;
bool m_discoveryPending = false;
=====================================
modules/gui/qt/medialibrary/mlbasemodel.hpp
=====================================
@@ -38,8 +38,6 @@
// Fordward declarations
class MLListCache;
class MediaLib;
-class MLItemCover;
-class CoverGenerator;
class MLBaseModel : public QAbstractListModel
{
@@ -194,11 +192,6 @@ protected:
//loader used to load single items
std::shared_ptr<BaseLoader> m_itemLoader;
-
-private: // Friends
- friend QString createGroupMediaCover(const MLBaseModel* model, MLItemCover* parent
- , int role
- , const std::shared_ptr<CoverGenerator> generator);
};
#endif // MLBASEMODEL_HPP
=====================================
modules/gui/qt/medialibrary/mlcustomcover.cpp
=====================================
@@ -0,0 +1,307 @@
+/*****************************************************************************
+ * Copyright (C) 2022 VLC authors and VideoLAN
+ *
+ * Authors: Prince Gupta <guptaprince8832 at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include "mlcustomcover.hpp"
+
+#include "medialibrary/medialib.hpp"
+#include "medialibrary/mlhelper.hpp"
+#include "medialibrary/thumbnailcollector.hpp"
+#include "util/asynctask.hpp"
+#include "util/covergenerator.hpp"
+
+#include <qhashfunctions.h>
+
+#include <QCache>
+#include <QMutex>
+#include <QUrl>
+#include <QUrlQuery>
+
+namespace
+{
+
+const QString ID_KEY = QStringLiteral("id");
+const QString TYPE_KEY = QStringLiteral("type");
+const QString WIDTH_KEY = QStringLiteral("width");
+const QString HEIGHT_KEY = QStringLiteral("height");
+const QString COUNTX_KEY = QStringLiteral("countX");
+const QString COUNTY_KEY = QStringLiteral("countY");
+const QString BLUR_KEY = QStringLiteral("blur");
+const QString SPLIT_KEY = QStringLiteral("split");
+const QString DEFAULT_COVER_KEY = QStringLiteral("default_cover");
+
+struct CoverData
+{
+ MLItemId id;
+ QSize size;
+ int countX;
+ int countY;
+ int blur;
+ int split;
+ QString defaultCover;
+};
+
+QUrlQuery toQuery(const CoverData &data)
+{
+ QUrlQuery query;
+ query.addQueryItem(ID_KEY, QString::number(data.id.id));
+ query.addQueryItem(TYPE_KEY, QString::number(data.id.type));
+ query.addQueryItem(WIDTH_KEY, QString::number(data.size.width()));
+ query.addQueryItem(HEIGHT_KEY, QString::number(data.size.height()));
+ query.addQueryItem(COUNTX_KEY, QString::number(data.countX));
+ query.addQueryItem(COUNTY_KEY, QString::number(data.countY));
+ query.addQueryItem(BLUR_KEY, QString::number(data.blur));
+ query.addQueryItem(SPLIT_KEY, QString::number(data.split));
+ query.addQueryItem(DEFAULT_COVER_KEY, data.defaultCover);
+ return query;
+}
+
+CoverData fromQuery(const QUrlQuery &query, QString *error)
+{
+ try
+ {
+ const auto getValue = [&](const QString &key)
+ {
+ if (!query.hasQueryItem(key))
+ throw QString("key '%1' doesn't exist").arg(key);
+
+ return query.queryItemValue(key);
+ };
+
+ const auto intValue = [&](const QString &key)
+ {
+ auto value = getValue(key);
+ bool ok;
+ int iValue = value.toInt(&ok);
+ if (!ok)
+ throw QString("invalid value for key '%1'").arg(key);
+
+ return iValue;
+ };
+
+ CoverData data;
+ data.id.id = intValue(ID_KEY);
+ data.id.type = static_cast<vlc_ml_parent_type>(intValue(TYPE_KEY));
+ data.size.setWidth(intValue(WIDTH_KEY));
+ data.size.setHeight(intValue(HEIGHT_KEY));
+ data.countX = intValue(COUNTX_KEY);
+ data.countY = intValue(COUNTY_KEY);
+ data.blur = intValue(BLUR_KEY);
+ data.split = intValue(SPLIT_KEY);
+ data.defaultCover = getValue(DEFAULT_COVER_KEY);
+
+ return data;
+ }
+ catch (const QString &e)
+ {
+ if (error)
+ *error = e;
+ return {};
+ }
+}
+
+
+struct ThumbnailList
+{
+ QSet<int64_t> toGenerate;
+ QStringList existing;
+};
+
+QStringList getGenreMediaThumbnails(vlc_medialibrary_t* p_ml, const int count, const int64_t id)
+{
+ QStringList thumbnails;
+
+ vlc_ml_query_params_t params {};
+
+ // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
+ params.i_nbResults = count * 2;
+
+ ml_unique_ptr<vlc_ml_album_list_t> list(vlc_ml_list_genre_albums(p_ml, ¶ms, id));
+
+ thumbnailCopy(ml_range_iterate<vlc_ml_album_t>(list), std::back_inserter(thumbnails), count);
+
+ return thumbnails;
+}
+
+ThumbnailList extractChildMediaThumbnailsOrIDs(vlc_medialibrary_t *p_ml, const int count, const MLItemId &itemID)
+{
+ ThumbnailList result;
+
+ vlc_ml_query_params_t params {};
+
+ // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
+ params.i_nbResults = count * 2;
+
+ ml_unique_ptr<vlc_ml_media_list_t> list(vlc_ml_list_media_of(p_ml, ¶ms, itemID.type, itemID.id));
+
+ for (const auto &media : ml_range_iterate<vlc_ml_media_t>(list))
+ {
+ const bool isThumbnailAvailable = (media.thumbnails[VLC_ML_THUMBNAIL_SMALL].i_status == VLC_ML_THUMBNAIL_STATUS_AVAILABLE);
+ if (isThumbnailAvailable)
+ {
+ result.existing.push_back(toValidLocalFile(media.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl));
+ } else if (media.i_type == VLC_ML_MEDIA_TYPE_VIDEO)
+ {
+ result.toGenerate.insert(media.i_id);
+ }
+ }
+
+ if (result.existing.size() > count)
+ {
+ const auto removeStart = result.existing.end() - (result.existing.size() - count);
+ result.existing.erase(removeStart, result.existing.end());
+ }
+
+ while (result.toGenerate.size() + result.existing.size() > count)
+ {
+ result.toGenerate.erase(result.toGenerate.begin());
+ }
+
+ return result;
+}
+
+} // anonymous namespace
+
+class CustomCoverImageResponse : public QQuickImageResponse
+{
+public:
+ CustomCoverImageResponse(CoverData data, MediaLib *ml)
+ : ml {ml}
+ , data{data}
+ {
+ // uses Qt::QueuedConnection to give the receiver time to connect to finish()
+ QMetaObject::invokeMethod(this, &CustomCoverImageResponse::start, Qt::QueuedConnection);
+ }
+
+ QQuickTextureFactory *textureFactory() const override
+ {
+ return !image.isNull() ? QQuickTextureFactory::textureFactoryForImage(image) : nullptr;
+ }
+
+private:
+ void start()
+ {
+ const int thumbnailCount = data.countX * data.countY;
+
+ ml->runOnMLThread<ThumbnailList>(this,
+ //ML thread (get child thumbnails or ids)
+ [itemId = data.id, thumbnailCount](vlc_medialibrary_t *p_ml, ThumbnailList &ctx)
+ {
+ if (itemId.type == VLC_ML_PARENT_GENRE)
+ ctx.existing = getGenreMediaThumbnails(p_ml, thumbnailCount, itemId.id);
+ else
+ ctx = extractChildMediaThumbnailsOrIDs(p_ml, thumbnailCount, itemId);
+ }
+ //UI Thread
+ , [=](quint64, ThumbnailList & ctx)
+ {
+ if (ctx.toGenerate.empty())
+ {
+ generateCover(ctx.existing);
+ return;
+ }
+
+ // request child thumbnail generation, when finished generate the cover
+ auto collector = new ThumbnailCollector(this);
+ QObject::connect(collector, &ThumbnailCollector::finished, this, [=]()
+ {
+ const auto thumbnails = ctx.existing + collector->allGenerated().values();
+ generateCover(thumbnails);
+
+ collector->deleteLater();
+ });
+
+ collector->start(ml, ctx.toGenerate);
+ }
+ );
+ }
+
+ void generateCover(const QStringList &thumbnails)
+ {
+ struct Context { QImage img; };
+
+ ml->runOnMLThread<Context>(this,
+ //ML thread
+ [data = this->data, thumbnails]
+ (vlc_medialibrary_t * , Context & ctx)
+ {
+ CoverGenerator generator;
+ generator.setCountX(data.countX);
+ generator.setCountY(data.countY);
+ generator.setSize(data.size);
+ generator.setSplit((CoverGenerator::Split)data.split);
+ generator.setBlur(data.blur);
+
+ if (!data.defaultCover.isEmpty())
+ generator.setDefaultThumbnail(data.defaultCover);
+
+ ctx.img = generator.execute(thumbnails);
+ },
+ //UI Thread
+ [this]
+ (quint64, Context & ctx)
+ {
+ doFinish(ctx.img);
+ }
+ );
+ }
+
+ void doFinish(const QImage &result)
+ {
+ image = result;
+ emit finished();
+ }
+
+ MediaLib *ml;
+ CoverData data;
+ QImage image;
+};
+
+
+MLCustomCover::MLCustomCover(const QString &providerId, MediaLib *ml)
+ : m_providerId {providerId}
+ , m_ml {ml}
+{
+}
+
+QString MLCustomCover::get(const MLItemId &parentId, const QSize &size, const QString &defaultCover
+ , const int countX, const int countY, const int blur, const bool split_duplicate)
+{
+ QUrl url;
+ url.setScheme(QStringLiteral("image"));
+ url.setHost(m_providerId);
+ url.setQuery(toQuery({parentId, size, countX, countY, blur, split_duplicate, defaultCover}));
+ return url.toString();
+}
+
+QQuickImageResponse *MLCustomCover::requestImageResponse(const QString &id, const QSize &requestedSize)
+{
+ QString error;
+ CoverData data = fromQuery(QUrlQuery(id), &error);
+ if (!error.isEmpty())
+ {
+ qDebug("failed to parse url %s, error %s", qUtf8Printable(id), qUtf8Printable(error));
+ return nullptr;
+ }
+
+ if (requestedSize.isValid())
+ data.size = requestedSize;
+
+ return new CustomCoverImageResponse(data, m_ml);
+}
=====================================
modules/gui/qt/medialibrary/mlitemcover.hpp → modules/gui/qt/medialibrary/mlcustomcover.hpp
=====================================
@@ -1,7 +1,7 @@
/*****************************************************************************
- * Copyright (C) 2021 VLC authors and VideoLAN
+ * Copyright (C) 2022 VLC authors and VideoLAN
*
- * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ * Authors: Prince Gupta <guptaprince8832 at gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,35 +18,34 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
-#ifndef MLITEMCOVER_HPP
-#define MLITEMCOVER_HPP
+#ifndef MLCUSTOMCOVER_HPP
+#define MLCUSTOMCOVER_HPP
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+#include <QQuickAsyncImageProvider>
-// Util includes
-#include "util/covergenerator.hpp"
+#include <memory>
-// MediaLibrary includes
-#include "mlqmltypes.hpp"
+class MLItemId;
+class MediaLib;
-class MLItemCover : public MLItem
+class MLCustomCover : public QQuickAsyncImageProvider
{
public:
- /* explicit */ MLItemCover(const MLItemId & id);
+ MLCustomCover(const QString &providerId, MediaLib *ml);
-public: // Interface
- bool hasGenerator() const;
- void setGenerator(bool generating);
+ QString get(const MLItemId &parentId
+ , const QSize &size
+ , const QString &defaultCover
+ , const int countX = 2
+ , const int countY = 2
+ , const int blur = 0
+ , const bool split_duplicate = false);
- QString getCover() const;
- void setCover(const QString & fileName);
+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize);
private:
- bool m_isGenerating = false;
-
- QString m_cover;
+ const QString m_providerId;
+ MediaLib *m_ml = nullptr;
};
-#endif
+#endif // MLCUSTOMCOVER_HPP
=====================================
modules/gui/qt/medialibrary/mlfolder.cpp
=====================================
@@ -23,7 +23,7 @@
// Ctor / dtor
MLFolder::MLFolder(const vlc_ml_folder_t * data)
- : MLItemCover(MLItemId(data->i_id, VLC_ML_PARENT_FOLDER))
+ : MLItem(MLItemId(data->i_id, VLC_ML_PARENT_FOLDER))
, m_present(data->b_present)
, m_banned(data->b_banned)
, m_title(data->psz_name)
=====================================
modules/gui/qt/medialibrary/mlfolder.hpp
=====================================
@@ -22,9 +22,9 @@
#define MLFOLDER_HPP
// MediaLibrary includes
-#include "mlitemcover.hpp"
+#include "mlqmltypes.hpp"
-class MLFolder : public MLItemCover
+class MLFolder : public MLItem
{
public:
MLFolder(const vlc_ml_folder_t * data);
=====================================
modules/gui/qt/medialibrary/mlgenre.cpp
=====================================
@@ -19,21 +19,12 @@
#include "mlgenre.hpp"
MLGenre::MLGenre(const vlc_ml_genre_t *_data )
- : MLItemCover( MLItemId( _data->i_id, VLC_ML_PARENT_GENRE ) )
+ : MLItem( MLItemId( _data->i_id, VLC_ML_PARENT_GENRE ) )
, m_name ( QString::fromUtf8( _data->psz_name ) )
, m_nbTracks ( (unsigned int)_data->i_nb_tracks )
{
assert(_data);
-
- for (int i = VLC_ML_THUMBNAIL_SMALL; i < VLC_ML_THUMBNAIL_SIZE_COUNT; ++i)
- {
- if (_data->thumbnails[i].psz_mrl)
- {
- setCover(_data->thumbnails[i].psz_mrl);
- break;
- }
- }
}
QString MLGenre::getName() const
=====================================
modules/gui/qt/medialibrary/mlgenre.hpp
=====================================
@@ -24,9 +24,9 @@
#endif
// MediaLibrary includes
-#include "mlitemcover.hpp"
+#include "mlqmltypes.hpp"
-class MLGenre : public MLItemCover
+class MLGenre : public MLItem
{
public:
MLGenre(const vlc_ml_genre_t * _data);
=====================================
modules/gui/qt/medialibrary/mlgenremodel.cpp
=====================================
@@ -22,14 +22,15 @@
#include "util/covergenerator.hpp"
// MediaLibrary includes
+#include "mlcustomcover.hpp"
#include "mlartistmodel.hpp"
//-------------------------------------------------------------------------------------------------
// Static variables
-// NOTE: We multiply by 2 to cover most dpi settings.
-static const int MLGENREMODEL_COVER_WIDTH = 260 * 2;
-static const int MLGENREMODEL_COVER_HEIGHT = 130 * 2;
+// NOTE: We multiply by 3 to cover most dpi settings.
+static const int MLGENREMODEL_COVER_WIDTH = 260 * 3;
+static const int MLGENREMODEL_COVER_HEIGHT = 130 * 3;
static const int MLGENREMODEL_COVER_COUNTX = 4;
static const int MLGENREMODEL_COVER_COUNTY = 2;
@@ -38,29 +39,6 @@ static const int MLGENREMODEL_COVER_BLUR = 4;
//-------------------------------------------------------------------------------------------------
-namespace
-{
-
-QStringList getGenreMediaThumbnails(vlc_medialibrary_t* p_ml, const int count, const int64_t id)
-{
- QStringList thumbnails;
-
- vlc_ml_query_params_t params;
-
- memset(¶ms, 0, sizeof(vlc_ml_query_params_t));
-
- // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
- params.i_nbResults = count * 2;
-
- ml_unique_ptr<vlc_ml_album_list_t> list(vlc_ml_list_genre_albums(p_ml, ¶ms, id));
-
- thumbnailCopy(ml_range_iterate<vlc_ml_album_t>(list), std::back_inserter(thumbnails), count);
-
- return thumbnails;
-}
-
-}
-
QHash<QByteArray, vlc_ml_sorting_criteria_t> MLGenreModel::M_names_to_criteria = {
{"title", VLC_ML_SORTING_ALPHA}
};
@@ -161,65 +139,13 @@ vlc_ml_sorting_criteria_t MLGenreModel::nameToCriteria(QByteArray name) const
QString MLGenreModel::getCover(MLGenre * genre) const
{
- QString cover = genre->getCover();
-
- // NOTE: Making sure we're not already generating a cover.
- if (cover.isNull() == false || genre->hasGenerator())
- return cover;
-
- MLItemId genreId = genre->getId();
- struct Context{
- QString cover;
- };
- genre->setGenerator(true);
- m_mediaLib->runOnMLThread<Context>(this,
- //ML thread
- [genreId, coverDefault = m_coverDefault]
- (vlc_medialibrary_t* ml, Context& ctx)
- {
- CoverGenerator generator {genreId};
-
- generator.setSize(QSize(MLGENREMODEL_COVER_WIDTH,
- MLGENREMODEL_COVER_HEIGHT));
-
- generator.setCountX(MLGENREMODEL_COVER_COUNTX);
- generator.setCountY(MLGENREMODEL_COVER_COUNTY);
-
- generator.setSplit(CoverGenerator::Duplicate);
-
- generator.setBlur(MLGENREMODEL_COVER_BLUR);
-
- if (!coverDefault.isEmpty())
- generator.setDefaultThumbnail(coverDefault);
-
- if (generator.cachedFileAvailable())
- ctx.cover = generator.cachedFileURL();
- else
- ctx.cover = generator.execute(getGenreMediaThumbnails(ml, MLGENREMODEL_COVER_COUNTX * MLGENREMODEL_COVER_COUNTY, genreId.id));
-
- vlc_ml_media_set_genre_thumbnail(ml, genreId.id, qtu(ctx.cover), VLC_ML_THUMBNAIL_SMALL);
- },
- //UI thread
- [this, genreId]
- (quint64, Context& ctx)
- {
- int row = 0;
- // NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
- auto genre = static_cast<MLGenre *>(findInCache(genreId, &row));
- if (!genre)
- return;
-
- genre->setCover(ctx.cover);
- genre->setGenerator(false);
-
- //we're running in a callback
- QModelIndex modelIndex =this->index(row);
- //we're running in a callback
- emit const_cast<MLGenreModel*>(this)->dataChanged(modelIndex, modelIndex, { GENRE_COVER });
- });
-
-
- return cover;
+ return ml()->customCover()->get(genre->getId()
+ , QSize(MLGENREMODEL_COVER_WIDTH, MLGENREMODEL_COVER_HEIGHT)
+ , m_coverDefault
+ , MLGENREMODEL_COVER_COUNTX
+ , MLGENREMODEL_COVER_COUNTY
+ , MLGENREMODEL_COVER_BLUR
+ , true);
}
//-------------------------------------------------------------------------------------------------
=====================================
modules/gui/qt/medialibrary/mlgroup.cpp
=====================================
@@ -28,7 +28,7 @@
//-------------------------------------------------------------------------------------------------
MLGroup::MLGroup(const vlc_ml_group_t * data)
- : MLItemCover(MLItemId(data->i_id, VLC_ML_PARENT_GROUP))
+ : MLItem(MLItemId(data->i_id, VLC_ML_PARENT_GROUP))
, m_title(qfu(data->psz_name))
, m_duration(data->i_duration)
, m_date(data->i_creation_date)
=====================================
modules/gui/qt/medialibrary/mlgroup.hpp
=====================================
@@ -26,9 +26,9 @@
#endif
// MediaLibrary includes
-#include "mlitemcover.hpp"
+#include "mlqmltypes.hpp"
-class MLGroup : public MLItemCover
+class MLGroup : public MLItem
{
public:
MLGroup(const vlc_ml_group_t * data);
=====================================
modules/gui/qt/medialibrary/mlhelper.cpp
=====================================
@@ -20,43 +20,6 @@
// MediaLibrary includes
#include "mlbasemodel.hpp"
-#include "mlitemcover.hpp"
-#include "thumbnailcollector.hpp"
-
-namespace
-{
-
-struct ThumbnailList
-{
- QSet<int64_t> toGenerate;
- QStringList existing;
-};
-
-ThumbnailList extractChildMediaThumbnailsOrIDs(vlc_medialibrary_t *p_ml, const int count, const MLItemId &itemID)
-{
- ThumbnailList result;
-
- vlc_ml_query_params_t params {};
- params.i_nbResults = count;
-
- ml_unique_ptr<vlc_ml_media_list_t> list(vlc_ml_list_media_of(p_ml, ¶ms, itemID.type, itemID.id));
-
- for (const auto &media : ml_range_iterate<vlc_ml_media_t>(list))
- {
- const bool isThumbnailAvailable = (media.thumbnails[VLC_ML_THUMBNAIL_SMALL].i_status == VLC_ML_THUMBNAIL_STATUS_AVAILABLE);
- if (isThumbnailAvailable)
- {
- result.existing.push_back(toValidLocalFile(media.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl));
- } else if (media.i_type == VLC_ML_MEDIA_TYPE_VIDEO)
- {
- result.toGenerate.insert(media.i_id);
- }
- }
-
- return result;
-}
-
-}
QString MsToString( int64_t time , bool doShort )
{
@@ -83,91 +46,6 @@ QString MsToString( int64_t time , bool doShort )
}
-QStringList extractMediaThumbnails(vlc_medialibrary_t *p_ml, const int count, const MLItemId &itemID)
-{
- // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
- return extractChildMediaThumbnailsOrIDs(p_ml, count * 2, itemID).existing;
-}
-
-QString createGroupMediaCover(const MLBaseModel* model, MLItemCover* parent
- , int role
- , const std::shared_ptr<CoverGenerator> generator)
-{
- QString cover = parent->getCover();
-
- // NOTE: Making sure we're not already generating a cover.
- if (cover.isNull() == false || parent->hasGenerator())
- return cover;
-
- if (generator->cachedFileAvailable())
- return generator->cachedFileURL();
-
- MLItemId itemId = parent->getId();
- parent->setGenerator(true);
-
- const auto generateCover = [=](const QStringList &childCovers)
- {
- struct Context { QString cover; };
-
- model->ml()->runOnMLThread<Context>(model,
- //ML thread
- [generator, childCovers]
- (vlc_medialibrary_t * , Context & ctx)
- {
- ctx.cover = generator->execute(childCovers);
- },
- //UI Thread
- [model, itemId, role]
- (quint64, Context & ctx)
- {
- int row;
-
- // NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
- auto item = static_cast<MLItemCover *>(model->findInCache(itemId, &row));
- if (!item)
- return;
-
- item->setCover(ctx.cover);
- item->setGenerator(false);
-
- QModelIndex modelIndex = model->index(row);
- emit const_cast<MLBaseModel *>(model)->dataChanged(modelIndex, modelIndex, { role });
- }
- );
- };
-
- model->ml()->runOnMLThread<ThumbnailList>(model,
- //ML thread (get child thumbnails or ids)
- [itemId, generator](vlc_medialibrary_t *p_ml, ThumbnailList &ctx)
- {
- ctx = extractChildMediaThumbnailsOrIDs(p_ml, generator->requiredNoOfThumbnails(), itemId);
- }
- //UI Thread
- , [=](quint64, ThumbnailList & ctx)
- {
- if (ctx.toGenerate.empty())
- {
- generateCover(ctx.existing);
- return;
- }
-
- // request child thumbnail generation, when finished generate the cover
- auto collector = new ThumbnailCollector(const_cast<MLBaseModel *>(model));
- QObject::connect(collector, &ThumbnailCollector::finished, model, [=]()
- {
- const auto thumbnails = ctx.existing + collector->allGenerated().values();
- generateCover(thumbnails);
-
- collector->deleteLater();
- });
-
- collector->start(model->ml(), ctx.toGenerate);
- }
- );
-
- return cover;
-}
-
QString toValidLocalFile(const char *mrl)
{
QUrl url(mrl);
=====================================
modules/gui/qt/medialibrary/mlhelper.hpp
=====================================
@@ -30,7 +30,6 @@
// Forward declarations
class MLBaseModel;
-class MLItemCover;
class MLItemId;
class CoverGenerator;
@@ -101,9 +100,4 @@ void thumbnailCopy(const MLListRange<T> &list, O dst, const int max)
QString MsToString( int64_t time, bool doShort = false );
-QStringList extractMediaThumbnails(vlc_medialibrary_t *p_ml, const int count, const MLItemId &itemID);
-
-QString createGroupMediaCover(const MLBaseModel* model, MLItemCover* parent
- , int role, const std::shared_ptr<CoverGenerator> generator);
-
#endif // MLHELPER_HPP
=====================================
modules/gui/qt/medialibrary/mlitemcover.cpp deleted
=====================================
@@ -1,55 +0,0 @@
-/*****************************************************************************
- * Copyright (C) 2021 VLC authors and VideoLAN
- *
- * Authors: Benjamin Arnaud <bunjee at omega.gg>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#include "mlitemcover.hpp"
-
-//-------------------------------------------------------------------------------------------------
-// Ctor / dtor
-//-------------------------------------------------------------------------------------------------
-
-MLItemCover::MLItemCover(const MLItemId & id)
- : MLItem(id)
-{}
-
-//-------------------------------------------------------------------------------------------------
-// Interface
-//-------------------------------------------------------------------------------------------------
-
-bool MLItemCover::hasGenerator() const
-{
- return m_isGenerating;
-}
-
-void MLItemCover::setGenerator(bool generating)
-{
- m_isGenerating = generating;
-}
-
-//-------------------------------------------------------------------------------------------------
-
-QString MLItemCover::getCover() const
-{
- return m_cover;
-}
-
-void MLItemCover::setCover(const QString & fileName)
-{
- m_cover = fileName;
-}
=====================================
modules/gui/qt/medialibrary/mlplaylist.cpp
=====================================
@@ -26,7 +26,7 @@
//-------------------------------------------------------------------------------------------------
MLPlaylist::MLPlaylist(const vlc_ml_playlist_t * data)
- : MLItemCover(MLItemId(data->i_id, VLC_ML_PARENT_PLAYLIST))
+ : MLItem(MLItemId(data->i_id, VLC_ML_PARENT_PLAYLIST))
, m_name(qfu(data->psz_name))
, m_duration(0) // TODO m_duration
, m_count(data->i_nb_media)
=====================================
modules/gui/qt/medialibrary/mlplaylist.hpp
=====================================
@@ -24,9 +24,9 @@
#endif
// MediaLibrary includes
-#include "mlitemcover.hpp"
+#include "mlqmltypes.hpp"
-class MLPlaylist : public MLItemCover
+class MLPlaylist : public MLItem
{
public:
MLPlaylist(const vlc_ml_playlist_t * data);
=====================================
modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
=====================================
@@ -28,6 +28,7 @@
// MediaLibrary includes
#include "mlhelper.hpp"
+#include "mlcustomcover.hpp"
#include "mlplaylist.hpp"
//-------------------------------------------------------------------------------------------------
@@ -36,9 +37,9 @@
namespace {
-// NOTE: We multiply by 2 to cover most dpi settings.
-const int MLPLAYLISTMODEL_COVER_WIDTH = 512 * 2; // 16 / 10 ratio
-const int MLPLAYLISTMODEL_COVER_HEIGHT = 320 * 2;
+// NOTE: We multiply by 3 to cover most dpi settings.
+const int MLPLAYLISTMODEL_COVER_WIDTH = 260 * 3; // 16 / 10 ratio
+const int MLPLAYLISTMODEL_COVER_HEIGHT = 162 * 3;
const int PLAYLIST_COVERX = 2;
const int PLAYLIST_COVERY = 2;
@@ -300,21 +301,10 @@ void MLPlaylistListModel::endTransaction()
QString MLPlaylistListModel::getCover(MLPlaylist * playlist) const
{
- auto generator = std::make_shared<CoverGenerator>(playlist->getId());
-
- generator->setCountX(PLAYLIST_COVERX);
- generator->setCountY(PLAYLIST_COVERY);
-
- generator->setSize(m_coverSize);
-
- if (!m_coverDefault.isEmpty())
- generator->setDefaultThumbnail(m_coverDefault);
-
- generator->setPrefix(m_coverPrefix);
-
- return createGroupMediaCover(this, playlist
- , PLAYLIST_THUMBNAIL
- , generator);
+ return ml()->customCover()->get(playlist->getId()
+ , m_coverSize
+ , m_coverDefault
+ , PLAYLIST_COVERX, PLAYLIST_COVERY);
}
//-------------------------------------------------------------------------------------------------
=====================================
modules/gui/qt/medialibrary/mlvideofoldersmodel.cpp
=====================================
@@ -31,14 +31,14 @@
#include "util/covergenerator.hpp"
// MediaLibrary includes
-#include "mlhelper.hpp"
+#include "mlcustomcover.hpp"
#include "mlfolder.hpp"
// Static variables
-// NOTE: We multiply by 2 to cover most dpi settings.
-static const int MLVIDEOFOLDERSMODEL_COVER_WIDTH = 512 * 2; // 16 / 10 ratio
-static const int MLVIDEOFOLDERSMODEL_COVER_HEIGHT = 320 * 2;
+// NOTE: We multiply by 3 to cover most dpi settings.
+static const int MLVIDEOFOLDERSMODEL_COVER_WIDTH = 260 * 3; // 16 / 10 ratio
+static const int MLVIDEOFOLDERSMODEL_COVER_HEIGHT = 162 * 3;
static const QHash<QByteArray, vlc_ml_sorting_criteria_t> criterias =
{
@@ -86,11 +86,9 @@ QVariant MLVideoFoldersModel::itemRoleData(MLItem * item, const int role) const
return QVariant::fromValue(folder->getTitle());
case FOLDER_THUMBNAIL:
{
- auto generator = std::make_shared<CoverGenerator>(folder->getId());
- generator->setSize(QSize(MLVIDEOFOLDERSMODEL_COVER_WIDTH, MLVIDEOFOLDERSMODEL_COVER_HEIGHT));
- generator->setDefaultThumbnail(":/noart_videoCover.svg");
-
- return createGroupMediaCover(this, folder, FOLDER_THUMBNAIL, generator);
+ return ml()->customCover()->get(folder->getId()
+ , QSize(MLVIDEOFOLDERSMODEL_COVER_WIDTH, MLVIDEOFOLDERSMODEL_COVER_HEIGHT)
+ , QStringLiteral(":/noart_videoCover.svg"));
}
case FOLDER_DURATION:
return QVariant::fromValue(folder->getDuration());
=====================================
modules/gui/qt/medialibrary/mlvideogroupsmodel.cpp
=====================================
@@ -31,16 +31,16 @@
#include "util/covergenerator.hpp"
// MediaLibrary includes
-#include "mlhelper.hpp"
+#include "mlcustomcover.hpp"
#include "mlgroup.hpp"
#include "mlvideo.hpp"
//-------------------------------------------------------------------------------------------------
// Static variables
-// NOTE: We multiply by 2 to cover most dpi settings.
-static const int MLVIDEOGROUPSMODEL_COVER_WIDTH = 512 * 2; // 16 / 10 ratio
-static const int MLVIDEOGROUPSMODEL_COVER_HEIGHT = 320 * 2;
+// NOTE: We multiply by 3 to cover most dpi settings.
+static const int MLVIDEOGROUPSMODEL_COVER_WIDTH = 260 * 3; // 16 / 10 ratio
+static const int MLVIDEOGROUPSMODEL_COVER_HEIGHT = 162 * 3;
static const QHash<QByteArray, vlc_ml_sorting_criteria_t> criterias =
{
@@ -93,13 +93,9 @@ QVariant MLVideoGroupsModel::itemRoleData(MLItem * item, const int role) const /
return QVariant::fromValue(group->getTitle());
case VIDEO_THUMBNAIL:
{
- auto generator = std::make_shared<CoverGenerator>(group->getId());
- generator->setSize(QSize(MLVIDEOGROUPSMODEL_COVER_WIDTH, MLVIDEOGROUPSMODEL_COVER_HEIGHT));
- generator->setDefaultThumbnail(":/noart_videoCover.svg");
-
- return createGroupMediaCover(this, group
- , VIDEO_THUMBNAIL
- , generator);
+ return ml()->customCover()->get(group->getId()
+ , QSize(MLVIDEOGROUPSMODEL_COVER_WIDTH, MLVIDEOGROUPSMODEL_COVER_HEIGHT)
+ , QStringLiteral(":/noart_videoCover.svg"));
}
case VIDEO_DURATION:
return QVariant::fromValue(group->getDuration());
=====================================
modules/gui/qt/util/covergenerator.cpp
=====================================
@@ -53,12 +53,10 @@ static const QString COVERGENERATOR_DEFAULT = ":/noart_albumCover.svg";
// Ctor / dtor
//-------------------------------------------------------------------------------------------------
-CoverGenerator::CoverGenerator(const MLItemId & itemId)
- : m_id(itemId)
- , m_countX(COVERGENERATOR_COUNT)
+CoverGenerator::CoverGenerator()
+ : m_countX(COVERGENERATOR_COUNT)
, m_countY(COVERGENERATOR_COUNT)
, m_split(Divide)
- , m_smooth(true)
, m_blur(0)
, m_default(COVERGENERATOR_DEFAULT) {}
@@ -66,13 +64,6 @@ CoverGenerator::CoverGenerator(const MLItemId & itemId)
// Interface
//-------------------------------------------------------------------------------------------------
-MLItemId CoverGenerator::getId()
-{
- return m_id;
-}
-
-//-------------------------------------------------------------------------------------------------
-
void CoverGenerator::setSize(const QSize & size)
{
m_size = size;
@@ -93,11 +84,6 @@ void CoverGenerator::setSplit(Split split)
m_split = split;
}
-void CoverGenerator::setSmooth(bool enabled)
-{
- m_smooth = enabled;
-}
-
void CoverGenerator::setBlur(int radius)
{
m_blur = radius;
@@ -108,52 +94,17 @@ void CoverGenerator::setDefaultThumbnail(const QString & fileName)
m_default = fileName;
}
-void CoverGenerator::setPrefix(const QString & prefix)
-{
- m_prefix = prefix;
-}
-
int CoverGenerator::requiredNoOfThumbnails() const
{
return m_countX * m_countY;
}
-bool CoverGenerator::cachedFileAvailable() const
-{
- return QFile::exists(fileName());
-}
-
-QString CoverGenerator::cachedFileURL() const
-{
- return QUrl::fromLocalFile(fileName()).toString();
-}
-
-QString CoverGenerator::fileName() const
-{
- QDir dir(config_GetUserDir(VLC_CACHE_DIR) + COVERGENERATOR_STORAGE);
- return dir.absoluteFilePath(QString("%1_thumbnail_%2_%3x%4.jpg")
- .arg((m_prefix.isEmpty() ? getPrefix(m_id.type) : m_prefix)
- , QString::number(m_id.id)
- , QString::number(m_size.width())
- , QString::number(m_size.height())));
-}
-
//-------------------------------------------------------------------------------------------------
// QRunnable implementation
//-------------------------------------------------------------------------------------------------
-QString CoverGenerator::execute(QStringList thumbnails) const
+QImage CoverGenerator::execute(QStringList thumbnails) const
{
- QDir dir(config_GetUserDir(VLC_CACHE_DIR) + COVERGENERATOR_STORAGE);
-
- dir.mkpath(dir.absolutePath());
-
- QString fileName = this->fileName();
- if (dir.exists(fileName))
- {
- return QUrl::fromLocalFile(fileName).toString();
- }
-
int count = m_countX * m_countY;
int countX;
@@ -216,9 +167,7 @@ QString CoverGenerator::execute(QStringList thumbnails) const
if (m_blur > 0)
blur(image);
- image.save(fileName, "jpg");
-
- return QUrl::fromLocalFile(fileName).toString();
+ return image;
}
//-------------------------------------------------------------------------------------------------
@@ -230,8 +179,8 @@ void CoverGenerator::draw(QPainter & painter,
{
int count = fileNames.count();
- int width = m_size.width() / countX;
- int height = m_size.height() / countY;
+ const int width = std::ceil(m_size.width() / static_cast<double>(countX));
+ const int height = std::ceil(m_size.height() / static_cast<double>(countY));
for (int y = 0; y < countY; y++)
{
@@ -285,35 +234,11 @@ void CoverGenerator::drawImage(QPainter & painter, const QString & fileName, con
QSize size = reader.size().scaled(target.width(),
target.height(), Qt::KeepAspectRatioByExpanding);
- QImage image;
-
- if (fileName.endsWith(".svg", Qt::CaseInsensitive))
- {
- if (size.isEmpty() == false)
- {
- reader.setScaledSize(size);
- }
-
- if (reader.read(&image) == false)
- return;
- }
- else
- {
- if (reader.read(&image) == false)
- return;
-
- if (size.isEmpty() == false)
- {
- // NOTE: Should we use Qt::SmoothTransformation or favor efficiency ?
- if (m_smooth)
- image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
- else
- image = image.scaled(size, Qt::IgnoreAspectRatio);
- }
- }
+ reader.setScaledSize(size);
+ QImage image = reader.read();
- int x = (image.width () - target.width ()) / 2;
- int y = (image.height() - target.height()) / 2;
+ int x = std::ceil((image.width() - target.width()) / 2.);
+ int y = std::ceil((image.height() - target.height()) / 2.);
QRect source(x, y, target.width(), target.height());
=====================================
modules/gui/qt/util/covergenerator.hpp
=====================================
@@ -48,7 +48,7 @@ public: // Enums
};
public:
- CoverGenerator(const MLItemId & itemId);
+ CoverGenerator();
public: // Interface
MLItemId getId();
@@ -69,16 +69,9 @@ public: // Interface
void setDefaultThumbnail(const QString & fileName);
- // NOTE: This lets us enforce a specific prefix for the cover fileName.
- void setPrefix(const QString & prefix);
-
int requiredNoOfThumbnails() const;
- bool cachedFileAvailable() const;
-
- QString cachedFileURL() const;
-
- QString execute(QStringList thumbnails) const;
+ QImage execute(QStringList thumbnails) const;
private: // Functions
QString fileName() const;
@@ -92,8 +85,6 @@ private: // Functions
QString getPrefix(vlc_ml_parent_type type) const;
private:
- MLItemId m_id;
-
QSize m_size;
int m_countX;
@@ -101,13 +92,9 @@ private:
Split m_split;
- bool m_smooth;
-
int m_blur;
QString m_default;
-
- QString m_prefix;
};
#endif // COVERGENERATOR_HPP
=====================================
modules/gui/qt/widgets/native/roundimage.cpp
=====================================
@@ -31,10 +31,12 @@
#include <QBuffer>
#include <QCache>
+#include <QFile>
#include <QImage>
#include <QImageReader>
#include <QPainter>
#include <QPainterPath>
+#include <QQuickImageProvider>
#include <QQuickWindow>
#include <QGuiApplication>
#include <QSGImageNode>
@@ -72,49 +74,228 @@ namespace
}
// images are cached (result of RoundImageGenerator) with the cost calculated from QImage::sizeInBytes
- QCache<ImageCacheKey, QImage> imageCache(2 * 1024 * 1024); // 2 MiB
+ QCache<ImageCacheKey, QImage> imageCache(32 * 1024 * 1024); // 32 MiB
- std::unique_ptr<QIODevice> getReadable(const QUrl &url)
- try
+ QRectF doPreserveAspectCrop(const QSizeF &sourceSize, const QSizeF &size)
{
- if (!QQmlFile::isLocalFile(url))
+ const qreal ratio = std::max(size.width() / sourceSize.width(), size.height() / sourceSize.height());
+ const QSizeF imageSize = sourceSize * ratio;
+ const QPointF alignedCenteredTopLeft {(size.width() - imageSize.width()) / 2., (size.height() - imageSize.height()) / 2.};
+ return {alignedCenteredTopLeft, imageSize};
+ }
+
+ class ImageReader : public AsyncTask<QImage>
+ {
+ public:
+ // requestedSize is only taken as hint, the Image is resized with PreserveAspectCrop
+ ImageReader(QIODevice *device, QSize requestedSize)
+ : device {device}
+ , requestedSize {requestedSize}
+ {
+ }
+
+ QString errorString() const { return errorStr; }
+
+ QImage execute()
+ {
+ QImageReader reader;
+ reader.setDevice(device);
+ const QSize sourceSize = reader.size();
+
+ if (requestedSize.isValid())
+ reader.setScaledSize(doPreserveAspectCrop(sourceSize, requestedSize).size().toSize());
+
+ auto img = reader.read();
+ errorStr = reader.errorString();
+ return img;
+ }
+
+ private:
+ QIODevice *device;
+ QSize requestedSize;
+ QString errorStr;
+ };
+
+ class LocalImageResponse : public QQuickImageResponse
+ {
+ public:
+ LocalImageResponse(const QString &fileName, const QSize &requestedSize)
+ {
+ auto file = new QFile(fileName);
+ reader.reset(new ImageReader(file, requestedSize));
+ file->setParent(reader.get());
+
+ connect(reader.get(), &ImageReader::result, this, &LocalImageResponse::handleImageRead);
+
+ reader->start(*QThreadPool::globalInstance());
+ }
+
+ QQuickTextureFactory *textureFactory() const override
+ {
+ return result.isNull() ? nullptr : QQuickTextureFactory::textureFactoryForImage(result);
+ }
+
+ QString errorString() const override
+ {
+ return errorStr;
+ }
+
+ private:
+ void handleImageRead()
{
+ result = reader->takeResult();
+ errorStr = reader->errorString();
+ reader.reset();
+
+ emit finished();
+ }
+
+ QImage result;
+ TaskHandle<ImageReader> reader;
+ QString errorStr;
+ };
+
#ifdef QT_NETWORK_LIB
- QNetworkAccessManager networkMgr;
- networkMgr.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
- auto reply = networkMgr.get(QNetworkRequest(url));
- QEventLoop loop;
- QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
- loop.exec();
+ class NetworkImageResponse : public QQuickImageResponse
+ {
+ public:
+ NetworkImageResponse(QNetworkReply *reply, QSize requestedSize) : reply {reply}, requestedSize {requestedSize}
+ {
+ QObject::connect(reply, &QNetworkReply::finished
+ , this, &NetworkImageResponse::handleNetworkReplyFinished);
+ }
+
+ QQuickTextureFactory *textureFactory() const override
+ {
+ return result.isNull() ? nullptr : QQuickTextureFactory::textureFactoryForImage(result);
+ }
+
+ QString errorString() const override
+ {
+ return error;
+ }
+
+ void cancel() override
+ {
+ if (reply->isRunning())
+ reply->abort();
+
+ reader.reset();
+ }
+ private:
+ void handleNetworkReplyFinished()
+ {
if (reply->error() != QNetworkReply::NoError)
- throw std::runtime_error(reply->errorString().toStdString());
+ {
+ error = reply->errorString();
+ emit finished();
+ return;
+ }
- class DataOwningBuffer : private QByteArray, public QBuffer
+ reader.reset(new ImageReader(reply, requestedSize));
+ QObject::connect(reader.get(), &ImageReader::result, this, [this]()
{
- public:
- explicit DataOwningBuffer(const QByteArray &data)
- : QByteArray(data), QBuffer(this, nullptr) { }
- };
-
- auto file = std::make_unique<DataOwningBuffer>(reply->readAll());
- file->open(QIODevice::ReadOnly);
- return file;
-#else
- throw std::runtime_error("Qt Network Library is not available!");
+ result = reader->takeResult();
+ error = reader->errorString();
+ reader.reset();
+
+ emit finished();
+ });
+
+ reader->start(*QThreadPool::globalInstance());
+ }
+
+ QNetworkReply *reply;
+ QSize requestedSize;
+ TaskHandle<ImageReader> reader;
+ QImage result;
+ QString error;
+ };
#endif
+
+ class ImageProviderAsyncAdaptor : public QQuickImageResponse
+ {
+ public:
+ ImageProviderAsyncAdaptor(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize)
+ {
+ task.reset(new ProviderImageGetter(provider, id, requestedSize));
+ connect(task.get(), &ProviderImageGetter::result, this, [this]()
+ {
+ result = task->takeResult();
+ task.reset();
+
+ emit finished();
+ });
+
+ task->start(*QThreadPool::globalInstance());
}
- else
+
+ QQuickTextureFactory *textureFactory() const override
{
- auto file = std::make_unique<QFile>(QQmlFile::urlToLocalFileOrQrc(url));
- file->open(QIODevice::ReadOnly);
- return file;
+ return result.isNull() ? nullptr : QQuickTextureFactory::textureFactoryForImage(result);
}
- }
- catch (const std::exception& error)
+
+ private:
+ class ProviderImageGetter : public AsyncTask<QImage>
+ {
+ public:
+ ProviderImageGetter(QQuickImageProvider *provider, const QString &id, const QSize &requestedSize)
+ : provider {provider}
+ , id{id}
+ , requestedSize{requestedSize}
+ {
+ }
+
+ QImage execute() override
+ {
+ return provider->requestImage(id, &sourceSize, requestedSize);
+ }
+
+ private:
+ QQuickImageProvider *provider;
+ QString id;
+ QSize requestedSize;
+ QSize sourceSize;
+ };
+
+ TaskHandle<ProviderImageGetter> task;
+ QImage result;
+ };
+
+ QQuickImageResponse *getAsyncImageResponse(const QUrl &url, const QSize &requestedSize, QQmlEngine *engine)
{
- qWarning() << "Could not load source image:" << url << error.what();
- return {};
+ if (url.scheme() == QStringLiteral("image"))
+ {
+ auto provider = engine->imageProvider(url.host());
+ if (!provider)
+ return nullptr;
+
+ assert(provider->imageType() == QQmlImageProviderBase::Image
+ || provider->imageType() == QQmlImageProviderBase::ImageResponse);
+
+ const auto imageId = url.toString(QUrl::RemoveScheme | QUrl::RemoveAuthority).mid(1);;
+
+ if (provider->imageType() == QQmlImageProviderBase::Image)
+ return new ImageProviderAsyncAdaptor(static_cast<QQuickImageProvider *>(provider), imageId, requestedSize);
+ if (provider->imageType() == QQmlImageProviderBase::ImageResponse)
+ return static_cast<QQuickAsyncImageProvider *>(provider)->requestImageResponse(imageId, requestedSize);
+
+ return nullptr;
+ }
+ else if (QQmlFile::isLocalFile(url))
+ {
+ return new LocalImageResponse(QQmlFile::urlToLocalFileOrQrc(url), requestedSize);
+ }
+#ifdef QT_NETWORK_LIB
+ else
+ {
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
+ auto reply = engine->networkAccessManager()->get(request);
+ return new NetworkImageResponse(reply, requestedSize);
+ }
+#endif
}
}
@@ -127,6 +308,11 @@ RoundImage::RoundImage(QQuickItem *parent) : QQuickItem {parent}
connect(this, &QQuickItem::widthChanged, this, &RoundImage::regenerateRoundImage);
}
+RoundImage::~RoundImage()
+{
+ resetImageRequest();
+}
+
QSGNode *RoundImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
auto node = static_cast<QSGImageNode *>(oldNode);
@@ -229,114 +415,134 @@ void RoundImage::setDPR(const qreal value)
regenerateRoundImage();
}
-void RoundImage::regenerateRoundImage()
+void RoundImage::handleImageRequestFinished()
{
- if (!isComponentComplete() || m_enqueuedGeneration)
- return;
+ const QString error = m_activeImageRequest->errorString();
+ QImage image;
+ if (auto textureFactory = m_activeImageRequest->textureFactory())
+ {
+ image = textureFactory->image();
+ delete textureFactory;
+ }
- // remove old contents
- m_dirty = true;
- m_roundImage = {};
- update();
- setFlag(ItemHasContents, false); // update() is still required
+ resetImageRequest();
- m_roundImageGenerator.reset();
+ if (image.isNull())
+ {
+ qDebug() << "failed to get image, error" << error << source();
+ return;
+ }
- // use Qt::QueuedConnection to delay generation, so that dependent properties
- // subsequent updates can be merged, f.e when VLCStyle.scale changes
- m_enqueuedGeneration = true;
+ const qreal scaledWidth = this->width() * m_dpr;
+ const qreal scaledHeight = this->height() * m_dpr;
+ const qreal scaledRadius = this->radius() * m_dpr;
+
+ const ImageCacheKey key {source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius};
- QMetaObject::invokeMethod(this, [this] ()
+ // Image is generated in size factor of `m_dpr` to avoid scaling artefacts when
+ // generated image is set with device pixel ratio
+ m_roundImageGenerator.reset(new RoundImageGenerator(image, scaledWidth, scaledHeight, scaledRadius));
+ connect(m_roundImageGenerator.get(), &BaseAsyncTask::result, this, [this, key]()
{
- m_enqueuedGeneration = false;
- assert(!m_roundImageGenerator);
+ const auto image = new QImage(m_roundImageGenerator->takeResult());
- const qreal scaledWidth = this->width() * m_dpr;
- const qreal scaledHeight = this->height() * m_dpr;
- const qreal scaledRadius = this->radius() * m_dpr;
+ m_roundImageGenerator.reset();
- const ImageCacheKey key {source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius};
- if (auto image = imageCache.object(key)) // should only by called in mainthread
+ if (image->isNull())
{
- m_roundImage = *image;
- m_dirty = true;
- setFlag(ItemHasContents, true);
- update();
+ delete image;
+ setRoundImage({});
return;
}
- // Image is generated in size factor of `m_dpr` to avoid scaling artefacts when
- // generated image is set with device pixel ratio
- m_roundImageGenerator.reset(new RoundImageGenerator(m_source, scaledWidth, scaledHeight, scaledRadius));
- connect(m_roundImageGenerator.get(), &BaseAsyncTask::result, this, [this, key]()
- {
- const auto image = new QImage(m_roundImageGenerator->takeResult());
+ image->setDevicePixelRatio(m_dpr);
+ setRoundImage(*image);
- m_roundImageGenerator.reset();
+ imageCache.insert(key, image, image->sizeInBytes());
+ });
- if (!image->isNull())
- {
- image->setDevicePixelRatio(m_dpr);
+ m_roundImageGenerator->start(*QThreadPool::globalInstance());
+}
- imageCache.insert(key, image, image->sizeInBytes());
+void RoundImage::resetImageRequest()
+{
+ if (!m_activeImageRequest)
+ return;
- setFlag(ItemHasContents, true);
+ m_activeImageRequest->disconnect(this);
+ m_activeImageRequest->deleteLater();
+ m_activeImageRequest = nullptr;
+}
- m_roundImage = *image;
+void RoundImage::load()
+{
+ m_enqueuedGeneration = false;
+ assert(!m_roundImageGenerator);
- m_dirty = true;
- }
- else
- {
- delete image;
- m_dirty = false;
- setFlag(ItemHasContents, false);
- }
+ auto engine = qmlEngine(this);
+ if (!engine || m_source.isEmpty() || !size().isValid() || size().isEmpty())
+ return;
+
+ const qreal scaledWidth = this->width() * m_dpr;
+ const qreal scaledHeight = this->height() * m_dpr;
+ const qreal scaledRadius = this->radius() * m_dpr;
- update();
- });
+ const ImageCacheKey key {source(), QSizeF {scaledWidth, scaledHeight}.toSize(), scaledRadius};
+ if (auto image = imageCache.object(key)) // should only by called in mainthread
+ {
+ setRoundImage(*image);
+ return;
+ }
- m_roundImageGenerator->start(*QThreadPool::globalInstance());
- }, Qt::QueuedConnection);
+ m_activeImageRequest = getAsyncImageResponse(source(), QSizeF {scaledWidth, scaledHeight}.toSize(), engine);
+ connect(m_activeImageRequest, &QQuickImageResponse::finished, this, &RoundImage::handleImageRequestFinished);
}
-RoundImage::RoundImageGenerator::RoundImageGenerator(const QUrl &source, qreal width, qreal height, qreal radius)
- : source(source)
- , width(width)
- , height(height)
- , radius(radius)
+void RoundImage::setRoundImage(QImage image)
{
+ m_dirty = true;
+ m_roundImage = image;
+
+ // remove old contents, setting ItemHasContent to false will
+ // inhibit updatePaintNode() call and old content will remain
+ if (image.isNull())
+ update();
+
+ setFlag(ItemHasContents, not image.isNull());
+ update();
}
-QImage RoundImage::RoundImageGenerator::execute()
+void RoundImage::regenerateRoundImage()
{
- if (width <= 0 || height <= 0)
- return {};
+ if (!isComponentComplete() || m_enqueuedGeneration)
+ return;
- if (source.isEmpty())
- return {};
+ // remove old contents
+ setRoundImage({});
- auto file = getReadable(source);
- if (!file || !file->isOpen())
- return {};
+ resetImageRequest();
- QImageReader sourceReader(file.get());
+ m_roundImageGenerator.reset();
- // do PreserveAspectCrop
- const QSizeF size {width, height};
- QSizeF defaultSize = sourceReader.size();
- if (!defaultSize.isValid())
- defaultSize = size;
+ // use Qt::QueuedConnection to delay generation, so that dependent properties
+ // subsequent updates can be merged, f.e when VLCStyle.scale changes
+ m_enqueuedGeneration = true;
- const qreal ratio = std::max(size.width() / defaultSize.width(), size.height() / defaultSize.height());
- const QSizeF targetSize = defaultSize * ratio;
- const QPointF alignedCenteredTopLeft {(size.width() - targetSize.width()) / 2., (size.height() - targetSize.height()) / 2.};
- sourceReader.setScaledSize(targetSize.toSize());
+ QMetaObject::invokeMethod(this, &RoundImage::load, Qt::QueuedConnection);
+}
- if (Q_UNLIKELY(radius <= 0))
- {
- return sourceReader.read();
- }
+RoundImage::RoundImageGenerator::RoundImageGenerator(const QImage &sourceImage, qreal width, qreal height, qreal radius)
+ : sourceImage(sourceImage)
+ , width(width)
+ , height(height)
+ , radius(radius)
+{
+}
+
+QImage RoundImage::RoundImageGenerator::execute()
+{
+ if (width <= 0 || height <= 0 || sourceImage.isNull())
+ return {};
QImage target(width, height, QImage::Format_ARGB32_Premultiplied);
if (target.isNull())
@@ -353,7 +559,10 @@ QImage RoundImage::RoundImageGenerator::execute()
path.addRoundedRect(0, 0, width, height, radius, radius);
painter.setClipPath(path);
- painter.drawImage({alignedCenteredTopLeft, targetSize}, sourceReader.read());
+ // do PreserveAspectCrop
+ const auto imageSize = sourceImage.size();
+ const QPointF alignedCenteredTopLeft {(width - imageSize.width()) / 2., (height - imageSize.height()) / 2.};
+ painter.drawImage(QRectF {alignedCenteredTopLeft, imageSize}, sourceImage);
}
return target;
=====================================
modules/gui/qt/widgets/native/roundimage.hpp
=====================================
@@ -31,6 +31,8 @@
#include <QQuickItem>
#include <QUrl>
+class QQuickImageResponse;
+
class RoundImage : public QQuickItem
{
Q_OBJECT
@@ -42,6 +44,7 @@ class RoundImage : public QQuickItem
public:
RoundImage(QQuickItem *parent = nullptr);
+ ~RoundImage();
void componentComplete() override;
@@ -64,18 +67,22 @@ private:
class RoundImageGenerator : public AsyncTask<QImage>
{
public:
- RoundImageGenerator(const QUrl &source, qreal width, qreal height, qreal radius);
+ RoundImageGenerator(const QImage &sourceImage, qreal width, qreal height, qreal radius);
QImage execute();
private:
- QUrl source;
+ QImage sourceImage;
qreal width;
qreal height;
qreal radius;
};
void setDPR(qreal value);
+ void handleImageRequestFinished();
+ void resetImageRequest();
+ void load();
+ void setRoundImage(QImage image);
void regenerateRoundImage();
QUrl m_source;
@@ -86,6 +93,7 @@ private:
bool m_dirty = false;
TaskHandle<RoundImageGenerator> m_roundImageGenerator {};
+ QQuickImageResponse *m_activeImageRequest {};
bool m_enqueuedGeneration = false;
};
=====================================
po/POTFILES.in
=====================================
@@ -783,8 +783,6 @@ modules/gui/qt/medialibrary/mlfoldersmodel.cpp
modules/gui/qt/medialibrary/mlfoldersmodel.hpp
modules/gui/qt/medialibrary/mlgroup.cpp
modules/gui/qt/medialibrary/mlgroup.hpp
-modules/gui/qt/medialibrary/mlitemcover.cpp
-modules/gui/qt/medialibrary/mlitemcover.hpp
modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
modules/gui/qt/medialibrary/mlplaylistlistmodel.hpp
modules/gui/qt/medialibrary/mlplaylistmedia.cpp
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/a00c5e0744a169ef73abae92293157a5aeb377b9...716ff4b79ee52227010ff2f39ca61cb6316923c9
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/a00c5e0744a169ef73abae92293157a5aeb377b9...716ff4b79ee52227010ff2f39ca61cb6316923c9
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list