diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp index 9cf2c103..da768b22 100644 --- a/lib/documentview/rasterimageview.cpp +++ b/lib/documentview/rasterimageview.cpp @@ -1,568 +1,563 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2011 Aurélien Gâteau 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, Cambridge, MA 02110-1301, USA. */ // Self #include "rasterimageview.h" // Local #include #include #include #include // KDE // Qt #include #include #include #include #include namespace Gwenview { struct RasterImageViewPrivate { RasterImageView* q; ImageScaler* mScaler; bool mEmittedCompleted; // Config AbstractImageView::AlphaBackgroundMode mAlphaBackgroundMode; QColor mAlphaBackgroundColor; cmsUInt32Number mRenderingIntent; bool mEnlargeSmallerImages; // /Config bool mBufferIsEmpty; QPixmap mCurrentBuffer; // The alternate buffer is useful when scrolling: existing content is copied // to mAlternateBuffer and buffers are swapped. This avoids allocating a new // QPixmap every time the image is scrolled. QPixmap mAlternateBuffer; QTimer* mUpdateTimer; QPointer mTool; bool mApplyDisplayTransform; // Defaults to true. Can be set to false if there is no need or no way to apply color profile cmsHTRANSFORM mDisplayTransform; void updateDisplayTransform(QImage::Format format) { GV_RETURN_IF_FAIL(format != QImage::Format_Invalid); mApplyDisplayTransform = false; if (mDisplayTransform) { cmsDeleteTransform(mDisplayTransform); } mDisplayTransform = nullptr; Cms::Profile::Ptr profile = q->document()->cmsProfile(); if (!profile) { // The assumption that something unmarked is *probably* sRGB is better than failing to apply any transform when one // has a wide-gamut screen. profile = Cms::Profile::getSRgbProfile(); } Cms::Profile::Ptr monitorProfile = Cms::Profile::getMonitorProfile(); if (!monitorProfile) { qWarning() << "Could not get monitor color profile"; return; } cmsUInt32Number cmsFormat = 0; switch (format) { case QImage::Format_RGB32: case QImage::Format_ARGB32: cmsFormat = TYPE_BGRA_8; break; #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) case QImage::Format_Grayscale8: cmsFormat = TYPE_GRAY_8; break; #endif default: qWarning() << "Gwenview can only apply color profile on RGB32 or ARGB32 images"; return; } mDisplayTransform = cmsCreateTransform(profile->handle(), cmsFormat, monitorProfile->handle(), cmsFormat, mRenderingIntent, cmsFLAGS_BLACKPOINTCOMPENSATION); mApplyDisplayTransform = true; } void setupUpdateTimer() { mUpdateTimer = new QTimer(q); mUpdateTimer->setInterval(500); mUpdateTimer->setSingleShot(true); QObject::connect(mUpdateTimer, SIGNAL(timeout()), q, SLOT(updateBuffer())); } void startAnimationIfNecessary() { if (q->document() && q->isVisible()) { q->document()->startAnimation(); } } QRectF mapViewportToZoomedImage(const QRectF& viewportRect) const { return QRectF( viewportRect.topLeft() - q->imageOffset() + q->scrollPos(), viewportRect.size() ); } void setScalerRegionToVisibleRect() { QRectF rect = mapViewportToZoomedImage(q->boundingRect()); mScaler->setDestinationRegion(QRegion(rect.toRect())); } void resizeBuffer() { QSize size = q->visibleImageSize().toSize(); if (size == mCurrentBuffer.size()) { return; } if (!size.isValid()) { mAlternateBuffer = QPixmap(); mCurrentBuffer = QPixmap(); return; } mAlternateBuffer = QPixmap(size); mAlternateBuffer.fill(Qt::transparent); { QPainter painter(&mAlternateBuffer); painter.drawPixmap(0, 0, mCurrentBuffer); } qSwap(mAlternateBuffer, mCurrentBuffer); mAlternateBuffer = QPixmap(); } void drawAlphaBackground(QPainter* painter, const QRect& viewportRect, const QPoint& zoomedImageTopLeft, QPixmap texture) { switch (mAlphaBackgroundMode) { case AbstractImageView::AlphaBackgroundNone: painter->fillRect(viewportRect, Qt::transparent); break; case AbstractImageView::AlphaBackgroundCheckBoard: { const QPoint textureOffset( zoomedImageTopLeft.x() % texture.width(), zoomedImageTopLeft.y() % texture.height()); painter->drawTiledPixmap( viewportRect, texture, textureOffset); break; } case AbstractImageView::AlphaBackgroundSolid: painter->fillRect(viewportRect, mAlphaBackgroundColor); break; default: Q_ASSERT(0); } } }; RasterImageView::RasterImageView(QGraphicsItem* parent) : AbstractImageView(parent) , d(new RasterImageViewPrivate) { d->q = this; d->mEmittedCompleted = false; d->mApplyDisplayTransform = true; d->mDisplayTransform = nullptr; d->mAlphaBackgroundMode = AlphaBackgroundNone; d->mAlphaBackgroundColor = Qt::black; d->mRenderingIntent = INTENT_PERCEPTUAL; d->mEnlargeSmallerImages = false; d->mBufferIsEmpty = true; d->mScaler = new ImageScaler(this); connect(d->mScaler, &ImageScaler::scaledRect, this, &RasterImageView::updateFromScaler); d->setupUpdateTimer(); } RasterImageView::~RasterImageView() { if (d->mTool) { d->mTool.data()->toolDeactivated(); } if (d->mDisplayTransform) { cmsDeleteTransform(d->mDisplayTransform); } delete d; } void RasterImageView::setAlphaBackgroundMode(AlphaBackgroundMode mode) { d->mAlphaBackgroundMode = mode; if (document() && document()->hasAlphaChannel()) { d->mCurrentBuffer = QPixmap(); updateBuffer(); } } void RasterImageView::setAlphaBackgroundColor(const QColor& color) { d->mAlphaBackgroundColor = color; if (document() && document()->hasAlphaChannel()) { d->mCurrentBuffer = QPixmap(); updateBuffer(); } } void RasterImageView::setRenderingIntent(const RenderingIntent::Enum& renderingIntent) { if (d->mRenderingIntent != renderingIntent) { d->mRenderingIntent = renderingIntent; updateBuffer(); } } void RasterImageView::loadFromDocument() { Document::Ptr doc = document(); if (!doc) { return; } connect(doc.data(), SIGNAL(metaInfoLoaded(QUrl)), SLOT(slotDocumentMetaInfoLoaded())); connect(doc.data(), SIGNAL(isAnimatedUpdated()), SLOT(slotDocumentIsAnimatedUpdated())); const Document::LoadingState state = doc->loadingState(); if (state == Document::MetaInfoLoaded || state == Document::Loaded) { slotDocumentMetaInfoLoaded(); } } void RasterImageView::slotDocumentMetaInfoLoaded() { if (document()->size().isValid()) { QMetaObject::invokeMethod(this, "finishSetDocument", Qt::QueuedConnection); } else { // Could not retrieve image size from meta info, we need to load the // full image now. connect(document().data(), SIGNAL(loaded(QUrl)), SLOT(finishSetDocument())); document()->startLoadingFullImage(); } } void RasterImageView::finishSetDocument() { GV_RETURN_IF_FAIL(document()->size().isValid()); d->mScaler->setDocument(document()); d->resizeBuffer(); applyPendingScrollPos(); connect(document().data(), SIGNAL(imageRectUpdated(QRect)), SLOT(updateImageRect(QRect))); if (zoomToFit()) { // Force the update otherwise if computeZoomToFit() returns 1, setZoom() // will think zoom has not changed and won't update the image setZoom(computeZoomToFit(), QPointF(-1, -1), ForceUpdate); } else if (zoomToFill()) { setZoom(computeZoomToFill(), QPointF(-1, -1), ForceUpdate); } else { + // Not only call updateBuffer, but also ensure the initial transformation mode + // of the image scaler is set correctly when zoom is unchanged (see Bug 396736). onZoomChanged(); } d->startAnimationIfNecessary(); update(); } void RasterImageView::updateImageRect(const QRect& imageRect) { QRectF viewRect = mapToView(imageRect); if (!viewRect.intersects(boundingRect())) { return; } if (zoomToFit()) { setZoom(computeZoomToFit()); } else if (zoomToFill()) { setZoom(computeZoomToFill()); } else { applyPendingScrollPos(); } d->setScalerRegionToVisibleRect(); update(); emit imageRectUpdated(); } void RasterImageView::slotDocumentIsAnimatedUpdated() { d->startAnimationIfNecessary(); } void RasterImageView::updateFromScaler(int zoomedImageLeft, int zoomedImageTop, const QImage& image) { if (d->mApplyDisplayTransform) { d->updateDisplayTransform(image.format()); if (d->mDisplayTransform) { quint8 *bytes = const_cast(image.bits()); cmsDoTransform(d->mDisplayTransform, bytes, bytes, image.width() * image.height()); } } d->resizeBuffer(); int viewportLeft = zoomedImageLeft - scrollPos().x(); int viewportTop = zoomedImageTop - scrollPos().y(); d->mBufferIsEmpty = false; { QPainter painter(&d->mCurrentBuffer); painter.setCompositionMode(QPainter::CompositionMode_Source); if (document()->hasAlphaChannel()) { d->drawAlphaBackground( &painter, QRect(viewportLeft, viewportTop, image.width(), image.height()), QPoint(zoomedImageLeft, zoomedImageTop), alphaBackgroundTexture() ); // This is required so transparent pixels don't replace our background painter.setCompositionMode(QPainter::CompositionMode_SourceOver); } painter.drawImage(viewportLeft, viewportTop, image); } update(); if (!d->mEmittedCompleted) { d->mEmittedCompleted = true; completed(); } } void RasterImageView::onZoomChanged() { - // 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 - if (zoom() < 4.) { - d->mScaler->setTransformationMode(Qt::SmoothTransformation); - } else { - d->mScaler->setTransformationMode(Qt::FastTransformation); - } + d->mScaler->setZoom(zoom()); if (!d->mUpdateTimer->isActive()) { updateBuffer(); } } void RasterImageView::onImageOffsetChanged() { update(); } void RasterImageView::onScrollPosChanged(const QPointF& oldPos) { QPointF delta = scrollPos() - oldPos; // Scroll existing { if (d->mAlternateBuffer.size() != d->mCurrentBuffer.size()) { d->mAlternateBuffer = QPixmap(d->mCurrentBuffer.size()); } d->mAlternateBuffer.fill(Qt::transparent); QPainter painter(&d->mAlternateBuffer); painter.drawPixmap(-delta, d->mCurrentBuffer); } qSwap(d->mCurrentBuffer, d->mAlternateBuffer); // Scale missing parts QRegion bufferRegion = QRegion(d->mCurrentBuffer.rect().translated(scrollPos().toPoint())); QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint()); updateBuffer(updateRegion); update(); } void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { QPointF topLeft = imageOffset(); if (zoomToFit()) { // In zoomToFit mode, scale crudely the buffer to fit the screen. This // provide an approximate rendered which will be replaced when the scheduled // proper scale is ready. // Round point and size independently, to keep consistency with the below (non zoomToFit) painting const QRect rect = QRect(topLeft.toPoint(), (documentSize() * zoom()).toSize()); painter->drawPixmap(rect, d->mCurrentBuffer); } else { painter->drawPixmap(topLeft.toPoint(), d->mCurrentBuffer); } if (d->mTool) { d->mTool.data()->paint(painter); } // Debug #if 0 QSizeF visibleSize = documentSize() * zoom(); painter->setPen(Qt::red); painter->drawRect(topLeft.x(), topLeft.y(), visibleSize.width() - 1, visibleSize.height() - 1); painter->setPen(Qt::blue); painter->drawRect(topLeft.x(), topLeft.y(), d->mCurrentBuffer.width() - 1, d->mCurrentBuffer.height() - 1); #endif } void RasterImageView::resizeEvent(QGraphicsSceneResizeEvent* event) { // If we are in zoomToFit mode and have something in our buffer, delay the // update: paint() will paint a scaled version of the buffer until resizing // is done. This is much faster than rescaling the whole image for each // resize event we receive. // mUpdateTimer must be started before calling AbstractImageView::resizeEvent() // because AbstractImageView::resizeEvent() will call onZoomChanged(), which // will trigger an immediate update unless the mUpdateTimer is active. if (zoomToFit() && !d->mBufferIsEmpty) { d->mUpdateTimer->start(); } else if (zoomToFill() && !d->mBufferIsEmpty) { d->mUpdateTimer->start(); } AbstractImageView::resizeEvent(event); if (!zoomToFit()) { // Only update buffer if we are not in zoomToFit mode: if we are // onZoomChanged() will have already updated the buffer. updateBuffer(); } else if (!zoomToFill()) { updateBuffer(); } } void RasterImageView::updateBuffer(const QRegion& region) { d->mUpdateTimer->stop(); - d->mScaler->setZoom(zoom()); if (region.isEmpty()) { d->setScalerRegionToVisibleRect(); } else { d->mScaler->setDestinationRegion(region); } } void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) { if (d->mTool) { d->mTool.data()->toolDeactivated(); d->mTool.data()->deleteLater(); } d->mTool = tool; if (d->mTool) { d->mTool.data()->toolActivated(); } updateCursor(); currentToolChanged(tool); update(); } AbstractRasterImageViewTool* RasterImageView::currentTool() const { return d->mTool.data(); } void RasterImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mousePressEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::mousePressEvent(event); } void RasterImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mouseMoveEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::mouseMoveEvent(event); } void RasterImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mouseReleaseEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::mouseReleaseEvent(event); } void RasterImageView::wheelEvent(QGraphicsSceneWheelEvent* event) { if (d->mTool) { d->mTool.data()->wheelEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::wheelEvent(event); } void RasterImageView::keyPressEvent(QKeyEvent* event) { if (d->mTool) { d->mTool.data()->keyPressEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::keyPressEvent(event); } void RasterImageView::keyReleaseEvent(QKeyEvent* event) { if (d->mTool) { d->mTool.data()->keyReleaseEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::keyReleaseEvent(event); } void RasterImageView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { if (d->mTool) { d->mTool.data()->hoverMoveEvent(event); if (event->isAccepted()) { return; } } AbstractImageView::hoverMoveEvent(event); } } // namespace diff --git a/lib/imagescaler.cpp b/lib/imagescaler.cpp index 833f6ccd..320afcda 100644 --- a/lib/imagescaler.cpp +++ b/lib/imagescaler.cpp @@ -1,212 +1,212 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 #include #include // KDE // Local #include #include #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) qDebug() << 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; 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(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(), SIGNAL(downSampledImageReady()), SLOT(doScale())); // Used when scaler asked for a full image connect(d->mDocument.data(), SIGNAL(loaded(QUrl)), SLOT(doScale())); } void ImageScaler::setZoom(qreal zoom) { - d->mZoom = 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; -void ImageScaler::setTransformationMode(Qt::TransformationMode mode) -{ - d->mTransformationMode = mode; + 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"); Q_FOREACH(const QRect & rect, d->mRegion.rects()) { LOG(rect); scaleRect(rect); } LOG("Done"); } void ImageScaler::scaleRect(const QRect& rect) { const qreal REAL_DELTA = 0.001; if (qAbs(d->mZoom - 1.0) < REAL_DELTA) { QImage tmp = d->mDocument->image().copy(rect); 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; } // If rect contains "half" pixels, make sure sourceRect includes them QRectF sourceRectF( rect.left() / zoom, rect.top() / zoom, rect.width() / zoom, rect.height() / zoom); sourceRectF = sourceRectF.intersected(image.rect()); QRect sourceRect = PaintUtils::containingRect(sourceRectF); 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(image.rect().right() - sourceRect.right(), SMOOTH_MARGIN); sourceBottomMargin = qMin(image.rect().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 QRectF destRectF = QRectF( sourceRect.left() * zoom, sourceRect.top() * zoom, sourceRect.width() * zoom, sourceRect.height() * zoom ); QRect destRect = PaintUtils::containingRect(destRectF); QImage tmp; tmp = image.copy(sourceRect); tmp = tmp.scaled( destRect.width(), destRect.height(), Qt::IgnoreAspectRatio, // Do not use KeepAspectRatio, it can lead to skipped rows or columns d->mTransformationMode); if (needsSmoothMargins) { tmp = tmp.copy( destLeftMargin, destTopMargin, destRect.width() - (destLeftMargin + destRightMargin), destRect.height() - (destTopMargin + destBottomMargin) ); } emit scaledRect(destRect.left() + destLeftMargin, destRect.top() + destTopMargin, tmp); } } // namespace diff --git a/lib/imagescaler.h b/lib/imagescaler.h index 8684e0d0..bf919987 100644 --- a/lib/imagescaler.h +++ b/lib/imagescaler.h @@ -1,67 +1,65 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 // KDE // local #include #include 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(); void setDocument(Document::Ptr); void setZoom(qreal); void setDestinationRegion(const QRegion&); - void setTransformationMode(Qt::TransformationMode); - 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 */ diff --git a/tests/auto/imagescalertest.cpp b/tests/auto/imagescalertest.cpp index 033dcf30..fdcc9534 100644 --- a/tests/auto/imagescalertest.cpp +++ b/tests/auto/imagescalertest.cpp @@ -1,214 +1,215 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 #include "imagescalertest.h" #include "../lib/imagescaler.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); + 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("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