Export JPEG: a zoomable full-size preview, its buttons and the View menu's zoom keys (13.4b)
This commit is contained in:
1 parent
bc49b722eb
commit
64ebd1cdf9
17 files changed
+702
-74
No files matched your search
@@ -2,7 +2,9 @@
|
||||
#include "Rendering/AdjustmentSurface.h"
|
||||
#include "Rendering/EditorCanvas.h"
|
||||
#include "RenderFixtures.h"
|
||||
#include "UI/JPEGExportSheet.h"
|
||||
#include "UI/LevelsSheet.h"
|
||||
#include <QScrollBar>
|
||||
#include <QtTest>
|
||||
|
||||
// Widgets at device pixel ratio 2: two coordinate systems.
|
||||
@@ -41,6 +43,7 @@ private slots:
|
||||
void theSurfaceCoversAnOffsetWidgetInDevicePixels();
|
||||
void theCanvasDrawsDevicePixels();
|
||||
void levelsEyedroppersDrawDevicePixels();
|
||||
void theJPEGPreviewCountsDevicePixels();
|
||||
};
|
||||
|
||||
void HighDpiCanvasTests::theScreenScalesByTwo()
|
||||
@@ -168,6 +171,28 @@ void HighDpiCanvasTests::levelsEyedroppersDrawDevicePixels()
|
||||
}
|
||||
}
|
||||
|
||||
void HighDpiCanvasTests::theJPEGPreviewCountsDevicePixels()
|
||||
{
|
||||
QImage image(1200, 800, QImage::Format_RGBA8888_Premultiplied);
|
||||
for (int y = 0; y < 800; ++y)
|
||||
for (int x = 0; x < 1200; ++x)
|
||||
image.setPixelColor(x, y, QColor((x * 7 + y) % 256, (x + y * 3) % 256, (x * y) % 256));
|
||||
JPEGPreview preview(QSize(1200, 800), [] {}, nullptr);
|
||||
preview.setImage(image, false);
|
||||
preview.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&preview));
|
||||
// 600 by 400 points: Fit 0.825, averaged per device pixel.
|
||||
QCOMPARE(preview.shownZoom(), 0.825);
|
||||
QImage fitted = preview.viewport()->grab().toImage().convertToFormat(QImage::Format_RGB32);
|
||||
fitted.setDevicePixelRatio(1);
|
||||
QCOMPARE(fitted.size(), QSize(1120, 660));
|
||||
QCOMPARE(fitted.copy(65, 0, 990, 660), image.scaled(990, 660, Qt::IgnoreAspectRatio, Qt::SmoothTransformation).convertToFormat(QImage::Format_RGB32));
|
||||
// At 100% an image pixel is a device pixel.
|
||||
preview.setZoom(1);
|
||||
QCoreApplication::processEvents();
|
||||
QCOMPARE(preview.horizontalScrollBar()->maximum(), int(std::ceil(600 - preview.viewport()->width())));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Every widget on this screen scales by two.
|
||||
|
||||
@@ -100,7 +100,7 @@ void JPEGExportSheetTests::theSheetShowsTheRasterAndWaitsForItsPreview()
|
||||
QCOMPARE(wait.interval(), 200);
|
||||
QCOMPARE(wait.timerType(), Qt::PreciseTimer);
|
||||
QVERIFY(!shown.ready());
|
||||
QCOMPARE(shown.note(), QString("Updating preview…"));
|
||||
QCOMPARE(shown.note(), QString("Updating…"));
|
||||
QVERIFY(shown.find<QProgressBar>("jpegSpinner").isVisible());
|
||||
QVERIFY(!shown.find<QLabel>("jpegBytes").isVisible() && !shown.find<QLabel>("jpegError").isVisible());
|
||||
// The material plate behind the spinner, over the dark gray.
|
||||
@@ -111,8 +111,7 @@ void JPEGExportSheetTests::theSheetShowsTheRasterAndWaitsForItsPreview()
|
||||
QTRY_VERIFY(shown.ready());
|
||||
const JPEGResult expected = ImageExporter::jpeg(halfRed(1200, 800), {});
|
||||
QCOMPARE(shown.find<QLabel>("jpegBytes").text(), ByteCounts::file(expected.data.size()));
|
||||
QVERIFY(shown.find<QLabel>("jpegBytes").isVisible());
|
||||
QCOMPARE(shown.note(), QString("· encoded preview, fitted to window"));
|
||||
QVERIFY(shown.find<QLabel>("jpegBytes").isVisible() && !shown.find<QLabel>("jpegNote").isVisible());
|
||||
QVERIFY(!shown.find<QProgressBar>("jpegSpinner").isVisible());
|
||||
// Fitted to 495 × 330 and centred: white, then red.
|
||||
QCOMPARE(shown.previewAt({2, 2}), QColor(31, 31, 31));
|
||||
@@ -131,7 +130,7 @@ void JPEGExportSheetTests::aChangeSupersedesThePreview()
|
||||
quality.setValue(10);
|
||||
QCOMPARE(shown.find<QLabel>("jpegPercent").text(), QString("10%"));
|
||||
QVERIFY(!shown.ready());
|
||||
QCOMPARE(shown.note(), QString("Updating preview…"));
|
||||
QCOMPARE(shown.note(), QString("Updating…"));
|
||||
QVERIFY(shown.find<QTimer>("jpegWait").isActive());
|
||||
QVERIFY(shown.find<QProgressBar>("jpegSpinner").isVisible());
|
||||
quality.setValue(100);
|
||||
@@ -171,7 +170,7 @@ void JPEGExportSheetTests::theMatteComesFromTheAppsPicker()
|
||||
QTRY_VERIFY(shown.ready());
|
||||
auto &matte = shown.find<DialogColorSwatch>("jpegMatte");
|
||||
QVERIFY(matte.accessibleName() == QString("JPEG Background") && matte.toolTip() == QString("Color that fills transparent areas"));
|
||||
QCOMPARE(qobject_cast<QHBoxLayout *>(shown.sheet.layout()->itemAt(3)->layout())->spacing(), 8);
|
||||
QCOMPARE(qobject_cast<QHBoxLayout *>(shown.sheet.layout()->itemAt(2)->layout())->spacing(), 8);
|
||||
matte.click();
|
||||
QCOMPARE(shown.session.colorPicker().value().target.title(), QString("Color Picker (JPEG Background)"));
|
||||
QVERIFY(shown.session.colorPicker().value().original == PaletteColor::white());
|
||||
@@ -266,7 +265,7 @@ void JPEGExportSheetTests::anEncodingThatFailsIsShown()
|
||||
// A change clears the error while it waits.
|
||||
shown.find<QSlider>("jpegQuality").setValue(50);
|
||||
QVERIFY(!error.isVisible());
|
||||
QCOMPARE(shown.note(), QString("Updating preview…"));
|
||||
QCOMPARE(shown.note(), QString("Updating…"));
|
||||
QTRY_VERIFY(error.isVisible());
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -14,7 +14,7 @@ private slots:
|
||||
void transparencyUsesChosenMatteAndProducesOpaqueSRGB_data();
|
||||
void transparencyUsesChosenMatteAndProducesOpaqueSRGB();
|
||||
void qualityChangesBytesAndDecodedPixels();
|
||||
void thePreviewIsDecodedFromTheFileAndKeptSmall();
|
||||
void thePreviewIsDecodedFromTheFileUpToItsLimit();
|
||||
void pixelsLieOverTheMatteOnce();
|
||||
void whatCannotBeEncodedDecodedOrAllocatedThrows();
|
||||
void cancellationStopsAtSwiftsThreePoints();
|
||||
@@ -88,22 +88,25 @@ void JPEGExportTests::qualityChangesBytesAndDecodedPixels()
|
||||
QCOMPARE(written.readAll(), high.data);
|
||||
}
|
||||
|
||||
void JPEGExportTests::thePreviewIsDecodedFromTheFileAndKeptSmall()
|
||||
void JPEGExportTests::thePreviewIsDecodedFromTheFileUpToItsLimit()
|
||||
{
|
||||
const JPEGResult small = ImageExporter::jpeg({noise(64, 48, 1)}, {.quality = 0.1});
|
||||
QCOMPARE(small.preview.size(), QSize(64, 48));
|
||||
// Compression shows in the preview: it is not the source.
|
||||
QCOMPARE(small.preview.convertToFormat(QImage::Format_RGB888), QImage::fromData(small.data).convertToFormat(QImage::Format_RGB888));
|
||||
QVERIFY(small.preview.convertToFormat(QImage::Format_RGB888) != noise(64, 48, 1).convertToFormat(QImage::Format_RGB888));
|
||||
const JPEGResult wide = ImageExporter::jpeg({solid(2400, 600, qRgba(10, 200, 30, 255))}, {});
|
||||
QCOMPARE(wide.preview.size(), QSize(1000, 250));
|
||||
QCOMPARE(QImage::fromData(wide.data).size(), QSize(2400, 600));
|
||||
const JPEGResult tall = ImageExporter::jpeg({solid(500, 2000, qRgba(10, 200, 30, 255))}, {});
|
||||
QCOMPARE(tall.preview.size(), QSize(250, 1000));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(1000, 1000, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(1000, 1000));
|
||||
// Full size for 100%, past Qt's 128 MB decode limit.
|
||||
QCOMPARE(ImageExporter::jpeg({solid(6000, 6000, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(6000, 6000));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(2400, 600, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(2400, 600));
|
||||
QCOMPARE(JPEGResult::previewLimit, 8192);
|
||||
QCOMPARE(ImageExporter::jpeg({solid(8192, 4, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(8192, 4));
|
||||
const JPEGResult wide = ImageExporter::jpeg({solid(9000, 900, qRgba(10, 200, 30, 255))}, {});
|
||||
QCOMPARE(wide.preview.size(), QSize(8192, 819));
|
||||
QCOMPARE(QImage::fromData(wide.data).size(), QSize(9000, 900));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(900, 9000, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(819, 8192));
|
||||
// Thin images keep a pixel across.
|
||||
QCOMPARE(ImageExporter::jpeg({solid(2400, 1, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(1000, 1));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(1, 2400, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(1, 1000));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(9000, 1, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(8192, 1));
|
||||
QCOMPARE(ImageExporter::jpeg({solid(1, 9000, qRgba(10, 200, 30, 255))}, {}).preview.size(), QSize(1, 8192));
|
||||
}
|
||||
|
||||
void JPEGExportTests::pixelsLieOverTheMatteOnce()
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
#include "UI/JPEGExportSheet.h"
|
||||
#include "Document/EditorSession.h"
|
||||
#include "UI/KeyboardShortcuts.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QRandomGenerator>
|
||||
#include <QScrollBar>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtTest>
|
||||
|
||||
// Swift 1.3.5's JPEGPreview: fitted or zoomed, dragged, its keys.
|
||||
class JPEGPreviewTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase() { QStandardPaths::setTestModeEnabled(true); }
|
||||
void fitAndStepsFollowSwift();
|
||||
void itDrawsFittedAndBelowOneThroughAnAverage();
|
||||
void fromOneUpEachPixelShowsAsItIs();
|
||||
void theUpdatingPlateSitsInTheMiddle();
|
||||
void zoomKeepsTheMiddleAndDragsMoveIt();
|
||||
void theSheetsButtonsAndKeysZoomIt();
|
||||
};
|
||||
|
||||
namespace {
|
||||
QImage noise(int width, int height, quint32 seed)
|
||||
{
|
||||
QImage image(width, height, QImage::Format_RGBA8888_Premultiplied);
|
||||
QRandomGenerator random(seed);
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x)
|
||||
image.setPixelColor(x, y, QColor::fromRgb(random.bounded(256), random.bounded(256), random.bounded(256)));
|
||||
return image;
|
||||
}
|
||||
|
||||
QImage drawn(JPEGPreview &preview)
|
||||
{
|
||||
return preview.viewport()->grab().toImage().convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
|
||||
// The part of the view at `rect` equals `reference`.
|
||||
bool shows(JPEGPreview &preview, QRect rect, const QImage &reference)
|
||||
{
|
||||
return drawn(preview).copy(rect) == reference.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
|
||||
// A move with `held` down.
|
||||
void dragTo(QWidget *widget, QPoint at, Qt::MouseButtons held = Qt::LeftButton)
|
||||
{
|
||||
QMouseEvent event(QEvent::MouseMove, at, widget->mapToGlobal(at), Qt::NoButton, held, Qt::NoModifier);
|
||||
QApplication::sendEvent(widget, &event);
|
||||
}
|
||||
|
||||
// Counts a widget's repaints.
|
||||
struct Paints : QObject {
|
||||
int count = 0;
|
||||
explicit Paints(QWidget *widget) { widget->installEventFilter(this); }
|
||||
bool eventFilter(QObject *, QEvent *event) override
|
||||
{
|
||||
count += event->type() == QEvent::Paint;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct Shown {
|
||||
JPEGPreview preview;
|
||||
explicit Shown(QSize pixels, std::function<void()> zoomed = [] {}) : preview(pixels, std::move(zoomed), nullptr)
|
||||
{
|
||||
preview.show();
|
||||
if (!QTest::qWaitForWindowExposed(&preview))
|
||||
throw std::runtime_error("the preview never showed");
|
||||
}
|
||||
// The zoom, then the scroll bars it brings.
|
||||
void zoom(std::optional<double> zoom)
|
||||
{
|
||||
preview.setZoom(zoom);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::fitAndStepsFollowSwift()
|
||||
{
|
||||
QCOMPARE(JPEGPreview::frame, QSize(560, 330));
|
||||
QCOMPARE(JPEGPreview::fitZoom(1200, 800, QSizeF(560, 330), 1), 330.0 / 800);
|
||||
QCOMPARE(JPEGPreview::fitZoom(1200, 800, QSizeF(560, 330), 2), 330.0 / 400);
|
||||
QCOMPARE(JPEGPreview::fitZoom(100, 400, QSizeF(560, 330), 0.5), 330.0 / 400);
|
||||
QCOMPARE(JPEGPreview::step(0.4125, 1), std::optional(0.5));
|
||||
QCOMPARE(JPEGPreview::step(0.4125, -1), std::optional(0.25));
|
||||
// A step a hair away counts as that step.
|
||||
QCOMPARE(JPEGPreview::step(0.9995, 1), std::optional(2.0));
|
||||
QCOMPARE(JPEGPreview::step(1.0005, -1), std::optional(0.5));
|
||||
QCOMPARE(JPEGPreview::step(0.998, 1), std::optional(1.0));
|
||||
QCOMPARE(JPEGPreview::step(1.002, -1), std::optional(1.0));
|
||||
QCOMPARE(JPEGPreview::step(8, 1), std::nullopt);
|
||||
QCOMPARE(JPEGPreview::step(0.25, -1), std::nullopt);
|
||||
QCOMPARE(JPEGPreview::step(20, -1), std::optional(8.0));
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::itDrawsFittedAndBelowOneThroughAnAverage()
|
||||
{
|
||||
Shown shown(QSize(1100, 660));
|
||||
JPEGPreview &preview = shown.preview;
|
||||
QVERIFY(preview.size() == QSize(560, 330) && preview.viewport()->cursor().shape() == Qt::ArrowCursor);
|
||||
QCOMPARE(preview.toolTip(), QString("Drag or scroll to move around; double-click switches between Fit and 100%"));
|
||||
QVERIFY(preview.horizontalScrollBar()->maximum() == 0 && preview.verticalScrollBar()->maximum() == 0);
|
||||
QVERIFY(!preview.horizontalScrollBar()->isVisible() && preview.horizontalScrollBar()->singleStep() == 20 && preview.verticalScrollBar()->singleStep() == 20);
|
||||
// Nothing yet: the dark gray.
|
||||
QCOMPARE(drawn(preview).pixelColor(280, 165), QColor(31, 31, 31));
|
||||
Paints paints(preview.viewport());
|
||||
const QImage first = noise(1100, 660, 1);
|
||||
preview.setImage(first, false);
|
||||
QTRY_VERIFY(paints.count > 0);
|
||||
// Fitted, 550 by 330 in the middle, averaged down.
|
||||
QVERIFY(shows(preview, QRect(5, 0, 550, 330), first.scaled(550, 330, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
|
||||
QCOMPARE(drawn(preview).pixelColor(4, 165), QColor(31, 31, 31));
|
||||
// A new image of the same size is averaged anew.
|
||||
const QImage second = noise(1100, 660, 2);
|
||||
preview.setImage(second, false);
|
||||
QVERIFY(shows(preview, QRect(5, 0, 550, 330), second.scaled(550, 330, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
|
||||
// A quarter, then Fit again: each size averaged for itself.
|
||||
QCoreApplication::processEvents();
|
||||
paints.count = 0;
|
||||
shown.zoom(0.25);
|
||||
QTRY_VERIFY(paints.count > 0);
|
||||
QVERIFY(shows(preview, QRect(143, 83, 275, 165), second.scaled(275, 165, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
|
||||
QVERIFY(drawn(preview).pixelColor(141, 165) == QColor(31, 31, 31) && drawn(preview).pixelColor(280, 81) == QColor(31, 31, 31));
|
||||
QCOMPARE(preview.horizontalScrollBar()->maximum(), 0);
|
||||
shown.zoom(std::nullopt);
|
||||
QVERIFY(shows(preview, QRect(5, 0, 550, 330), second.scaled(550, 330, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)));
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::fromOneUpEachPixelShowsAsItIs()
|
||||
{
|
||||
Shown shown(QSize(1100, 660));
|
||||
JPEGPreview &preview = shown.preview;
|
||||
const QImage image = noise(1100, 660, 3);
|
||||
preview.setImage(image, false);
|
||||
shown.zoom(2);
|
||||
// Bars come once laid out; ranges follow the view.
|
||||
QScrollBar &across = *preview.horizontalScrollBar(), &down = *preview.verticalScrollBar();
|
||||
const QSize view = preview.viewport()->size();
|
||||
QVERIFY(across.isVisible() && down.isVisible() && view.width() < 560 && view.height() < 330);
|
||||
QVERIFY(across.maximum() == 2200 - view.width() && down.maximum() == 1320 - view.height());
|
||||
QVERIFY(across.pageStep() == view.width() && down.pageStep() == view.height());
|
||||
// Twice, nearest: each JPEG pixel a 2 by 2 block.
|
||||
across.setValue(100);
|
||||
down.setValue(60);
|
||||
QVERIFY(shows(preview, QRect(0, 0, 200, 100), image.copy(50, 30, 100, 50).scaled(200, 100)));
|
||||
// A smaller decode still spans the export's pixels.
|
||||
preview.setImage(image.scaled(550, 330), false);
|
||||
shown.zoom(1);
|
||||
QCOMPARE(down.maximum(), 660 - preview.viewport()->height());
|
||||
// A size between pixels reaches its last one.
|
||||
Shown odd(QSize(1201, 1201));
|
||||
odd.preview.setImage(noise(1201, 1201, 4), false);
|
||||
odd.zoom(0.5);
|
||||
QCOMPARE(odd.preview.horizontalScrollBar()->maximum(), int(std::ceil(600.5 - odd.preview.viewport()->width())));
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::theUpdatingPlateSitsInTheMiddle()
|
||||
{
|
||||
Shown shown(QSize(1100, 660));
|
||||
shown.preview.setImage(QImage(), true);
|
||||
const QImage image = drawn(shown.preview);
|
||||
const QColor window = shown.preview.palette().color(QPalette::Window);
|
||||
const int plate = qRound(window.red() * 0.85 + 31 * 0.15);
|
||||
// 64 square, no rim: its edges sit on whole pixels.
|
||||
QVERIFY(std::abs(image.pixelColor(248, 165).red() - plate) <= 1 && std::abs(image.pixelColor(311, 165).red() - plate) <= 1);
|
||||
QVERIFY(image.pixelColor(247, 165) == QColor(31, 31, 31) && image.pixelColor(312, 165) == QColor(31, 31, 31));
|
||||
QVERIFY(image.pixelColor(280, 132) == QColor(31, 31, 31) && std::abs(image.pixelColor(280, 133).red() - plate) <= 1);
|
||||
// Its corners round by 8, antialiased.
|
||||
QCOMPARE(image.pixelColor(248, 133), QColor(31, 31, 31));
|
||||
const int corner = image.pixelColor(250, 135).red();
|
||||
QVERIFY(corner > 40 && corner < plate - 10);
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::zoomKeepsTheMiddleAndDragsMoveIt()
|
||||
{
|
||||
int heard = 0;
|
||||
Shown shown(QSize(1200, 800), [&heard] { ++heard; });
|
||||
JPEGPreview &preview = shown.preview;
|
||||
preview.setImage(noise(1200, 800, 5), false);
|
||||
// From Fit, the middle of the image.
|
||||
Paints paints(preview.viewport());
|
||||
preview.setZoom(1);
|
||||
QVERIFY(heard == 1 && paints.count == 0);
|
||||
QTRY_VERIFY(paints.count > 0);
|
||||
QScrollBar &across = *preview.horizontalScrollBar(), &down = *preview.verticalScrollBar();
|
||||
QVERIFY(across.value() == 320 && down.value() == 235);
|
||||
QCoreApplication::processEvents();
|
||||
// Zooming keeps the point in the middle where it was.
|
||||
const QSize view = preview.viewport()->size();
|
||||
across.setValue(0);
|
||||
down.setValue(0);
|
||||
shown.zoom(2);
|
||||
QVERIFY(across.value() == int(std::lround(view.width() / 2.0)) && down.value() == int(std::lround(view.height() / 2.0)));
|
||||
shown.zoom(2);
|
||||
QCOMPARE(heard, 2);
|
||||
// Smaller than the view, its middle is the image's middle.
|
||||
shown.zoom(0.25);
|
||||
preview.setZoom(0.5);
|
||||
QVERIFY(across.value() == 20 && down.value() == 35);
|
||||
// A half-pixel landing rounds, as Swift scrolls to it.
|
||||
Shown odd(QSize(1201, 801));
|
||||
odd.preview.setZoom(1);
|
||||
QVERIFY(odd.preview.horizontalScrollBar()->value() == 321 && odd.preview.verticalScrollBar()->value() == 236);
|
||||
shown.zoom(2);
|
||||
// A drag moves the image with the pointer.
|
||||
across.setValue(500);
|
||||
down.setValue(300);
|
||||
QTest::mousePress(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
|
||||
QCOMPARE(preview.viewport()->cursor().shape(), Qt::OpenHandCursor);
|
||||
dragTo(preview.viewport(), QPoint(60, 80));
|
||||
QVERIFY(across.value() == 540 && down.value() == 320 && preview.viewport()->cursor().shape() == Qt::ClosedHandCursor);
|
||||
QTest::mouseRelease(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(60, 80));
|
||||
QCOMPARE(preview.viewport()->cursor().shape(), Qt::OpenHandCursor);
|
||||
dragTo(preview.viewport(), QPoint(20, 20));
|
||||
QCOMPARE(across.value(), 540);
|
||||
// Another button drags nothing; a new zoom ends a drag.
|
||||
QTest::mousePress(preview.viewport(), Qt::RightButton, Qt::NoModifier, QPoint(100, 100));
|
||||
dragTo(preview.viewport(), QPoint(60, 80), Qt::RightButton);
|
||||
QTest::mouseRelease(preview.viewport(), Qt::RightButton, Qt::NoModifier, QPoint(60, 80));
|
||||
QCOMPARE(across.value(), 540);
|
||||
QTest::mousePress(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
|
||||
shown.zoom(4);
|
||||
const int zoomed = across.value();
|
||||
dragTo(preview.viewport(), QPoint(60, 80));
|
||||
QVERIFY(across.value() == zoomed && preview.viewport()->cursor().shape() == Qt::OpenHandCursor);
|
||||
QTest::mouseRelease(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(60, 80));
|
||||
// Double-click: 100% to Fit and back; Fit drags nothing.
|
||||
QTest::mouseDClick(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
|
||||
QVERIFY(!preview.zoom() && preview.viewport()->cursor().shape() == Qt::ArrowCursor);
|
||||
QTest::mousePress(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
|
||||
dragTo(preview.viewport(), QPoint(60, 80));
|
||||
QCOMPARE(preview.viewport()->cursor().shape(), Qt::ArrowCursor);
|
||||
QTest::mouseRelease(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(60, 80));
|
||||
QTest::mouseDClick(preview.viewport(), Qt::RightButton, Qt::NoModifier, QPoint(50, 50));
|
||||
QVERIFY(!preview.zoom());
|
||||
QTest::mouseDClick(preview.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
|
||||
QCOMPARE(preview.zoom(), std::optional(1.0));
|
||||
}
|
||||
|
||||
void JPEGPreviewTests::theSheetsButtonsAndKeysZoomIt()
|
||||
{
|
||||
QSettings().remove(JPEGExportSheet::qualityKey);
|
||||
EditorSession session;
|
||||
QImage red(1200, 800, QImage::Format_RGBA8888_Premultiplied);
|
||||
red.fill(Qt::red);
|
||||
JPEGExportSheet sheet({red, 72}, session, [](std::optional<QByteArray>) {});
|
||||
sheet.show();
|
||||
QVERIFY(QTest::qWaitForWindowActive(&sheet));
|
||||
auto &preview = *sheet.findChild<JPEGPreview *>();
|
||||
auto &fit = *sheet.findChild<QPushButton *>(QStringLiteral("jpegFit"));
|
||||
auto &in = *sheet.findChild<QPushButton *>(QStringLiteral("jpegZoomIn"));
|
||||
auto &out = *sheet.findChild<QPushButton *>(QStringLiteral("jpegZoomOut"));
|
||||
// Title row 8 above the preview; bottom row 12 apart.
|
||||
auto *top = qobject_cast<QVBoxLayout *>(sheet.layout()->itemAt(0)->layout());
|
||||
auto *title = qobject_cast<QHBoxLayout *>(top->itemAt(0)->layout());
|
||||
QVERIFY(top->spacing() == 8 && title->spacing() == 8 && title->itemAt(2)->widget() == &fit && title->itemAt(4)->widget() == &out);
|
||||
auto *bottom = qobject_cast<QHBoxLayout *>(sheet.layout()->itemAt(3)->layout());
|
||||
QVERIFY(bottom->spacing() == 12 && bottom->itemAt(0)->widget()->objectName() == QString("jpegSize") && bottom->itemAt(1)->spacerItem());
|
||||
QVERIFY(!fit.isEnabled() && in.isEnabled() && out.isEnabled() && in.accessibleName() == QString("Zoom in"));
|
||||
QCOMPARE(fit.toolTip(), QString("Show the whole image (Ctrl+0)"));
|
||||
QCOMPARE(in.toolTip(), QString("Zoom in (Ctrl+=), now 41%. At 100% each pixel of the JPEG is one pixel of the screen, as on the canvas"));
|
||||
QCOMPARE(out.toolTip(), QString("Zoom out (Ctrl+-), now 41%"));
|
||||
// The magnifiers in the button ink, redrawn with the theme.
|
||||
const QImage before = in.icon().pixmap(16).toImage();
|
||||
QPalette palette = sheet.palette();
|
||||
palette.setColor(QPalette::ButtonText, Qt::red);
|
||||
sheet.setPalette(palette);
|
||||
QVERIFY(in.icon().pixmap(16).toImage() != before && out.icon().pixmap(16).toImage() != in.icon().pixmap(16).toImage());
|
||||
out.click();
|
||||
QVERIFY(preview.zoom() == std::optional(0.25) && !out.isEnabled() && fit.isEnabled());
|
||||
QCOMPARE(in.toolTip(), QString("Zoom in (Ctrl+=), now 25%. At 100% each pixel of the JPEG is one pixel of the screen, as on the canvas"));
|
||||
in.click();
|
||||
QCOMPARE(preview.zoom(), std::optional(0.5));
|
||||
fit.click();
|
||||
QVERIFY(!preview.zoom());
|
||||
// The View menu's keys, blocked by the sheet, zoom it.
|
||||
QTest::keyClick(&sheet, Qt::Key_1, Qt::ControlModifier);
|
||||
QCOMPARE(preview.zoom(), std::optional(1.0));
|
||||
for (int step = 0; step < 4; ++step)
|
||||
QTest::keyClick(&sheet, Qt::Key_Equal, Qt::ControlModifier);
|
||||
QVERIFY(preview.zoom() == std::optional(8.0) && !in.isEnabled());
|
||||
QTest::keyClick(&sheet, Qt::Key_Minus, Qt::ControlModifier);
|
||||
QCOMPARE(preview.zoom(), std::optional(4.0));
|
||||
QTest::keyClick(&sheet, Qt::Key_0, Qt::ControlModifier);
|
||||
QVERIFY(!preview.zoom());
|
||||
// Remapped, the new key zooms and the tooltip says it.
|
||||
QSettings().remove(QLatin1String(ShortcutSettings::storageKey));
|
||||
QVERIFY(ShortcutSettings::shared().save({{QStringLiteral("Menus:Zoom In"), ShortcutChord(QStringLiteral("k"), 1)}}));
|
||||
QVERIFY(in.toolTip().startsWith(QStringLiteral("Zoom in (Ctrl+K), now 41%")));
|
||||
QTest::keyClick(&sheet, Qt::Key_Equal, Qt::ControlModifier);
|
||||
QVERIFY(!preview.zoom());
|
||||
QTest::keyClick(&sheet, Qt::Key_K, Qt::ControlModifier);
|
||||
QCOMPARE(preview.zoom(), std::optional(0.5));
|
||||
QSettings().remove(QLatin1String(ShortcutSettings::storageKey));
|
||||
ShortcutSettings::shared().reload();
|
||||
}
|
||||
|
||||
QTEST_MAIN(JPEGPreviewTests)
|
||||
#include "JPEGPreviewTests.moc"
|
||||
@@ -252,7 +252,17 @@ void ProjectExportTests::theFileMenuOffersBothExports()
|
||||
bar.window.show();
|
||||
bar.action("exportJPEG").trigger();
|
||||
QTRY_VERIFY(bar.window.findChild<JPEGExportSheet *>());
|
||||
QTest::keyClick(bar.window.findChild<JPEGExportSheet *>(), Qt::Key_Escape);
|
||||
// The menus' zoom keys reach the preview, not the canvas.
|
||||
JPEGExportSheet &sheet = *bar.window.findChild<JPEGExportSheet *>();
|
||||
QTRY_COMPARE(QApplication::activeWindow(), sheet.window());
|
||||
const double zoom = bar.session().viewport.zoom();
|
||||
QTest::keyClick(&sheet, Qt::Key_1, Qt::ControlModifier);
|
||||
QCOMPARE(sheet.findChild<JPEGPreview *>()->zoom(), std::optional(1.0));
|
||||
QCOMPARE(bar.session().viewport.zoom(), zoom);
|
||||
// In the dialog only Export takes Return.
|
||||
for (const char *name : {"jpegFit", "jpegZoomIn", "jpegZoomOut"})
|
||||
QVERIFY(!sheet.findChild<QPushButton *>(QString::fromLatin1(name))->autoDefault());
|
||||
QTest::keyClick(&sheet, Qt::Key_Escape);
|
||||
QTRY_VERIFY(!bar.session().isProjectBusy());
|
||||
QCOMPARE(desk.seen.size(), 1);
|
||||
}
|
||||
|
||||
@@ -229,7 +229,9 @@ void RemappedKeysTests::everySheetBindsSwiftsKeys()
|
||||
QCOMPARE(keys(CanvasSizeSheet(canvas, session, [](std::optional<CanvasSizeOptions>) {})), both);
|
||||
QCOMPARE(keys(ImageSizeSheet(canvas, [](std::optional<ImageSizeOptions>) {})), both);
|
||||
QCOMPARE(keys(TrimSheet([](std::optional<TrimOptions>) {})), both);
|
||||
QCOMPARE(keys(JPEGExportSheet(ExportRaster{red, 72}, session, [](std::optional<QByteArray>) {})), both);
|
||||
// Export JPEG also takes the View menu's four zoom keys.
|
||||
const QSet<QString> zooms{"Ctrl+K", "Ctrl+Q", "Ctrl+0", "Ctrl+1", "Ctrl+=", "Ctrl+-"};
|
||||
QCOMPARE(keys(JPEGExportSheet(ExportRaster{red, 72}, session, [](std::optional<QByteArray>) {})), zooms);
|
||||
QCOMPARE(keys(ColorPickerSheet(session, [](bool) {})), both);
|
||||
QCOMPARE(keys(NewCanvasSheet(session, [](int, int) {}, [] {})), QSet<QString>{"Ctrl+K"});
|
||||
QCOMPARE(keys(TransformInspector(session)), both);
|
||||
|
||||
@@ -179,6 +179,7 @@ compositor_test(ColorRangeTests)
|
||||
compositor_test(ColorRangeCanvasTests)
|
||||
compositor_test(ColorRangeSheetTests)
|
||||
compositor_test(DialogColorPickerTests)
|
||||
compositor_test(JPEGPreviewTests)
|
||||
# It calls the colour-range kernel itself.
|
||||
target_include_directories(ColorRangeTests PRIVATE ${OMAPHOTO_KERNELS})
|
||||
compositor_test(DitherTests)
|
||||
|
||||
Reference in new issue
Block a user