Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QML: Add ImageProvider for Cover Art #3913

Merged
merged 4 commits into from
Jun 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/skin/legacy/legacyskinparser.cpp
src/skin/legacy/pixmapsource.cpp
src/skin/legacy/legacyskin.cpp
src/skin/qml/asyncimageprovider.cpp
src/skin/qml/qmlcontrolproxy.cpp
src/skin/qml/qmlplayermanagerproxy.cpp
src/skin/qml/qmlplayerproxy.cpp
Expand Down
25 changes: 24 additions & 1 deletion res/skins/QMLDemo/DeckInfoBar.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "." as Skin
import Mixxx 0.1 as Mixxx
import Mixxx.Controls 0.1 as MixxxControls
import QtGraphicalEffects 1.12
import QtQuick 2.12
import "Theme"

Expand All @@ -15,14 +16,36 @@ Rectangle {
radius: 5
height: 56

Rectangle {
Image {
id: coverArt

anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 5
width: height
source: root.deckPlayer.coverArtUrl
visible: false
}

Rectangle {
id: coverArtCircle

anchors.fill: coverArt
radius: height / 2
visible: false
}

OpacityMask {
anchors.fill: coverArt
source: coverArt
maskSource: coverArtCircle
}

Rectangle {
id: spinnyCircle

anchors.fill: coverArt
radius: height / 2
border.width: 2
border.color: Theme.deckLineColor
Expand Down
79 changes: 79 additions & 0 deletions src/skin/qml/asyncimageprovider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "skin/qml/asyncimageprovider.h"

#include "library/coverartcache.h"

namespace {
const QString kCoverArtPrefix = QStringLiteral("coverart/");
}

namespace mixxx {
namespace skin {
namespace qml {

AsyncImageResponse::AsyncImageResponse(const QString& id, const QSize& requestedSize)
: m_id(id), m_requestedSize(requestedSize) {
setAutoDelete(false);
}

QQuickTextureFactory* AsyncImageResponse::textureFactory() const {
return QQuickTextureFactory::textureFactoryForImage(m_image);
}

void AsyncImageResponse::run() {
if (m_id.startsWith(kCoverArtPrefix)) {
QString trackLocation = AsyncImageProvider::coverArtUrlIdToTrackLocation(m_id);

// TODO: This code does not allow to override embedded cover art with
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
// a custom image, which is possible in Mixxx. We need to access the
// actual CoverInfo of the track instead of constructing a default
// instance on the fly.
//
// Unfortunately, TrackCollectionManager::getTrackByRef will not work
// when called from another thread (like this one). We need a solution
// for that.
CoverInfo coverInfo(CoverInfoRelative(), trackLocation);
coverInfo.type = CoverInfoRelative::METADATA;
CoverInfo::LoadedImage loadedImage = coverInfo.loadImage();
if (loadedImage.result != CoverInfo::LoadedImage::Result::Ok) {
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
coverInfo.type = CoverInfoRelative::FILE;
loadedImage = coverInfo.loadImage();
}
m_image = loadedImage.image;
} else {
qWarning() << "ImageProvider: Unknown ID " << m_id;
}

if (!m_image.isNull() && m_requestedSize.isValid()) {
m_image = m_image.scaled(m_requestedSize);
}

emit finished();
}

QQuickImageResponse* AsyncImageProvider::requestImageResponse(
const QString& id, const QSize& requestedSize) {
AsyncImageResponse* response = new AsyncImageResponse(id, requestedSize);
pool.start(response);
return response;
}

// static
const QString AsyncImageProvider::kProviderName = QStringLiteral("mixxx");

// static
QUrl AsyncImageProvider::trackLocationToCoverArtUrl(const QString& trackLocation) {
QUrl url("image://" + kProviderName + "/" + kCoverArtPrefix);
return url.resolved(
QString::fromLatin1(trackLocation.toUtf8().toBase64(
QByteArray::Base64UrlEncoding)));
}

//static
QString AsyncImageProvider::coverArtUrlIdToTrackLocation(const QString& coverArtUrlId) {
return QString::fromUtf8(QByteArray::fromBase64(
coverArtUrlId.mid(kCoverArtPrefix.size()).toLatin1(), QByteArray::Base64UrlEncoding));
}

} // namespace qml
} // namespace skin
} // namespace mixxx
40 changes: 40 additions & 0 deletions src/skin/qml/asyncimageprovider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <QImage>
#include <QQuickAsyncImageProvider>
#include <QRunnable>
#include <QSize>
#include <QString>
#include <QThreadPool>

#include "library/coverart.h"

namespace mixxx {
namespace skin {
namespace qml {

class AsyncImageResponse : public QQuickImageResponse, public QRunnable {
public:
AsyncImageResponse(const QString& id, const QSize& requestedSize);
QQuickTextureFactory* textureFactory() const override;
void run() override;

QString m_id;
QSize m_requestedSize;
QImage m_image;
};

class AsyncImageProvider : public QQuickAsyncImageProvider {
public:
QQuickImageResponse* requestImageResponse(
const QString& id, const QSize& requestedSize) override;

static const QString kProviderName;
static QUrl trackLocationToCoverArtUrl(const QString& trackLocation);
static QString coverArtUrlIdToTrackLocation(const QString& coverArtUrlId);

private:
QThreadPool pool;
};

} // namespace qml
} // namespace skin
} // namespace mixxx
12 changes: 12 additions & 0 deletions src/skin/qml/qmlplayerproxy.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "skin/qml/qmlplayerproxy.h"

#include "mixer/basetrackplayer.h"
#include "skin/qml/asyncimageprovider.h"

#define PROPERTY_IMPL(TYPE, NAME, GETTER, SETTER) \
TYPE QmlPlayerProxy::GETTER() const { \
Expand Down Expand Up @@ -125,6 +126,7 @@ void QmlPlayerProxy::slotTrackChanged() {
emit commentChanged();
emit keyTextChanged();
emit colorChanged();
emit coverArtUrlChanged();
}

PROPERTY_IMPL(QString, artist, getArtist, setArtist)
Expand Down Expand Up @@ -156,6 +158,16 @@ void QmlPlayerProxy::setColor(const QColor& value) {
}
}

QUrl QmlPlayerProxy::getCoverArtUrl() const {
const TrackPointer pTrack = m_pCurrentTrack;
if (pTrack == nullptr) {
return QUrl();
}

const CoverInfo coverInfo = pTrack->getCoverInfoWithLocation();
uklotzde marked this conversation as resolved.
Show resolved Hide resolved
return AsyncImageProvider::trackLocationToCoverArtUrl(coverInfo.trackLocation);
}

} // namespace qml
} // namespace skin
} // namespace mixxx
4 changes: 4 additions & 0 deletions src/skin/qml/qmlplayerproxy.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <QColor>
#include <QObject>
#include <QString>
#include <QUrl>

#include "track/track.h"

Expand All @@ -27,6 +28,7 @@ class QmlPlayerProxy : public QObject {
Q_PROPERTY(QString comment READ getComment WRITE setComment NOTIFY commentChanged)
Q_PROPERTY(QString keyText READ getKeyText WRITE setKeyText NOTIFY keyTextChanged)
Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QUrl coverArtUrl READ getCoverArtUrl NOTIFY coverArtUrlChanged)

public:
explicit QmlPlayerProxy(BaseTrackPlayer* pTrackPlayer, QObject* parent);
Expand All @@ -45,6 +47,7 @@ class QmlPlayerProxy : public QObject {
QString getComment() const;
QString getKeyText() const;
QColor getColor() const;
QUrl getCoverArtUrl() const;

public slots:
void slotTrackLoaded(TrackPointer pTrack);
Expand Down Expand Up @@ -84,6 +87,7 @@ class QmlPlayerProxy : public QObject {
void commentChanged();
void keyTextChanged();
void colorChanged();
void coverArtUrlChanged();

private:
BaseTrackPlayer* m_pTrackPlayer;
Expand Down
6 changes: 6 additions & 0 deletions src/skin/qml/qmlskin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <functional>

#include "coreservices.h"
#include "skin/qml/asyncimageprovider.h"
#include "skin/qml/qmlcontrolproxy.h"
#include "skin/qml/qmlplayermanagerproxy.h"
#include "skin/qml/qmlplayerproxy.h"
Expand Down Expand Up @@ -155,6 +156,11 @@ QWidget* QmlSkin::loadSkin(QWidget* pParent,

pWidget->engine()->setBaseUrl(QUrl::fromLocalFile(m_path.absoluteFilePath()));
pWidget->engine()->addImportPath(m_path.absoluteFilePath());

// No memory leak here, the QQmlENgine takes ownership of the provider
QQuickAsyncImageProvider* pImageProvider = new AsyncImageProvider();
pWidget->engine()->addImageProvider(AsyncImageProvider::kProviderName, pImageProvider);

pWidget->setSource(QUrl::fromLocalFile(dir().absoluteFilePath(kMainQmlFileName)));
pWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
if (pWidget->status() != QQuickWidget::Ready) {
Expand Down