Commit 35b46560 authored by Arjen Hiemstra's avatar Arjen Hiemstra Committed by Nate Graham
Browse files

Remove ImageScaler

This seeems to have only been used by RasterImageView and now even that
no longer uses is, so remove it.
parent ab7ae240
......@@ -99,7 +99,6 @@ set(gwenviewlib_SRCS
hud/hudwidget.cpp
graphicswidgetfloater.cpp
imagemetainfomodel.cpp
imagescaler.cpp
imageutils.cpp
invisiblebuttongroup.cpp
iodevicejpegsourcemanager.cpp
......
......@@ -23,7 +23,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA
// Local
#include <lib/documentview/abstractrasterimageviewtool.h>
#include <lib/imagescaler.h>
#include <lib/cms/cmsprofile.h>
#include <lib/gvdebug.h>
#include <lib/paintutils.h>
......
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
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 "imagescaler.h"
// Qt
#include <QImage>
#include <QRegion>
#include <QApplication>
// KF
// Local
#include "gwenview_lib_debug.h"
#include <lib/document/document.h>
#include <lib/paintutils.h>
#undef ENABLE_LOG
#undef LOG
//#define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) qCDebug(GWENVIEW_LIB_LOG) << x
#else
#define LOG(x) ;
#endif
namespace Gwenview
{
// Amount of pixels to keep so that smooth scale is correct
static const int SMOOTH_MARGIN = 3;
static inline QRectF scaledRect(const QRectF& rect, qreal factor)
{
return QRectF(rect.x() * factor,
rect.y() * factor,
rect.width() * factor,
rect.height() * factor);
}
static inline QRect scaledRect(const QRect& rect, qreal factor)
{
return scaledRect(QRectF(rect), factor).toAlignedRect();
}
struct ImageScalerPrivate
{
Qt::TransformationMode mTransformationMode;
Document::Ptr mDocument;
qreal mZoom;
QRegion mRegion;
};
ImageScaler::ImageScaler(QObject* parent)
: QObject(parent)
, d(new ImageScalerPrivate)
{
d->mTransformationMode = Qt::FastTransformation;
d->mZoom = 0;
}
ImageScaler::~ImageScaler()
{
delete d;
}
void ImageScaler::setDocument(const Document::Ptr &document)
{
if (d->mDocument) {
disconnect(d->mDocument.data(), nullptr, this, nullptr);
}
d->mDocument = document;
// Used when scaler asked for a down-sampled image
connect(d->mDocument.data(), &Document::downSampledImageReady,
this, &ImageScaler::doScale);
// Used when scaler asked for a full image
connect(d->mDocument.data(), &Document::loaded,
this, &ImageScaler::doScale);
}
void ImageScaler::setZoom(qreal zoom)
{
// If we zoom to 400% or more, then assume the user wants to see the real
// pixels, for example to fine tune a crop operation
d->mTransformationMode = zoom < 4. ? Qt::SmoothTransformation
: Qt::FastTransformation;
d->mZoom = zoom;
}
void ImageScaler::setDestinationRegion(const QRegion& region)
{
LOG(region);
d->mRegion = region;
if (d->mRegion.isEmpty()) {
return;
}
if (d->mDocument && d->mZoom > 0) {
doScale();
}
}
void ImageScaler::doScale()
{
if (d->mZoom < Document::maxDownSampledZoom()) {
if (!d->mDocument->prepareDownSampledImageForZoom(d->mZoom)) {
LOG("Asked for a down sampled image");
return;
}
} else if (d->mDocument->image().isNull()) {
LOG("Asked for the full image");
d->mDocument->startLoadingFullImage();
return;
}
LOG("Starting");
for (const QRect &rect : qAsConst(d->mRegion)) {
LOG(rect);
scaleRect(rect);
}
LOG("Done");
}
void ImageScaler::scaleRect(const QRect& rect)
{
const qreal dpr = qApp->devicePixelRatio();
// variables prefixed with dp are in device pixels
const QRect dpRect = Gwenview::scaledRect(rect, dpr);
const qreal REAL_DELTA = 0.001;
if (qAbs(d->mZoom - 1.0) < REAL_DELTA) {
QImage tmp = d->mDocument->image().copy(dpRect);
tmp.setDevicePixelRatio(dpr);
emit scaledRect(rect.left(), rect.top(), tmp);
return;
}
QImage image;
qreal zoom;
if (d->mZoom < Document::maxDownSampledZoom()) {
image = d->mDocument->downSampledImageForZoom(d->mZoom);
Q_ASSERT(!image.isNull());
qreal zoom1 = qreal(image.width()) / d->mDocument->width();
zoom = d->mZoom / zoom1;
} else {
image = d->mDocument->image();
zoom = d->mZoom;
}
const QRect imageRect = Gwenview::scaledRect(image.rect(), 1.0 / dpr);
// If rect contains "half" pixels, make sure sourceRect includes them
QRectF sourceRectF = Gwenview::scaledRect(QRectF(rect), 1.0 / zoom);
sourceRectF = sourceRectF.intersected(imageRect);
QRect sourceRect = sourceRectF.toAlignedRect();
if (sourceRect.isEmpty()) {
return;
}
// Compute smooth margin
bool needsSmoothMargins = d->mTransformationMode == Qt::SmoothTransformation;
int sourceLeftMargin, sourceRightMargin, sourceTopMargin, sourceBottomMargin;
int destLeftMargin, destRightMargin, destTopMargin, destBottomMargin;
if (needsSmoothMargins) {
sourceLeftMargin = qMin(sourceRect.left(), SMOOTH_MARGIN);
sourceTopMargin = qMin(sourceRect.top(), SMOOTH_MARGIN);
sourceRightMargin = qMin(imageRect.right() - sourceRect.right(), SMOOTH_MARGIN);
sourceBottomMargin = qMin(imageRect.bottom() - sourceRect.bottom(), SMOOTH_MARGIN);
sourceRect.adjust(
-sourceLeftMargin,
-sourceTopMargin,
sourceRightMargin,
sourceBottomMargin);
destLeftMargin = int(sourceLeftMargin * zoom);
destTopMargin = int(sourceTopMargin * zoom);
destRightMargin = int(sourceRightMargin * zoom);
destBottomMargin = int(sourceBottomMargin * zoom);
} else {
sourceLeftMargin = sourceRightMargin = sourceTopMargin = sourceBottomMargin = 0;
destLeftMargin = destRightMargin = destTopMargin = destBottomMargin = 0;
}
// destRect is almost like rect, but it contains only "full" pixels
QRect destRect = Gwenview::scaledRect(sourceRect, zoom);
QRect dpSourceRect = Gwenview::scaledRect(sourceRect, dpr);
QRect dpDestRect = Gwenview::scaledRect(dpSourceRect, zoom);
QImage tmp;
tmp = image.copy(dpSourceRect);
tmp = tmp.scaled(
dpDestRect.width(),
dpDestRect.height(),
Qt::IgnoreAspectRatio, // Do not use KeepAspectRatio, it can lead to skipped rows or columns
d->mTransformationMode);
if (needsSmoothMargins) {
tmp = tmp.copy(
destLeftMargin * dpr, destTopMargin * dpr,
dpDestRect.width() - (destLeftMargin + destRightMargin) * dpr,
dpDestRect.height() - (destTopMargin + destBottomMargin) * dpr
);
}
tmp.setDevicePixelRatio(dpr);
emit scaledRect(destRect.left() + destLeftMargin, destRect.top() + destTopMargin, tmp);
}
} // namespace
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
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.
*/
#ifndef IMAGESCALER_H
#define IMAGESCALER_H
// Qt
#include <QObject>
// KF
// local
#include <lib/gwenviewlib_export.h>
#include <document/document.h>
class QImage;
class QRect;
class QRegion;
namespace Gwenview
{
class Document;
struct ImageScalerPrivate;
class GWENVIEWLIB_EXPORT ImageScaler : public QObject
{
Q_OBJECT
public:
explicit ImageScaler(QObject* parent = nullptr);
~ImageScaler() override;
void setDocument(const Document::Ptr&);
void setZoom(qreal);
void setDestinationRegion(const QRegion&);
Q_SIGNALS:
void scaledRect(int left, int top, const QImage&);
private:
ImageScalerPrivate * const d;
void scaleRect(const QRect&);
private Q_SLOTS:
void doScale();
};
} // namespace
#endif /* IMAGESCALER_H */
......@@ -26,7 +26,6 @@ include_directories(
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
gv_add_unit_test(imagescalertest testutils.cpp)
if (KF5KDcraw_FOUND)
gv_add_unit_test(documenttest testutils.cpp)
endif()
......
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
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 <QTest>
#include "imagescalertest.h"
#include "../lib/document/documentfactory.h"
#include "testutils.h"
QTEST_MAIN(ImageScalerTest)
using namespace Gwenview;
/**
* Scale whole image in one pass
*/
void ImageScalerTest::testScaleFullImage()
{
const qreal zoom = 2;
QUrl url = urlForTestFile("test.png");
Document::Ptr doc = DocumentFactory::instance()->load(url);
// Wait for meta info because we need the document size
while (doc->loadingState() < Document::MetaInfoLoaded) {
QTest::qWait(500);
}
ImageScaler scaler;
ImageScalerClient client(&scaler);
scaler.setDocument(doc);
scaler.setZoom(zoom);
scaler.setDestinationRegion(QRect(QPoint(0, 0), doc->size() * zoom));
QSignalSpy spy(&scaler, SIGNAL(scaledRect(int,int,QImage)));
bool ok = spy.wait(30);
QVERIFY2(ok, "ImageScaler did not emit scaledRect() signal in time");
// Document should be fully loaded by the time image scaler is done
QCOMPARE(doc->loadingState(), Document::Loaded);
QImage scaledImage = client.createFullImage();
QImage expectedImage = doc->image().scaled(doc->size() * zoom,
Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
QVERIFY(TestUtils::imageCompare(scaledImage, expectedImage));
}
#if 0
/**
* Scale parts of an image
* In this test, the result image should be missing its bottom-right corner
*/
void ImageScalerTest::testScalePartialImage()
{
QImage image(10, 10, QImage::Format_ARGB32);
const int zoom = 2;
{
QPainter painter(&image);
painter.fillRect(image.rect(), Qt::white);
painter.drawText(0, image.height(), "X");
}
Gwenview::ImageScaler scaler;
ImageScalerClient client(&scaler);
scaler.setImage(&image);
scaler.setZoom(zoom);
QRegion region;
region |= QRect(
0, 0,
image.width() * zoom / 2, image.height() * zoom);
region |= QRect(
0, 0,
image.width() * zoom, image.height() * zoom / 2);
scaler.setDestinationRegion(region);
QImage expectedImage(image.size() * zoom, image.format());
expectedImage.fill(0);
{
QPainter painter(&expectedImage);
QImage tmp;
tmp = image.copy(0, 0,
expectedImage.width() / zoom / 2,
expectedImage.height() / zoom);
painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom));
tmp = image.copy(0, 0,
expectedImage.width() / zoom,
expectedImage.height() / zoom / 2);
painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom));
}
QImage scaledImage = client.createFullImage();
QCOMPARE(scaledImage, expectedImage);
}
/**
* Scale whole image in two passes, not using exact pixel boundaries
*/
void ImageScalerTest::testScaleFullImageTwoPasses()
{
QFETCH(qreal, zoom);
QImage image(10, 10, QImage::Format_ARGB32);
{
QPainter painter(&image);
painter.fillRect(image.rect(), Qt::white);
painter.drawLine(0, 0, image.width(), image.height());
}
Gwenview::ImageScaler scaler;
ImageScalerClient client(&scaler);
scaler.setImage(&image);
scaler.setZoom(zoom);
int zWidth = int(image.width() * zoom);
int zHeight = int(image.width() * zoom);
int partialZWidth = zWidth / 3;
scaler.setDestinationRegion(
QRect(
0, 0,
partialZWidth, zHeight)
);
scaler.setDestinationRegion(
QRect(
partialZWidth, 0,
zWidth - partialZWidth, zHeight)
);
QImage expectedImage = image.scaled(image.size() * zoom);
QImage scaledImage = client.createFullImage();
QCOMPARE(expectedImage, scaledImage);
}
void ImageScalerTest::testScaleFullImageTwoPasses_data()
{
QTest::addColumn<qreal>("zoom");
QTest::newRow("0.5") << 0.5;
QTest::newRow("2.0") << 2.0;
QTest::newRow("4.0") << 4.0;
}
/**
* When zooming out, make sure that we don't crash when scaling an area which
* have one dimension smaller than one pixel in the destination.
*/
void ImageScalerTest::testScaleThinArea()
{
QImage image(10, 10, QImage::Format_ARGB32);
image.fill(0);
Gwenview::ImageScaler scaler;
const qreal zoom = 0.25;
scaler.setImage(&image);
scaler.setZoom(zoom);
scaler.setDestinationRegion(QRect(0, 0, image.width(), 2));
}
/**
* Test instantiating a scaler without setting an image won't crash
*/
void ImageScalerTest::testDontCrashWithoutImage()
{
Gwenview::ImageScaler scaler;
scaler.setZoom(1.0);
scaler.setDestinationRegion(QRect(0, 0, 10, 10));
}
/**
* Test that scaling down a big image (==bigger than MAX_CHUNK_AREA) does not
* produce any gap
*/
void ImageScalerTest::testScaleDownBigImage()
{
QImage image(1704, 2272, QImage::Format_RGB32);
image.fill(255);
Gwenview::ImageScaler scaler;
ImageScalerClient client(&scaler);
const qreal zoom = 0.28125;
scaler.setImage(&image);
scaler.setZoom(zoom);
scaler.setDestinationRegion(QRect(QPoint(0, 0), image.size() * zoom));
QImage scaledImage = client.createFullImage();
QImage expectedImage = image.scaled(scaledImage.size());
QCOMPARE(expectedImage, scaledImage);
}
#endif
/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
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.
*/
#ifndef IMAGESCALERTEST_H
#define IMAGESCALERTEST_H
#include "../lib/imagescaler.h"
// Qt
#include <QImage>
#include <QPainter>
#include <QDebug>
// KF
class ImageScalerClient : public QObject
{
Q_OBJECT
public:
ImageScalerClient(Gwenview::ImageScaler* scaler)
{
connect(scaler, &Gwenview::ImageScaler::scaledRect,
this, &ImageScalerClient::slotScaledRect);
}
struct ImageInfo
{
int left;
int top;
QImage image;
};
QVector<ImageInfo> mImageInfoList;
QImage createFullImage()
{
Q_ASSERT(mImageInfoList.size() > 0);
QImage::Format format = mImageInfoList[0].image.format();
int imageWidth = 0;
int imageHeight = 0;
for (const ImageInfo & info : qAsConst(mImageInfoList)) {
int right = info.left + info.image.width();
int bottom = info.top + info.image.height();
imageWidth = qMax(imageWidth, right);
imageHeight = qMax(imageHeight, bottom);
}
QImage image(imageWidth, imageHeight, format);
image.fill(0);
QPainter painter(&image);
for (const ImageInfo & info : qAsConst(mImageInfoList)) {
painter.drawImage(info.left, info.top, info.image);
}
return image;
}
public Q_SLOTS:
void slotScaledRect(int left, int top, const QImage& image)
{
ImageInfo info;
info.left = left;
info.top = top;
info.image = image;
mImageInfoList.append(info);
}
};
class ImageScalerTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testScaleFullImage();