14.2: RAW import rounds to 8 bits with fine noise, in the kernel; the develop sheet shows Importing… until the layer lands
This commit is contained in:
14 files changed
+470
-65
No files matched your search
@@ -183,8 +183,10 @@ void ContentView::showRawDevelopSheet()
|
||||
shown->reject();
|
||||
return;
|
||||
}
|
||||
if (m_rawSheet)
|
||||
if (m_rawSheet) {
|
||||
m_rawSheet->findChild<RawDevelopSheet *>()->setImporting(m_session.rawImporting());
|
||||
return;
|
||||
}
|
||||
m_rawSheet = new QDialog(this);
|
||||
m_rawSheet->setObjectName(QStringLiteral("rawDevelopSheet"));
|
||||
m_rawSheet->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
@@ -208,13 +208,25 @@ void EditorSession::developRaw(const QString &path, std::function<void(std::opti
|
||||
|
||||
void EditorSession::finishRawDevelop(std::optional<RawDevelopSettings> settings)
|
||||
{
|
||||
// Importing, the sheet waits for the develop; Cancel closes it.
|
||||
if (settings && m_rawAnswer) {
|
||||
m_rawImporting = true;
|
||||
notify();
|
||||
} else {
|
||||
endRawDevelop();
|
||||
}
|
||||
// The import resumes from the event loop, as a task.
|
||||
if (const auto answer = std::exchange(m_rawAnswer, {}))
|
||||
QMetaObject::invokeMethod(this, [answer, settings] { answer(settings); }, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void EditorSession::endRawDevelop()
|
||||
{
|
||||
m_rawImporting = false;
|
||||
m_rawDevelop = std::nullopt;
|
||||
notify();
|
||||
// Frees the preview's decode on a worker, behind any preview.
|
||||
(void)QtConcurrent::run([] { RawImporter::Queue::shared().release(); });
|
||||
// The import resumes from the event loop, as a task.
|
||||
if (const auto answer = std::exchange(m_rawAnswer, {}))
|
||||
QMetaObject::invokeMethod(this, [answer, settings] { answer(settings); }, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void EditorSession::finishDecode()
|
||||
@@ -224,6 +236,9 @@ void EditorSession::finishDecode()
|
||||
insert(*decoded.asset, m_importPoint);
|
||||
else
|
||||
m_importFailures << decoded.failure;
|
||||
// Swift's defer: the develop sheet goes once the frame lands.
|
||||
if (m_rawImporting)
|
||||
endRawDevelop();
|
||||
m_importIndex += 1;
|
||||
decodeNext();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// The import queue: files, Photoshop and RAW.
|
||||
public:
|
||||
// Files decode off the UI thread; `done` follows this request.
|
||||
void importImages(const QList<QUrl> &urls, std::optional<QPointF> point = std::nullopt, std::function<void()> done = {});
|
||||
// The sheet shows while a Photoshop file is read.
|
||||
void beginPSDReading(const QString &title, const QString &confirmTitle);
|
||||
void finishPSDReading(const std::vector<PSDConversion> &conversions, std::function<void(bool)> answer);
|
||||
void endPSDReading();
|
||||
void finishConversion(bool confirmed);
|
||||
void insertPhotoshop(const PSDImport &imported, const QString &named, std::optional<QPointF> centeredAt = std::nullopt);
|
||||
// The RAW file the develop sheet shows, while it shows.
|
||||
const std::optional<RawDevelopRequest> &rawDevelop() const { return m_rawDevelop; }
|
||||
void decodeRaw(const QString &path, qint64 remaining);
|
||||
void developRaw(const QString &path, std::function<void(std::optional<RawDevelopSettings>)> answer);
|
||||
void finishRawDevelop(std::optional<RawDevelopSettings> settings);
|
||||
// Import pressed: the sheet stays until the layer is in.
|
||||
bool rawImporting() const { return m_rawImporting; }
|
||||
// Closes the sheet once the RAW is in, or cancelled.
|
||||
void endRawDevelop();
|
||||
|
||||
private:
|
||||
void drainImports();
|
||||
void decodeNext();
|
||||
qint64 remainingPixels() const;
|
||||
void decode(const QUrl &url, bool flattened);
|
||||
void finishDecode();
|
||||
void finishPhotoshopRead();
|
||||
@@ -74,9 +74,6 @@ public:
|
||||
void setImportError(std::optional<QString> error);
|
||||
bool showsConversionSheet() const { return m_showsConversionSheet; }
|
||||
const std::optional<PSDConversionRequest> &conversionRequest() const { return m_conversionRequest; }
|
||||
// The RAW file the develop sheet shows, while it shows.
|
||||
const std::optional<RawDevelopRequest> &rawDevelop() const { return m_rawDevelop; }
|
||||
void finishRawDevelop(std::optional<RawDevelopSettings> settings);
|
||||
std::optional<QUuid> renamingLayerID() const { return m_renamingLayerID; }
|
||||
void setRenamingLayerID(std::optional<QUuid> id);
|
||||
|
||||
@@ -236,17 +233,7 @@ public:
|
||||
bool canMoveActiveLayer(int offset) const;
|
||||
void moveActiveLayer(int offset);
|
||||
|
||||
// Files decode off the UI thread; `done` follows this request.
|
||||
void importImages(const QList<QUrl> &urls, std::optional<QPointF> point = std::nullopt, std::function<void()> done = {});
|
||||
void insert(const ImportedImage &asset, std::optional<QPointF> centeredAt = std::nullopt);
|
||||
// The sheet shows while a Photoshop file is read.
|
||||
void beginPSDReading(const QString &title, const QString &confirmTitle);
|
||||
void finishPSDReading(const std::vector<PSDConversion> &conversions, std::function<void(bool)> answer);
|
||||
void endPSDReading();
|
||||
void finishConversion(bool confirmed);
|
||||
void decodeRaw(const QString &path, qint64 remaining);
|
||||
void developRaw(const QString &path, std::function<void(std::optional<RawDevelopSettings>)> answer);
|
||||
void insertPhotoshop(const PSDImport &imported, const QString &named, std::optional<QPointF> centeredAt = std::nullopt);
|
||||
void createDocument(int width, int height, bool emptyLayer = false);
|
||||
void fit();
|
||||
void zoom(double value, std::optional<QPointF> anchor = std::nullopt);
|
||||
@@ -280,6 +267,7 @@ public:
|
||||
void notify() { emit changed(); }
|
||||
// Swift's extensions, each declared in its twin's header.
|
||||
#include "Document/EditorSession+Projects.h"
|
||||
#include "Document/EditorSession+Imports.h"
|
||||
#include "IO/ImageResizer+Session.h"
|
||||
#include "Document/LayerMask+Session.h"
|
||||
#include "Document/LayerAppearance+Session.h"
|
||||
@@ -324,12 +312,6 @@ private:
|
||||
void restore(const DocumentHistory::Snapshot &snapshot);
|
||||
void resumeFileRequests();
|
||||
void updateBusyIndicator();
|
||||
void drainImports();
|
||||
void decodeNext();
|
||||
qint64 remainingPixels() const;
|
||||
void decode(const QUrl &url, bool flattened);
|
||||
void finishDecode();
|
||||
void finishPhotoshopRead();
|
||||
void endDuplicateTransform();
|
||||
// Clone Stamp is family 1, the Smear 2, others 0.
|
||||
static int tipFamily(NavigationTool tool);
|
||||
@@ -397,6 +379,7 @@ private:
|
||||
std::function<void(bool)> m_conversionAnswer;
|
||||
std::optional<RawDevelopRequest> m_rawDevelop;
|
||||
std::function<void(std::optional<RawDevelopSettings>)> m_rawAnswer;
|
||||
bool m_rawImporting = false;
|
||||
// Cancel pressed while the file was still read.
|
||||
bool m_conversionCancelled = false;
|
||||
std::optional<QString> m_brushError;
|
||||
|
||||
+13
-5
@@ -11,6 +11,10 @@
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <libraw.h>
|
||||
#include <memory>
|
||||
extern "C" {
|
||||
#include "DitherPixels.h"
|
||||
}
|
||||
|
||||
namespace {
|
||||
[[noreturn]] void refuse(const QString &path, const char *step, int code)
|
||||
@@ -173,21 +177,25 @@ QImage render(LibRaw &raw, const QString &path, const RawDevelopSettings &settin
|
||||
image.setColorSpace(QColorSpace::SRgb);
|
||||
const double gain = std::pow(2.0, double(settings.exposure)) * smallest;
|
||||
const auto *samples = reinterpret_cast<const quint16 *>(made->data);
|
||||
uchar *const bits = image.bits();
|
||||
const qsizetype stride = image.bytesPerLine();
|
||||
// The look at 16 bits, as Core Image renders it.
|
||||
const std::unique_ptr<quint16[]> wide(new (std::nothrow) quint16[size_t(image.width()) * size_t(image.height()) * 4]);
|
||||
if (!wide)
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
std::vector<int> rows(size_t(image.height()));
|
||||
std::iota(rows.begin(), rows.end(), 0);
|
||||
PoolMap::blocking(rows, [&](int y) {
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
uchar *out = bits + y * stride + x * 4;
|
||||
quint16 *out = wide.get() + (qsizetype(y) * image.width() + x) * 4;
|
||||
for (int channel = 0; channel < 3; ++channel) {
|
||||
const double linear = std::min(samples[(qsizetype(y) * image.width() + x) * 3 + channel] / 65535.0 * gain, 1.0);
|
||||
const double flat = srgb(linear);
|
||||
out[channel] = uchar(std::lround(255 * (flat + settings.boost * (bt709(linear) - flat))));
|
||||
out[channel] = quint16(std::lround(65535 * (flat + settings.boost * (bt709(linear) - flat))));
|
||||
}
|
||||
out[3] = 255;
|
||||
out[3] = 65535;
|
||||
}
|
||||
});
|
||||
// To 8 bits with fine noise: gradients don't band.
|
||||
dither_quantize16(wide.get(), image.bits(), size_t(image.width()), size_t(image.height()), size_t(image.bytesPerLine()));
|
||||
if (!limit || std::max(image.width(), image.height()) <= *limit)
|
||||
return image;
|
||||
const QImage fitted = image.scaled(QSize(int(*limit), int(*limit)), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
+80
-21
@@ -5,6 +5,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QFutureWatcher>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
@@ -22,6 +23,8 @@ class RawDevelopSheet::Preview : public QWidget {
|
||||
public:
|
||||
using QWidget::QWidget;
|
||||
QImage image;
|
||||
// Swift's regular material, behind the Importing plate.
|
||||
QWidget *plate = nullptr;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
@@ -31,20 +34,30 @@ protected:
|
||||
QPainterPath plate;
|
||||
plate.addRoundedRect(QRectF(rect()), 6, 6);
|
||||
painter.fillPath(plate, QColor::fromRgbF(0, 0, 0, 0.35f));
|
||||
if (image.isNull())
|
||||
return;
|
||||
QRectF target(QPointF(), QSizeF(image.size()).scaled(QSizeF(size()), Qt::KeepAspectRatio));
|
||||
target.moveCenter(QRectF(rect()).center());
|
||||
QPainterPath clip;
|
||||
clip.addRoundedRect(target, 6, 6);
|
||||
painter.setClipPath(clip);
|
||||
painter.drawImage(target, image);
|
||||
if (!image.isNull()) {
|
||||
QRectF target(QPointF(), QSizeF(image.size()).scaled(QSizeF(size()), Qt::KeepAspectRatio));
|
||||
target.moveCenter(QRectF(rect()).center());
|
||||
QPainterPath clip;
|
||||
clip.addRoundedRect(target, 6, 6);
|
||||
painter.save();
|
||||
painter.setClipPath(clip);
|
||||
painter.drawImage(target, image);
|
||||
painter.restore();
|
||||
}
|
||||
if (this->plate && this->plate->isVisible()) {
|
||||
QColor material = palette().color(QPalette::Window);
|
||||
material.setAlphaF(0.85f);
|
||||
QPainterPath behind;
|
||||
behind.addRoundedRect(QRectF(this->plate->geometry()), 10, 10);
|
||||
painter.fillPath(behind, material);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RawDevelopSheet::RawDevelopSheet(RawDevelopRequest request, std::function<void(std::optional<RawDevelopSettings>)> finish, QWidget *parent)
|
||||
: QWidget(parent), m_path(std::move(request.path)), m_finish(std::move(finish)), m_settings(request.settings), m_wait(new QTimer(this)),
|
||||
m_preview(new Preview(this)), m_spinner(new QProgressBar(m_preview)), m_reset(new QPushButton(QStringLiteral("Reset"), this))
|
||||
m_preview(new Preview(this)), m_spinner(new QProgressBar(m_preview)), m_reset(new QPushButton(QStringLiteral("Reset"), this)),
|
||||
m_cancel(new QPushButton(QStringLiteral("Cancel"), this)), m_import(new QPushButton(QStringLiteral("Import"), this)), m_importing(new QWidget(m_preview))
|
||||
{
|
||||
auto *column = new QVBoxLayout(this);
|
||||
column->setContentsMargins(24, 24, 24, 24);
|
||||
@@ -63,6 +76,23 @@ RawDevelopSheet::RawDevelopSheet(RawDevelopRequest request, std::function<void(s
|
||||
m_spinner->setRange(0, 0);
|
||||
m_spinner->setTextVisible(false);
|
||||
m_spinner->setGeometry(272, 162, 16, 16);
|
||||
// Swift's VStack: a spinner over "Importing…", padded 16.
|
||||
m_importing->setObjectName(QStringLiteral("rawImporting"));
|
||||
auto *importingColumn = new QVBoxLayout(m_importing);
|
||||
importingColumn->setContentsMargins(16, 16, 16, 16);
|
||||
importingColumn->setSpacing(8);
|
||||
auto *importingSpinner = new QProgressBar(m_importing);
|
||||
importingSpinner->setRange(0, 0);
|
||||
importingSpinner->setTextVisible(false);
|
||||
importingSpinner->setFixedSize(32, 32);
|
||||
importingColumn->addWidget(importingSpinner, 0, Qt::AlignHCenter);
|
||||
auto *importingWords = new QLabel(QStringLiteral("Importing…"), m_importing);
|
||||
importingWords->setForegroundRole(QPalette::PlaceholderText);
|
||||
importingColumn->addWidget(importingWords, 0, Qt::AlignHCenter);
|
||||
m_importing->adjustSize();
|
||||
m_importing->move((m_preview->width() - m_importing->width()) / 2, (m_preview->height() - m_importing->height()) / 2);
|
||||
m_importing->hide();
|
||||
m_preview->plate = m_importing;
|
||||
column->addWidget(m_preview);
|
||||
|
||||
// Swift's sliders are continuous; these step finer than shown.
|
||||
@@ -89,18 +119,16 @@ RawDevelopSheet::RawDevelopSheet(RawDevelopRequest request, std::function<void(s
|
||||
m_settings.reset();
|
||||
changed();
|
||||
});
|
||||
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
|
||||
cancel->setObjectName(QStringLiteral("rawCancel"));
|
||||
cancel->setAutoDefault(false);
|
||||
connect(cancel, &QPushButton::clicked, this, [this] { m_finish(std::nullopt); });
|
||||
auto *importButton = new QPushButton(QStringLiteral("Import"), this);
|
||||
importButton->setObjectName(QStringLiteral("rawImport"));
|
||||
importButton->setDefault(true);
|
||||
connect(importButton, &QPushButton::clicked, this, [this] { m_finish(m_settings); });
|
||||
m_cancel->setObjectName(QStringLiteral("rawCancel"));
|
||||
m_cancel->setAutoDefault(false);
|
||||
connect(m_cancel, &QPushButton::clicked, this, [this] { m_finish(std::nullopt); });
|
||||
m_import->setObjectName(QStringLiteral("rawImport"));
|
||||
m_import->setDefault(true);
|
||||
connect(m_import, &QPushButton::clicked, this, [this] { m_finish(m_settings); });
|
||||
buttons->addWidget(m_reset);
|
||||
buttons->addStretch(1);
|
||||
buttons->addWidget(cancel);
|
||||
buttons->addWidget(importButton);
|
||||
buttons->addWidget(m_cancel);
|
||||
buttons->addWidget(m_import);
|
||||
column->addLayout(buttons);
|
||||
|
||||
// The first preview starts at once; slider bursts coalesce.
|
||||
@@ -120,6 +148,37 @@ RawDevelopSheet::~RawDevelopSheet()
|
||||
m_cancelled->store(true);
|
||||
}
|
||||
|
||||
void RawDevelopSheet::setImporting(bool importing)
|
||||
{
|
||||
if (importing == !m_importing->isHidden())
|
||||
return;
|
||||
m_importing->setVisible(importing);
|
||||
if (importing)
|
||||
m_spinner->hide();
|
||||
for (const Row &row : m_rows)
|
||||
row.slider->setEnabled(!importing);
|
||||
m_cancel->setEnabled(!importing);
|
||||
m_import->setEnabled(!importing);
|
||||
m_preview->update();
|
||||
synchronize();
|
||||
// Swift's interactiveDismissDisabled: Escape and close wait.
|
||||
if (importing)
|
||||
window()->installEventFilter(this);
|
||||
else
|
||||
window()->removeEventFilter(this);
|
||||
}
|
||||
|
||||
bool RawDevelopSheet::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched != window())
|
||||
return false;
|
||||
if (event->type() == QEvent::Close) {
|
||||
event->ignore();
|
||||
return true;
|
||||
}
|
||||
return event->type() == QEvent::KeyPress && static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape;
|
||||
}
|
||||
|
||||
void RawDevelopSheet::addRow(const QString &title, float RawDevelopSettings::*field, double low, double high, double scale, int precision, const QString &unit)
|
||||
{
|
||||
auto *slider = new QSlider(Qt::Horizontal, this);
|
||||
@@ -151,7 +210,7 @@ void RawDevelopSheet::changed()
|
||||
void RawDevelopSheet::develop()
|
||||
{
|
||||
m_cancelled = std::make_shared<std::atomic_bool>(false);
|
||||
m_spinner->show();
|
||||
m_spinner->setVisible(m_importing->isHidden());
|
||||
auto *watcher = new QFutureWatcher<QImage>(this);
|
||||
connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher, cancelled = m_cancelled] {
|
||||
watcher->deleteLater();
|
||||
@@ -184,5 +243,5 @@ void RawDevelopSheet::synchronize()
|
||||
row.slider->setValue(int(std::lround(value * row.scale)));
|
||||
row.value->setText(QString::asprintf("%.*f", row.precision, value) + row.unit);
|
||||
}
|
||||
m_reset->setEnabled(!m_settings.isAsShot());
|
||||
m_reset->setEnabled(!m_settings.isAsShot() && m_importing->isHidden());
|
||||
}
|
||||
@@ -19,6 +19,11 @@ class RawDevelopSheet : public QWidget {
|
||||
public:
|
||||
RawDevelopSheet(RawDevelopRequest request, std::function<void(std::optional<RawDevelopSettings>)> finish, QWidget *parent = nullptr);
|
||||
~RawDevelopSheet() override;
|
||||
// Import pressed: the frame develops; nothing can be changed.
|
||||
void setImporting(bool importing);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
class Preview;
|
||||
@@ -45,4 +50,7 @@ private:
|
||||
QProgressBar *const m_spinner;
|
||||
std::vector<Row> m_rows;
|
||||
QPushButton *const m_reset;
|
||||
QPushButton *const m_cancel;
|
||||
QPushButton *const m_import;
|
||||
QWidget *const m_importing;
|
||||
};
|
||||
Reference in new issue
Block a user