[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>
// ==========================================================================
// BooksCoverWidget::ScaleTask
// BooksCoverWidget::Scaler
// ==========================================================================
class BooksCoverWidget::ScaleTask : public HarbourTask
class BooksCoverWidget::Scaler
{
public:
ScaleTask(QThreadPool* aPool, QImage aImage, int aWidth, int aHeight,
bool aStretch);
static QImage scale(QImage aImage, int aWidth, int aHeight, bool aStretch);
Scaler(QImage aImage, int aWidth, int aHeight, Mode aMode);
static QColor leftBackground(const QImage& aImage);
static QColor rightBackground(const QImage& aImage);
static QColor topBackground(const QImage& aImage);
static QColor bottomBackground(const QImage& aImage);
static QColor pickColor(const QHash<QRgb,int>& aColorCounts);
void performTask();
void performTask(HarbourTask* aTask);
public:
QImage iImage;
@ -64,33 +64,18 @@ public:
QColor iBackground2; // Right or bottom
int iWidth;
int iHeight;
bool iStretch;
Mode iMode;
};
BooksCoverWidget::ScaleTask::ScaleTask(QThreadPool* aPool, QImage aImage,
int aWidth, int aHeight, bool aStretch) : HarbourTask(aPool), iImage(aImage),
iBackground1(Qt::transparent), iBackground2(Qt::transparent),
iWidth(aWidth), iHeight(aHeight),
iStretch(aStretch)
BooksCoverWidget::Scaler::Scaler(QImage aImage, int aWidth, int aHeight,
Mode aMode) : iImage(aImage), iWidth(aWidth), iHeight(aHeight),
iMode(aMode)
{
}
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
// at the edges of the picture.
QColor BooksCoverWidget::ScaleTask::leftBackground(const QImage& aImage)
QColor BooksCoverWidget::Scaler::leftBackground(const QImage& aImage)
{
QHash<QRgb,int> counts;
if (aImage.width() > 0) {
@ -105,7 +90,7 @@ QColor BooksCoverWidget::ScaleTask::leftBackground(const QImage& aImage)
return color;
}
QColor BooksCoverWidget::ScaleTask::rightBackground(const QImage& aImage)
QColor BooksCoverWidget::Scaler::rightBackground(const QImage& aImage)
{
QHash<QRgb,int> counts;
const int w = aImage.width();
@ -121,7 +106,7 @@ QColor BooksCoverWidget::ScaleTask::rightBackground(const QImage& aImage)
return color;
}
QColor BooksCoverWidget::ScaleTask::topBackground(const QImage& aImage)
QColor BooksCoverWidget::Scaler::topBackground(const QImage& aImage)
{
QHash<QRgb,int> counts;
if (aImage.height() > 0) {
@ -136,7 +121,7 @@ QColor BooksCoverWidget::ScaleTask::topBackground(const QImage& aImage)
return color;
}
QColor BooksCoverWidget::ScaleTask::bottomBackground(const QImage& aImage)
QColor BooksCoverWidget::Scaler::bottomBackground(const QImage& aImage)
{
QHash<QRgb,int> counts;
const int h = aImage.height();
@ -152,8 +137,9 @@ QColor BooksCoverWidget::ScaleTask::bottomBackground(const QImage& aImage)
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) {
QRgb rgb;
int max;
@ -166,29 +152,110 @@ QColor BooksCoverWidget::ScaleTask::pickColor(const QHash<QRgb,int>& aCounts)
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::Scaler::performTask(HarbourTask* aTask)
{
if (!iImage.isNull()) {
const int wh = iWidth * iImage.height();
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) {
iBackground1 = leftBackground(iScaledImage);
if (!aTask || !aTask->isCanceled()) {
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 (iMode != Bottom && iScaledImage.height() < iHeight) {
iBackground1 = topBackground(iScaledImage);
if (!aTask || !aTask->isCanceled()) {
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()
{
if (!iImage.isNull() && !isCanceled()) {
iScaledImage = scale(iImage, iWidth, iHeight, iStretch);
if (!isCanceled()) {
if (iScaledImage.width() < iWidth) {
iBackground1 = leftBackground(iScaledImage);
if (!isCanceled()) {
iBackground2 = rightBackground(iScaledImage);
}
} else if (iScaledImage.height() < iHeight) {
iBackground1 = topBackground(iScaledImage);
if (!isCanceled()) {
iBackground2 = bottomBackground(iScaledImage);
}
}
}
}
iScaler.performTask(this);
}
// ==========================================================================
@ -467,20 +534,16 @@ void BooksCoverWidget::scaleImage(bool aWasEmpty)
}
if (!iCoverImage.isNull()) {
const bool stretch = (iMode == Stretch);
if (iSynchronous) {
iScaledImage = ScaleTask::scale(iCoverImage, w, h, stretch);
if (iScaledImage.width() < w) {
iBackground1 = ScaleTask::leftBackground(iScaledImage);
iBackground2 = ScaleTask::rightBackground(iScaledImage);
} else if (iScaledImage.height() < h) {
iBackground1 = ScaleTask::topBackground(iScaledImage);
iBackground2 = ScaleTask::bottomBackground(iScaledImage);
}
Scaler scaler(iCoverImage, w, h, iMode);
scaler.performTask(Q_NULLPTR);
iScaledImage = scaler.iScaledImage;
iBackground1 = scaler.iBackground1;
iBackground2 = scaler.iBackground2;
update();
} else {
(iScaleTask = new ScaleTask(iTaskQueue->pool(), iCoverImage,
w, h, stretch))->submit(this, SLOT(onScaleTaskDone()));
w, h, iMode))->submit(this, SLOT(onScaleTaskDone()));
}
} else {
iScaledImage = QImage();
@ -501,9 +564,9 @@ void BooksCoverWidget::onScaleTaskDone()
{
const bool wasEmpty(empty());
HASSERT(iScaleTask == sender());
iScaledImage = iScaleTask->iScaledImage;
iBackground1 = iScaleTask->iBackground1;
iBackground2 = iScaleTask->iBackground2;
iScaledImage = iScaleTask->iScaler.iScaledImage;
iBackground1 = iScaleTask->iScaler.iBackground1;
iBackground2 = iScaleTask->iScaler.iBackground2;
iScaleTask->release(this);
iScaleTask = NULL;
update();

View file

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