Output device color curves correction

Summary:
Extends the output device and output configuration interfaces with the
ability
to query and set the RGB color intensity curves (gamma ramps) of the
associated output.

Test Plan: Manually. Auto tests will be added to this diff soon.

Reviewers: #frameworks, graesslin, romangg

Subscribers: kde-frameworks-devel, graesslin, davidedmundson, zzag,
cfeck

Tags: #frameworks

Differential Revision: https://phabricator.kde.org/D12388
This commit is contained in:
Roman Gilg 2018-07-25 22:26:26 +02:00 committed by David Edmundson
parent fa1b65bb5a
commit 17832e8adc
8 changed files with 216 additions and 0 deletions

View file

@ -46,6 +46,7 @@ private Q_SLOTS:
void testModeChanges();
void testScaleChange_legacy();
void testScaleChange();
void testColorCurvesChange();
void testSubPixel_data();
void testSubPixel();
@ -62,6 +63,7 @@ private:
KWayland::Server::Display *m_display;
KWayland::Server::OutputDeviceInterface *m_serverOutputDevice;
QByteArray m_edid;
KWayland::Server::OutputDeviceInterface::ColorCurves m_initColorCurves;
KWayland::Client::ConnectionThread *m_connection;
KWayland::Client::EventQueue *m_queue;
QThread *m_thread;
@ -115,6 +117,21 @@ void TestWaylandOutputDevice::init()
m_edid = QByteArray::fromBase64("AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg==");
m_serverOutputDevice->setEdid(m_edid);
m_initColorCurves.red.clear();
m_initColorCurves.green.clear();
m_initColorCurves.blue.clear();
// 8 bit color ramps
for (int i = 0; i < 256; i++) {
quint16 val = (double)i / 255 * UINT16_MAX;
m_initColorCurves.red << val ;
m_initColorCurves.green << val ;
}
// 10 bit color ramp
for (int i = 0; i < 320; i++) {
m_initColorCurves.blue << (double)i / 319 * UINT16_MAX;
}
m_serverOutputDevice->setColorCurves(m_initColorCurves);
m_serverOutputDevice->create();
// setup connection
@ -183,6 +200,9 @@ void TestWaylandOutputDevice::testRegistry()
QCOMPARE(output.pixelSize(), QSize());
QCOMPARE(output.refreshRate(), 0);
QCOMPARE(output.scale(), 1);
QCOMPARE(output.colorCurves().red, QVector<quint16>());
QCOMPARE(output.colorCurves().green, QVector<quint16>());
QCOMPARE(output.colorCurves().blue, QVector<quint16>());
QCOMPARE(output.subPixel(), KWayland::Client::OutputDevice::SubPixel::Unknown);
QCOMPARE(output.transform(), KWayland::Client::OutputDevice::Transform::Normal);
QCOMPARE(output.enabled(), OutputDevice::Enablement::Enabled);
@ -203,6 +223,9 @@ void TestWaylandOutputDevice::testRegistry()
QCOMPARE(output.pixelSize(), QSize(1024, 768));
QCOMPARE(output.refreshRate(), 60000);
QCOMPARE(output.scale(), 1);
QCOMPARE(output.colorCurves().red, m_initColorCurves.red);
QCOMPARE(output.colorCurves().green, m_initColorCurves.green);
QCOMPARE(output.colorCurves().blue, m_initColorCurves.blue);
// for xwayland output it's unknown
QCOMPARE(output.subPixel(), KWayland::Client::OutputDevice::SubPixel::Unknown);
// for xwayland transform is normal
@ -384,6 +407,53 @@ void TestWaylandOutputDevice::testScaleChange()
QCOMPARE(wl_fixed_from_double(output.scaleF()), wl_fixed_from_double(4.9));
}
void TestWaylandOutputDevice::testColorCurvesChange()
{
KWayland::Client::Registry registry;
QSignalSpy interfacesAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
QVERIFY(interfacesAnnouncedSpy.isValid());
QSignalSpy announced(&registry, &KWayland::Client::Registry::outputDeviceAnnounced);
registry.setEventQueue(m_queue);
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
wl_display_flush(m_connection->display());
QVERIFY(interfacesAnnouncedSpy.wait());
KWayland::Client::OutputDevice output;
QSignalSpy outputChanged(&output, &KWayland::Client::OutputDevice::done);
QVERIFY(outputChanged.isValid());
output.setup(registry.bindOutputDevice(announced.first().first().value<quint32>(), announced.first().last().value<quint32>()));
wl_display_flush(m_connection->display());
QVERIFY(outputChanged.wait());
QCOMPARE(output.colorCurves().red, m_initColorCurves.red);
QCOMPARE(output.colorCurves().green, m_initColorCurves.green);
QCOMPARE(output.colorCurves().blue, m_initColorCurves.blue);
// change the color curves
outputChanged.clear();
KWayland::Server::OutputDeviceInterface::ColorCurves cc;
cc.red = QVector<quint16>(256, 0);
cc.green = QVector<quint16>(256, UINT16_MAX);
cc.blue = QVector<quint16>(320, 1);
m_serverOutputDevice->setColorCurves(cc);
QVERIFY(outputChanged.wait());
QCOMPARE(output.colorCurves().red, cc.red);
QCOMPARE(output.colorCurves().green, cc.green);
QCOMPARE(output.colorCurves().blue, cc.blue);
// change once more
outputChanged.clear();
cc.red = QVector<quint16>(256, 0);
cc.green = QVector<quint16>(256, UINT16_MAX);
cc.blue = QVector<quint16>(320, UINT16_MAX);
m_serverOutputDevice->setColorCurves(cc);
QVERIFY(outputChanged.wait());
QCOMPARE(output.colorCurves().red, cc.red);
QCOMPARE(output.colorCurves().green, cc.green);
QCOMPARE(output.colorCurves().blue, cc.blue);
}
void TestWaylandOutputDevice::testSubPixel_data()
{
using namespace KWayland::Client;

View file

@ -250,6 +250,9 @@ void TestWaylandOutputManagement::applyPendingChanges(KWayland::Server::OutputCo
if (c->scaleChanged()) {
outputdevice->setScaleF(c->scaleF());
}
if (c->colorCurvesChanged()) {
outputdevice->setColorCurves(c->colorCurves());
}
}
}
@ -268,6 +271,9 @@ void TestWaylandOutputManagement::createOutputDevices()
QCOMPARE(output->pixelSize(), QSize());
QCOMPARE(output->refreshRate(), 0);
QCOMPARE(output->scale(), 1);
QCOMPARE(output->colorCurves().red, QVector<quint16>());
QCOMPARE(output->colorCurves().green, QVector<quint16>());
QCOMPARE(output->colorCurves().blue, QVector<quint16>());
QCOMPARE(output->subPixel(), KWayland::Client::OutputDevice::SubPixel::Unknown);
QCOMPARE(output->transform(), KWayland::Client::OutputDevice::Transform::Normal);
QCOMPARE(output->enabled(), OutputDevice::Enablement::Enabled);
@ -405,6 +411,8 @@ void TestWaylandOutputManagement::testMultipleSettings()
config->setTransform(output, OutputDevice::Transform::Rotated90);
config->setPosition(output, QPoint(13, 37));
config->setScale(output, 2);
const auto zeroVector = QVector<quint16>(256, 0);
config->setColorCurves(output, zeroVector, zeroVector, zeroVector);
config->setEnabled(output, OutputDevice::Enablement::Disabled);
config->apply();
@ -423,6 +431,8 @@ void TestWaylandOutputManagement::testMultipleSettings()
config->setTransform(output, OutputDevice::Transform::Normal);
config->setPosition(output, QPoint(0, 1920));
config->setScale(output, 1);
const auto oneVector = QVector<quint16>(256, 1);
config->setColorCurves(output, oneVector, oneVector, oneVector);
config->setEnabled(output, OutputDevice::Enablement::Enabled);
config->apply();

View file

@ -34,6 +34,7 @@ OutputChangeSet::Private::Private(OutputDeviceInterface *outputdevice, OutputCha
, transform(o->transform())
, position(o->globalPosition())
, scale(o->scale())
, colorCurves(o->colorCurves())
{
}
@ -117,5 +118,17 @@ qreal OutputChangeSet::scaleF() const
return d->scale;
}
bool OutputChangeSet::colorCurvesChanged() const
{
Q_D();
return d->colorCurves != d->o->colorCurves();
}
OutputDeviceInterface::ColorCurves OutputChangeSet::colorCurves() const
{
Q_D();
return d->colorCurves;
}
}
}

View file

@ -69,6 +69,10 @@ public:
* @returns @c true if the scale() property of the outputdevice has changed.
*/
bool scaleChanged() const;
/** Whether the colorCurves() property of the outputdevice changed.
* @returns @c true if the colorCurves() property of the outputdevice has changed.
*/
bool colorCurvesChanged() const;
/** The new value for enabled. */
OutputDeviceInterface::Enablement enabled() const;
@ -86,6 +90,10 @@ public:
* @since 5.XX
*/
qreal scaleF() const;
/** The new value for colorCurves.
* @since 5.XX
*/
OutputDeviceInterface::ColorCurves colorCurves() const;
private:
friend class OutputConfigurationInterface;

View file

@ -41,6 +41,7 @@ namespace KWayland
OutputDeviceInterface::Transform transform;
QPoint position;
qreal scale;
OutputDeviceInterface::ColorCurves colorCurves;
};
}
}

View file

@ -71,6 +71,9 @@ private:
static void applyCallback(wl_client *client, wl_resource *resource);
static void scaleFCallback(wl_client *client, wl_resource *resource,
wl_resource * outputdevice, wl_fixed_t scale);
static void colorcurvesCallback(wl_client *client, wl_resource *resource,
wl_resource * outputdevice,
wl_array *red, wl_array *green, wl_array *blue);
OutputConfigurationInterface *q_func() {
return reinterpret_cast<OutputConfigurationInterface *>(q);
@ -87,6 +90,7 @@ const struct org_kde_kwin_outputconfiguration_interface OutputConfigurationInter
scaleCallback,
applyCallback,
scaleFCallback,
colorcurvesCallback,
resourceDestroyedCallback
};
@ -213,6 +217,42 @@ void OutputConfigurationInterface::Private::applyCallback(wl_client *client, wl_
s->emitConfigurationChangeRequested();
}
void OutputConfigurationInterface::Private::colorcurvesCallback(wl_client *client, wl_resource *resource,
wl_resource * outputdevice,
wl_array *red, wl_array *green, wl_array *blue)
{
Q_UNUSED(client);
OutputDeviceInterface *o = OutputDeviceInterface::get(outputdevice);
OutputDeviceInterface::ColorCurves oldCc = o->colorCurves();
auto checkArg = [](const wl_array *newColor, const QVector<quint16> &oldColor) {
return (newColor->size % sizeof(uint16_t) == 0) &&
(newColor->size / sizeof(uint16_t) == static_cast<size_t>(oldColor.size()));
};
if (!checkArg(red, oldCc.red) || !checkArg(green, oldCc.green) || !checkArg(blue, oldCc.blue)) {
qCWarning(KWAYLAND_SERVER) << "Requested to change color curves, but have wrong size.";
return;
}
auto s = cast<Private>(resource);
Q_ASSERT(s);
OutputDeviceInterface::ColorCurves cc;
auto fillVector = [](const wl_array *array, QVector<quint16> *v) {
const uint16_t *pos = (uint16_t*)array->data;
while((char*)pos < (char*)array->data + array->size) {
v->append(*pos);
pos++;
}
};
fillVector(red, &cc.red);
fillVector(green, &cc.green);
fillVector(blue, &cc.blue);
s->pendingChanges(o)->d_func()->colorCurves = cc;
}
void OutputConfigurationInterface::Private::emitConfigurationChangeRequested() const
{
auto configinterface = reinterpret_cast<OutputConfigurationInterface *>(q);

View file

@ -45,6 +45,7 @@ public:
void sendDone(const ResourceData &data);
void updateGeometry();
void updateScale();
void updateColorCurves();
void sendUuid();
void sendEdid();
@ -57,6 +58,7 @@ public:
qreal scale = 1.0;
SubPixel subPixel = SubPixel::Unknown;
Transform transform = Transform::Normal;
ColorCurves colorCurves;
QList<Mode> modes;
QList<ResourceData> resources;
@ -74,6 +76,7 @@ private:
int32_t toSubPixel() const;
void sendGeometry(wl_resource *resource);
void sendScale(const ResourceData &data);
void sendColorCurves(const ResourceData &data);
static const quint32 s_version;
OutputDeviceInterface *q;
@ -139,6 +142,8 @@ OutputDeviceInterface::OutputDeviceInterface(Display *display, QObject *parent)
connect(this, &OutputDeviceInterface::modelChanged, this, [this, d] { d->updateGeometry(); });
connect(this, &OutputDeviceInterface::manufacturerChanged, this, [this, d] { d->updateGeometry(); });
connect(this, &OutputDeviceInterface::scaleFChanged, this, [this, d] { d->updateScale(); });
connect(this, &OutputDeviceInterface::scaleChanged, this, [this, d] { d->updateScale(); });
connect(this, &OutputDeviceInterface::colorCurvesChanged, this, [this, d] { d->updateColorCurves(); });
}
OutputDeviceInterface::~OutputDeviceInterface() = default;
@ -333,6 +338,7 @@ void OutputDeviceInterface::Private::bind(wl_client *client, uint32_t version, u
sendGeometry(resource);
sendScale(r);
sendColorCurves(r);
auto currentModeIt = modes.constEnd();
for (auto it = modes.constBegin(); it != modes.constEnd(); ++it) {
@ -409,6 +415,31 @@ void OutputDeviceInterface::Private::sendScale(const ResourceData &data)
}
}
void OutputDeviceInterface::Private::sendColorCurves(const ResourceData &data)
{
if (data.version < ORG_KDE_KWIN_OUTPUTDEVICE_COLORCURVES_SINCE_VERSION) {
return;
}
wl_array wlRed, wlGreen, wlBlue;
auto fillArray = [](const QVector<quint16> &origin, wl_array *dest) {
wl_array_init(dest);
const size_t memLength = sizeof(uint16_t) * origin.size();
void *s = wl_array_add(dest, memLength);
memcpy(s, origin.data(), memLength);
};
fillArray(colorCurves.red, &wlRed);
fillArray(colorCurves.green, &wlGreen);
fillArray(colorCurves.blue, &wlBlue);
org_kde_kwin_outputdevice_send_colorcurves(data.resource, &wlRed, &wlGreen, &wlBlue);
wl_array_release(&wlRed);
wl_array_release(&wlGreen);
wl_array_release(&wlBlue);
}
void OutputDeviceInterface::Private::sendDone(const ResourceData &data)
{
org_kde_kwin_outputdevice_send_done(data.resource);
@ -430,6 +461,22 @@ void OutputDeviceInterface::Private::updateScale()
}
}
void OutputDeviceInterface::Private::updateColorCurves()
{
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
sendColorCurves(*it);
sendDone(*it);
}
}
bool OutputDeviceInterface::ColorCurves::operator==(const ColorCurves &cc) const
{
return red == cc.red && green == cc.green && blue == cc.blue;
}
bool OutputDeviceInterface::ColorCurves::operator!=(const ColorCurves &cc) const {
return !operator==(cc);
}
#define SETTER(setterName, type, argumentName) \
void OutputDeviceInterface::setterName(type arg) \
{ \
@ -521,6 +568,12 @@ OutputDeviceInterface::Transform OutputDeviceInterface::transform() const
return d->transform;
}
OutputDeviceInterface::ColorCurves OutputDeviceInterface::colorCurves() const
{
Q_D();
return d->colorCurves;
}
QList< OutputDeviceInterface::Mode > OutputDeviceInterface::modes() const
{
Q_D();
@ -543,6 +596,17 @@ OutputDeviceInterface::Private *OutputDeviceInterface::d_func() const
return reinterpret_cast<Private*>(d.data());
}
void OutputDeviceInterface::setColorCurves(const ColorCurves &colorCurves)
{
Q_D();
if (d->colorCurves == colorCurves) {
return;
}
d->colorCurves = colorCurves;
emit colorCurvesChanged(d->colorCurves);
}
void OutputDeviceInterface::setEdid(const QByteArray &edid)
{
Q_D();

View file

@ -23,6 +23,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include <QObject>
#include <QPoint>
#include <QSize>
#include <QVector>
#include <KWayland/Server/kwaylandserver_export.h>
#include "global.h"
@ -91,6 +92,11 @@ public:
ModeFlags flags;
int id = -1;
};
struct ColorCurves {
QVector<quint16> red, green, blue;
bool operator==(const ColorCurves &cc) const;
bool operator!=(const ColorCurves &cc) const;
};
virtual ~OutputDeviceInterface();
QSize physicalSize() const;
@ -103,6 +109,7 @@ public:
qreal scaleF() const;
SubPixel subPixel() const;
Transform transform() const;
ColorCurves colorCurves() const;
QList<Mode> modes() const;
int currentModeId() const;
@ -118,6 +125,7 @@ public:
void setScaleF(qreal scale);
void setSubPixel(SubPixel subPixel);
void setTransform(Transform transform);
void setColorCurves(const ColorCurves &colorCurves);
void addMode(Mode &mode);
void setCurrentMode(const int modeId);
@ -140,6 +148,7 @@ Q_SIGNALS:
void scaleFChanged(qreal);
void subPixelChanged(SubPixel);
void transformChanged(Transform);
void colorCurvesChanged(ColorCurves);
void modesChanged();
void currentModeChanged();
@ -161,5 +170,6 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::OutputDeviceInterface::ModeFlags
Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::Enablement)
Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::SubPixel)
Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::Transform)
Q_DECLARE_METATYPE(KWayland::Server::OutputDeviceInterface::ColorCurves)
#endif