[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:
parent
6ad2fc1a21
commit
f79b2f17ad
2 changed files with 116 additions and 52 deletions
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue