[app] A few book cover rendering tweaks

Edge filling is only performed when both edges consist predominantly
of the same color and the aspect ratio significantly differs from
the target one. Otherwise the cover is stretched pretty much as before
except that stretched image is now centered. That seems to works nicely
in all the cases which I've encoutered so far.
This commit is contained in:
Slava Monich 2021-11-02 02:51:11 +02:00
parent 6ad2fc1a21
commit f79b2f17ad
2 changed files with 116 additions and 52 deletions

View file

@ -41,21 +41,21 @@
#include <QPainter> #include <QPainter>
// ========================================================================== // ==========================================================================
// BooksCoverWidget::ScaleTask // BooksCoverWidget::Scaler
// ========================================================================== // ==========================================================================
class BooksCoverWidget::ScaleTask : public HarbourTask class BooksCoverWidget::Scaler
{ {
public: public:
ScaleTask(QThreadPool* aPool, QImage aImage, int aWidth, int aHeight, Scaler(QImage aImage, int aWidth, int aHeight, Mode aMode);
bool aStretch);
static QImage scale(QImage aImage, int aWidth, int aHeight, bool aStretch);
static QColor leftBackground(const QImage& aImage); static QColor leftBackground(const QImage& aImage);
static QColor rightBackground(const QImage& aImage); static QColor rightBackground(const QImage& aImage);
static QColor topBackground(const QImage& aImage); static QColor topBackground(const QImage& aImage);
static QColor bottomBackground(const QImage& aImage); static QColor bottomBackground(const QImage& aImage);
static QColor pickColor(const QHash<QRgb,int>& aColorCounts); static QColor pickColor(const QHash<QRgb,int>& aColorCounts);
void performTask();
void performTask(HarbourTask* aTask);
public: public:
QImage iImage; QImage iImage;
@ -64,33 +64,18 @@ public:
QColor iBackground2; // Right or bottom QColor iBackground2; // Right or bottom
int iWidth; int iWidth;
int iHeight; int iHeight;
bool iStretch; Mode iMode;
}; };
BooksCoverWidget::ScaleTask::ScaleTask(QThreadPool* aPool, QImage aImage, BooksCoverWidget::Scaler::Scaler(QImage aImage, int aWidth, int aHeight,
int aWidth, int aHeight, bool aStretch) : HarbourTask(aPool), iImage(aImage), Mode aMode) : iImage(aImage), iWidth(aWidth), iHeight(aHeight),
iBackground1(Qt::transparent), iBackground2(Qt::transparent), iMode(aMode)
iWidth(aWidth), iHeight(aHeight),
iStretch(aStretch)
{ {
} }
QImage BooksCoverWidget::ScaleTask::scale(QImage aImage,
int aWidth, int aHeight, bool aStretch)
{
if (aStretch){
return aImage.scaled(aWidth, aHeight, Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation);
} else {
return (aWidth*aImage.height() > aImage.width()*aHeight) ?
aImage.scaledToHeight(aHeight, Qt::SmoothTransformation) :
aImage.scaledToWidth(aWidth, Qt::SmoothTransformation);
}
}
// The idea is to pick the colors which occur more often // The idea is to pick the colors which occur more often
// at the edges of the picture. // at the edges of the picture.
QColor BooksCoverWidget::ScaleTask::leftBackground(const QImage& aImage) QColor BooksCoverWidget::Scaler::leftBackground(const QImage& aImage)
{ {
QHash<QRgb,int> counts; QHash<QRgb,int> counts;
if (aImage.width() > 0) { if (aImage.width() > 0) {
@ -105,7 +90,7 @@ QColor BooksCoverWidget::ScaleTask::leftBackground(const QImage& aImage)
return color; return color;
} }
QColor BooksCoverWidget::ScaleTask::rightBackground(const QImage& aImage) QColor BooksCoverWidget::Scaler::rightBackground(const QImage& aImage)
{ {
QHash<QRgb,int> counts; QHash<QRgb,int> counts;
const int w = aImage.width(); const int w = aImage.width();
@ -121,7 +106,7 @@ QColor BooksCoverWidget::ScaleTask::rightBackground(const QImage& aImage)
return color; return color;
} }
QColor BooksCoverWidget::ScaleTask::topBackground(const QImage& aImage) QColor BooksCoverWidget::Scaler::topBackground(const QImage& aImage)
{ {
QHash<QRgb,int> counts; QHash<QRgb,int> counts;
if (aImage.height() > 0) { if (aImage.height() > 0) {
@ -136,7 +121,7 @@ QColor BooksCoverWidget::ScaleTask::topBackground(const QImage& aImage)
return color; return color;
} }
QColor BooksCoverWidget::ScaleTask::bottomBackground(const QImage& aImage) QColor BooksCoverWidget::Scaler::bottomBackground(const QImage& aImage)
{ {
QHash<QRgb,int> counts; QHash<QRgb,int> counts;
const int h = aImage.height(); const int h = aImage.height();
@ -152,8 +137,9 @@ QColor BooksCoverWidget::ScaleTask::bottomBackground(const QImage& aImage)
return color; return color;
} }
QColor BooksCoverWidget::ScaleTask::pickColor(const QHash<QRgb,int>& aCounts) QColor BooksCoverWidget::Scaler::pickColor(const QHash<QRgb,int>& aCounts)
{ {
QColor color;
if (aCounts.size() > 0) { if (aCounts.size() > 0) {
QRgb rgb; QRgb rgb;
int max; int max;
@ -166,31 +152,112 @@ QColor BooksCoverWidget::ScaleTask::pickColor(const QHash<QRgb,int>& aCounts)
rgb = it.key(); rgb = it.key();
} }
} }
return QColor(rgb); // A simple criteria for detecting an edge consisting
// predominantly of the same color
if (max > aCounts.count()) {
color = QColor(rgb);
HDEBUG(color << max << "out of" << aCounts.count());
}
} }
return QColor(); return color;
} }
void BooksCoverWidget::ScaleTask::performTask() void BooksCoverWidget::Scaler::performTask(HarbourTask* aTask)
{ {
if (!iImage.isNull() && !isCanceled()) { if (!iImage.isNull()) {
iScaledImage = scale(iImage, iWidth, iHeight, iStretch); const int wh = iWidth * iImage.height();
if (!isCanceled()) { const int hw = iHeight * iImage.width();
// Also stretch in Fill mode if aspect ratio is almost right
if (iMode == Stretch ||
(iMode == Fill && (10*wh > 9*hw && 10*wh < 11*hw))) {
int x, y;
if (wh > hw) {
// Scale and center
iScaledImage = iImage.scaledToWidth(iWidth,
Qt::SmoothTransformation);
x = 0;
y = (iScaledImage.height() - iHeight)/2;
} else {
iScaledImage = iImage.scaledToHeight(iHeight,
Qt::SmoothTransformation);
x = (iScaledImage.width() - iWidth)/2;
y = 0;
}
if (!aTask || !aTask->isCanceled()) {
iScaledImage = iScaledImage.copy(x, y, iWidth, iHeight);
}
} else {
iScaledImage = (wh > hw) ?
iImage.scaledToHeight(iHeight, Qt::SmoothTransformation) :
iImage.scaledToWidth(iWidth, Qt::SmoothTransformation);
}
if (!aTask || !aTask->isCanceled()) {
if (iScaledImage.width() < iWidth) { if (iScaledImage.width() < iWidth) {
iBackground1 = leftBackground(iScaledImage); iBackground1 = leftBackground(iScaledImage);
if (!isCanceled()) { if (!aTask || !aTask->isCanceled()) {
iBackground2 = rightBackground(iScaledImage); iBackground2 = rightBackground(iScaledImage);
if ((!aTask || !aTask->isCanceled()) &&
(!iBackground1.isValid() || !iBackground2.isValid())) {
// No predominant color
iBackground1 = iBackground2 = QColor();
iScaledImage = iImage.scaledToWidth(iWidth,
Qt::SmoothTransformation);
if (!aTask || !aTask->isCanceled()) {
iScaledImage = iScaledImage.copy
(0,(iScaledImage.height() - iHeight)/2,
iWidth, iHeight);
}
}
} }
} else if (iScaledImage.height() < iHeight) { } else if (iMode != Bottom && iScaledImage.height() < iHeight) {
iBackground1 = topBackground(iScaledImage); iBackground1 = topBackground(iScaledImage);
if (!isCanceled()) { if (!aTask || !aTask->isCanceled()) {
iBackground2 = bottomBackground(iScaledImage); iBackground2 = bottomBackground(iScaledImage);
if ((!aTask || !aTask->isCanceled()) &&
(!iBackground1.isValid() || !iBackground2.isValid())) {
// No predominant color
iBackground1 = iBackground2 = QColor();
iScaledImage = iImage.scaledToHeight(iHeight,
Qt::SmoothTransformation);
if (!aTask || !aTask->isCanceled()) {
iScaledImage = iScaledImage.copy
((iScaledImage.width() - iWidth)/2, 0,
iWidth, iHeight);
}
}
} }
} }
} }
} }
} }
// ==========================================================================
// BooksCoverWidget::ScaleTask
// ==========================================================================
class BooksCoverWidget::ScaleTask : public HarbourTask
{
public:
ScaleTask(QThreadPool* aPool, QImage aImage, int aWidth, int aHeight,
Mode aMode);
void performTask() Q_DECL_OVERRIDE;
public:
Scaler iScaler;
};
BooksCoverWidget::ScaleTask::ScaleTask(QThreadPool* aPool, QImage aImage,
int aWidth, int aHeight, Mode aMode) : HarbourTask(aPool),
iScaler(aImage, aWidth, aHeight, aMode)
{
}
void BooksCoverWidget::ScaleTask::performTask()
{
iScaler.performTask(this);
}
// ========================================================================== // ==========================================================================
// BooksCoverWidget::DefaultImage // BooksCoverWidget::DefaultImage
// ========================================================================== // ==========================================================================
@ -467,20 +534,16 @@ void BooksCoverWidget::scaleImage(bool aWasEmpty)
} }
if (!iCoverImage.isNull()) { if (!iCoverImage.isNull()) {
const bool stretch = (iMode == Stretch);
if (iSynchronous) { if (iSynchronous) {
iScaledImage = ScaleTask::scale(iCoverImage, w, h, stretch); Scaler scaler(iCoverImage, w, h, iMode);
if (iScaledImage.width() < w) { scaler.performTask(Q_NULLPTR);
iBackground1 = ScaleTask::leftBackground(iScaledImage); iScaledImage = scaler.iScaledImage;
iBackground2 = ScaleTask::rightBackground(iScaledImage); iBackground1 = scaler.iBackground1;
} else if (iScaledImage.height() < h) { iBackground2 = scaler.iBackground2;
iBackground1 = ScaleTask::topBackground(iScaledImage);
iBackground2 = ScaleTask::bottomBackground(iScaledImage);
}
update(); update();
} else { } else {
(iScaleTask = new ScaleTask(iTaskQueue->pool(), iCoverImage, (iScaleTask = new ScaleTask(iTaskQueue->pool(), iCoverImage,
w, h, stretch))->submit(this, SLOT(onScaleTaskDone())); w, h, iMode))->submit(this, SLOT(onScaleTaskDone()));
} }
} else { } else {
iScaledImage = QImage(); iScaledImage = QImage();
@ -501,9 +564,9 @@ void BooksCoverWidget::onScaleTaskDone()
{ {
const bool wasEmpty(empty()); const bool wasEmpty(empty());
HASSERT(iScaleTask == sender()); HASSERT(iScaleTask == sender());
iScaledImage = iScaleTask->iScaledImage; iScaledImage = iScaleTask->iScaler.iScaledImage;
iBackground1 = iScaleTask->iBackground1; iBackground1 = iScaleTask->iScaler.iBackground1;
iBackground2 = iScaleTask->iBackground2; iBackground2 = iScaleTask->iScaler.iBackground2;
iScaleTask->release(this); iScaleTask->release(this);
iScaleTask = NULL; iScaleTask = NULL;
update(); update();

View file

@ -125,6 +125,7 @@ private:
void updateCenter(); void updateCenter();
private: private:
class Scaler;
class ScaleTask; class ScaleTask;
class DefaultImage; class DefaultImage;
shared_ptr<BooksTaskQueue> iTaskQueue; shared_ptr<BooksTaskQueue> iTaskQueue;