Magic tool Object mode (Compositor 1.1.8)

This commit is contained in:
雷电芽衣 committed 2026-09-26 23:24:35 -04:00
1 parent 78bfa2445d
commit 3d56688ce4
39 files changed
+1193 -134

No files matched your search

+2 -1
View File
@@ -46,11 +46,12 @@ struct SessionJobs {
std::optional<QImage> image;
std::optional<QString> failure;
};
// What a wand click waits for and applies.
// The Magic tool's job, either mode, and its step's name.
struct Wanding {
SelectionMode mode;
QUuid documentID;
std::function<void()> done;
QString name;
};
struct Wanded {
std::optional<QPainterPath> path;
+1 -1
View File
@@ -96,7 +96,7 @@ QString label(NavigationTool tool)
case NavigationTool::move: return QStringLiteral("Move / Transform (V)");
case NavigationTool::marquee: return QStringLiteral("Marquee (M)");
case NavigationTool::lasso: return QStringLiteral("Lasso (L)");
case NavigationTool::wand: return QStringLiteral("Magic Wand (W)");
case NavigationTool::wand: return QStringLiteral("Magic (W) · Tab switches Wand and Object");
case NavigationTool::crop: return QStringLiteral("Crop (C)");
case NavigationTool::brush: return QStringLiteral("Brush (B) · Eraser (E)");
case NavigationTool::spotHealing: return QStringLiteral("Spot Healing Brush (J)");
+2
View File
@@ -165,6 +165,8 @@ void EditorSession::cycleToolMode()
return;
if (m_tool == NavigationTool::marquee)
toggleMarqueeKind();
else if (m_tool == NavigationTool::wand)
setWandMode(allWandModes[(size_t(m_wandMode) + 1) % allWandModes.size()]);
else if (m_tool == NavigationTool::lasso)
toggleLassoKind();
else if (m_tool == NavigationTool::shape)
+4
View File
@@ -8,6 +8,7 @@
#include "Document/LevelsAutomatic.h"
#include "Document/FloatingSelection.h"
#include "Document/MagicWand.h"
#include "Document/ObjectSelection.h"
#include "Document/SelectionClipboard.h"
#include "Document/SelectionEdits.h"
#include "Document/SmudgeLiquify.h"
@@ -298,6 +299,7 @@ public:
#include "Document/AdjustmentEditing+Session.h"
#include "Document/LayerEffects+Session.h"
#include "Document/Guides+Session.h"
#include "Document/ObjectSelection+Session.h"
signals:
void changed();
@@ -376,6 +378,8 @@ private:
std::optional<Inverting> m_inverting;
QFutureWatcher<Inverted> m_inverter;
WandSettings m_wandSettings;
WandMode m_wandMode = WandMode::wand;
ObjectSelectionSettings m_objectSelectionSettings;
std::optional<Wanding> m_wanding;
QFutureWatcher<Wanded> m_wand;
std::optional<Subjecting> m_subjecting;
+3 -1
View File
@@ -3,6 +3,8 @@ public:
// Off the UI thread; `done` follows the step or refusal.
void magicWand(QPointF point, SelectionMode mode, std::function<void()> done = {});
// What selection-from-image tools read, at document size.
std::optional<QImage> selectionSample(const CanvasDocument &document, bool sampleAllLayers) const;
private:
void finishWand();
std::optional<QImage> wandSample(const CanvasDocument &document) const;
+8 -8
View File
@@ -97,13 +97,13 @@ void EditorSession::setWandSettings(const WandSettings &settings)
}
// What the wand reads: composite, or the active layer's pixels.
std::optional<QImage> EditorSession::wandSample(const CanvasDocument &document) const
std::optional<QImage> EditorSession::selectionSample(const CanvasDocument &document, bool sampleAllLayers) const
{
try {
QImage context = BrushRaster::context(document.width, document.height, false);
QPainter painter(&context);
// A painted asset flattens here; the catch covers it too.
if (m_wandSettings.sampleAllLayers) {
if (sampleAllLayers) {
drawLiveComposite(document, painter);
} else if (const std::optional<ImageLayer> layer = activeLayer(); layer && !layer->isGroup && layer->asset) {
const LayerTransform transform = displayedTransform(*layer);
@@ -112,7 +112,7 @@ std::optional<QImage> EditorSession::wandSample(const CanvasDocument &document)
painter.end();
return context;
} catch (const ExportError &error) {
qCWarning(lcApp) << "the wand cannot sample the canvas:" << error.what();
qCWarning(lcApp) << "a selection tool cannot sample the canvas:" << error.what();
return std::nullopt;
}
}
@@ -131,13 +131,13 @@ void EditorSession::magicWand(QPointF point, SelectionMode mode, std::function<v
finish();
return;
}
const std::optional<QImage> sample = wandSample(*document);
const std::optional<QImage> sample = selectionSample(*document, m_wandSettings.sampleAllLayers);
if (!sample) {
finish();
return;
}
setIsProjectBusy(true);
m_wanding = Wanding{mode, document->id, std::move(done)};
m_wanding = Wanding{mode, document->id, std::move(done), QStringLiteral("Magic Wand")};
m_wand.setFuture(QtConcurrent::run([sample = *sample, point, settings = m_wandSettings]() -> Wanded {
try {
return Wanded{MagicWand::select(sample, point, settings), std::nullopt};
@@ -155,7 +155,7 @@ void EditorSession::finishWand()
const Wanding pending = std::exchange(m_wanding, std::nullopt).value();
setIsProjectBusy(false);
if (result.failure) {
qCWarning(lcApp).noquote() << "the wand could not select:" << *result.failure;
qCWarning(lcApp).noquote() << pending.name << "could not select:" << *result.failure;
setBrushError(result.failure);
} else if (m_document && m_document->id == pending.documentID) {
if (!result.path) {
@@ -164,9 +164,9 @@ void EditorSession::finishWand()
deselect();
} else if (pending.mode == SelectionMode::replace) {
// A traced outline lies on the canvas: no costly clip.
setSelection(DocumentSelection{*result.path, m_selectionAntialiased}, QStringLiteral("Magic Wand"));
setSelection(DocumentSelection{*result.path, m_selectionAntialiased}, pending.name);
} else {
applySelection(*result.path, pending.mode, QStringLiteral("Magic Wand"));
applySelection(*result.path, pending.mode, pending.name);
}
}
if (pending.done)
+8
View File
@@ -0,0 +1,8 @@
// Swift's ObjectSelection extension, and the Magic tool's mode.
public:
WandMode wandMode() const { return m_wandMode; }
void setWandMode(WandMode mode);
const ObjectSelectionSettings &objectSelectionSettings() const { return m_objectSelectionSettings; }
void setObjectSelectionSettings(const ObjectSelectionSettings &settings);
// Off the UI thread; `done` follows the step or refusal.
void selectObject(QPointF point, SelectionMode mode, std::function<void()> done = {});
+272
View File
@@ -0,0 +1,272 @@
#include "Document/ObjectSelection.h"
#include "Document/GuidedMatte.h"
#include "Document/EditorSession.h"
#include "Document/MagicWand.h"
#include "Document/SubjectRemoval.h"
#include "IO/ImageExporter.h"
#include <algorithm>
#include <cmath>
#include <deque>
#include <QtConcurrent>
#include <limits>
namespace {
double perpendicularDistance(QPointF point, QPointF a, QPointF b)
{
const double dx = b.x() - a.x(), dy = b.y() - a.y();
const double length = std::hypot(dx, dy);
if (!(length > 0))
return std::hypot(point.x() - a.x(), point.y() - a.y());
return std::abs(dy * point.x() - dx * point.y() + b.x() * a.y() - b.y() * a.x()) / length;
}
// Swift's Douglas–Peucker on an open polyline.
std::vector<QPointF> simplifyOpen(const std::vector<QPointF> &points, size_t first, size_t last, double tolerance)
{
if (last <= first + 1)
return {points[first], points[last]};
size_t farthest = first + 1;
double greatest = 0;
for (size_t index = first + 1; index < last; ++index) {
const double distance = perpendicularDistance(points[index], points[first], points[last]);
if (distance > greatest) {
greatest = distance;
farthest = index;
}
}
if (!(greatest > tolerance))
return {points[first], points[last]};
std::vector<QPointF> left = simplifyOpen(points, first, farthest, tolerance);
const std::vector<QPointF> right = simplifyOpen(points, farthest, last, tolerance);
left.pop_back();
left.insert(left.end(), right.begin(), right.end());
return left;
}
std::vector<QPointF> simplifyClosed(const std::vector<QPointF> &points, double tolerance)
{
if (points.size() < 4)
return points;
// Break at a stable extreme so the whole contour survives.
const size_t start = size_t(std::min_element(points.begin(), points.end(), [](QPointF lhs, QPointF rhs) {
return lhs.x() == rhs.x() ? lhs.y() < rhs.y() : lhs.x() < rhs.x();
}) - points.begin());
std::vector<QPointF> open(points.begin() + std::ptrdiff_t(start), points.end());
open.insert(open.end(), points.begin(), points.begin() + std::ptrdiff_t(start));
open.push_back(open.front());
open = simplifyOpen(open, 0, open.size() - 1, tolerance);
// Its ends are the same point.
open.pop_back();
return open.size() >= 3 ? open : points;
}
std::vector<QPointF> chaikin(std::vector<QPointF> points, int iterations)
{
for (int round = 0; round < iterations; ++round) {
std::vector<QPointF> next;
next.reserve(points.size() * 2);
for (size_t index = 0; index < points.size(); ++index) {
const QPointF a = points[index], b = points[(index + 1) % points.size()];
next.push_back(a * 0.75 + b * 0.25);
next.push_back(a * 0.25 + b * 0.75);
}
points = std::move(next);
}
return points;
}
bool any(const std::vector<uchar> &mask, int width, int height, int x, int y, bool set)
{
for (int ny = std::max(0, y - 1); ny <= std::min(height - 1, y + 1); ++ny) {
for (int nx = std::max(0, x - 1); nx <= std::min(width - 1, x + 1); ++nx) {
if ((mask[size_t(ny) * size_t(width) + size_t(nx)] != 0) == set)
return true;
}
}
return false;
}
std::optional<QPainterPath> traced(const QImage &image, QPointF point, int edgeOffset, bool smoothEdges)
{
const int width = image.width(), height = image.height();
const int x = int(std::floor(point.x())), y = int(std::floor(point.y()));
if (!std::isfinite(point.x()) || !std::isfinite(point.y()) || x < 0 || x >= width || y < 0 || y >= height)
return std::nullopt;
QImage foreground;
try {
foreground = SubjectRemoval::foreground(image);
} catch (const SubjectRemovalError &error) {
// No subject anywhere: Vision finds no instance.
if (error.kind == SubjectRemovalError::Kind::noSubject)
return std::nullopt;
throw;
}
using ObjectSelection::grid;
// Regions on the model's grid stand for Vision's instances.
const QImage cells = foreground.scaled(grid, grid, Qt::IgnoreAspectRatio, Qt::SmoothTransformation).convertToFormat(QImage::Format_Grayscale8);
if (cells.isNull())
throw ExportError(ExportError::Kind::render);
std::vector<uchar> binary(size_t(grid) * grid);
for (int row = 0; row < grid; ++row) {
for (int column = 0; column < grid; ++column)
binary[size_t(row) * grid + size_t(column)] = cells.constScanLine(row)[column] >= 128;
}
const int gx = std::clamp(int(point.x() / width * grid), 0, grid - 1), gy = std::clamp(int(point.y() / height * grid), 0, grid - 1);
const std::optional<std::vector<uchar>> coarse = ObjectSelection::region(binary, grid, grid, gx, gy);
if (!coarse)
return std::nullopt;
QImage small(grid, grid, QImage::Format_Grayscale8);
if (small.isNull())
throw ExportError(ExportError::Kind::render);
for (int row = 0; row < grid; ++row) {
for (int column = 0; column < grid; ++column)
small.scanLine(row)[column] = (*coarse)[size_t(row) * grid + size_t(column)] ? 255 : 0;
}
// Swift's edge-preserving upsampling: the image guides the edge.
const QImage up = small.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const QImage refined = GuidedMatte::refine(up, image, 5, std::numeric_limits<double>::max());
std::vector<uchar> mask(size_t(width) * size_t(height));
for (int row = 0; row < height; ++row) {
const uchar *line = refined.constScanLine(row);
for (int column = 0; column < width; ++column)
mask[size_t(row) * size_t(width) + size_t(column)] = line[column] >= 128 ? 255 : 0;
}
mask = ObjectSelection::adjusted(std::move(mask), width, height, edgeOffset);
const std::optional<QPainterPath> outline = MagicWand::outline(mask, width, height);
if (!outline)
return std::nullopt;
return smoothEdges ? ObjectSelection::smoothed(*outline) : *outline;
}
}
std::optional<QPainterPath> ObjectSelection::select(const QImage &image, QPointF point, int edgeOffset, bool smoothEdges)
{
try {
return traced(image, point, edgeOffset, smoothEdges);
} catch (const std::bad_alloc &) {
// Masks the size of the image found no memory.
throw ExportError(ExportError::Kind::render);
}
}
std::optional<std::vector<uchar>> ObjectSelection::region(const std::vector<uchar> &mask, int width, int height, int x, int y)
{
const auto at = [width](int column, int row) { return size_t(row) * size_t(width) + size_t(column); };
if (!mask[at(x, y)])
return std::nullopt;
std::vector<uchar> result(mask.size());
std::deque<std::pair<int, int>> pending{{x, y}};
// Eight neighbours join a region, the cell itself included.
while (!pending.empty()) {
const auto [column, row] = pending.front();
pending.pop_front();
for (int ny = std::max(0, row - 1); ny <= std::min(height - 1, row + 1); ++ny) {
for (int nx = std::max(0, column - 1); nx <= std::min(width - 1, column + 1); ++nx) {
if (mask[at(nx, ny)] && !result[at(nx, ny)]) {
result[at(nx, ny)] = 1;
pending.emplace_back(nx, ny);
}
}
}
}
return result;
}
std::vector<uchar> ObjectSelection::adjusted(std::vector<uchar> mask, int width, int height, int edgeOffset)
{
const int steps = std::min(10, std::abs(edgeOffset));
for (int step = 0; step < steps; ++step) {
std::vector<uchar> result = mask;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
const size_t index = size_t(y) * size_t(width) + size_t(x);
if (edgeOffset > 0 && mask[index])
result[index] = any(mask, width, height, x, y, false) ? 0 : 255;
else if (edgeOffset < 0 && !mask[index] && any(mask, width, height, x, y, true))
result[index] = 255;
}
}
mask = std::move(result);
}
return mask;
}
QPainterPath ObjectSelection::smoothed(const QPainterPath &path)
{
std::vector<std::vector<QPointF>> subpaths;
std::vector<QPointF> current;
const auto finish = [&] {
// Qt's close repeats the start, where a CGPath's adds none.
if (!current.empty() && current.front() == current.back())
current.pop_back();
if (current.size() >= 3)
subpaths.push_back(current);
};
for (int index = 0; index < path.elementCount(); ++index) {
const QPainterPath::Element element = path.elementAt(index);
if (element.isMoveTo()) {
finish();
current = {element};
} else if (element.isLineTo()) {
current.push_back(element);
} else if (element.type == QPainterPath::CurveToDataElement && (index + 1 == path.elementCount() || path.elementAt(index + 1).type != QPainterPath::CurveToDataElement)) {
// A curve's end point, as Swift keeps it.
current.push_back(element);
}
}
finish();
QPainterPath result;
result.setFillRule(path.fillRule());
for (const std::vector<QPointF> &subpath : subpaths) {
const std::vector<QPointF> points = chaikin(simplifyClosed(subpath, 1.6), 3);
result.moveTo(points.front());
for (size_t index = 1; index < points.size(); ++index)
result.lineTo(points[index]);
result.closeSubpath();
}
return result;
}
void EditorSession::setWandMode(WandMode mode)
{
m_wandMode = mode;
notify();
}
void EditorSession::setObjectSelectionSettings(const ObjectSelectionSettings &settings)
{
m_objectSelectionSettings = settings;
notify();
}
void EditorSession::selectObject(QPointF point, SelectionMode mode, std::function<void()> done)
{
// The caller resumes from the event loop, as after await.
const auto finish = [this, done] {
if (done)
QMetaObject::invokeMethod(this, done, Qt::QueuedConnection);
};
const std::optional<CanvasDocument> &document = m_document;
// Swift's positive bounds refuse a point that is no number.
const bool inside = document && point.x() >= 0 && point.y() >= 0 && point.x() < document->width && point.y() < document->height;
const std::optional<QImage> sample = canEditSelection() && !m_selectionMoveOrigin && inside
? selectionSample(*document, m_objectSelectionSettings.sampleAllLayers)
: std::nullopt;
if (!sample) {
finish();
return;
}
setIsProjectBusy(true);
m_wanding = Wanding{mode, document->id, std::move(done), QStringLiteral("Object Selection")};
m_wand.setFuture(QtConcurrent::run([sample = *sample, point, offset = m_objectSelectionSettings.edgeOffset, smooth = m_selectionAntialiased]() -> Wanded {
try {
return Wanded{ObjectSelection::select(sample, point, offset, smooth), std::nullopt};
} catch (const SubjectRemovalError &error) {
return Wanded{std::nullopt, QString::fromUtf8(error.what())};
} catch (const MagicWandError &error) {
return Wanded{std::nullopt, QString::fromUtf8(error.what())};
} catch (const ExportError &error) {
return Wanded{std::nullopt, QString::fromUtf8(error.what())};
}
}));
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <QImage>
#include <QPainterPath>
#include <optional>
struct ObjectSelectionSettings {
// Read the visible composite rather than the active layer.
bool sampleAllLayers = true;
// Positive erodes the mask inward; negative grows it outward.
int edgeOffset = 0;
friend bool operator==(const ObjectSelectionSettings &, const ObjectSelectionSettings &) = default;
};
// Swift's ObjectSelection; U²-Net regions stand for Vision's instances.
namespace ObjectSelection {
// The model's grid the regions are found on.
inline constexpr int grid = 320;
// The outline in pixels; none off the image or background.
std::optional<QPainterPath> select(const QImage &image, QPointF point, int edgeOffset, bool smoothEdges);
// The binary grid's region holding the cell, else none.
std::optional<std::vector<uchar>> region(const std::vector<uchar> &mask, int width, int height, int x, int y);
// Swift's steps, a pixel ring each: erode inward, dilate outward.
std::vector<uchar> adjusted(std::vector<uchar> mask, int width, int height, int edgeOffset);
// Stair steps rounded off; winding and subpath order kept.
QPainterPath smoothed(const QPainterPath &path);
}
+83
View File
@@ -0,0 +1,83 @@
#include "Document/EditorSession.h"
#include <cmath>
// Select's Expand, Contract and Feather, and their amounts.
void EditorSession::setSelectionExpandAmount(int amount)
{
m_selectionExpandAmount = amount;
notify();
}
void EditorSession::setSelectionContractAmount(int amount)
{
m_selectionContractAmount = amount;
notify();
}
void EditorSession::setSelectionFeatherAmount(int amount)
{
m_selectionFeatherAmount = amount;
notify();
}
void EditorSession::setSelectionAmountOperation(std::optional<SelectionAmountOperation> operation)
{
m_selectionAmountOperation = operation;
resumeFileRequests();
notify();
}
bool EditorSession::canModifySelection() const
{
const std::optional<DocumentSelection> current = selection();
return current && !current->isEmpty() && canEditSelection() && !m_lassoDraft;
}
void EditorSession::expandSelection(int amount)
{
resizeSelection(amount, QStringLiteral("Expand Selection"));
}
void EditorSession::contractSelection(int amount)
{
resizeSelection(-amount, QStringLiteral("Contract Selection"));
}
void EditorSession::promptSelectionAmount(SelectionAmountOperation operation)
{
if (!canModifySelection())
return;
setSelectionAmountOperation(operation);
}
void EditorSession::confirmSelectionAmount(int amount)
{
const std::optional<SelectionAmountOperation> operation = m_selectionAmountOperation;
if (!operation || amount < 1 || amount > (*operation == SelectionAmountOperation::feather ? 250 : 500))
return;
setSelectionAmountOperation(std::nullopt);
switch (*operation) {
case SelectionAmountOperation::expand:
setSelectionExpandAmount(amount);
expandSelection(amount);
break;
case SelectionAmountOperation::contract:
setSelectionContractAmount(amount);
contractSelection(amount);
break;
case SelectionAmountOperation::feather:
setSelectionFeatherAmount(amount);
featherSelection(amount);
break;
}
}
void EditorSession::featherSelection(int amount)
{
const std::optional<DocumentSelection> current = selection();
if (!canModifySelection() || !current || amount <= 0)
return;
// Two soft edges spread less than their sum.
const double softened = std::sqrt(current->feather * current->feather + double(amount) * amount);
setSelection(DocumentSelection{current->path, current->antialiased, std::min(250.0, softened)}, QStringLiteral("Feather Selection"));
}
+1
View File
@@ -16,6 +16,7 @@ public:
void cancelLasso();
void pressMarqueeKey();
void pressLassoKey();
void pressWandKey();
void toggleMarqueeKind();
void toggleLassoKind();
// Closes the outline and combines it with the selection.
+11 -80
View File
@@ -54,6 +54,11 @@ QString rawValue(SelectionAmountOperation operation)
throw std::logic_error("unknown selection amount operation");
}
QString rawValue(WandMode mode)
{
return mode == WandMode::object ? QStringLiteral("Object") : QStringLiteral("Wand");
}
QString rawValue(SelectionMode mode)
{
switch (mode) {
@@ -173,31 +178,6 @@ void EditorSession::setSelectionAntialiased(bool antialiased)
notify();
}
void EditorSession::setSelectionExpandAmount(int amount)
{
m_selectionExpandAmount = amount;
notify();
}
void EditorSession::setSelectionContractAmount(int amount)
{
m_selectionContractAmount = amount;
notify();
}
void EditorSession::setSelectionFeatherAmount(int amount)
{
m_selectionFeatherAmount = amount;
notify();
}
void EditorSession::setSelectionAmountOperation(std::optional<SelectionAmountOperation> operation)
{
m_selectionAmountOperation = operation;
resumeFileRequests();
notify();
}
void EditorSession::beginLasso(QPointF point, SelectionMode mode)
{
// The Magic Wand selects with a click, never an outline.
@@ -266,6 +246,12 @@ void EditorSession::pressMarqueeKey()
selectTool(NavigationTool::marquee);
}
// W picks the Magic tool; Tab switches its two modes.
void EditorSession::pressWandKey()
{
selectTool(NavigationTool::wand);
}
void EditorSession::pressLassoKey()
{
selectTool(NavigationTool::lasso);
@@ -400,61 +386,6 @@ void EditorSession::nudgeSelection(double dx, double dy)
endSelectionMove();
}
bool EditorSession::canModifySelection() const
{
const std::optional<DocumentSelection> current = selection();
return current && !current->isEmpty() && canEditSelection() && !m_lassoDraft;
}
void EditorSession::expandSelection(int amount)
{
resizeSelection(amount, QStringLiteral("Expand Selection"));
}
void EditorSession::contractSelection(int amount)
{
resizeSelection(-amount, QStringLiteral("Contract Selection"));
}
void EditorSession::promptSelectionAmount(SelectionAmountOperation operation)
{
if (!canModifySelection())
return;
setSelectionAmountOperation(operation);
}
void EditorSession::confirmSelectionAmount(int amount)
{
const std::optional<SelectionAmountOperation> operation = m_selectionAmountOperation;
if (!operation || amount < 1 || amount > (*operation == SelectionAmountOperation::feather ? 250 : 500))
return;
setSelectionAmountOperation(std::nullopt);
switch (*operation) {
case SelectionAmountOperation::expand:
setSelectionExpandAmount(amount);
expandSelection(amount);
break;
case SelectionAmountOperation::contract:
setSelectionContractAmount(amount);
contractSelection(amount);
break;
case SelectionAmountOperation::feather:
setSelectionFeatherAmount(amount);
featherSelection(amount);
break;
}
}
void EditorSession::featherSelection(int amount)
{
const std::optional<DocumentSelection> current = selection();
if (!canModifySelection() || !current || amount <= 0)
return;
// Two soft edges spread less than their sum.
const double softened = std::sqrt(current->feather * current->feather + double(amount) * amount);
setSelection(DocumentSelection{current->path, current->antialiased, std::min(250.0, softened)}, QStringLiteral("Feather Selection"));
}
void EditorSession::resizeSelection(double delta, const QString &name)
{
const std::optional<DocumentSelection> current = selection();
+6
View File
@@ -4,6 +4,7 @@
#include <QPointF>
#include <QRectF>
#include <QString>
#include <array>
#include <optional>
#include <vector>
@@ -31,6 +32,11 @@ struct DocumentSelection {
friend bool operator==(const DocumentSelection &, const DocumentSelection &) = default;
};
// The Magic tool's modes: similar colours, or the object clicked.
enum class WandMode { wand, object };
inline constexpr std::array allWandModes{WandMode::wand, WandMode::object};
QString rawValue(WandMode mode);
// The Marquee's outlines are rectangle and ellipse.
enum class LassoKind { freehand, polygonal, rectangle, ellipse };
// The name the tool bar shows.
+5
View File
@@ -222,6 +222,11 @@ QImage SubjectRemoval::subjectMask(const QImage &image, const std::optional<QIma
return combined;
}
QImage SubjectRemoval::foreground(const QImage &image)
{
return vision(image);
}
QImage SubjectRemoval::run(const QImage &image, const FilterSettings &settings)
{
// The preview refines a copy 1400 long at most: quick.
+2
View File
@@ -23,6 +23,8 @@ std::vector<float> modelInput(const QImage &image);
QImage modelMask(const float *prediction, QSize size);
// White over the subject in the layer's grid, times `existing`.
QImage subjectMask(const QImage &image, const std::optional<QImage> &existing, const FilterSettings &settings);
// The model's mask at the image's size, cached by image.
QImage foreground(const QImage &image);
// The preview: the layer with its background cleared.
QImage run(const QImage &image, const FilterSettings &settings);
}