Render-path containers survive running out of memory: std::map for QHash (12.8e)
This commit is contained in:
1 parent
56af936a7b
commit
bb8129e8d5
8 files changed
+210
-48
No files matched your search
@@ -6,10 +6,10 @@
|
||||
#include "IO/ImageExporter.h"
|
||||
#include "Logging.h"
|
||||
#include "Rendering/DownsampleCache.h"
|
||||
#include <QHash>
|
||||
#include "IO/ProjectStore.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
LayerMask::LayerMask(ImportedImage asset, bool isEnabled, std::optional<LayerTransform> placement, bool isLinked)
|
||||
@@ -220,12 +220,12 @@ void FolderMaskClip::draw(const std::vector<QUuid> &ids, const std::function<std
|
||||
const std::function<void(QUuid, const QImage &clip)> &drawLayer)
|
||||
{
|
||||
// Coverage through a folder and its ancestors; null for none.
|
||||
QHash<QUuid, QImage> covered;
|
||||
std::map<QUuid, QImage> covered;
|
||||
std::function<QImage(std::optional<QUuid>, int)> through = [&](std::optional<QUuid> folder, int depth) -> QImage {
|
||||
if (!folder || depth >= 64)
|
||||
return QImage();
|
||||
if (const auto found = covered.constFind(*folder); found != covered.constEnd())
|
||||
return *found;
|
||||
if (const auto found = covered.find(*folder); found != covered.end())
|
||||
return found->second;
|
||||
QImage coverage = through(parent(*folder), depth + 1);
|
||||
if (const std::optional<Applier> applier = clip(*folder)) {
|
||||
if (coverage.isNull()) {
|
||||
@@ -236,7 +236,7 @@ void FolderMaskClip::draw(const std::vector<QUuid> &ids, const std::function<std
|
||||
}
|
||||
(*applier)(context, coverage);
|
||||
}
|
||||
covered.insert(*folder, coverage);
|
||||
covered.insert_or_assign(*folder, coverage);
|
||||
return coverage;
|
||||
};
|
||||
for (const QUuid &id : ids)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include "Rendering/AdjustmentSurface.h"
|
||||
#include "Rendering/LayerRenderer.h"
|
||||
#include "Rendering/LiveMaskRenderer.h"
|
||||
#include <QHash>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSet>
|
||||
#include <QtConcurrent>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
void EditorSession::drawLiveComposite(const CanvasDocument &document, QPainter &context, bool onSurface) const
|
||||
@@ -73,27 +73,31 @@ void EditorSession::drawLiveComposite(const CanvasDocument &document, QPainter &
|
||||
|
||||
void LiveMaskGraph::validate(const std::vector<ProjectLayerRecord> &layers)
|
||||
{
|
||||
QHash<QUuid, const ProjectLayerRecord *> records;
|
||||
// Standard containers: a worker's render may run out of memory.
|
||||
std::map<QUuid, const ProjectLayerRecord *> records;
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
if (records.contains(layer.id))
|
||||
if (!records.emplace(layer.id, &layer).second)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
records.insert(layer.id, &layer);
|
||||
}
|
||||
const auto record = [&records](QUuid id) -> const ProjectLayerRecord * {
|
||||
const auto found = records.find(id);
|
||||
return found != records.end() ? found->second : nullptr;
|
||||
};
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
QSet<QUuid> path;
|
||||
std::set<QUuid> path;
|
||||
std::optional<QUuid> current = layer.id;
|
||||
while (current) {
|
||||
const ProjectLayerRecord *record = records.value(*current);
|
||||
if (path.size() >= 256 || path.contains(*current) || !record)
|
||||
const ProjectLayerRecord *found = record(*current);
|
||||
if (path.size() >= 256 || path.contains(*current) || !found)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
path.insert(*current);
|
||||
if (record->maskSourceID) {
|
||||
const ProjectLayerRecord *source = records.value(*record->maskSourceID);
|
||||
if (found->maskSourceID) {
|
||||
const ProjectLayerRecord *source = record(*found->maskSourceID);
|
||||
// Folders never clip; nothing clips to an adjustment.
|
||||
if (record->isGroup == true || !source || source->isGroup == true || source->adjustment)
|
||||
if (found->isGroup == true || !source || source->isGroup == true || source->adjustment)
|
||||
throw ProjectError(ProjectError::Kind::invalid);
|
||||
}
|
||||
current = record->maskSourceID;
|
||||
current = found->maskSourceID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,21 +114,20 @@ std::optional<ImportedImage> LiveMaskBaker::bake(const ProjectSnapshot &snapshot
|
||||
QImage surface = BrushRaster::context(width, height, false);
|
||||
const QTransform inverse = BrushRaster::pixelToDocument(layers[found].transform, width, height).inverted();
|
||||
// Swift's dictionary traps on an id that repeats.
|
||||
QHash<QUuid, const ProjectLayerRecord *> records;
|
||||
std::map<QUuid, const ProjectLayerRecord *> records;
|
||||
for (const ProjectLayerRecord &layer : layers) {
|
||||
if (records.contains(layer.id))
|
||||
if (!records.emplace(layer.id, &layer).second)
|
||||
throw std::logic_error("a snapshot to bake repeats a layer id");
|
||||
records.insert(layer.id, &layer);
|
||||
}
|
||||
// A source dims with its folders, as exported.
|
||||
const auto folded = [&records](const ProjectLayerRecord &layer) {
|
||||
return LayerOpacity::effective(layer.opacity.value_or(1), layer.parentID, [&records](QUuid id) -> std::optional<std::pair<double, std::optional<QUuid>>> {
|
||||
if (!records.contains(id))
|
||||
return std::nullopt;
|
||||
return std::pair(records.value(id)->opacity.value_or(1), records.value(id)->parentID);
|
||||
return std::pair(records.at(id)->opacity.value_or(1), records.at(id)->parentID);
|
||||
});
|
||||
};
|
||||
LiveMaskRenderer live([&](QUuid id) { return records.contains(id) ? records.value(id)->maskSourceID : std::nullopt; },
|
||||
LiveMaskRenderer live([&](QUuid id) { return records.contains(id) ? records.at(id)->maskSourceID : std::nullopt; },
|
||||
[&](QUuid id, QPainter &context, const QImage &clip) {
|
||||
// `at` is checked: end() would read wild memory.
|
||||
if (!records.contains(id) || !snapshot.images.contains(id))
|
||||
@@ -136,7 +139,7 @@ std::optional<ImportedImage> LiveMaskBaker::bake(const ProjectSnapshot &snapshot
|
||||
context, {.clip = clip});
|
||||
return;
|
||||
}
|
||||
const ProjectLayerRecord &layer = *records.value(id);
|
||||
const ProjectLayerRecord &layer = *records.at(id);
|
||||
const std::optional<LayerMask> mask = snapshot.mask(layer);
|
||||
const std::optional<QImage> shown =
|
||||
mask ? mask->clipImage(mask->placement, layer.transform, pixels.width(), pixels.height()) : std::nullopt;
|
||||
|
||||
Reference in new issue
Block a user