You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
9.1 KiB
335 lines
9.1 KiB
/*****************************************************************************
|
|
* Copyright (C) 2021 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Benjamin Arnaud <bunjee@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 "covergenerator.hpp"
|
|
|
|
// VLC includes
|
|
#include "qt.hpp"
|
|
|
|
// MediaLibrary includes
|
|
#include "medialibrary/mlhelper.hpp"
|
|
|
|
#include <cmath>
|
|
|
|
// Qt includes
|
|
#include <QDir>
|
|
#include <QImageReader>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QGraphicsBlurEffect>
|
|
#include <QUrl>
|
|
#include <QQmlFile>
|
|
|
|
// Qt private exported function
|
|
QT_BEGIN_NAMESPACE
|
|
extern void VLC_WEAK qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0);
|
|
QT_END_NAMESPACE
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Static variables
|
|
|
|
static const QString COVERGENERATOR_STORAGE = "/art/qt-covers";
|
|
|
|
static const int COVERGENERATOR_COUNT = 2;
|
|
|
|
static const QString COVERGENERATOR_DEFAULT = ":/placeholder/noart_albumCover.svg";
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Ctor / dtor
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
CoverGenerator::CoverGenerator()
|
|
: m_countX(COVERGENERATOR_COUNT)
|
|
, m_countY(COVERGENERATOR_COUNT)
|
|
, m_split(Divide)
|
|
, m_blur(0)
|
|
, m_default(COVERGENERATOR_DEFAULT) {}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Interface
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
void CoverGenerator::setSize(const QSize & size)
|
|
{
|
|
m_size = size;
|
|
}
|
|
|
|
void CoverGenerator::setCountX(int x)
|
|
{
|
|
m_countX = x;
|
|
}
|
|
|
|
void CoverGenerator::setCountY(int y)
|
|
{
|
|
m_countY = y;
|
|
}
|
|
|
|
void CoverGenerator::setSplit(Split split)
|
|
{
|
|
m_split = split;
|
|
}
|
|
|
|
void CoverGenerator::setBlur(int radius)
|
|
{
|
|
m_blur = radius;
|
|
}
|
|
|
|
void CoverGenerator::setDefaultThumbnail(const QString & fileName)
|
|
{
|
|
m_default = fileName;
|
|
}
|
|
|
|
int CoverGenerator::requiredNoOfThumbnails() const
|
|
{
|
|
return m_countX * m_countY;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// QRunnable implementation
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
QImage CoverGenerator::execute(QStringList thumbnails) const
|
|
{
|
|
int count = m_countX * m_countY;
|
|
|
|
int countX;
|
|
int countY;
|
|
|
|
if (thumbnails.isEmpty())
|
|
{
|
|
if (m_split == CoverGenerator::Duplicate)
|
|
{
|
|
while (thumbnails.count() != count)
|
|
{
|
|
thumbnails.append(m_default);
|
|
}
|
|
|
|
countX = m_countX;
|
|
countY = m_countY;
|
|
}
|
|
else
|
|
{
|
|
thumbnails.append(m_default);
|
|
|
|
countX = 1;
|
|
countY = 1;
|
|
}
|
|
}
|
|
else if (m_split == CoverGenerator::Duplicate)
|
|
{
|
|
int index = 0;
|
|
|
|
while (thumbnails.count() != count)
|
|
{
|
|
thumbnails.append(thumbnails.at(index));
|
|
|
|
index++;
|
|
}
|
|
|
|
countX = m_countX;
|
|
countY = m_countY;
|
|
}
|
|
else // if (m_split == CoverGenerator::Divide)
|
|
{
|
|
countX = m_countX;
|
|
|
|
// NOTE: We try to divide thumbnails as far as we can based on their total count.
|
|
countY = std::ceil((qreal) thumbnails.count() / m_countX);
|
|
}
|
|
|
|
QImage image(m_size, QImage::Format_RGB32);
|
|
|
|
image.fill(Qt::white);
|
|
|
|
QPainter painter;
|
|
|
|
painter.begin(&image);
|
|
|
|
draw(painter, thumbnails, countX, countY);
|
|
|
|
painter.end();
|
|
|
|
if (m_blur > 0)
|
|
blur(image);
|
|
|
|
return image;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Private functions
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
void CoverGenerator::draw(QPainter & painter,
|
|
const QStringList & fileNames, int countX, int countY) const
|
|
{
|
|
int count = fileNames.count();
|
|
|
|
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++)
|
|
{
|
|
for (int x = 0; x < countX; x++)
|
|
{
|
|
int index = countX * y + x;
|
|
|
|
if (index == count) return;
|
|
|
|
QRect rect;
|
|
|
|
// NOTE: This handles the wider thumbnail case (e.g. for a 2x1 grid).
|
|
if (index == count - 1 && x != countX - 1)
|
|
{
|
|
rect = QRect(width * x, height * y, width * countX - x, height);
|
|
}
|
|
else
|
|
rect = QRect(width * x, height * y, width, height);
|
|
|
|
QString fileName = fileNames.at(index);
|
|
|
|
if (fileName.isEmpty())
|
|
drawImage(painter, m_default, rect);
|
|
else
|
|
drawImage(painter, fileName, rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CoverGenerator::drawImage(QPainter & painter, const QString & fileName, const QRect & target) const
|
|
{
|
|
//QFile expect the :/ instead of qrc:// for resources files
|
|
const QUrl fileURL {fileName};
|
|
const QString adaptedFilename = QQmlFile::urlToLocalFileOrQrc(fileURL);
|
|
QFile file(adaptedFilename.isEmpty() ? fileName : adaptedFilename);
|
|
|
|
if (file.open(QIODevice::ReadOnly) == false)
|
|
{
|
|
// NOTE: This image does not seem valid so we paint the placeholder instead.
|
|
if (fileName != m_default)
|
|
drawImage(painter, m_default, target);
|
|
|
|
return;
|
|
}
|
|
|
|
QImageReader reader(&file);
|
|
|
|
if (reader.canRead() == false)
|
|
return;
|
|
|
|
QSize size = reader.size().scaled(target.width(),
|
|
target.height(), Qt::KeepAspectRatioByExpanding);
|
|
|
|
QImage image;
|
|
|
|
// NOTE: QImage::scaled provides a better quality compared to QImageReader::setScaledSize.
|
|
// Except for svg(s).
|
|
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: We are using Qt::SmoothTransformation to favor quality.
|
|
image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
painter.drawImage(target, image, source);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
void CoverGenerator::blur(QImage& image) const
|
|
{
|
|
if (Q_LIKELY(&qt_blurImage))
|
|
{
|
|
// A symbol is available for qt_blurImage()
|
|
// Exported function can be used directly within a separate thread:
|
|
qt_blurImage(image, 2.5 * (m_blur + 1), true);
|
|
}
|
|
else
|
|
{
|
|
const auto blurImage = [&]() {
|
|
QGraphicsScene scene;
|
|
|
|
QGraphicsPixmapItem item(QPixmap::fromImage(image));
|
|
|
|
QGraphicsBlurEffect effect;
|
|
|
|
effect.setBlurRadius(m_blur);
|
|
|
|
effect.setBlurHints(QGraphicsBlurEffect::QualityHint);
|
|
|
|
item.setGraphicsEffect(&effect);
|
|
|
|
scene.addItem(&item);
|
|
|
|
QPainter painter(&image);
|
|
|
|
scene.render(&painter);
|
|
};
|
|
|
|
if (qApp->thread() == QThread::currentThread())
|
|
{
|
|
blurImage();
|
|
}
|
|
else
|
|
{
|
|
// Not executing in Qt GUI thread, this is not supported.
|
|
// Block this thread, and blur the image in the GUI thread instead:
|
|
QMetaObject::invokeMethod(qApp, blurImage, Qt::BlockingQueuedConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
QString CoverGenerator::getPrefix(vlc_ml_parent_type type) const
|
|
{
|
|
switch (type)
|
|
{
|
|
case VLC_ML_PARENT_GENRE:
|
|
return "genre";
|
|
case VLC_ML_PARENT_GROUP:
|
|
return "group";
|
|
case VLC_ML_PARENT_FOLDER:
|
|
return "folder";
|
|
case VLC_ML_PARENT_PLAYLIST:
|
|
return "playlist";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|