#include "Document/EditorSession.h" #include "Document/LayerGroups.h" #include "Document/PixelAdjust.h" #include "IO/ImageExporter.h" #include "Logging.h" #include // Swift's AdjustmentEditing.swift: pixels beneath feed histogram and sampling. void EditorSession::beginAdjustmentEditing(QUuid id, std::function done) { const int index = m_document ? indexOf(m_document->layers, id) : -1; if (m_adjustmentEditingID != id || m_adjustmentOriginal || m_levels || m_hueSaturation || m_filterEdit || index < 0 || !m_document->layers[size_t(index)].adjustment) { if (done) QMetaObject::invokeMethod(this, done, Qt::QueuedConnection); return; } const LayerAdjustment original = *m_document->layers[size_t(index)].adjustment; ProjectSnapshot source = projectSnapshot().value(); // Records stay for live masks and folders; beneath alone shows. QSet underneath; for (const LayerHierarchy::Entry &entry : LayerHierarchy::entries(source.manifest.layers)) { if (entry.layer.id == id) break; underneath.insert(entry.layer.id); } for (ProjectLayerRecord &layer : source.manifest.layers) { if (!underneath.contains(layer.id)) layer.isVisible = false; } // A watcher per call, as Swift's calls are tasks. auto *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher, id, original, done = std::move(done)] { watcher->deleteLater(); openAdjustmentEditor(id, original, watcher->result()); if (done) QMetaObject::invokeMethod(this, done, Qt::QueuedConnection); }); // A painted layer flattens there, where a failure is caught. watcher->setFuture(QtConcurrent::run([source]() -> AdjustmentInput { try { const QImage image = ImageExporter::render(source).image; return {ImportedImage(image, PixelAdjust::thumbnail(image), QStringLiteral("Adjustment input")), std::nullopt}; } catch (const ExportError &error) { return {std::nullopt, QString::fromUtf8(error.what())}; } })); } void EditorSession::openAdjustmentEditor(QUuid id, const LayerAdjustment &original, const AdjustmentInput &made) { // Swift's guard after the await; an open editor wins. if (m_adjustmentEditingID != id || m_adjustmentOriginal) return; std::optional failure = made.failure; // Beyond Swift: an edit begun meanwhile keeps its editor. if (!failure && (m_levels || m_hueSaturation || m_filterEdit)) { qCWarning(lcApp) << "an adjustment's editor gave way to an edit begun meanwhile"; m_adjustmentEditingID.reset(); resumeFileRequests(); notify(); return; } if (!failure) { try { const ImageLayer layer(made.asset.value(), QPointF(0, 0)); switch (original.kind) { case AdjustmentKind::levels: m_levels.emplace(layer, std::nullopt); m_levels->settings = original.levels; countLevelsHistogram(); break; case AdjustmentKind::hsv: m_hueSaturation.emplace(layer.id, made.asset.value(), std::nullopt, layer.transform); m_hueSaturation->settings = original.resolvedHSV(); break; case AdjustmentKind::invert: throw std::logic_error("Invert has no editor to open"); case AdjustmentKind::curves: case AdjustmentKind::exposure: case AdjustmentKind::gradientMap: case AdjustmentKind::grain: case AdjustmentKind::addNoise: case AdjustmentKind::gaussianBlur: case AdjustmentKind::motionBlur: case AdjustmentKind::blackWhite: case AdjustmentKind::colorBalance: { FilterSettings settings; settings.curves = original.curves; settings.exposure = original.exposure(); settings.gradientMap = original.gradientMap(); settings.grain = original.grain(); settings.blackWhite = original.blackWhite(); settings.colorBalance = original.colorBalance(); settings.radius = original.gaussianRadius(); settings.angle = original.resolvedMotionAngle(); settings.distance = original.resolvedMotionDistance(); settings.amount = original.resolvedNoiseAmount(); settings.gaussian = original.resolvedNoiseGaussian(); settings.monochromatic = original.resolvedNoiseMonochromatic(); m_filterEdit.emplace(filterKind(original.kind).value(), layer, std::nullopt, settings, std::nullopt); break; } } } catch (const ExportError &error) { failure = QString::fromUtf8(error.what()); } } if (failure) { m_adjustmentEditingID.reset(); resumeFileRequests(); setBrushError(failure); return; } m_adjustmentOriginal = original; beginEdit(QStringLiteral("Edit %1 Adjustment").arg(rawValue(original.kind))); } // The kind's edit lives as long as the original does. LayerAdjustment EditorSession::editedAdjustment() const { LayerAdjustment value = m_adjustmentOriginal.value(); switch (value.kind) { case AdjustmentKind::levels: value.levels = m_levels.value().settings; break; case AdjustmentKind::hsv: value.hsvSettings = m_hueSaturation.value().settings; break; case AdjustmentKind::curves: value.curves = m_filterEdit.value().settings.curves; break; case AdjustmentKind::exposure: value.setExposure(m_filterEdit.value().settings.exposure); break; case AdjustmentKind::gradientMap: value.setGradientMap(m_filterEdit.value().settings.gradientMap); break; case AdjustmentKind::grain: value.setGrain(m_filterEdit.value().settings.grain); break; case AdjustmentKind::blackWhite: value.setBlackWhite(m_filterEdit.value().settings.blackWhite); break; case AdjustmentKind::colorBalance: value.setColorBalance(m_filterEdit.value().settings.colorBalance); break; case AdjustmentKind::gaussianBlur: value.setGaussianRadius(m_filterEdit.value().settings.radius); break; case AdjustmentKind::motionBlur: value.setResolvedMotionAngle(m_filterEdit.value().settings.angle); value.setResolvedMotionDistance(m_filterEdit.value().settings.distance); break; case AdjustmentKind::addNoise: value.setResolvedNoiseAmount(m_filterEdit.value().settings.amount); value.setResolvedNoiseGaussian(m_filterEdit.value().settings.gaussian); value.setResolvedNoiseMonochromatic(m_filterEdit.value().settings.monochromatic); break; case AdjustmentKind::invert: throw std::logic_error("Invert has no editor to read"); } return value; } bool EditorSession::previewAdjustmentEditing(bool preview) { if (!m_adjustmentEditingID || !m_adjustmentOriginal) return false; updateAdjustment(*m_adjustmentEditingID, preview ? editedAdjustment() : *m_adjustmentOriginal); return true; } bool EditorSession::finishAdjustmentEditing(bool commit) { if (!m_adjustmentEditingID || !m_adjustmentOriginal) return false; updateAdjustment(*m_adjustmentEditingID, commit ? editedAdjustment() : *m_adjustmentOriginal); m_hueSampleMode.reset(); m_hueTargeting = false; m_hueTargetDrag.reset(); m_levels.reset(); m_hueSaturation.reset(); m_filterEdit.reset(); endEdit(); m_adjustmentOriginal.reset(); m_adjustmentEditingID.reset(); resumeFileRequests(); ++m_canvasFocusRequest; ++m_brushRevision; notify(); return true; }