core: add color pipeline class
This allows encapsulating color operations in a generic way, which can then be used in KMS or shaders. The class automatically optimizes out unnecessary color operations like identity matrices, and combines consecutive operations like - matrix + matrix - multiplier + multiplier - matrix + multiplier - EOTF + inverse EOTF - relative EOTF + multiplier to improve efficiency and make KMS offloading easier
This commit is contained in:
parent
de85867675
commit
5aaab715b0
10 changed files with 395 additions and 25 deletions
|
@ -44,6 +44,7 @@ target_sources(kwin PRIVATE
|
|||
compositor_wayland.cpp
|
||||
core/colorlut.cpp
|
||||
core/colorlut3d.cpp
|
||||
core/colorpipeline.cpp
|
||||
core/colorpipelinestage.cpp
|
||||
core/colorspace.cpp
|
||||
core/colortransformation.cpp
|
||||
|
@ -547,6 +548,7 @@ install(FILES
|
|||
install(FILES
|
||||
core/colorlut.h
|
||||
core/colorlut3d.h
|
||||
core/colorpipeline.h
|
||||
core/colorpipelinestage.h
|
||||
core/colorspace.h
|
||||
core/colortransformation.h
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "drm_egl_layer.h"
|
||||
#include "core/colorpipeline.h"
|
||||
#include "core/iccprofile.h"
|
||||
#include "drm_backend.h"
|
||||
#include "drm_buffer.h"
|
||||
|
@ -54,7 +55,7 @@ std::optional<OutputLayerBeginFrameInfo> EglGbmLayer::doBeginFrame()
|
|||
// as the hardware cursor is more important than an incorrectly blended cursor edge
|
||||
|
||||
m_scanoutBuffer.reset();
|
||||
return m_surface.startRendering(targetRect().size(), m_pipeline->output()->transform().combine(OutputTransform::FlipY), m_pipeline->formats(m_type), m_pipeline->colorDescription(), m_pipeline->output()->channelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement(), m_pipeline->output()->brightness());
|
||||
return m_surface.startRendering(targetRect().size(), m_pipeline->output()->transform().combine(OutputTransform::FlipY), m_pipeline->formats(m_type), m_pipeline->colorDescription(), m_pipeline->output()->effectiveChannelFactors(), m_pipeline->iccProfile(), m_pipeline->output()->needsColormanagement());
|
||||
}
|
||||
|
||||
bool EglGbmLayer::doEndFrame(const QRegion &renderedRegion, const QRegion &damagedRegion, OutputFrame *frame)
|
||||
|
@ -90,12 +91,21 @@ bool EglGbmLayer::doAttemptScanout(GraphicsBuffer *buffer, const ColorDescriptio
|
|||
if (directScanoutDisabled) {
|
||||
return false;
|
||||
}
|
||||
if (m_pipeline->output()->channelFactors() != QVector3D(1, 1, 1) || (m_pipeline->output()->highDynamicRange() && m_pipeline->output()->brightness() != 1) || m_pipeline->iccProfile()) {
|
||||
// TODO use GAMMA_LUT, CTM and DEGAMMA_LUT to allow direct scanout with HDR
|
||||
if (m_pipeline->iccProfile()) {
|
||||
// TODO make the icc profile output a color pipeline too?
|
||||
return false;
|
||||
}
|
||||
const auto &targetColor = m_pipeline->colorDescription();
|
||||
if (color.containerColorimetry() != targetColor.containerColorimetry() || color.transferFunction() != targetColor.transferFunction()) {
|
||||
ColorPipeline pipeline = ColorPipeline::create(color, m_pipeline->colorDescription());
|
||||
if (m_pipeline->output()->needsColormanagement()) {
|
||||
// with color management enabled, the factors have to be applied in linear space
|
||||
// the pipeline will optimize out the unnecessary transformations
|
||||
pipeline.addTransferFunction(m_pipeline->colorDescription().transferFunction(), m_pipeline->colorDescription().referenceLuminance());
|
||||
}
|
||||
pipeline.addMultiplier(m_pipeline->output()->effectiveChannelFactors());
|
||||
if (m_pipeline->output()->needsColormanagement()) {
|
||||
pipeline.addInverseTransferFunction(m_pipeline->colorDescription().transferFunction(), m_pipeline->colorDescription().referenceLuminance());
|
||||
}
|
||||
if (!pipeline.isIdentity()) {
|
||||
return false;
|
||||
}
|
||||
// kernel documentation says that
|
||||
|
|
|
@ -74,7 +74,7 @@ void EglGbmLayerSurface::destroyResources()
|
|||
m_oldSurface = {};
|
||||
}
|
||||
|
||||
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement, double brightness)
|
||||
std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement)
|
||||
{
|
||||
if (!checkSurface(bufferSize, formats)) {
|
||||
return std::nullopt;
|
||||
|
@ -96,17 +96,12 @@ std::optional<OutputLayerBeginFrameInfo> EglGbmLayerSurface::startRendering(cons
|
|||
m_surface->currentSlot = slot;
|
||||
|
||||
if (m_surface->targetColorDescription != colorDescription || m_surface->channelFactors != channelFactors
|
||||
|| m_surface->colormanagementEnabled != enableColormanagement || m_surface->iccProfile != iccProfile
|
||||
|| m_surface->brightness != brightness) {
|
||||
|| m_surface->colormanagementEnabled != enableColormanagement || m_surface->iccProfile != iccProfile) {
|
||||
m_surface->damageJournal.clear();
|
||||
m_surface->colormanagementEnabled = enableColormanagement;
|
||||
m_surface->targetColorDescription = colorDescription;
|
||||
m_surface->channelFactors = channelFactors;
|
||||
m_surface->adaptedChannelFactors = Colorimetry::fromName(NamedColorimetry::BT709).toOther(colorDescription.containerColorimetry()) * channelFactors;
|
||||
// normalize red to be the original brightness value again
|
||||
m_surface->adaptedChannelFactors *= channelFactors.x() / m_surface->adaptedChannelFactors.x();
|
||||
m_surface->iccProfile = iccProfile;
|
||||
m_surface->brightness = brightness;
|
||||
if (iccProfile) {
|
||||
if (!m_surface->iccShader) {
|
||||
m_surface->iccShader = std::make_unique<IccShader>();
|
||||
|
@ -188,16 +183,12 @@ bool EglGbmLayerSurface::endRendering(const QRegion &damagedRegion, OutputFrame
|
|||
GLFramebuffer::pushFramebuffer(fbo);
|
||||
ShaderBinder binder = m_surface->iccShader ? ShaderBinder(m_surface->iccShader->shader()) : ShaderBinder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
|
||||
if (m_surface->iccShader) {
|
||||
m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription.referenceLuminance(), m_surface->adaptedChannelFactors);
|
||||
m_surface->iccShader->setUniforms(m_surface->iccProfile, m_surface->intermediaryColorDescription.referenceLuminance(), m_surface->channelFactors);
|
||||
} else {
|
||||
// enforce a 25 nits minimum sdr brightness
|
||||
constexpr double minBrightness = 25;
|
||||
const double referenceLuminance = m_surface->intermediaryColorDescription.referenceLuminance();
|
||||
const double brightnessFactor = (m_surface->brightness * (1 - (minBrightness / referenceLuminance))) + (minBrightness / referenceLuminance);
|
||||
QMatrix4x4 ctm;
|
||||
ctm(0, 0) = m_surface->adaptedChannelFactors.x() * brightnessFactor;
|
||||
ctm(1, 1) = m_surface->adaptedChannelFactors.y() * brightnessFactor;
|
||||
ctm(2, 2) = m_surface->adaptedChannelFactors.z() * brightnessFactor;
|
||||
ctm(0, 0) = m_surface->channelFactors.x();
|
||||
ctm(1, 1) = m_surface->channelFactors.y();
|
||||
ctm(2, 2) = m_surface->channelFactors.z();
|
||||
binder.shader()->setUniform(GLShader::Mat4Uniform::ColorimetryTransformation, ctm);
|
||||
binder.shader()->setUniform(GLShader::IntUniform::SourceNamedTransferFunction, m_surface->intermediaryColorDescription.transferFunction().type);
|
||||
binder.shader()->setUniform(GLShader::IntUniform::DestinationNamedTransferFunction, m_surface->targetColorDescription.transferFunction().type);
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
EglGbmLayerSurface(DrmGpu *gpu, EglGbmBackend *eglBackend, BufferTarget target = BufferTarget::Normal, FormatOption formatOption = FormatOption::PreferAlpha);
|
||||
~EglGbmLayerSurface();
|
||||
|
||||
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement, double brightness);
|
||||
std::optional<OutputLayerBeginFrameInfo> startRendering(const QSize &bufferSize, OutputTransform transformation, const QHash<uint32_t, QList<uint64_t>> &formats, const ColorDescription &colorDescription, const QVector3D &channelFactors, const std::shared_ptr<IccProfile> &iccProfile, bool enableColormanagement);
|
||||
bool endRendering(const QRegion &damagedRegion, OutputFrame *frame);
|
||||
|
||||
bool doesSurfaceFit(const QSize &size, const QHash<uint32_t, QList<uint64_t>> &formats) const;
|
||||
|
@ -102,7 +102,6 @@ private:
|
|||
ColorDescription intermediaryColorDescription = ColorDescription::sRGB;
|
||||
QVector3D channelFactors = {1, 1, 1};
|
||||
double brightness = 1.0;
|
||||
QVector3D adaptedChannelFactors = {1, 1, 1};
|
||||
std::unique_ptr<IccShader> iccShader;
|
||||
std::shared_ptr<IccProfile> iccProfile;
|
||||
|
||||
|
|
|
@ -493,9 +493,19 @@ bool DrmOutput::doSetChannelFactors(const QVector3D &rgb)
|
|||
return true;
|
||||
}
|
||||
|
||||
QVector3D DrmOutput::channelFactors() const
|
||||
QVector3D DrmOutput::effectiveChannelFactors() const
|
||||
{
|
||||
return m_channelFactors;
|
||||
QVector3D adaptedChannelFactors = Colorimetry::fromName(NamedColorimetry::BT709).toOther(m_state.colorDescription.containerColorimetry()) * m_channelFactors;
|
||||
// normalize red to be the original brightness value again
|
||||
adaptedChannelFactors *= m_channelFactors.x() / adaptedChannelFactors.x();
|
||||
if (m_state.highDynamicRange) {
|
||||
// enforce a minimum of 25 nits for the reference luminance
|
||||
constexpr double minLuminance = 25;
|
||||
const double brightnessFactor = (m_state.brightness * (1 - (minLuminance / m_state.referenceLuminance))) + (minLuminance / m_state.referenceLuminance);
|
||||
return adaptedChannelFactors * brightnessFactor;
|
||||
} else {
|
||||
return adaptedChannelFactors;
|
||||
}
|
||||
}
|
||||
|
||||
bool DrmOutput::needsColormanagement() const
|
||||
|
|
|
@ -58,7 +58,10 @@ public:
|
|||
void leaseEnded();
|
||||
|
||||
bool setChannelFactors(const QVector3D &rgb) override;
|
||||
QVector3D channelFactors() const;
|
||||
/**
|
||||
* channel factors adapted to the target color space + brightness setting multiplied in
|
||||
*/
|
||||
QVector3D effectiveChannelFactors() const;
|
||||
bool needsColormanagement() const;
|
||||
|
||||
void updateConnectorProperties();
|
||||
|
|
238
src/core/colorpipeline.cpp
Normal file
238
src/core/colorpipeline.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "colorpipeline.h"
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDescription &to)
|
||||
{
|
||||
const auto range1 = ValueRange(from.minLuminance(), from.maxHdrLuminance().value_or(from.referenceLuminance()));
|
||||
ColorPipeline ret(ValueRange{
|
||||
.min = from.transferFunction().nitsToEncoded(range1.min, from.referenceLuminance()),
|
||||
.max = from.transferFunction().nitsToEncoded(range1.max, from.referenceLuminance()),
|
||||
});
|
||||
ret.addTransferFunction(from.transferFunction(), from.referenceLuminance());
|
||||
ret.addMultiplier(to.referenceLuminance() / from.referenceLuminance());
|
||||
|
||||
// FIXME this assumes that the range stays the same with matrix multiplication
|
||||
// that's not necessarily true, and figuring out the actual range could be complicated..
|
||||
ret.addMatrix(from.containerColorimetry().toOther(to.containerColorimetry()), ret.currentOutputRange());
|
||||
|
||||
ret.addInverseTransferFunction(to.transferFunction(), to.referenceLuminance());
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorPipeline::ColorPipeline()
|
||||
: inputRange(ValueRange{
|
||||
.min = 0,
|
||||
.max = 1,
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
ColorPipeline::ColorPipeline(const ValueRange &inputRange)
|
||||
: inputRange(inputRange)
|
||||
{
|
||||
}
|
||||
|
||||
const ValueRange &ColorPipeline::currentOutputRange() const
|
||||
{
|
||||
return ops.empty() ? inputRange : ops.back().output;
|
||||
}
|
||||
|
||||
void ColorPipeline::addMultiplier(double factor)
|
||||
{
|
||||
addMultiplier(QVector3D(factor, factor, factor));
|
||||
}
|
||||
|
||||
void ColorPipeline::addMultiplier(const QVector3D &factors)
|
||||
{
|
||||
if (factors == QVector3D(1, 1, 1)) {
|
||||
return;
|
||||
}
|
||||
const ValueRange output{
|
||||
.min = currentOutputRange().min * std::min(factors.x(), std::min(factors.y(), factors.z())),
|
||||
.max = currentOutputRange().max * std::max(factors.x(), std::max(factors.y(), factors.z())),
|
||||
};
|
||||
if (!ops.empty()) {
|
||||
auto *lastOp = &ops.back().operation;
|
||||
if (const auto mat = std::get_if<ColorMatrix>(lastOp)) {
|
||||
mat->mat.scale(factors);
|
||||
ops.back().output = output;
|
||||
return;
|
||||
} else if (const auto mult = std::get_if<ColorMultiplier>(lastOp)) {
|
||||
mult->factors *= factors;
|
||||
if (mult->factors == QVector3D(1, 1, 1)) {
|
||||
ops.erase(ops.end() - 1);
|
||||
} else {
|
||||
ops.back().output = output;
|
||||
}
|
||||
return;
|
||||
} else if (factors.x() == factors.y() && factors.y() == factors.z()) {
|
||||
if (const auto tf = std::get_if<ColorTransferFunction>(lastOp); tf && tf->tf.isRelative()) {
|
||||
tf->referenceLuminance *= factors.x();
|
||||
ops.back().output = output;
|
||||
return;
|
||||
} else if (const auto tf = std::get_if<InverseColorTransferFunction>(lastOp); tf && tf->tf.isRelative()) {
|
||||
tf->referenceLuminance /= factors.x();
|
||||
ops.back().output = output;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
ops.push_back(ColorOp{
|
||||
.input = currentOutputRange(),
|
||||
.operation = ColorMultiplier(factors),
|
||||
.output = output,
|
||||
});
|
||||
}
|
||||
|
||||
void ColorPipeline::addTransferFunction(TransferFunction tf, double referenceLuminance)
|
||||
{
|
||||
if (tf == TransferFunction::linear) {
|
||||
return;
|
||||
}
|
||||
if (!ops.empty()) {
|
||||
if (const auto otherTf = std::get_if<InverseColorTransferFunction>(&ops.back().operation)) {
|
||||
if (otherTf->tf == tf) {
|
||||
const double reference = otherTf->referenceLuminance;
|
||||
ops.erase(ops.end() - 1);
|
||||
addMultiplier(referenceLuminance / reference);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tf == TransferFunction::scRGB) {
|
||||
addMultiplier(80.0);
|
||||
} else {
|
||||
ops.push_back(ColorOp{
|
||||
.input = currentOutputRange(),
|
||||
.operation = ColorTransferFunction(tf, referenceLuminance),
|
||||
.output = ValueRange{
|
||||
.min = tf.encodedToNits(currentOutputRange().min, referenceLuminance),
|
||||
.max = tf.encodedToNits(currentOutputRange().max, referenceLuminance),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPipeline::addInverseTransferFunction(TransferFunction tf, double referenceLuminance)
|
||||
{
|
||||
if (tf == TransferFunction::linear) {
|
||||
return;
|
||||
}
|
||||
if (!ops.empty()) {
|
||||
if (const auto otherTf = std::get_if<ColorTransferFunction>(&ops.back().operation)) {
|
||||
if (otherTf->tf == tf) {
|
||||
const double reference = otherTf->referenceLuminance;
|
||||
ops.erase(ops.end() - 1);
|
||||
addMultiplier(reference / referenceLuminance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tf == TransferFunction::scRGB) {
|
||||
addMultiplier(1.0 / 80.0);
|
||||
} else {
|
||||
ops.push_back(ColorOp{
|
||||
.input = currentOutputRange(),
|
||||
.operation = InverseColorTransferFunction(tf, referenceLuminance),
|
||||
.output = ValueRange{
|
||||
.min = tf.nitsToEncoded(currentOutputRange().min, referenceLuminance),
|
||||
.max = tf.nitsToEncoded(currentOutputRange().max, referenceLuminance),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ColorPipeline::addMatrix(const QMatrix4x4 &mat, const ValueRange &output)
|
||||
{
|
||||
if (mat.isIdentity()) {
|
||||
return;
|
||||
}
|
||||
if (!ops.empty()) {
|
||||
auto *lastOp = &ops.back().operation;
|
||||
if (const auto otherMat = std::get_if<ColorMatrix>(lastOp)) {
|
||||
otherMat->mat *= mat;
|
||||
ops.back().output = output;
|
||||
return;
|
||||
} else if (const auto mult = std::get_if<ColorMultiplier>(lastOp)) {
|
||||
QMatrix4x4 scaled = mat;
|
||||
scaled.scale(mult->factors);
|
||||
ops.back() = ColorOp{
|
||||
.input = currentOutputRange(),
|
||||
.operation = ColorMatrix(scaled),
|
||||
.output = output,
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
ops.push_back(ColorOp{
|
||||
.input = currentOutputRange(),
|
||||
.operation = ColorMatrix(mat),
|
||||
.output = output,
|
||||
});
|
||||
}
|
||||
|
||||
bool ColorPipeline::isIdentity() const
|
||||
{
|
||||
return ops.empty();
|
||||
}
|
||||
|
||||
void ColorPipeline::add(const ColorOp &op)
|
||||
{
|
||||
if (const auto mat = std::get_if<ColorMatrix>(&op.operation)) {
|
||||
addMatrix(mat->mat, op.output);
|
||||
} else if (const auto mult = std::get_if<ColorMultiplier>(&op.operation)) {
|
||||
addMultiplier(mult->factors);
|
||||
} else if (const auto tf = std::get_if<ColorTransferFunction>(&op.operation)) {
|
||||
addTransferFunction(tf->tf, tf->referenceLuminance);
|
||||
} else if (const auto tf = std::get_if<InverseColorTransferFunction>(&op.operation)) {
|
||||
addInverseTransferFunction(tf->tf, tf->referenceLuminance);
|
||||
}
|
||||
}
|
||||
|
||||
ColorPipeline ColorPipeline::merge(const ColorPipeline &onTop)
|
||||
{
|
||||
ColorPipeline ret{inputRange};
|
||||
ret.ops = ops;
|
||||
for (const auto &op : onTop.ops) {
|
||||
ret.add(op);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorTransferFunction::ColorTransferFunction(TransferFunction tf, double referenceLLuminance)
|
||||
: tf(tf)
|
||||
, referenceLuminance(referenceLLuminance)
|
||||
{
|
||||
}
|
||||
|
||||
InverseColorTransferFunction::InverseColorTransferFunction(TransferFunction tf, double referenceLLuminance)
|
||||
: tf(tf)
|
||||
, referenceLuminance(referenceLLuminance)
|
||||
{
|
||||
}
|
||||
|
||||
ColorMatrix::ColorMatrix(const QMatrix4x4 &mat)
|
||||
: mat(mat)
|
||||
{
|
||||
}
|
||||
|
||||
ColorMultiplier::ColorMultiplier(const QVector3D &factors)
|
||||
: factors(factors)
|
||||
{
|
||||
}
|
||||
|
||||
ColorMultiplier::ColorMultiplier(double factor)
|
||||
: factors(factor, factor, factor)
|
||||
{
|
||||
}
|
||||
}
|
102
src/core/colorpipeline.h
Normal file
102
src/core/colorpipeline.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#pragma once
|
||||
#include "colorspace.h"
|
||||
#include "kwin_export.h"
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class KWIN_EXPORT ValueRange
|
||||
{
|
||||
public:
|
||||
double min = 0;
|
||||
double max = 1;
|
||||
|
||||
bool operator==(const ValueRange &) const = default;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT ColorTransferFunction
|
||||
{
|
||||
public:
|
||||
explicit ColorTransferFunction(TransferFunction tf, double referenceLuminance);
|
||||
|
||||
bool operator==(const ColorTransferFunction &) const = default;
|
||||
|
||||
TransferFunction tf;
|
||||
double referenceLuminance;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT InverseColorTransferFunction
|
||||
{
|
||||
public:
|
||||
explicit InverseColorTransferFunction(TransferFunction tf, double referenceLuminance);
|
||||
|
||||
bool operator==(const InverseColorTransferFunction &) const = default;
|
||||
|
||||
TransferFunction tf;
|
||||
double referenceLuminance;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT ColorMatrix
|
||||
{
|
||||
public:
|
||||
explicit ColorMatrix(const QMatrix4x4 &mat);
|
||||
|
||||
bool operator==(const ColorMatrix &) const = default;
|
||||
|
||||
QMatrix4x4 mat;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT ColorMultiplier
|
||||
{
|
||||
public:
|
||||
explicit ColorMultiplier(double factor);
|
||||
explicit ColorMultiplier(const QVector3D &factors);
|
||||
|
||||
bool operator==(const ColorMultiplier &) const = default;
|
||||
|
||||
QVector3D factors;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT ColorOp
|
||||
{
|
||||
public:
|
||||
ValueRange input;
|
||||
std::variant<ColorTransferFunction, InverseColorTransferFunction, ColorMatrix, ColorMultiplier> operation;
|
||||
ValueRange output;
|
||||
|
||||
bool operator==(const ColorOp &) const = default;
|
||||
};
|
||||
|
||||
class KWIN_EXPORT ColorPipeline
|
||||
{
|
||||
public:
|
||||
explicit ColorPipeline();
|
||||
explicit ColorPipeline(const ValueRange &inputRange);
|
||||
|
||||
static ColorPipeline create(const ColorDescription &from, const ColorDescription &to);
|
||||
|
||||
ColorPipeline merge(const ColorPipeline &onTop);
|
||||
|
||||
bool isIdentity() const;
|
||||
bool operator==(const ColorPipeline &other) const = default;
|
||||
const ValueRange ¤tOutputRange() const;
|
||||
|
||||
void addMultiplier(double factor);
|
||||
void addMultiplier(const QVector3D &factors);
|
||||
void addTransferFunction(TransferFunction tf, double referenceLuminance);
|
||||
void addInverseTransferFunction(TransferFunction tf, double referenceLuminance);
|
||||
void addMatrix(const QMatrix4x4 &mat, const ValueRange &output);
|
||||
void add(const ColorOp &op);
|
||||
|
||||
ValueRange inputRange;
|
||||
std::vector<ColorOp> ops;
|
||||
};
|
||||
}
|
|
@ -367,4 +367,18 @@ QVector3D TransferFunction::nitsToEncoded(const QVector3D &nits, double referenc
|
|||
{
|
||||
return QVector3D(nitsToEncoded(nits.x(), referenceLuminance), nitsToEncoded(nits.y(), referenceLuminance), nitsToEncoded(nits.z(), referenceLuminance));
|
||||
}
|
||||
|
||||
bool TransferFunction::isRelative() const
|
||||
{
|
||||
switch (type) {
|
||||
case TransferFunction::gamma22:
|
||||
case TransferFunction::sRGB:
|
||||
return true;
|
||||
case TransferFunction::linear:
|
||||
case TransferFunction::PerceptualQuantizer:
|
||||
case TransferFunction::scRGB:
|
||||
return false;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,7 @@ public:
|
|||
|
||||
auto operator<=>(const TransferFunction &) const = default;
|
||||
|
||||
bool isRelative() const;
|
||||
double encodedToNits(double encoded, double referenceLuminance) const;
|
||||
double nitsToEncoded(double nits, double referenceLuminance) const;
|
||||
QVector3D encodedToNits(const QVector3D &encoded, double referenceLuminance) const;
|
||||
|
|
Loading…
Reference in a new issue