Masks painted anywhere on the canvas; mask thumbnails' ground white or black (12.12b)
This commit is contained in:
1 parent
136ee65a48
commit
f335818e53
20 files changed
+348
-68
No files matched your search
@@ -131,8 +131,9 @@ PaintSnapshot BrushStroke::paintSnapshot() const
|
||||
const QRectF placed = rect.translated(tile.rect.topLeft());
|
||||
bounds = bounds ? std::optional(bounds->united(placed)) : std::optional(placed);
|
||||
}
|
||||
const QRectF crop = bounds.value_or(committedBounds());
|
||||
const std::shared_ptr<const RasterSnapshot> raster = RasterSnapshot::replacing(m_source, sourceRect, patches(), crop, isMask);
|
||||
// A mask keeps every tile touched: past its pixels, grows.
|
||||
const QRectF crop = isMask ? committedBounds() : bounds.value_or(committedBounds());
|
||||
const std::shared_ptr<const RasterSnapshot> raster = RasterSnapshot::replacing(m_source, sourceRect, patches(), crop, isMask, maskBackground);
|
||||
return {ImportedImage(raster, raster->thumbnail(), layer.name), transform(crop), crop};
|
||||
}
|
||||
|
||||
@@ -142,7 +143,7 @@ BrushCommit::Input BrushStroke::commitInput() const
|
||||
std::vector<BrushPatch> shifted;
|
||||
for (const BrushPatch &patch : patches())
|
||||
shifted.push_back({patch.rect.translated(-bounds.topLeft()), patch.image});
|
||||
return {int(bounds.width()), int(bounds.height()), m_source, shifted, isMask, layer.name, sourceRect.translated(-bounds.topLeft())};
|
||||
return {int(bounds.width()), int(bounds.height()), m_source, shifted, isMask, layer.name, sourceRect.translated(-bounds.topLeft()), maskBackground};
|
||||
}
|
||||
|
||||
ImportedImage BrushCommit::expandMask(const ImportedImage &asset, const Input &input, const QRectF &crop)
|
||||
@@ -161,6 +162,8 @@ ImportedImage BrushCommit::expandMask(const ImportedImage &asset, const Input &i
|
||||
BrushCommit::Output BrushCommit::render(const Input &input)
|
||||
{
|
||||
QImage context = BrushRaster::context(input.width, input.height, input.mask);
|
||||
if (input.mask)
|
||||
context.fill(qRound(input.fill * 255));
|
||||
QPainter painter(&context);
|
||||
if (input.source)
|
||||
drawSource(*input.source, input.sourceRect, painter);
|
||||
|
||||
@@ -64,20 +64,26 @@ double BrushStroke::spacingFraction(double hardness)
|
||||
return hardness >= 1 ? 0.015 : 0.025;
|
||||
}
|
||||
|
||||
BrushStroke::Grid BrushStroke::grid(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas)
|
||||
BrushStroke::Grid BrushStroke::grid(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas, bool growsMask)
|
||||
{
|
||||
// A placed mask paints in its grid; else the layer's.
|
||||
const bool placedMask = mask && layer.mask && layer.mask->placement;
|
||||
const LayerTransform base = placedMask ? *layer.mask->placement : layer.transform;
|
||||
const int originalWidth = placedMask ? layer.mask->asset.size().width()
|
||||
: layer.asset ? layer.asset->size().width() : int(std::round(layer.size().width()));
|
||||
const int originalHeight = placedMask ? layer.mask->asset.size().height()
|
||||
: layer.asset ? layer.asset->size().height() : int(std::round(layer.size().height()));
|
||||
// A solid placed mask gets a pixel per document pixel.
|
||||
const bool solidPlaced = placedMask && layer.mask->asset.size().width() <= 2 && layer.mask->asset.size().height() <= 2;
|
||||
const int originalWidth = solidPlaced ? std::max(1, int(std::round(base.size.width())))
|
||||
: placedMask ? layer.mask->asset.size().width()
|
||||
: layer.asset ? layer.asset->size().width()
|
||||
: int(std::round(layer.size().width()));
|
||||
const int originalHeight = solidPlaced ? std::max(1, int(std::round(base.size.height())))
|
||||
: placedMask ? layer.mask->asset.size().height()
|
||||
: layer.asset ? layer.asset->size().height()
|
||||
: int(std::round(layer.size().height()));
|
||||
if (originalWidth < 1 || originalWidth > DocumentLimits::maxSide || originalHeight < 1 || originalHeight > DocumentLimits::maxSide)
|
||||
throw ProjectError(ProjectError::Kind::tooLarge);
|
||||
const QTransform originalMapping = BrushRaster::pixelToDocument(base, originalWidth, originalHeight);
|
||||
const QRectF originalBounds(0, 0, originalWidth, originalHeight);
|
||||
const QRectF extent = mask ? originalBounds : originalBounds.united(integral(originalMapping.inverted().mapRect(canvas)));
|
||||
const QRectF extent = mask && !growsMask ? originalBounds : originalBounds.united(integral(originalMapping.inverted().mapRect(canvas)));
|
||||
// Judged as a double: past int the conversion is undefined.
|
||||
if (extent.width() < 1 || extent.width() > 1'000'000'000 || extent.height() < 1 || extent.height() > 1'000'000'000)
|
||||
throw ProjectError(ProjectError::Kind::tooLarge);
|
||||
@@ -93,14 +99,15 @@ BrushStroke::Grid BrushStroke::grid(const ImageLayer &layer, bool mask, const Br
|
||||
return grid;
|
||||
}
|
||||
|
||||
BrushStroke::BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, QSizeF canvas)
|
||||
: BrushStroke(layer, mask, settings, QRectF(QPointF(0, 0), canvas), grid(layer, mask, settings, QRectF(QPointF(0, 0), canvas)))
|
||||
BrushStroke::BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, QSizeF canvas, bool growsMask)
|
||||
: BrushStroke(layer, mask, settings, QRectF(QPointF(0, 0), canvas), grid(layer, mask, settings, QRectF(QPointF(0, 0), canvas), growsMask))
|
||||
{
|
||||
}
|
||||
|
||||
BrushStroke::BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas, const Grid &grid)
|
||||
: layer(layer), isMask(mask), settings(settings), canvas(canvas), width(grid.width), height(grid.height), sourceRect(grid.sourceRect),
|
||||
pixelToDocument(grid.pixelToDocument), paintTransform(grid.paintTransform),
|
||||
maskBackground(mask && layer.mask ? LayerMask::background(layer.mask->asset.thumbnail) : 1),
|
||||
m_source(mask ? (layer.mask ? std::optional(layer.mask->asset) : std::nullopt) : layer.asset),
|
||||
m_paintColor(mask ? QColor::fromRgbF(settings.red, settings.red, settings.red) : QColor::fromRgbF(settings.red, settings.green, settings.blue))
|
||||
{
|
||||
@@ -306,6 +313,9 @@ void BrushStroke::allocateTile(qint64 key, qint64 x, qint64 y)
|
||||
throw ProjectError(ProjectError::Kind::tooLarge);
|
||||
m_allocatedBounds = nextBounds;
|
||||
QImage context = BrushRaster::context(int(rect.width()), int(rect.height()), isMask);
|
||||
// Past the mask's pixels a tile starts as its background.
|
||||
if (isMask)
|
||||
context.fill(qRound(maskBackground * 255));
|
||||
QPainter painter(&context);
|
||||
const QRectF initialRect = sourceRect.translated(-rect.topLeft());
|
||||
if (m_source && m_source->raster) {
|
||||
|
||||
@@ -75,6 +75,8 @@ struct Input {
|
||||
bool mask;
|
||||
QString name;
|
||||
QRectF sourceRect;
|
||||
// A grown mask where no old pixel or edit reaches.
|
||||
double fill = 1;
|
||||
};
|
||||
struct Output {
|
||||
ImportedImage asset;
|
||||
@@ -87,7 +89,8 @@ Output render(const Input &input);
|
||||
// Only touched 256 px tiles hold pixels; snapshots copy those.
|
||||
class BrushStroke {
|
||||
public:
|
||||
BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, QSizeF canvas);
|
||||
// `growsMask`: the brush and fills reach the canvas on masks.
|
||||
BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, QSizeF canvas, bool growsMask = false);
|
||||
|
||||
const ImageLayer layer;
|
||||
const bool isMask;
|
||||
@@ -98,6 +101,8 @@ public:
|
||||
const QRectF sourceRect;
|
||||
const QTransform pixelToDocument;
|
||||
const LayerTransform paintTransform;
|
||||
// A mask past its pixels: 1 reveals, 0 hides.
|
||||
const double maskBackground;
|
||||
// Swift's Int: the budget left may run below zero.
|
||||
qint64 pixelLimit = DocumentLimits::documentPixelBudget();
|
||||
// Limits every edit to the document selection; nil when none.
|
||||
@@ -163,7 +168,7 @@ private:
|
||||
QTransform pixelToDocument;
|
||||
LayerTransform paintTransform;
|
||||
};
|
||||
static Grid grid(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas);
|
||||
static Grid grid(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas, bool growsMask);
|
||||
BrushStroke(const ImageLayer &layer, bool mask, const BrushSettings &settings, const QRectF &canvas, const Grid &grid);
|
||||
std::vector<BrushSegment> continuousCurve(QPointF start, QPointF end, QPointF before, QPointF after) const;
|
||||
std::set<qint64> continuousKeys(const std::vector<BrushSegment> &segments) const;
|
||||
|
||||
@@ -38,11 +38,11 @@ bool EditorSession::canPaint() const
|
||||
}
|
||||
|
||||
// A tiled raster edit of pixels or mask, within budget.
|
||||
std::unique_ptr<BrushStroke> EditorSession::makeRasterEdit(const ImageLayer &layer, const BrushSettings &settings) const
|
||||
std::unique_ptr<BrushStroke> EditorSession::makeRasterEdit(const ImageLayer &layer, const BrushSettings &settings, bool growsMask) const
|
||||
{
|
||||
if (!m_document)
|
||||
throw ProjectError(ProjectError::Kind::tooLarge);
|
||||
auto stroke = std::make_unique<BrushStroke>(layer, m_isMaskSelected, settings, m_document->size());
|
||||
auto stroke = std::make_unique<BrushStroke>(layer, m_isMaskSelected, settings, m_document->size(), growsMask);
|
||||
qint64 used = 0;
|
||||
for (const ImageLayer &other : m_document->layers) {
|
||||
if (other.id == layer.id)
|
||||
@@ -108,7 +108,7 @@ void EditorSession::beginBrush(QPointF point)
|
||||
settings.green = settings.red;
|
||||
settings.blue = settings.red;
|
||||
}
|
||||
m_brushStroke = makeRasterEdit(layer, settings);
|
||||
m_brushStroke = makeRasterEdit(layer, settings, m_tool == NavigationTool::brush);
|
||||
m_brushStroke->clone = clone;
|
||||
m_brushStroke->isBlur = m_tool == NavigationTool::blur;
|
||||
m_brushStroke->append(point);
|
||||
@@ -225,6 +225,16 @@ void EditorSession::finishBrush()
|
||||
finishBrushImmediately();
|
||||
}
|
||||
|
||||
// Grown past its layer, or placed already: keeps its place.
|
||||
LayerMask EditorSession::placedMask(const LayerMask &mask, const ImportedImage &asset, const QRectF &bounds, const LayerTransform &transform,
|
||||
const BrushStroke &stroke)
|
||||
{
|
||||
LayerMask painted = mask.replacing(asset);
|
||||
if (mask.placement || bounds != stroke.sourceRect)
|
||||
painted.placement = transform;
|
||||
return painted;
|
||||
}
|
||||
|
||||
// Immutable tiles land at once, undo entry included.
|
||||
void EditorSession::commitPaintSnapshot(const BrushStroke &stroke)
|
||||
{
|
||||
@@ -249,7 +259,7 @@ void EditorSession::commitPaintSnapshot(const BrushStroke &stroke)
|
||||
: QStringLiteral("Brush Stroke")));
|
||||
ImageLayer &layer = m_document->layers[index];
|
||||
if (stroke.isMask) {
|
||||
layer.mask = current.mask ? current.mask->replacing(result.asset) : LayerMask(result.asset);
|
||||
layer.mask = current.mask ? placedMask(*current.mask, result.asset, result.bounds, result.transform, stroke) : LayerMask(result.asset);
|
||||
} else {
|
||||
layer.asset = result.asset;
|
||||
layer.transform = result.transform;
|
||||
@@ -327,7 +337,8 @@ void EditorSession::finishRasterCommit()
|
||||
beginEdit(pending.name);
|
||||
ImageLayer &layer = m_document->layers[index];
|
||||
if (stroke.isMask) {
|
||||
layer.mask = current.mask ? current.mask->replacing(result.asset) : LayerMask(result.asset);
|
||||
const QRectF bounds = result.pixelBounds.translated(stroke.committedBounds().topLeft());
|
||||
layer.mask = current.mask ? placedMask(*current.mask, result.asset, bounds, transform, stroke) : LayerMask(result.asset);
|
||||
} else {
|
||||
layer.asset = result.asset;
|
||||
layer.transform = transform;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Swift's EditorSession+Brush extension: a stroke from press to commit.
|
||||
public:
|
||||
bool canPaint() const;
|
||||
std::unique_ptr<BrushStroke> makeRasterEdit(const ImageLayer &layer, const BrushSettings &settings = BrushSettings()) const;
|
||||
std::unique_ptr<BrushStroke> makeRasterEdit(const ImageLayer &layer, const BrushSettings &settings = BrushSettings(), bool growsMask = false) const;
|
||||
void beginBrush(QPointF point);
|
||||
void continueBrush(QPointF point);
|
||||
// Where a Shift-click's line starts; nil on another target.
|
||||
@@ -22,5 +22,8 @@ public:
|
||||
|
||||
private:
|
||||
void finishRasterCommit();
|
||||
// A mask painted: grown or placed, it keeps its place.
|
||||
static LayerMask placedMask(const LayerMask &mask, const ImportedImage &asset, const QRectF &bounds, const LayerTransform &transform,
|
||||
const BrushStroke &stroke);
|
||||
// Where Smoothing lets the brush go; none while slack.
|
||||
std::optional<QPointF> smoothed(QPointF point);
|
||||
@@ -48,7 +48,7 @@ void EditorSession::beginGradient(QPointF point)
|
||||
return;
|
||||
finishOpacityEdit();
|
||||
try {
|
||||
m_gradientEdit = GradientEdit{std::shared_ptr<BrushStroke>(makeRasterEdit(*layer)), point, point};
|
||||
m_gradientEdit = GradientEdit{std::shared_ptr<BrushStroke>(makeRasterEdit(*layer, BrushSettings(), true)), point, point};
|
||||
++m_brushRevision;
|
||||
notify();
|
||||
} catch (const ProjectError &error) {
|
||||
|
||||
@@ -86,7 +86,8 @@ void EditorSession::applyPixelEdit(const ImageLayer &layer, const QString &name,
|
||||
{
|
||||
finishOpacityEdit();
|
||||
try {
|
||||
std::unique_ptr<BrushStroke> edit = makeRasterEdit(layer);
|
||||
// A fill on a mask covers the canvas too.
|
||||
std::unique_ptr<BrushStroke> edit = makeRasterEdit(layer, BrushSettings(), true);
|
||||
paint(*edit);
|
||||
if (edit->patches().empty()) {
|
||||
if (done)
|
||||
|
||||
@@ -42,8 +42,8 @@ void CanvasView::drawStroke(const BrushStroke &stroke, const ImageLayer &layer,
|
||||
// A placed mask: the layer draws through its own grid.
|
||||
if (!layer.asset)
|
||||
return;
|
||||
const std::optional<QImage> preview = stroke.placedMaskPreview(*stroke.layer.mask->placement);
|
||||
if (preview && drawSurface(placedMaskSurface(layer, stroke, *stroke.layer.mask->placement, *preview)))
|
||||
const std::optional<QImage> preview = stroke.placedMaskPreview(stroke.paintTransform);
|
||||
if (preview && drawSurface(placedMaskSurface(layer, stroke, *preview)))
|
||||
return;
|
||||
LayerRenderer::Options through = options;
|
||||
through.mask = preview.value_or(QImage());
|
||||
@@ -95,7 +95,7 @@ LayerEffectsSurface *CanvasView::strokeSurface(const ImageLayer &layer, const Br
|
||||
}
|
||||
|
||||
// A placed mask paints its grid; the surface, the layer's.
|
||||
LayerEffectsSurface *CanvasView::placedMaskSurface(const ImageLayer &layer, const BrushStroke &stroke, const LayerTransform &placement, const QImage &preview)
|
||||
LayerEffectsSurface *CanvasView::placedMaskSurface(const ImageLayer &layer, const BrushStroke &stroke, const QImage &preview)
|
||||
{
|
||||
const LayerEffects effects = layer.effects.value_or(LayerEffects()).visible();
|
||||
if (effects.isEmpty() || !effects.isValid() || !stroke.layer.asset)
|
||||
@@ -106,7 +106,7 @@ LayerEffectsSurface *CanvasView::placedMaskSurface(const ImageLayer &layer, cons
|
||||
m_strokeSurface = LayerEffectsSurface::make(layer.id, effects, full.size(), full);
|
||||
if (!m_strokeSurface)
|
||||
return nullptr;
|
||||
const QTransform toGrid = BrushRaster::pixelToDocument(placement, stroke.width, stroke.height)
|
||||
const QTransform toGrid = BrushRaster::pixelToDocument(stroke.paintTransform, stroke.width, stroke.height)
|
||||
* BrushRaster::pixelToDocument(stroke.layer.transform, base.width(), base.height()).inverted();
|
||||
m_strokeSurface->update(stroke.layer.asset, {}, std::nullopt, LayerEffectsSurface::MaskStroke{stroke.patches(), toGrid, [preview, full](const QRectF ®ion) {
|
||||
QImage coverage = BrushRaster::context(int(region.width()), int(region.height()), true);
|
||||
|
||||
@@ -342,8 +342,9 @@ void CanvasView::drawLayers(const CanvasDocument &document, double scale, const
|
||||
.mask = mask.value_or(QImage()), .clip = clip});
|
||||
return;
|
||||
}
|
||||
// A pixel stroke draws its grid by its own transform.
|
||||
const LayerTransform transform = stroke && !stroke->isMask ? stroke->paintTransform : m_session.displayedTransform(layer);
|
||||
// A stroke draws its grid, a placed mask's the layer's.
|
||||
const bool placedStroke = stroke && stroke->isMask && stroke->layer.mask && stroke->layer.mask->placement;
|
||||
const LayerTransform transform = stroke && !placedStroke ? stroke->paintTransform : m_session.displayedTransform(layer);
|
||||
const std::optional<LayerTransform> placement = m_session.displayedMaskPlacement(layer);
|
||||
std::optional<QImage> mask;
|
||||
if (const std::optional<QImage> distorted = m_session.maskDistortPreview(layer)) {
|
||||
|
||||
@@ -156,7 +156,7 @@ private:
|
||||
void drawNewText(bool &drawn, double scale, const Center ¢er, QPainter &target, const QImage &clip);
|
||||
void handOnDraftEffects(const CanvasDocument &document);
|
||||
// Swift's effects surface on the canvas (EditorCanvas+Effects.cpp).
|
||||
LayerEffectsSurface *placedMaskSurface(const ImageLayer &layer, const BrushStroke &stroke, const LayerTransform &placement, const QImage &preview);
|
||||
LayerEffectsSurface *placedMaskSurface(const ImageLayer &layer, const BrushStroke &stroke, const QImage &preview);
|
||||
LayerEffectsSurface *strokeSurface(const ImageLayer &layer, const BrushStroke &stroke, const std::optional<QImage> &mask);
|
||||
// The stroke is over: its surface stands in meanwhile.
|
||||
void handOnStrokeSurface();
|
||||
|
||||
@@ -48,16 +48,16 @@ std::vector<BrushPatch> without(const BrushPatch &piece, const QRectF &overlap)
|
||||
}
|
||||
|
||||
RasterSnapshot::RasterSnapshot(int width, int height, QImage base, QRectF baseRect, std::vector<BrushPatch> patches,
|
||||
bool isMask, std::optional<QPointF> alignment)
|
||||
bool isMask, std::optional<QPointF> alignment, double fill)
|
||||
: width(width), height(height), base(std::move(base)), baseRect(baseRect), patches(std::move(patches)),
|
||||
isMask(isMask), alignment(alignment.value_or(baseRect.topLeft()))
|
||||
isMask(isMask), alignment(alignment.value_or(baseRect.topLeft())), fill(fill)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<const RasterSnapshot> RasterSnapshot::replacing(const std::optional<ImportedImage> &source,
|
||||
const QRectF &sourceRect,
|
||||
const std::vector<BrushPatch> &added,
|
||||
const QRectF &crop, bool isMask)
|
||||
const QRectF &crop, bool isMask, double fill)
|
||||
{
|
||||
const std::shared_ptr<const RasterSnapshot> old = source ? source->raster : nullptr;
|
||||
const QPointF shift = sourceRect.topLeft() - crop.topLeft();
|
||||
@@ -107,7 +107,7 @@ std::shared_ptr<const RasterSnapshot> RasterSnapshot::replacing(const std::optio
|
||||
const QRectF baseRect = old ? old->baseRect.translated(shift) : sourceRect.translated(-crop.topLeft());
|
||||
const QPointF alignment = sourceRect.topLeft() + (old ? old->alignment : QPointF(0, 0)) - crop.topLeft();
|
||||
return std::make_shared<const RasterSnapshot>(int(crop.width()), int(crop.height()), base, baseRect,
|
||||
std::move(inside), isMask, alignment);
|
||||
std::move(inside), isMask, alignment, fill);
|
||||
}
|
||||
|
||||
void RasterSnapshot::draw(const QRectF &rect, QPainter &context) const
|
||||
@@ -115,9 +115,9 @@ void RasterSnapshot::draw(const QRectF &rect, QPainter &context) const
|
||||
context.save();
|
||||
context.setClipRect(rect, Qt::IntersectClip);
|
||||
context.setRenderHint(QPainter::Antialiasing, false);
|
||||
// A grown mask reveals the pixels outside it.
|
||||
// Past its pixels a grown mask is its background.
|
||||
if (isMask)
|
||||
context.fillRect(rect, Qt::white);
|
||||
context.fillRect(rect, QColor::fromRgbF(float(fill), float(fill), float(fill)));
|
||||
const double sx = rect.width() / width, sy = rect.height() / height;
|
||||
const auto mapped = [&](const QRectF &r) {
|
||||
return QRectF(rect.left() + r.left() * sx, rect.top() + r.top() * sy, r.width() * sx, r.height() * sy);
|
||||
|
||||
@@ -17,14 +17,16 @@ public:
|
||||
const bool isMask;
|
||||
// Origin of the halving grids, kept across commits.
|
||||
const QPointF alignment;
|
||||
// A mask beyond its base and patches: shown or hidden.
|
||||
const double fill;
|
||||
|
||||
RasterSnapshot(int width, int height, QImage base, QRectF baseRect, std::vector<BrushPatch> patches,
|
||||
bool isMask = false, std::optional<QPointF> alignment = std::nullopt);
|
||||
bool isMask = false, std::optional<QPointF> alignment = std::nullopt, double fill = 1);
|
||||
|
||||
static std::shared_ptr<const RasterSnapshot> replacing(const std::optional<ImportedImage> &source,
|
||||
const QRectF &sourceRect,
|
||||
const std::vector<BrushPatch> &patches,
|
||||
const QRectF &crop, bool isMask = false);
|
||||
const QRectF &crop, bool isMask = false, double fill = 1);
|
||||
|
||||
void draw(const QRectF &rect, QPainter &context) const;
|
||||
QImage makeImage() const;
|
||||
|
||||
@@ -65,27 +65,9 @@ QPixmap CanvasThumbnail::layer(const QImage &image, const LayerTransform &transf
|
||||
QPixmap CanvasThumbnail::mask(const QImage &image, const LayerTransform &transform, QSizeF canvas, double box)
|
||||
{
|
||||
return render(canvas, box, [&](QPainter &painter, QSize size, double scale) {
|
||||
const double tone = edgeTone(image);
|
||||
// White or black, as the canvas treats it, never gray.
|
||||
const double tone = LayerMask::background(image);
|
||||
painter.fillRect(QRect(QPoint(0, 0), size), QColor::fromRgbF(tone, tone, tone));
|
||||
place(image, transform, scale, painter);
|
||||
});
|
||||
}
|
||||
|
||||
double CanvasThumbnail::edgeTone(const QImage &image)
|
||||
{
|
||||
const QImage gray = image.convertToFormat(QImage::Format_Grayscale8);
|
||||
if (gray.width() <= 0 || gray.height() <= 0)
|
||||
return 1;
|
||||
qint64 total = 0, count = 0;
|
||||
for (int y = 0; y < gray.height(); ++y) {
|
||||
const uchar *row = gray.constScanLine(y);
|
||||
const bool edgeRow = y == 0 || y == gray.height() - 1;
|
||||
for (int x = 0; x < gray.width(); ++x) {
|
||||
if (edgeRow || x == 0 || x == gray.width() - 1) {
|
||||
total += row[x];
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count == 0 ? 1 : double(total) / double(count) / 255;
|
||||
}
|
||||
@@ -13,8 +13,6 @@ inline constexpr double backingScale = 2;
|
||||
QSize fittedSize(QSizeF canvas, double box);
|
||||
// The checkerboard with the pixels placed; null: empty canvas.
|
||||
QPixmap layer(const QImage &image, const LayerTransform &transform, QSizeF canvas, double box);
|
||||
// The mask placed; its edge tone fills the rest.
|
||||
// The mask placed; its background fills the rest.
|
||||
QPixmap mask(const QImage &image, const LayerTransform &transform, QSizeF canvas, double box);
|
||||
// The mean gray, 0 to 1, of the outermost pixels.
|
||||
double edgeTone(const QImage &image);
|
||||
}
|
||||
Reference in new issue
Block a user