Masks as Photoshop's: the button reveals the selection, Alt-click shows a mask alone (13.12)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-01 18:04:14 -04:00
1 parent cc9bf95722
commit 0b0d36a7df
28 files changed
+554 -72

No files matched your search

+6 -6
View File
@@ -36,7 +36,7 @@ private slots:
void hoverOverRowsAndCtrlKeepTheCursorRight();
void aSpentPressArmsNothingLater();
void aMaskPressReleasedInAPopupIsOver();
void altClicksOnAMaskThroughTheWindowChooseAndToggle();
void altClicksOnAMaskThroughTheWindowShowItAloneAndBack();
void anotherButtonsReleaseLeavesAMaskPress();
};
@@ -260,7 +260,7 @@ void LayerDragGestureTests::aMaskPressReleasedInAPopupIsOver()
QCOMPARE(startedDrags.back().rows, uuidString(shown.id(0)).toUtf8());
}
void LayerDragGestureTests::altClicksOnAMaskThroughTheWindowChooseAndToggle()
void LayerDragGestureTests::altClicksOnAMaskThroughTheWindowShowItAloneAndBack()
{
const auto session = sessionWithLayers(2);
Shown shown(*session);
@@ -272,11 +272,11 @@ void LayerDragGestureTests::altClicksOnAMaskThroughTheWindowChooseAndToggle()
const QPoint at = mask.mapTo(&shown.list, QPoint(5, 5));
// The window sees the release first; the click still lands.
QTest::mouseClick(shown.list.windowHandle(), Qt::LeftButton, Qt::AltModifier, at);
QVERIFY(session->isMaskSelected());
QVERIFY(session->activeLayer().value().mask.value().isEnabled);
QVERIFY(session->isMaskSelected() && session->viewsMaskAlone());
// Again, the image; Shift no longer toggles the mask.
QTest::mouseClick(shown.list.windowHandle(), Qt::LeftButton, Qt::AltModifier | Qt::ShiftModifier, at);
QVERIFY(session->isMaskSelected());
QVERIFY(!session->activeLayer().value().mask.value().isEnabled);
QVERIFY(session->isMaskSelected() && !session->viewsMaskAlone());
QVERIFY(session->activeLayer().value().mask.value().isEnabled);
QCOMPARE(startedDrags.size(), size_t(0));
}
+5 -5
View File
@@ -62,7 +62,7 @@ private slots:
void tabAndTheBrushKeysStayWithTheList();
void aSliderDragReleasedOverAPopupStillEnds();
void anotherButtonsReleaseLeavesTheSwipe();
void altDoubleClickOnTheStripClipsTwiceAndAltShiftTogglesAMask();
void altDoubleClickOnTheStripClipsTwiceAndAltShowsAMaskAlone();
};
void LayerListFocusTests::aMenuBorrowingFocusKeepsTheRenameAndTheField()
@@ -321,7 +321,7 @@ void LayerListFocusTests::anotherButtonsReleaseLeavesTheSwipe()
QVERIFY(session->canUndo());
}
void LayerListFocusTests::altDoubleClickOnTheStripClipsTwiceAndAltShiftTogglesAMask()
void LayerListFocusTests::altDoubleClickOnTheStripClipsTwiceAndAltShowsAMaskAlone()
{
const auto session = sessionWithLayers(2);
NativeLayerList list(*session);
@@ -339,13 +339,13 @@ void LayerListFocusTests::altDoubleClickOnTheStripClipsTwiceAndAltShiftTogglesAM
session->undo();
QCOMPARE(layerWith(*session, top.layerID()).maskSourceID, std::optional(list.cells().at(1)->layerID()));
session->redo();
// Alt+Shift on a mask chooses it and toggles it.
// Alt on a mask shows it alone; Shift adds nothing.
session->selectLayers({top.layerID()}, top.layerID());
session->addLayerMask();
session->selectLayerTarget(top.layerID(), false);
QTest::mouseClick(&top.maskThumbnail(), Qt::LeftButton, Qt::AltModifier | Qt::ShiftModifier, QPoint(5, 5));
QVERIFY(session->isMaskSelected());
QVERIFY(!session->activeLayer().value().mask.value().isEnabled);
QVERIFY(session->isMaskSelected() && session->viewsMaskAlone());
QVERIFY(session->activeLayer().value().mask.value().isEnabled);
}
void LayerListFocusTests::tabAndTheBrushKeysStayWithTheList()
+25 -11
View File
@@ -188,22 +188,36 @@ void LayersPanelTests::theMaskButtonUsesTheSelection()
panel.show();
QVERIFY(QTest::qWaitForWindowExposed(&panel));
auto &mask = find<QToolButton>(panel, "addLayerMask");
QCOMPARE(mask.toolTip(), QString("Add layer mask"));
QCOMPARE(mask.toolTip(), QString("Add layer mask (Alt-click for a black mask)"));
// Alt-click: all black without a selection.
QTest::mouseClick(&mask, Qt::LeftButton, Qt::AltModifier);
QCOMPARE(session.history.undoName(), QString("Add Hide-All Mask"));
session.undo();
QTest::mouseClick(&mask, Qt::LeftButton);
QCOMPARE(session.history.undoName(), QString("Add Reveal-All Mask"));
session.undo();
QPainterPath box;
box.addRect(QRectF(20, 10, 30, 20));
session.applySelection(box, SelectionMode::replace, "Select");
QCOMPARE(mask.toolTip(), QString("Add layer mask (the selection becomes black)"));
QCOMPARE(mask.toolTip(), QString("Add layer mask revealing the selection (Alt-click to hide it)"));
// The button reveals the selection, as Photoshop's.
QTest::mouseClick(&mask, Qt::LeftButton);
QCOMPARE(session.history.undoName(), QString("Add Mask from Selection"));
QCOMPARE(session.history.undoName(), QString("Reveal Selection"));
QVERIFY(!session.selection().has_value() && session.isMaskSelected() && !mask.isEnabled());
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(20)[30]), 0);
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(5)[5]), 255);
// The row's menu: black shows only the selection.
session.undo();
QVERIFY(session.selection().has_value());
LayerCell &cell = *panel.list().cells().at(0);
panel.list().menuFor(cell, QPoint(cell.width() - 20, 20))->findChild<QAction *>("addBlackMask")->trigger();
QCOMPARE(session.history.undoName(), QString("Add Mask from Selection"));
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(20)[30]), 255);
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(5)[5]), 0);
// Alt-click hides it instead.
session.undo();
QVERIFY(session.selection().has_value());
QTest::mouseClick(&mask, Qt::LeftButton, Qt::AltModifier);
QCOMPARE(session.history.undoName(), QString("Hide Selection"));
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(20)[30]), 0);
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(5)[5]), 255);
// The row's Hide All hides the selection too.
session.undo();
LayerCell &cell = *panel.list().cells().at(0);
panel.list().menuFor(cell, QPoint(cell.width() - 20, 20))->findChild<QAction *>("addBlackMask")->trigger();
QCOMPARE(session.history.undoName(), QString("Hide Selection"));
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(20)[30]), 0);
QCOMPARE(int(session.activeLayer().value().mask.value().asset.image().constScanLine(5)[5]), 255);
}
+298
View File
@@ -0,0 +1,298 @@
#include "BrushFixtures.h"
#include "CanvasFixtures.h"
#include "ContentView.h"
#include "UI/LayerMaskMenu.h"
#include "UI/NativeLayerList.h"
#include <QLabel>
#include <QToolButton>
// Swift's MaskAloneTests: Alt-click shows a mask by itself.
class MaskAloneTests : public QObject {
Q_OBJECT
private slots:
void optionClickTogglesTheMaskViewAndTargetingPixelsEndsIt();
void aLayerWithoutAMaskHasNothingToShow();
void anotherLayerOrAnEndedViewShowsTheImage();
void theCanvasShowsTheMaskInGrayInsteadOfTheImage();
void aStrokeOrAGradientShowsInTheMaskAsItIsLaid();
void aPlacedOrDisabledMaskShowsAlone();
void theBadgeNamesTheMaskAndTakesItsOwnClicks();
void altClickOnTheThumbnailTogglesItAndOutlinesItWhite();
};
namespace {
// A red layer, 200 by 100, its right half masked.
QUuid masked(EditorSession &session)
{
session.insert(filled(200, 100, qRgba(255, 0, 0, 255), QStringLiteral("Red")));
const QUuid id = session.activeLayerID().value();
QImage mask(200, 100, QImage::Format_Grayscale8);
mask.fill(0);
for (int y = 0; y < 100; ++y)
std::fill_n(mask.scanLine(y), 100, uchar(255));
rewrite(session, [&](ProjectSnapshot &snapshot) {
record(snapshot, id).transform = LayerTransform{.origin = {0, 0}, .size = {200, 100}};
setMask(snapshot, id, LayerMask::assetFrom(mask));
});
return id;
}
std::unique_ptr<EditorSession> maskedSession(QUuid &id)
{
auto session = std::make_unique<EditorSession>();
session->createDocument(200, 100);
id = masked(*session);
return session;
}
// The canvas at 1:1: view points are document pixels.
struct Canvas : Shown {
QUuid id;
Canvas() : Shown(QSize(200, 100), QSize(400, 300))
{
settle();
id = masked(session);
session.zoom(1);
canvas->synchronizeDisplay();
}
QColor at(QPointF point)
{
canvas->synchronizeDisplay();
const QPointF view = session.viewport.viewPoint(point, documentSize());
return canvas->grab().toImage().pixelColor(view.toPoint());
}
};
bool red(const QColor &colour)
{
return colour.red() > 204 && colour.blue() < 102 && colour.green() < 102;
}
bool gray(const QColor &colour, int value)
{
return std::abs(colour.red() - value) <= 2 && colour.red() == colour.green() && colour.green() == colour.blue();
}
}
void MaskAloneTests::optionClickTogglesTheMaskViewAndTargetingPixelsEndsIt()
{
QUuid id;
const auto session = maskedSession(id);
session->toggleMaskAlone(id);
QVERIFY(session->maskAloneLayer().value().id == id && session->isMaskSelected() && session->viewsMaskAlone());
session->toggleMaskAlone(id);
QVERIFY(!session->maskAloneLayer() && session->isMaskSelected());
session->toggleMaskAlone(id);
session->selectLayerTarget(id, false);
QVERIFY(!session->maskAloneLayer() && !session->viewsMaskAlone());
session->toggleMaskAlone(id);
session->deleteLayerMask();
QVERIFY(!session->maskAloneLayer() && !session->viewsMaskAlone());
session->undo();
QVERIFY(!session->maskAloneLayer());
}
void MaskAloneTests::aLayerWithoutAMaskHasNothingToShow()
{
QUuid id;
const auto session = maskedSession(id);
session->deleteLayerMask();
session->toggleMaskAlone(id);
QVERIFY(!session->maskAloneLayer() && !session->isMaskSelected() && !session->viewsMaskAlone());
// A busy project leaves the target, so the view too.
session->addLayerMask();
session->selectLayerTarget(id, false);
session->setIsProjectBusy(true);
session->toggleMaskAlone(id);
QVERIFY(!session->viewsMaskAlone());
// Busy, another layer's targeted mask stays unshown too.
session->setIsProjectBusy(false);
session->addBlankLayer();
const QUuid other = session->activeLayerID().value();
session->addLayerMask();
QVERIFY(session->isMaskSelected());
session->setIsProjectBusy(true);
session->toggleMaskAlone(id);
QVERIFY(session->activeLayerID() == other && !session->viewsMaskAlone());
}
void MaskAloneTests::anotherLayerOrAnEndedViewShowsTheImage()
{
QUuid id;
const auto session = maskedSession(id);
session->addBlankLayer();
const QUuid other = session->activeLayerID().value();
session->toggleMaskAlone(id);
QVERIFY(session->maskAloneLayer().has_value());
// Another layer targets its pixels: the image again.
session->selectLayer(other);
QVERIFY(!session->maskAloneLayer() && !session->viewsMaskAlone());
// Alt-clicking another layer's mask shows that one.
session->addLayerMask(false);
session->toggleMaskAlone(id);
session->toggleMaskAlone(other);
QCOMPARE(session->maskAloneLayer().value().id, other);
// The badge's button: each change is announced.
QSignalSpy changed(session.get(), &EditorSession::changed);
session->setViewsMaskAlone(false);
QVERIFY(!session->maskAloneLayer() && session->isMaskSelected());
QCOMPARE(changed.count(), 1);
session->setViewsMaskAlone(false);
QCOMPARE(changed.count(), 1);
}
void MaskAloneTests::theCanvasShowsTheMaskInGrayInsteadOfTheImage()
{
Canvas shown;
QVERIFY(red(shown.at(QPointF(50, 50))));
shown.session.toggleMaskAlone(shown.id);
QVERIFY(shown.canvas->synchronizeDisplay());
QVERIFY(gray(shown.at(QPointF(50, 50)), 255));
QVERIFY(gray(shown.at(QPointF(150, 50)), 0));
shown.session.toggleMaskAlone(shown.id);
QVERIFY(shown.canvas->synchronizeDisplay());
QVERIFY(red(shown.at(QPointF(50, 50))));
// At a crisp zoom too, past the halving.
shown.session.toggleMaskAlone(shown.id);
shown.session.zoom(8);
QVERIFY(gray(shown.at(QPointF(80.5, 50.5)), 255));
QVERIFY(gray(shown.at(QPointF(120.5, 50.5)), 0));
}
void MaskAloneTests::aStrokeOrAGradientShowsInTheMaskAsItIsLaid()
{
Canvas shown;
EditorSession &session = shown.session;
session.toggleMaskAlone(shown.id);
session.selectTool(NavigationTool::brush);
session.setMaskPaintWhite(false);
session.setBrushSettings(brush(20, 1, 0, 0, 0));
session.beginBrush(QPointF(50, 50));
session.continueBrush(QPointF(60, 50));
QVERIFY(gray(shown.at(QPointF(55, 50)), 0));
QVERIFY(gray(shown.at(QPointF(55, 80)), 255));
QVERIFY(session.finishBrushImmediately());
QVERIFY(gray(shown.at(QPointF(55, 50)), 0));
QVERIFY(session.maskAloneLayer().has_value());
// A gradient shows as it is dragged, off its line.
session.selectTool(NavigationTool::gradient);
session.setMaskPaintWhite(true);
session.beginGradient(QPointF(0, 50));
session.moveGradient(std::nullopt, QPointF(200, 50));
QVERIFY(session.gradientEdit().has_value());
const QColor left = shown.at(QPointF(10, 80)), right = shown.at(QPointF(190, 80));
// Before it, the right half was black.
QVERIFY(gray(right, right.red()) && right.red() > 6 && right.red() < 128);
// What shows is what the gradient commits.
bool done = false;
session.commitGradient([&] { done = true; });
QTRY_VERIFY(done);
const QImage committed = session.activeLayer().value().mask.value().asset.image();
QVERIFY(gray(left, committed.pixelColor(10, 80).red()) && gray(right, committed.pixelColor(190, 80).red()));
QVERIFY(gray(shown.at(QPointF(10, 80)), left.red()));
}
void MaskAloneTests::aPlacedOrDisabledMaskShowsAlone()
{
Canvas shown;
EditorSession &session = shown.session;
// Turned off, the composite ignores it; alone, it shows.
session.selectLayerTarget(shown.id, true);
session.toggleLayerMask();
QVERIFY(red(shown.at(QPointF(150, 50))));
session.toggleMaskAlone(shown.id);
QVERIFY(gray(shown.at(QPointF(150, 50)), 0));
QVERIFY(gray(shown.at(QPointF(50, 50)), 255));
// Placed apart, at its placement; its edge tone past it.
rewrite(session, [&](ProjectSnapshot &snapshot) {
record(snapshot, shown.id).maskLinked = false;
record(snapshot, shown.id).maskPlacement = LayerTransform{.origin = {50, 0}, .size = {100, 50}};
});
session.toggleMaskAlone(shown.id);
QVERIFY(session.maskAloneLayer().has_value());
QVERIFY(gray(shown.at(QPointF(60, 20)), 255));
QVERIFY(gray(shown.at(QPointF(140, 20)), 0));
const int edge = qRound(LayerMask::background(session.activeLayer().value().mask.value().asset.thumbnail) * 255);
QVERIFY(gray(shown.at(QPointF(20, 80)), edge));
QVERIFY(edge > 0 && gray(shown.at(QPointF(120, 70)), edge));
}
void MaskAloneTests::theBadgeNamesTheMaskAndTakesItsOwnClicks()
{
EditorSession session;
ContentView view(session);
view.resize(900, 600);
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
session.createDocument(200, 100);
const QUuid id = masked(session);
QVERIFY(!view.findChild<MaskAloneBadge *>()->isVisible());
session.toggleMaskAlone(id);
MaskAloneBadge *badge = nullptr;
QTRY_VERIFY((badge = view.findChild<MaskAloneBadge *>()) && badge->isVisible());
QCOMPARE(badge->findChild<QLabel *>("maskAloneTitle")->text(), QString("Layer Mask"));
QCOMPARE(badge->findChild<QLabel *>("maskAloneName")->text(), QString("Red"));
QCOMPARE(badge->height(), 26);
QCOMPARE(badge->layout()->contentsMargins(), QMargins(11, 0, 8, 0));
QCOMPARE(badge->layout()->spacing(), 7);
// At the canvas's foot, 14 points up, in the middle.
auto *canvas = view.findChild<CanvasView *>();
const QRect onView(badge->mapTo(&view, QPoint(0, 0)), badge->size()), canvasOnView(canvas->mapTo(&view, QPoint(0, 0)), canvas->size());
QCOMPARE(canvasOnView.bottom() - onView.bottom(), 14);
QVERIFY(std::abs(onView.center().x() - canvasOnView.center().x()) <= 1);
// A long name ends in an ellipsis within 220 points.
rewrite(session, [&](ProjectSnapshot &snapshot) { record(snapshot, id).name = QString(80, QLatin1Char('W')); });
session.toggleMaskAlone(id);
QTRY_VERIFY((badge = view.findChild<MaskAloneBadge *>()) && badge->isVisible());
QLabel &name = *badge->findChild<QLabel *>("maskAloneName");
QVERIFY(name.width() <= 220 && name.text().endsWith(QChar(0x2026)));
// Its button, not the canvas under it, takes the click.
auto &close = *badge->findChild<QToolButton *>("maskAloneClose");
QCOMPARE(close.toolTip(), QString("Show the image again (or Alt-click the mask thumbnail)"));
QCOMPARE(close.accessibleName(), QString("Stop viewing the mask"));
const QPoint middle = close.mapTo(view.window(), close.rect().center());
QCOMPARE(view.window()->childAt(middle), &close);
QTest::mouseClick(view.window()->windowHandle(), Qt::LeftButton, Qt::NoModifier, middle);
QVERIFY(!session.maskAloneLayer() && session.isMaskSelected());
QTRY_VERIFY(!badge->isVisible());
// The canvas under the badge's foot still takes clicks.
session.toggleMaskAlone(id);
QTRY_VERIFY((badge = view.findChild<MaskAloneBadge *>()) && badge->isVisible());
const QPoint below = badge->mapTo(view.window(), QPoint(badge->width() / 2, badge->height() + 7));
QCOMPARE(view.window()->childAt(below), canvas);
}
void MaskAloneTests::altClickOnTheThumbnailTogglesItAndOutlinesItWhite()
{
EditorSession session;
NativeLayerList list(session);
list.resize(300, 400);
list.show();
QVERIFY(QTest::qWaitForWindowExposed(&list));
session.createDocument(200, 100);
const QUuid id = masked(session);
LayerCell &cell = *list.cells().at(0);
auto *thumbnail = cell.findChild<LayerThumbnailButton *>("maskThumbnail");
QVERIFY(thumbnail);
QCOMPARE(thumbnail->toolTip(), QString("Select layer mask; Alt-click to view it alone; Shift-click to enable/disable; Ctrl-click to select its black "
"areas (Ctrl-Shift adds, Ctrl-Alt subtracts)"));
session.selectLayerTarget(id, false);
const QPoint middle = thumbnail->rect().center();
QTest::mouseClick(thumbnail, Qt::LeftButton, Qt::AltModifier, middle);
QCOMPARE(session.maskAloneLayer().value().id, id);
// Outlined in white rather than the accent.
const QColor ring = thumbnail->grab().toImage().pixelColor(1, middle.y());
QCOMPARE(ring, QColor(Qt::white));
// Released off the thumbnail, the press does nothing.
QTest::mousePress(thumbnail, Qt::LeftButton, Qt::AltModifier, middle);
QTest::mouseRelease(thumbnail, Qt::LeftButton, Qt::AltModifier, QPoint(-40, middle.y()));
QCOMPARE(session.maskAloneLayer().value().id, id);
// Shift no longer toggles the mask on an Alt-click.
QTest::mouseClick(thumbnail, Qt::LeftButton, Qt::AltModifier | Qt::ShiftModifier, middle);
QVERIFY(!session.maskAloneLayer() && session.activeLayer().value().mask.value().isEnabled);
const QColor accent = thumbnail->grab().toImage().pixelColor(1, middle.y());
QCOMPARE(accent, thumbnail->palette().color(QPalette::Highlight));
}
QTEST_MAIN(MaskAloneTests)
#include "MaskAloneTests.moc"
+1 -1
View File
@@ -469,7 +469,7 @@ void NativeLayerListTests::ctrlClicksOnThumbnailsLoadSelections()
Shown shown(session);
LayerCell &row = shown.row(0);
QCOMPARE(row.thumbnail().toolTip(), QString("Select image pixels"));
QCOMPARE(row.maskThumbnail().toolTip(), QString("Select layer mask; Shift-click to enable/disable; Ctrl-click to select its black areas (Ctrl-Shift adds, Ctrl-Alt subtracts)"));
QCOMPARE(row.maskThumbnail().toolTip(), QString("Select layer mask; Alt-click to view it alone; Shift-click to enable/disable; Ctrl-click to select its black areas (Ctrl-Shift adds, Ctrl-Alt subtracts)"));
// Ctrl on the picture: its opaque pixels, the row untouched.
QTest::mouseClick(&row.thumbnail(), Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(session.history.undoName(), QString("Load Layer Selection"));
+15 -15
View File
@@ -55,8 +55,8 @@ void invert(EditorSession &session)
class SelectionEditTests : public QObject {
Q_OBJECT
private slots:
void maskButtonAddsWhiteMaskOrHidesTheSelection();
void layerMenuMasksUseTheSelection();
void maskButtonAddsWhiteMaskOrRevealsTheSelection();
void hidingMasksUseTheSelection();
void maskFromSelectionLinesUpOnScaledLayers();
void deletingWithTheMaskTargetedRemovesOnlyTheMask();
void invertKeepsTransparencyStaysInSelectionAndWorksOnMasks();
@@ -67,7 +67,7 @@ private slots:
void aPaintedAssetThatCannotFlattenReportsAndFreesTheProject();
};
void SelectionEditTests::maskButtonAddsWhiteMaskOrHidesTheSelection()
void SelectionEditTests::maskButtonAddsWhiteMaskOrRevealsTheSelection()
{
const auto session = editSession();
session->insert(filledRed());
@@ -79,12 +79,12 @@ void SelectionEditTests::maskButtonAddsWhiteMaskOrHidesTheSelection()
QVERIFY(!session->activeLayer().value().mask.has_value());
select(*session, QRectF(20, 10, 30, 20));
session->addMask();
QCOMPARE(session->history.undoName(), QString("Add Mask from Selection"));
QCOMPARE(session->history.undoName(), QString("Reveal Selection"));
QVERIFY(!session->selection().has_value() && session->isMaskSelected());
const QImage result = render(*session);
// The selected area is black, hidden; the rest white.
QCOMPARE(pixel(result, 30, 20)[3], 0);
QCOMPARE(pixel(result, 5, 5)[3], 255);
// The selected area is white, visible; the rest black.
QCOMPARE(pixel(result, 30, 20)[3], 255);
QCOMPARE(pixel(result, 5, 5)[3], 0);
session->undo();
QVERIFY(!session->activeLayer().value().mask.has_value() && session->selection().has_value());
// A layer with a mask takes no second one.
@@ -95,18 +95,18 @@ void SelectionEditTests::maskButtonAddsWhiteMaskOrHidesTheSelection()
QCOMPARE(session->history.undoCount(), count + 1);
}
void SelectionEditTests::layerMenuMasksUseTheSelection()
void SelectionEditTests::hidingMasksUseTheSelection()
{
const auto session = editSession();
session->insert(filledRed());
select(*session, QRectF(20, 10, 30, 20));
session->addMask(false);
QCOMPARE(session->history.undoName(), QString("Add Mask from Selection"));
QCOMPARE(session->history.undoName(), QString("Hide Selection"));
QVERIFY(!session->selection().has_value() && session->isMaskSelected());
const QImage result = render(*session);
// The selected area is white, visible; the rest black.
QCOMPARE(pixel(result, 30, 20)[3], 255);
QCOMPARE(pixel(result, 5, 5)[3], 0);
// The selected area is black, hidden; the rest white.
QCOMPARE(pixel(result, 30, 20)[3], 0);
QCOMPARE(pixel(result, 5, 5)[3], 255);
session->undo();
QVERIFY(!session->activeLayer().value().mask.has_value() && session->selection().has_value());
session->deselect();
@@ -128,7 +128,7 @@ void SelectionEditTests::maskFromSelectionLinesUpOnScaledLayers()
record(snapshot, id).transform.size = QSizeF(100, 100);
});
select(*session, QRectF(0, 0, 50, 50));
session->addMask();
session->addMask(false);
// The mask uses the layer's pixel grid.
QCOMPARE(session->activeLayer().value().mask.value().asset.size(), QSize(50, 50));
const QImage result = render(*session);
@@ -139,7 +139,7 @@ void SelectionEditTests::maskFromSelectionLinesUpOnScaledLayers()
session->undo();
session->addBlankLayer();
select(*session, QRectF(10, 10, 20, 20));
session->addMask();
session->addMask(false);
QCOMPARE(session->activeLayer().value().mask.value().asset.size(), QSize(100, 100));
QCOMPARE(int(session->activeLayer().value().mask.value().asset.image().constScanLine(15)[15]), 0);
QCOMPARE(int(session->activeLayer().value().mask.value().asset.image().constScanLine(5)[5]), 255);
@@ -153,7 +153,7 @@ void SelectionEditTests::maskFromSelectionLinesUpOnScaledLayers()
session->undo();
session->setSelectionAntialiased(soft);
session->applySelection(triangle, SelectionMode::replace, "Select");
session->addMask();
session->addMask(false);
const QImage edge = session->activeLayer().value().mask.value().asset.image();
int partial = 0;
for (int x = 0; x < 100; ++x) {
+1
View File
@@ -135,6 +135,7 @@ compositor_test(LiveMaskDeleteTests)
compositor_test(LiveMaskEditTests)
compositor_test(LiveMaskSessionTests)
compositor_test(LiveMaskTests)
compositor_test(MaskAloneTests)
compositor_test(MaskPlacementTests)
compositor_test(MaskSignalTests)
compositor_test(MaskTracingTests)